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
This commit is contained in:
Adit Sarfaty 2019-06-30 10:57:50 +03:00
parent 5216a35ffd
commit 905310e2fc
9 changed files with 205 additions and 91 deletions

View File

@ -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",

View File

@ -38,7 +38,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:
@ -80,8 +80,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()
@ -269,6 +270,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.
@ -284,6 +290,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 = {}
@ -304,7 +311,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}))
@ -390,6 +397,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 "
@ -400,7 +408,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):
@ -450,7 +459,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 "
@ -561,7 +570,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})

View File

@ -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

View File

@ -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 "

View File

@ -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
@ -70,6 +72,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
@ -95,6 +98,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
@ -2761,6 +2775,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):

View File

@ -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
@ -214,6 +215,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()
@ -1787,6 +1792,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']
@ -1857,6 +1863,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):
network_id = self._get_interface_network(context, interface_info)
extern_net = self._network_is_external(context, network_id)
@ -2131,10 +2138,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
@ -2639,6 +2643,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 Group & communication map on the NSX
self._create_security_group_backend_resources(

View File

@ -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
@ -2383,17 +2382,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']
@ -2473,7 +2462,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)
@ -2670,18 +2660,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):
network_id = self._get_interface_network(context, interface_info)
extern_net = self._network_is_external(context, network_id)
@ -2717,8 +2696,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:
subnet = self.get_subnet(context, info['subnet_ids'][0])
port = self.get_port(context, info['port_id'])
@ -2919,23 +2898,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']
@ -3122,14 +3084,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.
@ -3190,22 +3144,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()

View File

@ -206,6 +206,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:

View File

@ -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')