From d92876f7fad28eb3bd2ac2ca5b43be393fb40bbc Mon Sep 17 00:00:00 2001 From: chenhuayi2 Date: Thu, 2 Feb 2017 19:07:14 -0800 Subject: [PATCH] Implement neutron network protection plugin Closes-Bug: 1572006 Change-Id: I304db6de21d16e8dbff695ffa2c76c8f8cc373ee --- karbor/common/constants.py | 15 + karbor/exception.py | 5 + .../network/network_plugin_schemas.py | 38 ++ .../network/neutron_protection_plugin.py | 545 +++++++++++++++++- karbor/tests/fullstack/test_restores.py | 19 + .../test_neutron_protection_plugin.py | 342 +++++++++++ 6 files changed, 951 insertions(+), 13 deletions(-) create mode 100644 karbor/services/protection/protection_plugins/network/network_plugin_schemas.py create mode 100644 karbor/tests/unit/protection/test_neutron_protection_plugin.py diff --git a/karbor/common/constants.py b/karbor/common/constants.py index 0b0dd54f..11e88707 100644 --- a/karbor/common/constants.py +++ b/karbor/common/constants.py @@ -29,6 +29,21 @@ OPERATION_TYPES = ( # plugin type PLUGIN_BANK = 'bank' +# supported network resource types +NETWORK_RESOURCE_TYPES = (NET_RESOURCE_TYPE, + SUBNET_RESOURCE_TYPE, + ROUTER_RESOURCE_TYPE, + ROUTERINTERFACE_RESOURCE_TYPE, + PORT_RESOURCE_TYPE, + SECURITYGROUP_RESOURCE_TYPE, + ) = ('OS::Neutron::Net', + 'OS::Neutron::Subnet', + 'OS::Neutron::Router', + 'OS::Neutron::RouterInterface', + 'OS::Neutron::Port', + 'OS::Neutron::SecurityGroup', + ) + # supported resource types RESOURCE_TYPES = (PROJECT_RESOURCE_TYPE, SERVER_RESOURCE_TYPE, diff --git a/karbor/exception.py b/karbor/exception.py index 3695cd00..52f46dcb 100644 --- a/karbor/exception.py +++ b/karbor/exception.py @@ -383,3 +383,8 @@ class CheckpointNotAvailable(KarborException): class CheckpointNotBeDeleted(KarborException): message = _("The checkpoint %(checkpoint_id)s can not be deleted.") + + +class GetProtectionNetworkSubResourceFailed(KarborException): + message = _("Get protection network sub-resources of type %(type)s failed:" + " %(reason)s") diff --git a/karbor/services/protection/protection_plugins/network/network_plugin_schemas.py b/karbor/services/protection/protection_plugins/network/network_plugin_schemas.py new file mode 100644 index 00000000..41f382e8 --- /dev/null +++ b/karbor/services/protection/protection_plugins/network/network_plugin_schemas.py @@ -0,0 +1,38 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +OPTIONS_SCHEMA = { + "title": "Network Protection Options", + "type": "object", + "properties": {}, + "required": [] +} + +RESTORE_SCHEMA = { + "title": "Network Protection Restore", + "type": "object", + "properties": { + "restore_name": { + "type": "string", + "title": "Restore Network Name", + "description": "The name of the restore network", + }, + }, + "required": ["restore_name"] +} + +SAVED_INFO_SCHEMA = { + "title": "Network Protection Saved Info", + "type": "object", + "properties": {}, + "required": [] +} diff --git a/karbor/services/protection/protection_plugins/network/neutron_protection_plugin.py b/karbor/services/protection/protection_plugins/network/neutron_protection_plugin.py index 967528b3..3a6f9ad8 100644 --- a/karbor/services/protection/protection_plugins/network/neutron_protection_plugin.py +++ b/karbor/services/protection/protection_plugins/network/neutron_protection_plugin.py @@ -9,35 +9,556 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import six from karbor.common import constants +from karbor import exception +from karbor.services.protection.client_factory import ClientFactory from karbor.services.protection import protection_plugin +from karbor.services.protection.protection_plugins.network \ + import network_plugin_schemas +from karbor.services.protection.restore_heat import HeatResource +from oslo_log import log as logging +from uuid import uuid4 + +LOG = logging.getLogger(__name__) + + +def get_network_id(cntxt): + network_id = cntxt.project_id + return network_id + + +class ProtectOperation(protection_plugin.Operation): + _SUPPORT_RESOURCE_TYPES = [constants.NETWORK_RESOURCE_TYPE] + + def _get_resources_by_network(self, cntxt, neutron_client): + try: + networks = neutron_client.list_networks().get('networks') + networks_metadata = {} + + allowed_keys = [ + 'id', + 'admin_state_up', + 'availability_zone_hints', + 'description', + 'ipv4_address_scope', + 'ipv6_address_scope', + 'mtu', + 'name', + 'port_security_enabled', + 'router:external', + 'shared', + 'status', + 'subnets', + 'tags', + 'tenant_id' + ] + + for network in networks: + network_metadata = { + k: network[k] for k in network if k in allowed_keys} + networks_metadata[network["id"]] = network_metadata + return networks_metadata + except Exception as e: + LOG.exception("List all summary networks from neutron failed.") + raise exception.GetProtectionNetworkSubResourceFailed( + type=self._SUPPORT_RESOURCE_TYPES, + reason=six.text_type(e)) + else: + return [] + + def _get_resources_by_subnet(self, cntxt, neutron_client): + try: + subnets = neutron_client.list_subnets().get('subnets') + subnets_metadata = {} + + allowed_keys = [ + 'cidr', + 'allocation_pools', + 'description', + 'dns_nameservers', + 'enable_dhcp', + 'gateway_ip', + 'host_routes', + 'id', + 'ip_version', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'name', + 'network_id', + 'subnetpool_id', + 'tenant_id' + ] + + for subnet in subnets: + subnet_metadata = { + k: subnet[k] for k in subnet if k in allowed_keys} + subnets_metadata[subnet["id"]] = subnet_metadata + + return subnets_metadata + except Exception as e: + LOG.exception("List all summary subnets from neutron failed.") + raise exception.GetProtectionNetworkSubResourceFailed( + type=self._SUPPORT_RESOURCE_TYPES, + reason=six.text_type(e)) + else: + return [] + + def _get_resources_by_port(self, cntxt, neutron_client): + try: + ports = neutron_client.list_ports().get('ports') + ports_metadata = {} + + allowed_keys = [ + 'admin_state_up', + 'allowed_address_pairs', + 'description', + 'device_id', + 'device_owner', + 'extra_dhcp_opts', + 'fixed_ips', + 'id', + 'mac_address', + 'name', + 'network_id', + 'port_security_enabled', + 'security_groups', + 'status', + 'tenant_id' + ] + + for port in ports: + port_metadata = { + k: port[k] for k in port if k in allowed_keys} + ports_metadata[port["id"]] = port_metadata + return ports_metadata + except Exception as e: + LOG.exception("List all summary ports from neutron failed.") + raise exception.GetProtectionNetworkSubResourceFailed( + type=self._SUPPORT_RESOURCE_TYPES, + reason=six.text_type(e)) + else: + return [] + + def _get_resources_by_router(self, cntxt, neutron_client): + try: + routers = neutron_client.list_routers().get('routers') + routers_metadata = {} + + allowed_keys = [ + 'admin_state_u', + 'availability_zone_hints', + 'description', + 'external_gateway_info', + 'id', + 'name', + 'routes', + 'status' + ] + + for router in routers: + router_metadata = { + k: router[k] for k in router if k in allowed_keys} + routers_metadata[router["id"]] = router_metadata + + return routers_metadata + except Exception as e: + LOG.exception("List all summary routers from neutron failed.") + raise exception.GetProtectionNetworkSubResourceFailed( + type=self._SUPPORT_RESOURCE_TYPES, + reason=six.text_type(e)) + else: + return [] + + def _get_resources_by_security_group(self, cntxt, neutron_client): + try: + sgs = neutron_client.list_security_groups().get('security_groups') + sgs_metadata = {} + + allowed_keys = [ + 'id', + 'description', + 'name', + 'security_group_rules', + 'tenant_id' + ] + + for sg in sgs: + sg_metadata = {k: sg[k] for k in sg if k in allowed_keys} + sgs_metadata[sg["id"]] = sg_metadata + return sgs_metadata + except Exception as e: + LOG.exception("List all summary security_groups from neutron " + "failed.") + raise exception.GetProtectionNetworkSubResourceFailed( + type=self._SUPPORT_RESOURCE_TYPES, + reason=six.text_type(e)) + else: + return [] + + def on_main(self, checkpoint, resource, context, parameters, **kwargs): + network_id = get_network_id(context) + backup_name = kwargs.get("backup_name", "karbor network backup") + bank_section = checkpoint.get_resource_bank_section(network_id) + neutron_client = ClientFactory.create_client("neutron", context) + + resource_definition = {"resource_id": network_id} + resource_definition["backup_name"] = backup_name + resource_definition["network_metadata"] = ( + self._get_resources_by_network(context, neutron_client)) + resource_definition["subnet_metadata"] = ( + self._get_resources_by_subnet(context, neutron_client)) + resource_definition["port_metadata"] = ( + self._get_resources_by_port(context, neutron_client)) + resource_definition["router_metadata"] = ( + self._get_resources_by_router(context, neutron_client)) + resource_definition["security-group_metadata"] = ( + self._get_resources_by_security_group(context, neutron_client)) + + try: + bank_section.update_object("status", + constants.RESOURCE_STATUS_PROTECTING) + + # write resource_definition in bank + bank_section.update_object("metadata", resource_definition) + + # update resource_definition backup_status + bank_section.update_object("status", + constants.CHECKPOINT_STATUS_AVAILABLE) + LOG.info("finish backup network, network_id: %s.", network_id) + except Exception as err: + # update resource_definition backup_status + LOG.error("create backup failed, network_id: %s.", network_id) + bank_section.update_object("status", + constants.CHECKPOINT_STATUS_ERROR) + raise exception.CreateBackupFailed( + reason=err, + resource_id=network_id, + resource_type=self._SUPPORT_RESOURCE_TYPES) + + +class RestoreOperation(protection_plugin.Operation): + def _heat_restore_networks(self, heat_template, nets_meta): + for net_meta in nets_meta: + net_data = nets_meta[net_meta] + if net_data["router:external"]: + continue + heat_resource_id = net_data["name"] + net_heat_resource = HeatResource(heat_resource_id, + constants.NET_RESOURCE_TYPE) + properties = {} + properties["admin_state_up"] = net_data["admin_state_up"] + properties["port_security_enabled"] = ( + net_data["port_security_enabled"]) + properties["shared"] = net_data["shared"] + properties["name"] = net_data["name"] + + for key, value in properties.items(): + net_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, net_heat_resource) + + def _get_dependent_net(self, network_id, nets_meta): + for net_meta in nets_meta: + net_data = nets_meta[net_meta] + if network_id == net_data["id"]: + return net_data["name"] + + def _is_external_subnet(self, network_id, nets_meta): + for net_meta in nets_meta: + net_data = nets_meta[net_meta] + if network_id == net_data["id"]: + return net_data["router:external"] + + def _heat_restore_subnets(self, heat_template, nets_meta, subs_meta): + for sub_meta in subs_meta: + sub_data = subs_meta[sub_meta] + + is_ext_subnet = self._is_external_subnet(sub_data["network_id"], + nets_meta) + + if is_ext_subnet: + continue + + heat_resource_id = sub_data["name"] + sub_heat_resource = HeatResource(heat_resource_id, + constants.SUBNET_RESOURCE_TYPE) + properties = {} + properties["cidr"] = sub_data["cidr"] + properties["allocation_pools"] = sub_data["allocation_pools"] + properties["dns_nameservers"] = sub_data["dns_nameservers"] + properties["enable_dhcp"] = sub_data["enable_dhcp"] + properties["gateway_ip"] = sub_data["gateway_ip"] + properties["host_routes"] = sub_data["host_routes"] + properties["name"] = sub_data["name"] + properties["ip_version"] = sub_data["ip_version"] + net_name = self._get_dependent_net(sub_data["network_id"], + nets_meta) + properties["network_id"] = ( + heat_template.get_resource_reference(net_name)) + properties["tenant_id"] = sub_data["tenant_id"] + + for key, value in properties.items(): + sub_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, sub_heat_resource) + + def _get_subnet_by_subnetid(self, subnet_id, subs_meta): + for sub_meta in subs_meta: + sub_data = subs_meta[sub_meta] + if subnet_id == sub_data["id"]: + return sub_data["name"] + + return "" + + def _get_new_fixed_ips(self, heat_template, subs_meta, fixed_ips_meta): + new_fixed_ips = [] + for fixed_ip in fixed_ips_meta: + properties = {} + properties["ip_address"] = fixed_ip["ip_address"] + subnet_name = self._get_subnet_by_subnetid(fixed_ip["subnet_id"], + subs_meta) + properties["subnet_id"] = ( + heat_template.get_resource_reference(subnet_name)) + new_fixed_ips.append(properties) + + return new_fixed_ips + + def _heat_restore_ports(self, heat_template, + nets_meta, subs_meta, ports_meta): + for port_meta in ports_meta: + port_data = ports_meta[port_meta] + heat_resource_id = port_data["name"] + port_heat_resource = HeatResource(heat_resource_id, + constants.PORT_RESOURCE_TYPE) + + if (port_data["device_owner"] == "network:router_interface") or ( + port_data["device_owner"] == "network:router_gateway") or ( + port_data["device_owner"] == "network:dhcp") or ( + port_data["device_owner"] == "network:floatingip"): + continue + + properties = {} + properties["admin_state_up"] = port_data["admin_state_up"] + properties["allowed_address_pairs"] = ( + port_data["allowed_address_pairs"]) + properties["device_id"] = port_data["device_id"] + properties["device_owner"] = port_data["device_owner"] + new_fixed_ips = self._get_new_fixed_ips(heat_template, + subs_meta, + port_data["fixed_ips"]) + properties["fixed_ips"] = new_fixed_ips + properties["mac_address"] = port_data["mac_address"] + properties["name"] = port_data["name"] + net_name = self._get_dependent_net(port_data["network_id"], + nets_meta) + properties["network_id"] = ( + heat_template.get_resource_reference(net_name)) + properties["port_security_enabled"] = ( + port_data["port_security_enabled"]) + properties["security_groups"] = port_data["security_groups"] + + for key, value in properties.items(): + port_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, port_heat_resource) + + def _get_new_external_gateway(self, public_network_id, + gateway_info, subs_meta, neutron_client): + new_ext_gw = {} + + # get public network id + if not public_network_id: + networks = neutron_client.list_networks().get('networks') + for network in networks: + if network['router:external'] is True: + public_network_id = network['id'] + break + + new_ext_gw["network"] = public_network_id + new_ext_gw["enable_snat"] = gateway_info["enable_snat"] + return new_ext_gw + + def _heat_restore_routers(self, public_network_id, heat_template, + subs_meta, routers_meta, neutron_client): + for router_meta in routers_meta: + router_data = routers_meta[router_meta] + heat_resource_id = router_data["name"] + router_heat_resource = HeatResource(heat_resource_id, + constants.ROUTER_RESOURCE_TYPE) + properties = {} + org_external_gateway = router_data["external_gateway_info"] + new_external_gateway = ( + self._get_new_external_gateway(public_network_id, + org_external_gateway, + subs_meta, + neutron_client)) + properties["external_gateway_info"] = new_external_gateway + properties["name"] = router_data["name"] + + for key, value in properties.items(): + router_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, router_heat_resource) + + def _get_router_name(self, device_id, routers_meta): + for router_meta in routers_meta: + router_data = routers_meta[router_meta] + if device_id == router_data["id"]: + return router_data["name"] + + return "" + + def _get_subnet_name_by_fixed_ips(self, fixed_ips, subs_meta): + subnet_id = fixed_ips[0]["subnet_id"] + for sub_meta in subs_meta: + sub_data = subs_meta[sub_meta] + if subnet_id == sub_data["id"]: + return sub_data["name"] + + return "" + + def _heat_restore_routerinterfaces(self, heat_template, + subs_meta, routers_meta, ports_meta): + for port_meta in ports_meta: + port_data = ports_meta[port_meta] + heat_resource_id = str(uuid4()) + port_heat_resource = ( + HeatResource(heat_resource_id, + constants.ROUTERINTERFACE_RESOURCE_TYPE)) + + if port_data["device_owner"] != "network:router_interface": + continue + + properties = {} + router_name = self._get_router_name(port_data["device_id"], + routers_meta) + properties["router"] = ( + heat_template.get_resource_reference(router_name)) + subnet_name = ( + self._get_subnet_name_by_fixed_ips(port_data["fixed_ips"], + subs_meta)) + properties["subnet"] = ( + heat_template.get_resource_reference(subnet_name)) + + for key, value in properties.items(): + port_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, port_heat_resource) + + def _get_security_group_rules(self, security_group_rules): + new_security_group_rules = [] + for sg in security_group_rules: + if sg["remote_ip_prefix"] is None: + continue + + security_group_rule = {} + security_group_rule["direction"] = sg["direction"] + security_group_rule["ethertype"] = sg["ethertype"] + security_group_rule["port_range_max"] = sg["port_range_max"] + security_group_rule["port_range_min"] = sg["port_range_min"] + security_group_rule["protocol"] = sg["protocol"] + security_group_rule["remote_group_id"] = sg["remote_group_id"] + security_group_rule["remote_ip_prefix"] = sg["remote_ip_prefix"] + + if "remote_mode" in sg: + security_group_rule["remote_mode"] = sg["remote_mode"] + new_security_group_rules.append(security_group_rule) + + return new_security_group_rules + + def _heat_restore_securitygroups(self, heat_template, sgs_meta): + for sg_meta in sgs_meta: + sg_data = sgs_meta[sg_meta] + + # Skip the default securitygroups + if sg_data["name"] == "default": + continue + + heat_resource_id = sg_data["name"] + sg_heat_resource = ( + HeatResource(heat_resource_id, + constants.SECURITYGROUP_RESOURCE_TYPE)) + properties = {} + sg_rules = sg_data["security_group_rules"] + properties["description"] = sg_data["description"] + properties["name"] = sg_data["name"] + properties["rules"] = self._get_security_group_rules(sg_rules) + + for key, value in properties.items(): + sg_heat_resource.set_property(key, value) + heat_template.put_resource(heat_resource_id, sg_heat_resource) + + def on_main(self, checkpoint, resource, context, + parameters, heat_template, **kwargs): + neutron_client = ClientFactory.create_client("neutron", context) + network_id = get_network_id(context) + public_network_id = parameters.get("public_network_id") + bank_section = checkpoint.get_resource_bank_section(network_id) + + try: + resource_definition = bank_section.get_object("metadata") + + # Config Net + if "network_metadata" in resource_definition: + nets_meta = resource_definition["network_metadata"] + self._heat_restore_networks(heat_template, nets_meta) + + # Config Subnet + if "subnet_metadata" in resource_definition: + subs_meta = resource_definition["subnet_metadata"] + self._heat_restore_subnets(heat_template, nets_meta, subs_meta) + + # Config Port + if "port_metadata" in resource_definition: + ports_meta = resource_definition["port_metadata"] + self._heat_restore_ports(heat_template, nets_meta, + subs_meta, ports_meta) + + # Config Router + if "router_metadata" in resource_definition: + routers_meta = resource_definition["router_metadata"] + self._heat_restore_routers(public_network_id, + heat_template, + subs_meta, + routers_meta, + neutron_client) + + # Config RouterInterface + if [subs_meta is not None] and ( + [routers_meta is not None] and [ports_meta is not None]): + self._heat_restore_routerinterfaces(heat_template, subs_meta, + routers_meta, ports_meta) + + # Config Securiy-group + if "security-group_metadata" in resource_definition: + sgs_meta = resource_definition["security-group_metadata"] + self._heat_restore_securitygroups(heat_template, sgs_meta) + + except Exception as e: + LOG.error("restore network backup failed, network_id: %s.", + network_id) + raise exception.RestoreBackupFailed( + reason=six.text_type(e), + resource_id=network_id, + resource_type=constants.NETWORK_RESOURCE_TYPE + ) class NeutronProtectionPlugin(protection_plugin.ProtectionPlugin): _SUPPORT_RESOURCE_TYPES = [constants.NETWORK_RESOURCE_TYPE] - def __init__(self, config=None): - super(NeutronProtectionPlugin, self).__init__(config) - @classmethod def get_supported_resources_types(self): return self._SUPPORT_RESOURCE_TYPES @classmethod def get_options_schema(self, resources_type): - # TODO(chenhuayi) - pass + return network_plugin_schemas.OPTIONS_SCHEMA @classmethod def get_restore_schema(self, resources_type): - # TODO(chenhuayi) - pass + return network_plugin_schemas.RESTORE_SCHEMA @classmethod def get_saved_info_schema(self, resources_type): - # TODO(chenhuayi) - pass + return network_plugin_schemas.SAVED_INFO_SCHEMA @classmethod def get_saved_info(self, metadata_store, resource): @@ -45,12 +566,10 @@ class NeutronProtectionPlugin(protection_plugin.ProtectionPlugin): pass def get_protect_operation(self, resource): - # TODO(chenhuayi) - pass + return ProtectOperation() def get_restore_operation(self, resource): - # TODO(chenhuayi) - pass + return RestoreOperation() def get_delete_operation(self, resource): # TODO(chenhuayi) diff --git a/karbor/tests/fullstack/test_restores.py b/karbor/tests/fullstack/test_restores.py index 310fa6ba..494cd944 100644 --- a/karbor/tests/fullstack/test_restores.py +++ b/karbor/tests/fullstack/test_restores.py @@ -170,6 +170,25 @@ class RestoresTest(karbor_base.KarborBaseTest): self.assertEqual(1, len(item.resources_status)) self._store(item.resources_status) + def test_restore_network_resources(self): + network = self.store(objects.Network()) + network.create() + plan = self.store(objects.Plan()) + plan.create(self.provider_id_os, [network, ]) + checkpoint = self.store(objects.Checkpoint()) + checkpoint.create(self.provider_id_os, plan.id) + network.close() + + restore_target = self.get_restore_target(self.keystone_endpoint) + restore = self.store(objects.Restore()) + restore.create(self.provider_id_os, checkpoint.id, + restore_target, self.parameters, self.restore_auth) + + item = self.karbor_client.restores.get(restore.id) + self.assertEqual(constants.RESTORE_STATUS_SUCCESS, + item.status) + self._store(item.resources_status) + def test_restore_resources_with_fs_bank(self): volume = self.store(objects.Volume()) volume.create(1) diff --git a/karbor/tests/unit/protection/test_neutron_protection_plugin.py b/karbor/tests/unit/protection/test_neutron_protection_plugin.py new file mode 100644 index 00000000..fef3ee8c --- /dev/null +++ b/karbor/tests/unit/protection/test_neutron_protection_plugin.py @@ -0,0 +1,342 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +from karbor.common import constants +from karbor.context import RequestContext +from karbor.resource import Resource +from karbor.services.protection.bank_plugin import Bank +from karbor.services.protection.bank_plugin import BankPlugin +from karbor.services.protection.bank_plugin import BankSection +from karbor.services.protection import client_factory +from karbor.services.protection.protection_plugins.network \ + import network_plugin_schemas +from karbor.services.protection.protection_plugins.network. \ + neutron_protection_plugin import NeutronProtectionPlugin +from karbor.tests import base +import mock +from oslo_config import cfg + +FakeNetworks = {'networks': [ + {u'status': u'ACTIVE', + u'router:external': False, + u'availability_zone_hints': [], + u'availability_zones': [u'nova'], + u'ipv4_address_scope': None, + u'description': u'', + u'provider:physical_network': None, + u'subnets': [u'129f1bc5-4282-4f9d-ae60-4db1e1cac22d', + u'0b42e051-5a33-4ac4-9a4f-691d0891d760'], + u'updated_at': u'2016-04-23T05:07:06', + u'tenant_id': u'f6f6d0b2591f41acb8257656d70029fc', + u'created_at': u'2016-04-23T05:07:06', + u'tags': [], + u'ipv6_address_scope': None, + u'provider:segmentation_id': 1057, + u'provider:network_type': u'vxlan', + u'port_security_enabled': True, + u'admin_state_up': True, + u'shared': False, + u'mtu': 1450, + u'id': u'9b68fb64-39d4-4d41-8cc9-f27846c6e5f5', + u'name': u'private'}, + + {u'provider:physical_network': None, + u'ipv6_address_scope': None, + u'port_security_enabled': True, + u'provider:network_type': u'local', + u'id': u'49ef013d-9bb2-4b8f-9eea-e45563efc420', + u'router:external': True, + u'availability_zone_hints': [], + u'availability_zones': [u'nova'], + u'ipv4_address_scope': None, + u'shared': False, + u'status': u'ACTIVE', + u'subnets': [u'808c3b3f-3d79-4c5b-a5b6-95dd07abeb2d'], + u'description': u'', + u'tags': [], + u'updated_at': u'2016-04-25T07:14:53', + u'is_default': False, + u'provider:segmentation_id': None, + u'name': u'ext_net', + u'admin_state_up': True, + u'tenant_id': u'f6f6d0b2591f41acb8257656d70029fc', + u'created_at': u'2016-04-25T07:14:53', + u'mtu': 1500} + ]} + +FakeSubnets = {'subnets': [ + {u'description': u'', + u'enable_dhcp': True, + u'network_id': u'49ef013d-9bb2-4b8f-9eea-e45563efc420', + u'tenant_id': u'f6f6d0b2591f41acb8257656d70029fc', + u'created_at': u'2016-04-25T07:15:25', + u'dns_nameservers': [], + u'updated_at': u'2016-04-25T07:15:25', + u'ipv6_ra_mode': None, + u'allocation_pools': [{u'start': u'192.168.21.2', + u'end': u'192.168.21.254'}], + u'gateway_ip': u'192.168.21.1', + u'ipv6_address_mode': None, + u'ip_version': 4, + u'host_routes': [], + u'cidr': u'192.168.21.0/24', + u'id': u'808c3b3f-3d79-4c5b-a5b6-95dd07abeb2d', + u'subnetpool_id': None, + u'name': u'ext_subnet'}, + ]} + +FakePorts = {'ports': [ + {u'allowed_address_pairs': [], + u'extra_dhcp_opts': [], + u'updated_at': u'2016-04-25T07:15:59', + u'device_owner': + u'network:router_gateway', + u'port_security_enabled': False, + u'binding:profile': {}, + u'fixed_ips': [{u'subnet_id': u'808c3b3f-3d79-4c5b-a5b6-95dd07abeb2d', + u'ip_address': u'192.168.21.3'}], + u'id': u'2b34c97a-4ccc-44c0-bc50-b7bbfc3508eb', + u'security_groups': [], + u'binding:vif_details': {}, + u'binding:vif_type': u'unbound', + u'mac_address': u'fa:16:3e:00:47:f2', + u'status': u'DOWN', + u'binding:host_id': u'', + u'description': u'', + u'device_id': u'7fc86d4b-4c0e-4ed8-8d39-e27b7c1b7ae8', + u'name': u'', + u'admin_state_up': True, + u'network_id': u'49ef013d-9bb2-4b8f-9eea-e45563efc420', + u'dns_name': None, + u'created_at': u'2016-04-25T07:15:59', + u'binding:vnic_type': u'normal', + u'tenant_id': u''}, + ]} + +FakeRoutes = {'routers': [ + {u'status': u'ACTIVE', + u'external_gateway_info': + {u'network_id': u'49ef013d-9bb2-4b8f-9eea-e45563efc420', + u'enable_snat': True, + u'external_fixed_ips': + [{u'subnet_id': u'808c3b3f-3d79-4c5b-a5b6-95dd07abeb2d', + u'ip_address': u'192.168.21.3'} + ]}, + u'availability_zone_hints': [], + u'availability_zones': [], + u'description': u'', + u'admin_state_up': True, + u'tenant_id': u'f6f6d0b2591f41acb8257656d70029fc', + u'distributed': False, + u'routes': [], + u'ha': False, + u'id': u'7fc86d4b-4c0e-4ed8-8d39-e27b7c1b7ae8', + u'name': u'provider_route'} + ]} + +FakeSecGroup = {'security_groups': [ + {u'tenant_id': u'23b119d06168447c8dbb4483d9567bd8', + u'name': u'default', + u'id': u'97910ed4-1dcb-4704-8814-3ddca818ac16', + u'description': u'Default security group', + u'security_group_rules': [ + {u'remote_group_id': u'ac4a6134-0176-44db-abab-559d284c4cdc', + u'direction': u'ingress', + u'protocol': None, + u'description': u'', + u'ethertype': u'IPv4', + u'remote_ip_prefix': None, + u'port_range_max': None, + u'security_group_id': u'ac4a6134-0176-44db-abab-559d284c4cdc', + u'port_range_min': None, + u'tenant_id': u'23b119d06168447c8dbb4483d9567bd8', + u'id': u'21416a24-6a7a-4830-bbec-1426b21e085a'}, + + {u'remote_group_id': u'ac4a6134-0176-44db-abab-559d284c4cdc', + u'direction': u'ingress', + u'protocol': None, + u'description': u'', + u'ethertype': u'IPv6', + u'remote_ip_prefix': None, + u'port_range_max': None, + u'security_group_id': u'ac4a6134-0176-44db-abab-559d284c4cdc', + u'port_range_min': None, + u'tenant_id': u'23b119d06168447c8dbb4483d9567bd8', + u'id': u'47f67d6a-4e73-465a-9f4d-d9b850f85f22'}, + + {u'remote_group_id': None, + u'direction': u'egress', + u'protocol': None, + u'description': u'', + u'ethertype': u'IPv6', + u'remote_ip_prefix': None, + u'port_range_max': None, + u'security_group_id': u'ac4a6134-0176-44db-abab-559d284c4cdc', + u'port_range_min': None, + u'tenant_id': u'23b119d06168447c8dbb4483d9567bd8', + u'id': u'c24e7148-820c-4147-9032-6fcdb96db6f7'}]}, + ]} + + +def call_hooks(operation, checkpoint, resource, context, parameters, **kwargs): + def noop(*args, **kwargs): + pass + + hooks = ( + 'on_prepare_begin', + 'on_prepare_finish', + 'on_main', + 'on_complete', + ) + for hook_name in hooks: + hook = getattr(operation, hook_name, noop) + hook(checkpoint, resource, context, parameters, **kwargs) + + +class FakeNeutronClient(object): + def list_networks(self): + return FakeNetworks + + def list_subnets(self): + return FakeSubnets + + def list_ports(self): + return FakePorts + + def list_routers(self): + return FakeRoutes + + def list_security_groups(self): + return FakeSecGroup + + +class FakeBankPlugin(BankPlugin): + def __init__(self): + self._objects = {} + + def create_object(self, key, value): + self._objects[key] = value + + def update_object(self, key, value): + self._objects[key] = value + + def get_object(self, key): + value = self._objects.get(key, None) + if value is None: + raise Exception + return value + + def list_objects(self, prefix=None, limit=None, marker=None): + objects_name = [] + if prefix is not None: + for key, value in self._objects.items(): + if key.find(prefix) == 0: + objects_name.append(key.lstrip(prefix)) + else: + objects_name = self._objects.keys() + return objects_name + + def delete_object(self, key): + self._objects.pop(key) + + def get_owner_id(self): + return + +fake_checkpointid = "checkpoint_id" +fake_project_id = "abcd" +fake_bank = Bank(FakeBankPlugin()) +fake_bank_section = BankSection(bank=fake_bank, section="fake") + +ResourceNode = collections.namedtuple( + "ResourceNode", + ["value"] +) + + +class CheckpointCollection(object): + def __init__(self): + self.bank_section = fake_bank_section + + def get_resource_bank_section(self, resource_id): + return self.bank_section + + +class NeutronProtectionPluginTest(base.TestCase): + def setUp(self): + super(NeutronProtectionPluginTest, self).setUp() + + self.plugin = NeutronProtectionPlugin() + + cfg.CONF.set_default('neutron_endpoint', + 'http://127.0.0.1:9696', + 'neutron_client') + + self.cntxt = RequestContext(user_id='admin', + project_id='abcd', + auth_token='efgh') + + self.neutron_client = client_factory.ClientFactory.create_client( + "neutron", self.cntxt) + self.checkpoint = CheckpointCollection() + + def test_get_options_schema(self): + options_schema = self.plugin.get_options_schema( + 'OS::Neutron::Network') + self.assertEqual(options_schema, + network_plugin_schemas.OPTIONS_SCHEMA) + + def test_get_restore_schema(self): + options_schema = self.plugin.get_restore_schema( + 'OS::Neutron::Network') + self.assertEqual(options_schema, + network_plugin_schemas.RESTORE_SCHEMA) + + def test_get_saved_info_schema(self): + options_schema = self.plugin.get_saved_info_schema( + 'OS::Neutron::Network') + self.assertEqual(options_schema, + network_plugin_schemas.SAVED_INFO_SCHEMA) + + def test_get_supported_resources_types(self): + types = self.plugin.get_supported_resources_types() + self.assertEqual(types, + [constants.NETWORK_RESOURCE_TYPE]) + + @mock.patch('karbor.services.protection.clients.neutron.create') + def test_create_backup(self, mock_neutron_create): + resource = Resource(id="network_id_1", + type=constants.NETWORK_RESOURCE_TYPE, + name="test") + + fake_bank_section.update_object = mock.MagicMock() + + protect_operation = self.plugin.get_protect_operation(resource) + mock_neutron_create.return_value = self.neutron_client + + self.neutron_client.list_networks = mock.MagicMock() + self.neutron_client.list_networks.return_value = FakeNetworks + + self.neutron_client.list_subnets = mock.MagicMock() + self.neutron_client.list_subnets.return_value = FakeSubnets + + self.neutron_client.list_ports = mock.MagicMock() + self.neutron_client.list_ports.return_value = FakePorts + + self.neutron_client.list_routers = mock.MagicMock() + self.neutron_client.list_routers.return_value = FakeRoutes + + self.neutron_client.list_security_groups = mock.MagicMock() + self.neutron_client.list_security_groups.return_value = FakeSecGroup + + call_hooks(protect_operation, self.checkpoint, resource, self.cntxt, + {})