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>]
|
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
|
Config
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ BGP_GW_EDGE = 'bgp-gw-edge'
|
|||||||
ROUTING_REDIS_RULE = 'routing-redistribution-rule'
|
ROUTING_REDIS_RULE = 'routing-redistribution-rule'
|
||||||
BGP_NEIGHBOUR = 'bgp-neighbour'
|
BGP_NEIGHBOUR = 'bgp-neighbour'
|
||||||
NSX_PORTGROUPS = 'nsx-portgroups'
|
NSX_PORTGROUPS = 'nsx-portgroups'
|
||||||
|
NSX_MIGRATE_V_T = 'nsx-migrate-v2t'
|
||||||
|
|
||||||
# NSXTV only Resource Constants
|
# NSXTV only Resource Constants
|
||||||
PROJECTS = 'projects'
|
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,
|
constants.BGP_NEIGHBOUR: Resource(constants.BGP_NEIGHBOUR,
|
||||||
[Operations.CREATE.value,
|
[Operations.CREATE.value,
|
||||||
Operations.DELETE.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.db import nsxv_db
|
||||||
from vmware_nsx.dvs import dvs_utils
|
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.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.nsxv.resources import utils as nsxv_utils
|
||||||
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils as nsxv3_utils
|
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils as nsxv3_utils
|
||||||
from vmware_nsx.shell import resources
|
from vmware_nsx.shell import resources
|
||||||
@ -61,6 +62,7 @@ class AbstractTestAdminUtils(base.BaseTestCase):
|
|||||||
# remove resource registration conflicts
|
# remove resource registration conflicts
|
||||||
resource_registry.unregister_all_resources()
|
resource_registry.unregister_all_resources()
|
||||||
|
|
||||||
|
self.edgeapi = nsxv_utils.NeutronDbClient()
|
||||||
# Init the neutron config
|
# Init the neutron config
|
||||||
neutron_config.init(args=['--config-file', BASE_CONF_PATH,
|
neutron_config.init(args=['--config-file', BASE_CONF_PATH,
|
||||||
'--config-file', NSX_INI_PATH])
|
'--config-file', NSX_INI_PATH])
|
||||||
@ -115,9 +117,7 @@ class AbstractTestAdminUtils(base.BaseTestCase):
|
|||||||
data = {'router': {'tenant_id': tenant_id}}
|
data = {'router': {'tenant_id': tenant_id}}
|
||||||
data['router']['name'] = 'dummy'
|
data['router']['name'] = 'dummy'
|
||||||
data['router']['admin_state_up'] = True
|
data['router']['admin_state_up'] = True
|
||||||
|
return self._plugin.create_router(self.edgeapi.context, data)
|
||||||
edgeapi = nsxv_utils.NeutronDbClient()
|
|
||||||
return self._plugin.create_router(edgeapi.context, data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
||||||
@ -160,12 +160,16 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
|||||||
side_effect=get_plugin_mock).start()
|
side_effect=get_plugin_mock).start()
|
||||||
|
|
||||||
# Create a router to make sure we have deployed an edge
|
# 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):
|
def tearDown(self):
|
||||||
if self.router and self.router.get('id'):
|
if self.router and self.router.get('id'):
|
||||||
edgeapi = nsxv_utils.NeutronDbClient()
|
self._plugin.delete_router(
|
||||||
self._plugin.delete_router(edgeapi.context, self.router['id'])
|
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()
|
super(TestNsxvAdminUtils, self).tearDown()
|
||||||
|
|
||||||
def test_nsxv_resources(self):
|
def test_nsxv_resources(self):
|
||||||
@ -176,7 +180,7 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
|||||||
args['property'].extend(params)
|
args['property'].extend(params)
|
||||||
self._test_resource('edges', 'nsx-update', **args)
|
self._test_resource('edges', 'nsx-update', **args)
|
||||||
|
|
||||||
def create_router(self):
|
def _create_router(self):
|
||||||
# Create an exclusive router (with an edge)
|
# Create an exclusive router (with an edge)
|
||||||
tenant_id = uuidutils.generate_uuid()
|
tenant_id = uuidutils.generate_uuid()
|
||||||
data = {'router': {'tenant_id': tenant_id}}
|
data = {'router': {'tenant_id': tenant_id}}
|
||||||
@ -184,12 +188,31 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
|||||||
data['router']['admin_state_up'] = True
|
data['router']['admin_state_up'] = True
|
||||||
data['router']['router_type'] = 'exclusive'
|
data['router']['router_type'] = 'exclusive'
|
||||||
|
|
||||||
edgeapi = nsxv_utils.NeutronDbClient()
|
return self._plugin.create_router(self.edgeapi.context, data)
|
||||||
return self._plugin.create_router(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):
|
def get_edge_id(self):
|
||||||
edgeapi = nsxv_utils.NeutronDbClient()
|
bindings = nsxv_db.get_nsxv_router_bindings(
|
||||||
bindings = nsxv_db.get_nsxv_router_bindings(edgeapi.context.session)
|
self.edgeapi.context.session)
|
||||||
for binding in bindings:
|
for binding in bindings:
|
||||||
if binding.edge_id:
|
if binding.edge_id:
|
||||||
return binding.edge_id
|
return binding.edge_id
|
||||||
@ -243,6 +266,17 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
|||||||
args = {'property': ["edge-id=%s" % edge_id]}
|
args = {'property': ["edge-id=%s" % edge_id]}
|
||||||
self._test_resource('routers', 'nsx-recreate', **args)
|
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,
|
class TestNsxv3AdminUtils(AbstractTestAdminUtils,
|
||||||
test_v3_plugin.NsxV3PluginTestCaseMixin):
|
test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user