Pre-migration checks admin utility

Change-Id: I864ed65b68c632014b0e0414942d5a3aedca9d9c
This commit is contained in:
Adit Sarfaty 2019-10-07 09:50:50 +03:00
parent 0dc2627c8f
commit 49fa1887ea
5 changed files with 233 additions and 11 deletions

View File

@ -313,6 +313,13 @@ Metadata
nsxadmin -r metadata -o status [--property network_id=<net_id>]
V2T migration
~~~~~~~~~~~~~
- Validate the configuration of the NSX-V plugin befor migrating to NSX-T::
nsxadmin -r nsx-migrate-v2t -o validate [--property transit-network=<cidr>]
Config
~~~~~~

View File

@ -67,6 +67,7 @@ BGP_GW_EDGE = 'bgp-gw-edge'
ROUTING_REDIS_RULE = 'routing-redistribution-rule'
BGP_NEIGHBOUR = 'bgp-neighbour'
NSX_PORTGROUPS = 'nsx-portgroups'
NSX_MIGRATE_V_T = 'nsx-migrate-v2t'
# NSXTV only Resource Constants
PROJECTS = 'projects'

View File

@ -0,0 +1,178 @@
# Copyright 2019 VMware, Inc. All rights reserved.
#
# 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 netaddr
from oslo_log import log as logging
from neutron.db import l3_db
from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api import validators
from neutron_lib.callbacks import registry
from neutron_lib import constants as nl_constants
from neutron_lib import context as n_context
from vmware_nsx.common import nsxv_constants
from vmware_nsx.common import utils as c_utils
from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv.resources import utils
from vmware_nsx.shell import resources as shell
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
LOG = logging.getLogger(__name__)
@admin_utils.output_header
def validate_config_for_migration(resource, event, trigger, **kwargs):
"""Validate the nsxv configuration before migration to nsx-t"""
transit_networks = ["100.64.0.0/16"]
if kwargs.get('property'):
# input validation
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
transit_network = properties.get('transit-network')
if transit_network:
transit_networks = [transit_network]
# Max number of allowed address pairs (allowing 3 for fixed ips)
num_allowed_addr_pairs = nsxlib_consts.NUM_ALLOWED_IP_ADDRESSES - 3
admin_context = n_context.get_admin_context()
n_errors = 0
with utils.NsxVPluginWrapper() as plugin:
# Ports validations:
ports = plugin.get_ports(admin_context)
for port in ports:
net_id = port['network_id']
# Too many address pairs in a port
address_pairs = port.get(addr_apidef.ADDRESS_PAIRS)
if len(address_pairs) > num_allowed_addr_pairs:
n_errors = n_errors + 1
LOG.error("%s allowed address pairs for port %s. Only %s are "
"allowed.",
len(address_pairs), port['id'],
num_allowed_addr_pairs)
# Compute port on external network
if (port.get('device_owner', '').startswith(
nl_constants.DEVICE_OWNER_COMPUTE_PREFIX) and
plugin._network_is_external(admin_context, net_id)):
n_errors = n_errors + 1
LOG.error("Compute port %s on external network %s is not "
"allowed.", port['id'], net_id)
# Networks & subnets validations:
networks = plugin.get_networks(admin_context)
for net in networks:
# skip internal networks
if net['project_id'] == nsxv_constants.INTERNAL_TENANT_ID:
continue
# VXLAN or portgroup provider networks
net_type = net.get(pnet.NETWORK_TYPE)
if (net_type == c_utils.NsxVNetworkTypes.VXLAN or
net_type == c_utils.NsxVNetworkTypes.PORTGROUP):
n_errors = n_errors + 1
LOG.error("Network %s of type %s is not supported.",
net['id'], net_type)
subnets = plugin._get_subnets_by_network(admin_context, net['id'])
n_dhcp_subnets = 0
# Multiple DHCP subnets per network
for subnet in subnets:
if subnet['enable_dhcp']:
n_dhcp_subnets = n_dhcp_subnets + 1
if n_dhcp_subnets > 1:
n_errors = n_errors + 1
LOG.error("Network %s has %s dhcp subnets. Only 1 is allowed.",
net['id'], n_dhcp_subnets)
# Subnets overlapping with the transit network
for subnet in subnets:
# get the subnet IPs
if ('allocation_pools' in subnet and
validators.is_attr_set(subnet['allocation_pools'])):
# use the pools instead of the cidr
subnet_networks = [
netaddr.IPRange(pool.get('start'), pool.get('end'))
for pool in subnet.get('allocation_pools')]
else:
cidr = subnet.get('cidr')
if not validators.is_attr_set(cidr):
return
subnet_networks = [netaddr.IPNetwork(subnet['cidr'])]
for subnet_net in subnet_networks:
if (netaddr.IPSet(subnet_net) &
netaddr.IPSet(transit_networks)):
n_errors = n_errors + 1
LOG.error("Subnet %s overlaps with the transit "
"network ips: %s.",
subnet['id'], transit_networks)
# Network attached to multiple routers
port_filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
'network_id': [net['id']]}
intf_ports = plugin.get_ports(admin_context, filters=port_filters)
if len(intf_ports) > 1:
n_errors = n_errors + 1
LOG.error("Network %s has interfaces on multiple routers. "
"Only 1 is allowed.", net['id'])
# Routers validations:
routers = plugin.get_routers(admin_context)
for router in routers:
# Interface subnets overlap with the GW subnet
gw_subnets = plugin._find_router_gw_subnets(admin_context, router)
gw_cidrs = [subnet['cidr'] for subnet in gw_subnets]
gw_ip_set = netaddr.IPSet(gw_cidrs)
if_cidrs = plugin._find_router_subnets_cidrs(
admin_context, router['id'])
if_ip_set = netaddr.IPSet(if_cidrs)
if gw_ip_set & if_ip_set:
n_errors = n_errors + 1
LOG.error("Interface network of router %s cannot overlap with "
"router GW network", router['id'])
# TODO(asarfaty): missing validations:
# - Vlan provider network with the same VLAN tag as the uplink
# profile tag used in the relevant transport node
# (cannot check this without access to the T manager)
# - Unsupported load balancing topologies
# (e.g.: Load Balancer with members from various subnets
# not uplinked to the same edge router)
# First need to decide if this is for nlbaas or Octavia
# General validations:
# TODO(asarfaty): multiple transport zones (migrator limitation)?
if n_errors > 0:
plural = n_errors > 1
LOG.error("The NSX-V plugin configuration is not ready to be "
"migrated to NSX-T. %s error%s found.", n_errors,
's were' if plural else ' was')
exit(n_errors)
LOG.info("The NSX-V plugin configuration is ready to be migrated to "
"NSX-T.")
registry.subscribe(validate_config_for_migration,
constants.NSX_MIGRATE_V_T,
shell.Operations.VALIDATE.value)

View File

@ -243,6 +243,8 @@ nsxv_resources = {
constants.BGP_NEIGHBOUR: Resource(constants.BGP_NEIGHBOUR,
[Operations.CREATE.value,
Operations.DELETE.value]),
constants.NSX_MIGRATE_V_T: Resource(constants.NSX_MIGRATE_V_T,
[Operations.VALIDATE.value]),
}

View File

@ -34,6 +34,7 @@ from vmware_nsx.common import config # noqa
from vmware_nsx.db import nsxv_db
from vmware_nsx.dvs import dvs_utils
from vmware_nsx.shell.admin.plugins.nsxp.resources import utils as nsxp_utils
from vmware_nsx.shell.admin.plugins.nsxv.resources import migration
from vmware_nsx.shell.admin.plugins.nsxv.resources import utils as nsxv_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils as nsxv3_utils
from vmware_nsx.shell import resources
@ -61,6 +62,7 @@ class AbstractTestAdminUtils(base.BaseTestCase):
# remove resource registration conflicts
resource_registry.unregister_all_resources()
self.edgeapi = nsxv_utils.NeutronDbClient()
# Init the neutron config
neutron_config.init(args=['--config-file', BASE_CONF_PATH,
'--config-file', NSX_INI_PATH])
@ -115,9 +117,7 @@ class AbstractTestAdminUtils(base.BaseTestCase):
data = {'router': {'tenant_id': tenant_id}}
data['router']['name'] = 'dummy'
data['router']['admin_state_up'] = True
edgeapi = nsxv_utils.NeutronDbClient()
return self._plugin.create_router(edgeapi.context, data)
return self._plugin.create_router(self.edgeapi.context, data)
class TestNsxvAdminUtils(AbstractTestAdminUtils,
@ -160,12 +160,16 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
side_effect=get_plugin_mock).start()
# Create a router to make sure we have deployed an edge
self.router = self.create_router()
self.router = self._create_router()
self.network = self._create_net()
def tearDown(self):
if self.router and self.router.get('id'):
edgeapi = nsxv_utils.NeutronDbClient()
self._plugin.delete_router(edgeapi.context, self.router['id'])
self._plugin.delete_router(
self.edgeapi.context, self.router['id'])
if self.network and self.network.get('id'):
self._plugin.delete_network(
self.edgeapi.context, self.network['id'])
super(TestNsxvAdminUtils, self).tearDown()
def test_nsxv_resources(self):
@ -176,7 +180,7 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
args['property'].extend(params)
self._test_resource('edges', 'nsx-update', **args)
def create_router(self):
def _create_router(self):
# Create an exclusive router (with an edge)
tenant_id = uuidutils.generate_uuid()
data = {'router': {'tenant_id': tenant_id}}
@ -184,12 +188,31 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
data['router']['admin_state_up'] = True
data['router']['router_type'] = 'exclusive'
edgeapi = nsxv_utils.NeutronDbClient()
return self._plugin.create_router(edgeapi.context, data)
return self._plugin.create_router(self.edgeapi.context, data)
def _create_net(self):
tenant_id = uuidutils.generate_uuid()
data = {'network': {'tenant_id': tenant_id,
'name': 'dummy',
'admin_state_up': True,
'shared': False}}
net = self._plugin.create_network(self.edgeapi.context, data)
data = {'subnet': {'tenant_id': tenant_id,
'name': 'dummy',
'admin_state_up': True,
'network_id': net['id'],
'cidr': '1.1.1.0/16',
'enable_dhcp': True,
'ip_version': 4,
'dns_nameservers': None,
'host_routes': None,
'allocation_pools': None}}
self._plugin.create_subnet(self.edgeapi.context, data)
return net
def get_edge_id(self):
edgeapi = nsxv_utils.NeutronDbClient()
bindings = nsxv_db.get_nsxv_router_bindings(edgeapi.context.session)
bindings = nsxv_db.get_nsxv_router_bindings(
self.edgeapi.context.session)
for binding in bindings:
if binding.edge_id:
return binding.edge_id
@ -243,6 +266,17 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
args = {'property': ["edge-id=%s" % edge_id]}
self._test_resource('routers', 'nsx-recreate', **args)
def test_migration_validation(self):
# check that validation fails
args = {'property': ["transit-network=1.1.1.0/24"]}
try:
migration.validate_config_for_migration(
'nsx-migrate-v2t', 'validate', None, **args)
except SystemExit:
return
else:
self.assertTrue(False)
class TestNsxv3AdminUtils(AbstractTestAdminUtils,
test_v3_plugin.NsxV3PluginTestCaseMixin):