[V2T] Helper admin utils for validation and N/S cutover

This patch adds an operation for the NSX-V and two operations for
the NSX-T plugin. The goal of these operation are:

- Find routers without any downlink. They cannot be migrated and
  should be removed before migration to NSX-T.
- Patch and restore Neutron routers without gateway. For N/S
  cutover, each router must have a T0 uplink or an SR. The 'fixup'
  operation ensures the NSX T1 routers for these neutron routers
  will have a T0 uplink. (They surely do not have a SR).
  The 'restore' operation returns T1 routers to their original state.

Change-Id: Iffd1a5e43e08fdc997a591829c87bcc0bb806c77
This commit is contained in:
Salvatore Orlando 2021-11-25 02:49:29 -08:00
parent 50837fc04a
commit 59da27e9f5
3 changed files with 195 additions and 2 deletions

View File

@ -18,10 +18,12 @@ import sys
import netaddr import netaddr
from neutron_lib.callbacks import registry from neutron_lib.callbacks import registry
from neutron_lib import context
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from vmware_nsx.shell.admin.plugins.common import constants from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import formatters
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxp.resources import utils as p_utils from vmware_nsx.shell.admin.plugins.nsxp.resources import utils as p_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import migration from vmware_nsx.shell.admin.plugins.nsxv3.resources import migration
@ -213,6 +215,156 @@ def migration_validate_external_cidrs(resource, event, trigger, **kwargs):
exit(0) exit(0)
@admin_utils.output_header
def patch_routers_without_gateway(resource, event, trigger, **kwargs):
state_filename = None
tier0_id = None
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
state_filename = properties.get('state-file')
tier0_id = properties.get('tier0')
if not state_filename:
LOG.error("Must provide a filename for saving T1 GW state")
return
if not tier0_id:
LOG.error("Cannot execute if a Tier-0 GW uuid is not provided")
return
nsxpolicy = p_utils.get_connected_nsxpolicy()
try:
nsxpolicy.tier0.get(tier0_id)
except Exception as e:
LOG.error("An error occurred while retrieving Tier0 gw router %s: %s",
tier0_id, e)
raise SystemExit(e)
ctx = context.get_admin_context()
fixed_routers = []
# Open state file, if exists, read data
try:
with open(state_filename) as f:
state_data = jsonutils.load(f)
except FileNotFoundError:
LOG.debug("State file not created yet")
state_data = {}
with p_utils.NsxPolicyPluginWrapper() as plugin:
routers = plugin.get_routers(ctx)
try:
for router in routers:
router_id = router['id']
if plugin._extract_external_gw(ctx, {'router': router}):
continue
# Skip router if already fixed up
if router_id in state_data:
LOG.info("It seems router %s has already been patched. "
"Skipping it.", router_id)
continue
# Fetch T1
t1_data = nsxpolicy.tier1.get(router_id)
route_adv_data = t1_data.get('route_advertisement_types', [])
# append state data
state_data[router_id] = route_adv_data
# Update T1: connect to T0, disable route advertisment
nsxpolicy.tier1.update_route_advertisement(
router_id,
static_routes=False,
subnets=False,
nat=False,
lb_vip=False,
lb_snat=False,
ipsec_endpoints=False,
tier0=tier0_id)
fixed_routers.append(router)
except Exception as e:
LOG.error("Failure while patching routers without "
"gateway: %s", e)
# do not call sys.exit here
finally:
# Save state data
with open(state_filename, 'w') as f:
# Pretty print in case someone needs to insepct it
jsonutils.dump(state_data, f, indent=4)
LOG.info(formatters.output_formatter(
"Attached following routers to T0 %s" % tier0_id,
fixed_routers,
['id', 'name', 'project_id']))
return fixed_routers
@admin_utils.output_header
def restore_routers_without_gateway(resource, event, trigger, **kwargs):
state_filename = None
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
state_filename = properties.get('state-file')
if not state_filename:
LOG.error("Must provide a filename for saving T1 GW state")
return
nsxpolicy = p_utils.get_connected_nsxpolicy()
ctx = context.get_admin_context()
restored_routers = []
# Open state file,read data
# Fail if file does not exist
try:
with open(state_filename) as f:
state_data = jsonutils.load(f)
except FileNotFoundError:
LOG.error("State file %s was not found. Aborting", state_filename)
sys.exit(1)
with p_utils.NsxPolicyPluginWrapper() as plugin:
routers = plugin.get_routers(ctx)
try:
for router in routers:
router_id = router['id']
if plugin._extract_external_gw(ctx, {'router': router}):
continue
adv_info = state_data.get(router_id)
# Disconnect T0, set route adv from state file
if adv_info:
nsxpolicy.tier1.update_route_advertisement(
router_id,
static_routes=("TIER1_STATIC_ROUTES" in adv_info),
subnets=("TIER1_CONNECTED" in adv_info),
nat=("TIER1_NAT" in adv_info),
lb_vip=("TIER1_LB_VIP" in adv_info),
lb_snat=("TIER1_LB_SNAT" in adv_info),
ipsec_endpoints=("TIER1_IPSEC_LOCAL_ENDPOINT" in
adv_info),
tier0=None)
else:
# Only disconnect T0
LOG.info("Router advertisment info not found in state "
"file for router %s", router_id)
nsxpolicy.tier1.update_route_advertisement(
router_id, tier0=None)
state_data.pop(router_id, None)
restored_routers.append(router)
except Exception as e:
LOG.error("Failure while restoring routers without "
"gateway: %s", e)
finally:
with open(state_filename, 'w') as f:
# Pretty print in case someone needs to insepct it
jsonutils.dump(state_data, f, indent=4)
LOG.info(formatters.output_formatter(
"Restored following routers",
restored_routers,
['id', 'name', 'project_id']))
return restored_routers
registry.subscribe(cleanup_db_mappings, registry.subscribe(cleanup_db_mappings,
constants.NSX_MIGRATE_T_P, constants.NSX_MIGRATE_T_P,
shell.Operations.CLEAN_ALL.value) shell.Operations.CLEAN_ALL.value)
@ -228,3 +380,11 @@ registry.subscribe(migration_tier0_redistribute,
registry.subscribe(migration_validate_external_cidrs, registry.subscribe(migration_validate_external_cidrs,
constants.NSX_MIGRATE_V_T, constants.NSX_MIGRATE_V_T,
shell.Operations.VALIDATE.value) shell.Operations.VALIDATE.value)
registry.subscribe(patch_routers_without_gateway,
constants.NSX_MIGRATE_V_T,
shell.Operations.PATCH_RTR_NOGW.value)
registry.subscribe(restore_routers_without_gateway,
constants.NSX_MIGRATE_V_T,
shell.Operations.RESTORE_RTR_NOGW.value)

View File

@ -646,6 +646,29 @@ def list_ports_vif_ids(resource, event, trigger, **kwargs):
LOG.info("Mapping data saved into %s", filename) LOG.info("Mapping data saved into %s", filename)
@admin_utils.output_header
def get_routers_without_interfaces(resource, event, trigger, **kwargs):
context = n_context.get_admin_context()
empty_routers = []
with utils.NsxVPluginWrapper() as plugin:
routers = plugin.get_routers(context)
for router in routers:
# Routers in the metadata internal project will always have an
# interface, but it won't be returned by the method below, since
# the device_owner is network:md_interface
if router['project_id'] == 'metadata_internal_project':
LOG.debug("Skipping internal router %s", router['id'])
continue
interfaces = plugin._get_router_interfaces(
context, router['id'])
if not interfaces:
empty_routers.append(router)
LOG.info(formatters.output_formatter(
"Routers without interfaces", empty_routers,
['id', 'name', 'project_id']))
return empty_routers
@admin_utils.output_header @admin_utils.output_header
def build_edge_mapping_file(resource, event, trigger, **kwargs): def build_edge_mapping_file(resource, event, trigger, **kwargs):
filename = None filename = None
@ -681,6 +704,10 @@ def build_edge_mapping_file(resource, event, trigger, **kwargs):
LOG.info("Edge mapping data saved into %s", filename) LOG.info("Edge mapping data saved into %s", filename)
registry.subscribe(get_routers_without_interfaces,
constants.NSX_MIGRATE_V_T,
shell.Operations.LIST_RTR_NO_IFACE.value)
registry.subscribe(validate_config_for_migration, registry.subscribe(validate_config_for_migration,
constants.NSX_MIGRATE_V_T, constants.NSX_MIGRATE_V_T,
shell.Operations.VALIDATE.value) shell.Operations.VALIDATE.value)

View File

@ -83,6 +83,9 @@ class Operations(enum.Enum):
SET_STATUS_ERROR = 'set-status-error' SET_STATUS_ERROR = 'set-status-error'
CHECK_COMPUTE_CLUSTERS = 'check-compute-clusters' CHECK_COMPUTE_CLUSTERS = 'check-compute-clusters'
CUTOVER_MAPPINGS = 'mappings-for-edge-cutover' CUTOVER_MAPPINGS = 'mappings-for-edge-cutover'
LIST_RTR_NO_IFACE = 'list-routers-no-interfaces'
PATCH_RTR_NOGW = 'cutover-fixup-router-nogw'
RESTORE_RTR_NOGW = 'cutover-restore-router-nogw'
ops = [op.value for op in Operations] ops = [op.value for op in Operations]
@ -270,7 +273,8 @@ nsxv_resources = {
Operations.DELETE.value]), Operations.DELETE.value]),
constants.NSX_MIGRATE_V_T: Resource(constants.NSX_MIGRATE_V_T, constants.NSX_MIGRATE_V_T: Resource(constants.NSX_MIGRATE_V_T,
[Operations.VALIDATE.value, [Operations.VALIDATE.value,
Operations.CUTOVER_MAPPINGS.value]), Operations.CUTOVER_MAPPINGS.value,
Operations.LIST_RTR_NO_IFACE.value]),
constants.PORTS: Resource(constants.PORTS, constants.PORTS: Resource(constants.PORTS,
[Operations.LIST.value]), [Operations.LIST.value]),
constants.LOADBALANCERS: Resource(constants.LOADBALANCERS, constants.LOADBALANCERS: Resource(constants.LOADBALANCERS,
@ -314,7 +318,9 @@ nsxp_resources = {
constants.NSX_MIGRATE_V_T: Resource(constants.NSX_MIGRATE_V_T, constants.NSX_MIGRATE_V_T: Resource(constants.NSX_MIGRATE_V_T,
[Operations.CLEAN_ALL.value, [Operations.CLEAN_ALL.value,
Operations.VALIDATE.value, Operations.VALIDATE.value,
Operations.NSX_REDISTRIBUTE.value]), Operations.NSX_REDISTRIBUTE.value,
Operations.PATCH_RTR_NOGW.value,
Operations.RESTORE_RTR_NOGW.value]),
constants.LOADBALANCERS: Resource(constants.LOADBALANCERS, constants.LOADBALANCERS: Resource(constants.LOADBALANCERS,
[Operations.SET_STATUS_ERROR.value]), [Operations.SET_STATUS_ERROR.value]),
} }