Pre-migration checks admin utility
Change-Id: I864ed65b68c632014b0e0414942d5a3aedca9d9c
This commit is contained in:
parent
a15e7bcc4e
commit
de50f5bcf3
@ -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
|
||||
~~~~~~
|
||||
|
||||
|
@ -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'
|
||||
|
178
vmware_nsx/shell/admin/plugins/nsxv/resources/migration.py
Normal file
178
vmware_nsx/shell/admin/plugins/nsxv/resources/migration.py
Normal 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)
|
@ -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]),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user