From d9e7ccf856ead12384fb1652bc5ea7f128456daf Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Sun, 30 Jun 2019 10:57:50 +0300 Subject: [PATCH] api_replay for migration into the policy plugin - Add api reply support in the policy plugin - Move some api_replay common code to the common_v3 plugin - Add support for avaiablity zones in the api_replay Change-Id: Idb376e2d8c0b24f2fea5f051af2191f77831803c (cherry picked from commit 905310e2fcf1cc5683da1b35d07b54b4ea6efe2d) --- vmware_nsx/api_replay/cli.py | 7 +- vmware_nsx/api_replay/client.py | 21 +++-- vmware_nsx/api_replay/utils.py | 34 +++++-- vmware_nsx/common/config.py | 2 +- vmware_nsx/plugins/common_v3/plugin.py | 48 +++++++++- vmware_nsx/plugins/nsx_p/plugin.py | 15 ++- vmware_nsx/plugins/nsx_v3/plugin.py | 74 ++------------- .../admin/plugins/nsxtvd/resources/migrate.py | 1 + .../tests/unit/nsx_p/test_api_replay.py | 94 +++++++++++++++++++ 9 files changed, 205 insertions(+), 91 deletions(-) create mode 100644 vmware_nsx/tests/unit/nsx_p/test_api_replay.py diff --git a/vmware_nsx/api_replay/cli.py b/vmware_nsx/api_replay/cli.py index cb1ef620f8..ae03b90a28 100644 --- a/vmware_nsx/api_replay/cli.py +++ b/vmware_nsx/api_replay/cli.py @@ -35,6 +35,7 @@ class ApiReplayCli(object): dest_os_user_domain_id=args.dest_os_user_domain_id, dest_os_password=args.dest_os_password, dest_os_auth_url=args.dest_os_auth_url, + dest_plugin=args.dest_plugin, use_old_keystone=args.use_old_keystone, logfile=args.logfile) @@ -101,7 +102,11 @@ class ApiReplayCli(object): parser.add_argument( "--dest-os-auth-url", required=True, - help="They keystone api endpoint for this user.") + help="The keystone api endpoint for this user.") + parser.add_argument( + "--dest-plugin", + default='nsx-p', + help="The core plugin of the destination nsx-t/nsx-p.") parser.add_argument( "--use-old-keystone", diff --git a/vmware_nsx/api_replay/client.py b/vmware_nsx/api_replay/client.py index 0e021f176c..4e740a0ff4 100644 --- a/vmware_nsx/api_replay/client.py +++ b/vmware_nsx/api_replay/client.py @@ -37,7 +37,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): source_os_password, source_os_auth_url, dest_os_username, dest_os_user_domain_id, dest_os_tenant_name, dest_os_tenant_domain_id, - dest_os_password, dest_os_auth_url, + dest_os_password, dest_os_auth_url, dest_plugin, use_old_keystone, logfile): if logfile: @@ -79,8 +79,9 @@ class ApiReplayClient(utils.PrepareObjectForMigration): tenant_domain_id=dest_os_tenant_domain_id, password=dest_os_password, auth_url=dest_os_auth_url) + self.dest_plugin = dest_plugin - LOG.info("Starting NSX migration.") + LOG.info("Starting NSX migration to %s.", self.dest_plugin) # Migrate all the objects self.migrate_security_groups() self.migrate_qos_policies() @@ -268,6 +269,11 @@ class ApiReplayClient(utils.PrepareObjectForMigration): # is raised here but that's okay. pass + def get_dest_availablity_zones(self, resource): + azs = self.dest_neutron.list_availability_zones()['availability_zones'] + az_names = [az['name'] for az in azs if az['resource'] == resource] + return az_names + def migrate_routers(self): """Migrates routers from source to dest neutron. @@ -283,6 +289,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): source_routers = [] dest_routers = self.dest_neutron.list_routers()['routers'] + dest_azs = self.get_dest_availablity_zones('router') update_routes = {} gw_info = {} @@ -297,7 +304,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): dest_router = self.have_id(router['id'], dest_routers) if dest_router is False: - body = self.prepare_router(router) + body = self.prepare_router(router, dest_azs=dest_azs) try: new_router = (self.dest_neutron.create_router( {'router': body})) @@ -383,6 +390,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): dest_default_public_net = True subnetpools_map = self.migrate_subnetpools() + dest_azs = self.get_dest_availablity_zones('network') total_num = len(source_networks) LOG.info("Migrating %(nets)s networks, %(subnets)s subnets and " @@ -393,7 +401,8 @@ class ApiReplayClient(utils.PrepareObjectForMigration): external_net = network.get('router:external') body = self.prepare_network( network, remove_qos=remove_qos, - dest_default_public_net=dest_default_public_net) + dest_default_public_net=dest_default_public_net, + dest_azs=dest_azs) # only create network if the dest server doesn't have it if self.have_id(network['id'], dest_networks): @@ -435,7 +444,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): body['enable_dhcp'] = False if count_dhcp_subnet > 1: # Do not allow dhcp on the subnet if there is already - # another subnet with DHCP as the v3 plugin supports + # another subnet with DHCP as the v3 plugins supports # only one LOG.warning("Disabling DHCP for subnet on net %s: " "The plugin doesn't support multiple " @@ -546,7 +555,7 @@ class ApiReplayClient(utils.PrepareObjectForMigration): # script multiple times as we don't track this. # Note(asarfaty): also if the same network in # source is attached to 2 routers, which the v3 - # plugin does not support. + # plugins does not support. LOG.error("Failed to add router interface port" "(%(port)s): %(e)s", {'port': port, 'e': e}) diff --git a/vmware_nsx/api_replay/utils.py b/vmware_nsx/api_replay/utils.py index 8917213b07..40299b368c 100644 --- a/vmware_nsx/api_replay/utils.py +++ b/vmware_nsx/api_replay/utils.py @@ -46,10 +46,10 @@ def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True): class PrepareObjectForMigration(object): - """Helper class to modify V objects before creating them in T""" + """Helper class to modify source objects before creating them in dest""" # Remove some fields before creating the new object. # Some fields are not supported for a new object, and some are not - # supported by the nsx-v3 plugin + # supported by the destination plugin basic_ignore_fields = ['updated_at', 'created_at', 'tags', @@ -64,7 +64,6 @@ class PrepareObjectForMigration(object): 'ha', 'external_gateway_info', 'router_type', - 'availability_zone_hints', 'availability_zones', 'distributed', 'flavor_id'] @@ -89,7 +88,6 @@ class PrepareObjectForMigration(object): 'status', 'subnets', 'availability_zones', - 'availability_zone_hints', 'ipv4_address_scope', 'ipv6_address_scope', 'mtu'] @@ -124,10 +122,18 @@ class PrepareObjectForMigration(object): self.fix_description(sg) return self.drop_fields(sg, self.drop_sg_fields) - def prepare_router(self, rtr, direct_call=False): + def prepare_router(self, rtr, dest_azs=None, direct_call=False): self.fix_description(rtr) body = self.drop_fields(rtr, self.drop_router_fields) - if direct_call: + if dest_azs: + if body.get('availability_zone_hints'): + az = body['availability_zone_hints'][0] + if az not in dest_azs: + if az != 'default': + LOG.warning("Ignoring AZ %s in router %s as it is not " + "defined in destination", az, rtr['id']) + body['availability_zone_hints'] = [] + elif direct_call: body['availability_zone_hints'] = [] return body @@ -136,7 +142,7 @@ class PrepareObjectForMigration(object): return self.drop_fields(pool, self.drop_subnetpool_fields) def prepare_network(self, net, dest_default_public_net=True, - remove_qos=False, direct_call=False): + remove_qos=False, dest_azs=None, direct_call=False): self.fix_description(net) body = self.drop_fields(net, self.drop_network_fields) @@ -151,13 +157,13 @@ class PrepareObjectForMigration(object): del body[field] # vxlan network with segmentation id should be translated to a regular - # network in nsx-v3. + # network in nsx-v3/P. if (body.get('provider:network_type') == 'vxlan' and body.get('provider:segmentation_id') is not None): del body['provider:network_type'] del body['provider:segmentation_id'] - # flat network should be translated to a regular network in nsx-v3. + # flat network should be translated to a regular network in nsx-v3/P. if (body.get('provider:network_type') == 'flat'): del body['provider:network_type'] if 'provider:physical_network' in body: @@ -179,7 +185,15 @@ class PrepareObjectForMigration(object): body['is_default'] = False LOG.warning("Public network %s was set to non default network", body['id']) - if direct_call: + if dest_azs: + if body.get('availability_zone_hints'): + az = body['availability_zone_hints'][0] + if az not in dest_azs: + if az != 'default': + LOG.warning("Ignoring AZ %s in net %s as it is not " + "defined in destination", az, body['id']) + body['availability_zone_hints'] = [] + elif direct_call: body['availability_zone_hints'] = [] return body diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 0bf48355a9..c45de69998 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -246,7 +246,7 @@ nsx_common_opts = [ help=_("If true, the server then allows the caller to " "specify the id of resources. This should only " "be enabled in order to allow one to migrate an " - "existing install of neutron to the NSX-T plugin.")), + "existing install of neutron to a new VMWare plugin.")), cfg.ListOpt('nsx_extension_drivers', default=[], help=_("An ordered list of extension driver " diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index f6923909d0..f6356bb4db 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -13,7 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. - +import decorator +import mock import netaddr from oslo_config import cfg from oslo_db import exception as db_exc @@ -38,6 +39,7 @@ from neutron.db import extraroute_db from neutron.db import l3_attrs_db from neutron.db import l3_db from neutron.db import l3_gwmode_db +from neutron.db.models import securitygroup as securitygroup_model from neutron.db import models_v2 from neutron.db import portbindings_db from neutron.db import portsecurity_db @@ -71,6 +73,7 @@ from neutron_lib.services.qos import constants as qos_consts from neutron_lib.utils import helpers from neutron_lib.utils import net as nl_net_utils +from vmware_nsx.api_replay import utils as api_replay_utils from vmware_nsx.common import availability_zones as nsx_com_az from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import locking @@ -96,6 +99,17 @@ from vmware_nsxlib.v3 import utils as nsxlib_utils LOG = logging.getLogger(__name__) +@decorator.decorator +def api_replay_mode_wrapper(f, *args, **kwargs): + if cfg.CONF.api_replay_mode: + # NOTE(arosen): the mock.patch here is needed for api_replay_mode + with mock.patch("neutron_lib.plugins.utils._fixup_res_dict", + side_effect=api_replay_utils._fixup_res_dict): + return f(*args, **kwargs) + else: + return f(*args, **kwargs) + + # NOTE(asarfaty): the order of inheritance here is important. in order for the # QoS notification to work, the AgentScheduler init must be called first # NOTE(arosen): same is true with the ExtendedSecurityGroupPropertiesMixin @@ -2800,6 +2814,38 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return restricted_vlans + @api_replay_mode_wrapper + def _create_floating_ip_wrapper(self, context, floatingip): + initial_status = (constants.FLOATINGIP_STATUS_ACTIVE + if floatingip['floatingip']['port_id'] + else constants.FLOATINGIP_STATUS_DOWN) + return super(NsxPluginV3Base, self).create_floatingip( + context, floatingip, initial_status=initial_status) + + def _ensure_default_security_group(self, context, tenant_id): + # NOTE(arosen): if in replay mode we'll create all the default + # security groups for the user with their data so we don't + # want this to be called. + if not cfg.CONF.api_replay_mode: + return super(NsxPluginV3Base, self)._ensure_default_security_group( + context, tenant_id) + + def _handle_api_replay_default_sg(self, context, secgroup_db): + """Set default api-replay migrated SG as default manually""" + if (secgroup_db['name'] == 'default'): + # this is a default security group copied from another cloud + # Ugly patch! mark it as default manually + with context.session.begin(subtransactions=True): + try: + default_entry = securitygroup_model.DefaultSecurityGroup( + security_group_id=secgroup_db['id'], + project_id=secgroup_db['project_id']) + context.session.add(default_entry) + except Exception as e: + LOG.error("Failed to mark migrated security group %(id)s " + "as default %(e)s", + {'id': secgroup_db['id'], 'e': e}) + class TagsCallbacks(object): diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 7954579ac8..a664fd6956 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -68,6 +68,7 @@ from vmware_nsx.common import locking from vmware_nsx.common import managers from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db +from vmware_nsx.extensions import api_replay from vmware_nsx.extensions import maclearning as mac_ext from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import providersecuritygroup as provider_sg @@ -216,6 +217,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): if cfg.CONF.vlan_transparent: self.supported_extension_aliases.append(vlan_apidef.ALIAS) + # Support api-reply for migration environments to the policy plugin + if cfg.CONF.api_replay_mode: + self.supported_extension_aliases.append(api_replay.ALIAS) + nsxlib_utils.set_inject_headers_callback(v3_utils.inject_headers) self._validate_nsx_policy_version() self._validate_config() @@ -1810,6 +1815,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): router_id, static_route_id=self._get_static_route_id(route)) + @nsx_plugin_common.api_replay_mode_wrapper def update_router(self, context, router_id, router): gw_info = self._extract_external_gw(context, router, is_extract=False) router_data = router['router'] @@ -1870,6 +1876,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): cidr_prefix = int(subnet['cidr'].split('/')[1]) return "%s/%s" % (subnet['gateway_ip'], cidr_prefix) + @nsx_plugin_common.api_replay_mode_wrapper def add_router_interface(self, context, router_id, interface_info): # NOTE: In dual stack case, neutron would create a separate interface # for each IP version @@ -2142,10 +2149,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._assert_on_assoc_floatingip_to_special_ports( fip_data, port_data) - new_fip = super(NsxPolicyPlugin, self).create_floatingip( - context, floatingip, initial_status=( - const.FLOATINGIP_STATUS_ACTIVE - if port_id else const.FLOATINGIP_STATUS_DOWN)) + new_fip = self._create_floating_ip_wrapper(context, floatingip) router_id = new_fip['router_id'] if not router_id: return new_fip @@ -2680,6 +2684,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): secgroup, default_sg) + if cfg.CONF.api_replay_mode: + self._handle_api_replay_default_sg(context, secgroup_db) + try: # create all the rule entries sg_rules = secgroup_db['security_group_rules'] diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index c9ac6385a3..6f67630df4 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -69,7 +69,6 @@ from oslo_utils import importutils from oslo_utils import uuidutils from vmware_nsx._i18n import _ -from vmware_nsx.api_replay import utils as api_replay_utils from vmware_nsx.common import config # noqa from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import l3_rpc_agent_api @@ -2395,17 +2394,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, return ret_val - def _update_router_wrapper(self, context, router_id, router): - if cfg.CONF.api_replay_mode: - # NOTE(arosen): the mock.patch here is needed for api_replay_mode - with mock.patch("neutron_lib.plugins.utils._fixup_res_dict", - side_effect=api_replay_utils._fixup_res_dict): - return super(NsxV3Plugin, self).update_router( - context, router_id, router) - else: - return super(NsxV3Plugin, self).update_router( - context, router_id, router) - + @nsx_plugin_common.api_replay_mode_wrapper def update_router(self, context, router_id, router): gw_info = self._extract_external_gw(context, router, is_extract=False) router_data = router['router'] @@ -2485,7 +2474,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, nsx_router_id, description=router_data['description']) - return self._update_router_wrapper(context, router_id, router) + return super(NsxV3Plugin, self).update_router( + context, router_id, router) except nsx_lib_exc.ResourceNotFound: with db_api.CONTEXT_WRITER.using(context): router_db = self._get_router(context, router_id) @@ -2680,18 +2670,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, return (ports, address_groups) - def _add_router_interface_wrapper(self, context, router_id, - interface_info): - if cfg.CONF.api_replay_mode: - # NOTE(arosen): the mock.patch here is needed for api_replay_mode - with mock.patch("neutron_lib.plugins.utils._fixup_res_dict", - side_effect=api_replay_utils._fixup_res_dict): - return super(NsxV3Plugin, self).add_router_interface( - context, router_id, interface_info) - else: - return super(NsxV3Plugin, self).add_router_interface( - context, router_id, interface_info) - + @nsx_plugin_common.api_replay_mode_wrapper def add_router_interface(self, context, router_id, interface_info): # In case on dual stack, neutron creates a separate interface per # IP version @@ -2728,8 +2707,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, [network_id]) # Update the interface of the neutron router - info = self._add_router_interface_wrapper(context, router_id, - interface_info) + info = super(NsxV3Plugin, self).add_router_interface( + context, router_id, interface_info) try: nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( context.session, info['port_id']) @@ -2929,23 +2908,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, vs_client.update_virtual_server_with_vip(vs['id'], vip_address) - def _create_floating_ip_wrapper(self, context, floatingip): - if cfg.CONF.api_replay_mode: - # NOTE(arosen): the mock.patch here is needed for api_replay_mode - with mock.patch("neutron_lib.plugins.utils._fixup_res_dict", - side_effect=api_replay_utils._fixup_res_dict): - return super(NsxV3Plugin, self).create_floatingip( - context, floatingip, initial_status=( - const.FLOATINGIP_STATUS_ACTIVE - if floatingip['floatingip']['port_id'] - else const.FLOATINGIP_STATUS_DOWN)) - else: - return super(NsxV3Plugin, self).create_floatingip( - context, floatingip, initial_status=( - const.FLOATINGIP_STATUS_ACTIVE - if floatingip['floatingip']['port_id'] - else const.FLOATINGIP_STATUS_DOWN)) - def create_floatingip(self, context, floatingip): # First do some validations fip_data = floatingip['floatingip'] @@ -3132,14 +3094,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, super(NsxV3Plugin, self).disassociate_floatingips( context, port_id, do_notify=False) - def _ensure_default_security_group(self, context, tenant_id): - # NOTE(arosen): if in replay mode we'll create all the default - # security groups for the user with their data so we don't - # want this to be called. - if (cfg.CONF.api_replay_mode is False): - return super(NsxV3Plugin, self)._ensure_default_security_group( - context, tenant_id) - def _create_fw_section_for_secgroup(self, nsgroup, is_provider): # NOTE(arosen): if a security group is provider we want to # insert our rules at the top. @@ -3200,22 +3154,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, logging_enabled, action, _sg_rules, ruleid_2_remote_nsgroup_map) - def _handle_api_replay_default_sg(self, context, secgroup_db): - """Set default api-replay migrated SG as default manually""" - if (secgroup_db['name'] == 'default'): - # this is a default security group copied from another cloud - # Ugly patch! mark it as default manually - with context.session.begin(subtransactions=True): - try: - default_entry = securitygroup_model.DefaultSecurityGroup( - security_group_id=secgroup_db['id'], - project_id=secgroup_db['project_id']) - context.session.add(default_entry) - except Exception as e: - LOG.error("Failed to mark migrated security group %(id)s " - "as default %(e)s", - {'id': secgroup_db['id'], 'e': e}) - def create_security_group(self, context, security_group, default_sg=False): secgroup = security_group['security_group'] secgroup['id'] = secgroup.get('id') or uuidutils.generate_uuid() diff --git a/vmware_nsx/shell/admin/plugins/nsxtvd/resources/migrate.py b/vmware_nsx/shell/admin/plugins/nsxtvd/resources/migrate.py index 7fefc0edcd..b7e7dcfcd5 100644 --- a/vmware_nsx/shell/admin/plugins/nsxtvd/resources/migrate.py +++ b/vmware_nsx/shell/admin/plugins/nsxtvd/resources/migrate.py @@ -214,6 +214,7 @@ def create_t_resources(context, objects, ext_net): # fix object before creation using the api replay code orig_id = obj['id'] prepare_object = getattr(prepare, "prepare_%s" % resource) + # TODO(asarfaty): Add availability zones support too obj_data = prepare_object(obj, direct_call=True) enable_dhcp = False # special cases for different objects before create: diff --git a/vmware_nsx/tests/unit/nsx_p/test_api_replay.py b/vmware_nsx/tests/unit/nsx_p/test_api_replay.py new file mode 100644 index 0000000000..6731a2905c --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_p/test_api_replay.py @@ -0,0 +1,94 @@ +# Copyright (c) 2019 OpenStack Foundation. +# +# 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. + +from vmware_nsx.extensions import api_replay +from vmware_nsx.tests.unit.nsx_p import test_plugin + +from neutron_lib.api import attributes +from neutron_lib.plugins import directory +from oslo_config import cfg + + +class TestApiReplay(test_plugin.NsxPTestL3NatTest): + + def setUp(self, plugin=None, ext_mgr=None, service_plugins=None): + # enables api_replay_mode for these tests + cfg.CONF.set_override('api_replay_mode', True) + + super(TestApiReplay, self).setUp() + + def tearDown(self): + # disables api_replay_mode for these tests + cfg.CONF.set_override('api_replay_mode', False) + + # remove the extension from the plugin + directory.get_plugin().supported_extension_aliases.remove( + api_replay.ALIAS) + + # Revert the attributes map back to normal + for attr_name in ('ports', 'networks', 'security_groups', + 'security_group_rules', 'routers', 'policies'): + attr_info = attributes.RESOURCES[attr_name] + attr_info['id']['allow_post'] = False + + super(TestApiReplay, self).tearDown() + + def test_create_port_specify_id(self): + specified_network_id = '555e762b-d7a1-4b44-b09b-2a34ada56c9f' + specified_port_id = 'e55e762b-d7a1-4b44-b09b-2a34ada56c9f' + network_res = self._create_network(self.fmt, + 'test-network', + True, + arg_list=('id',), + id=specified_network_id) + network = self.deserialize(self.fmt, network_res) + self.assertEqual(specified_network_id, network['network']['id']) + port_res = self._create_port(self.fmt, + network['network']['id'], + arg_list=('id',), + id=specified_port_id) + port = self.deserialize(self.fmt, port_res) + self.assertEqual(specified_port_id, port['port']['id']) + + def _create_router(self, fmt, tenant_id, name=None, + admin_state_up=None, + arg_list=None, **kwargs): + data = {'router': {'tenant_id': tenant_id}} + if name: + data['router']['name'] = name + if admin_state_up: + data['router']['admin_state_up'] = admin_state_up + for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())): + # Arg must be present and not empty + if kwargs.get(arg): + data['router'][arg] = kwargs[arg] + router_req = self.new_create_request('routers', data, fmt) + return router_req.get_response(self.ext_api) + + def test_create_update_router(self): + specified_router_id = '555e762b-d7a1-4b44-b09b-2a34ada56c9f' + router_res = self._create_router(self.fmt, + 'test-tenant', + 'test-rtr', + arg_list=('id',), + id=specified_router_id) + router = self.deserialize(self.fmt, router_res) + self.assertEqual(specified_router_id, router['router']['id']) + + # This part tests _fixup_res_dict as well + body = self._update('routers', specified_router_id, + {'router': {'name': 'new_name'}}) + body = self._show('routers', specified_router_id) + self.assertEqual(body['router']['name'], 'new_name')