# Copyright 2020 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 copy import sys import netaddr from neutron_lib.callbacks import registry from neutron_lib import context from oslo_log import log as logging from oslo_serialization import jsonutils 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.nsxp.resources import utils as p_utils from vmware_nsx.shell.admin.plugins.nsxv3.resources import migration from vmware_nsx.shell import resources as shell from vmware_nsxlib.v3.policy import constants as policy_constants LOG = logging.getLogger(__name__) @admin_utils.output_header def cleanup_db_mappings(resource, event, trigger, **kwargs): """Delete all entries from nsx-t mapping tables in DB""" # Don't annotate with unpack_payload because this is just a wrapper for # another callback! return migration.MP2Policy_cleanup_db_mappings( resource, event, trigger, **kwargs) @admin_utils.output_header @admin_utils.unpack_payload def post_v2t_migration_cleanups(resource, event, trigger, **kwargs): """Cleanup unneeded migrated resources after v2t migration is done""" nsxpolicy = p_utils.get_connected_nsxpolicy() # Clean all migrated DFW sections sections = nsxpolicy.comm_map.list(policy_constants.DEFAULT_DOMAIN) for section in sections: # Look for the tag marking the migrated sections for tag in section.get('tags', []): if tag['scope'] == 'v_origin': LOG.info("Deleting migrated: %s", tag['tag']) nsxpolicy.comm_map.delete(policy_constants.DEFAULT_DOMAIN, section['id']) continue # Cleanup migrated DVS ports (belong to the edges that are not in use) segments = nsxpolicy.segment.list() for seg in segments: # Skip non-neutron segments if not p_utils.is_neutron_resource(seg): continue ports = nsxpolicy.segment_port.list(seg['id']) # Find the non-neutron ports and delete them for port in ports: if not p_utils.is_neutron_resource(port): nsxpolicy.segment_port.delete(seg['id'], port['id']) LOG.error("Deleted migrated non-neutron port %s", port['id']) @admin_utils.output_header @admin_utils.unpack_payload def migration_tier0_redistribute(resource, event, trigger, **kwargs): """Disable/Restore tier0s route redistribution during V2T migration""" errmsg = ("Need to specify --property action=disable/restore and a comma " "separated tier0 list as --property tier0s") if not kwargs.get('property'): LOG.error("%s", errmsg) return properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) action = properties.get('action') tier0string = properties.get('tier0s') state_filename = properties.get('state-file') if not tier0string or not action or not state_filename: LOG.error("%s", errmsg) return tier0s = tier0string.split(",") nsxpolicy = p_utils.get_connected_nsxpolicy() if action.lower() == 'disable': try: f = open(state_filename, "r") orig_conf_map = jsonutils.loads(f.read()) f.close() except Exception: LOG.info("State file %s not found:", state_filename) orig_conf_map = {} for tier0 in tier0s: # get the current config try: orig_conf = nsxpolicy.tier0.get_route_redistribution_config( tier0) except Exception: LOG.error("Did not find Tier0 %s", tier0) continue if not orig_conf: LOG.info("Tier0 %s does not have route redistribution config", tier0) continue fixed_conf = copy.deepcopy(orig_conf) if (not (orig_conf['bgp_enabled'] or orig_conf['ospf_enabled'] or orig_conf.get('redistribution_rules'))): # Already disabled LOG.info("Tier0 %s route redistribution config was not " "changed because it is disabled", tier0) continue # Check if any of the rules have tier1 flags enabled rule_num = 0 for rule in orig_conf.get('redistribution_rules', []): fixed_types = [] for route_type in rule['route_redistribution_types']: if not route_type.startswith('TIER1'): fixed_types.append(route_type) fixed_conf['redistribution_rules'][rule_num][ 'route_redistribution_types'] = fixed_types rule_num = rule_num + 1 # Save the original config so it can be reverted later orig_conf_map[tier0] = orig_conf fixed_conf['bgp_enabled'] = False fixed_conf['ospf_enabled'] = False nsxpolicy.tier0.update_route_redistribution_config( tier0, fixed_conf) LOG.info("Disabled Tier0 %s route redistribution config for " "Tier1 routes", tier0) f = open(state_filename, "w") f.write("%s" % jsonutils.dumps(orig_conf_map)) f.close() elif action.lower() == 'restore': try: f = open(state_filename, "r") orig_conf_map = jsonutils.loads(f.read()) f.close() except Exception: LOG.warning("State file %s not found:", state_filename) sys.exit(1) for tier0 in tier0s: if tier0 in orig_conf_map: # Restore its original config: try: nsxpolicy.tier0.update_route_redistribution_config( tier0, orig_conf_map[tier0]) LOG.info("Restored Tier0 %s original route redistribution " "config", tier0) except Exception: LOG.error("Failed to update redistribution of Tier0 %s", tier0) else: LOG.info("Tier0 %s route redistribution config was not " "changed", tier0) else: LOG.error("%s", errmsg) sys.exit(1) def _cidrs_overlap(cidr0, cidr1): return cidr0.first <= cidr1.last and cidr1.first <= cidr0.last @admin_utils.output_header @admin_utils.unpack_payload def migration_validate_external_cidrs(resource, event, trigger, **kwargs): """Before V2T migration, validate that the external subnets cidrs do not overlap the tier0 uplinks """ errmsg = ("Need to specify --property ext-net= --property " "ext-cidr=") if not kwargs.get('property'): LOG.error("%s", errmsg) return properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) ext_net_file = properties.get('ext-net') ext_cidr_file = properties.get('ext-cidr') if not ext_net_file or not ext_cidr_file: LOG.error("%s", errmsg) return with open(ext_net_file, 'r') as myfile: # maps external network neutron id to tier0 data = myfile.read() external_networks = jsonutils.loads(data) with open(ext_cidr_file, 'r') as myfile: # maps external network neutron id to its cidr data = myfile.read() external_cidrs = jsonutils.loads(data) nsxpolicy = p_utils.get_connected_nsxpolicy() for net_id in external_cidrs: net_cidr = netaddr.IPNetwork(external_cidrs[net_id]).cidr tier0 = external_networks.get(net_id) if not tier0: LOG.error("Could not find network %s in %s. Please ensure " "external networks are correctly listed in " "migrator configuration ", net_id, ext_net_file) sys.exit(1) else: tier0_cidrs = nsxpolicy.tier0.get_uplink_cidrs(tier0) for cidr in tier0_cidrs: tier0_subnet = netaddr.IPNetwork(cidr).cidr if _cidrs_overlap(tier0_subnet, net_cidr): LOG.error("External subnet of network %s cannot overlap " "with T0 %s uplink cidr %s", net_id, tier0, cidr) sys.exit(1) sys.exit(0) @admin_utils.output_header @admin_utils.unpack_payload 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: data = f.read() state_data = jsonutils.loads(data) 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 @admin_utils.unpack_payload 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: data = f.read() state_data = jsonutils.loads(data) 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, constants.NSX_MIGRATE_T_P, shell.Operations.CLEAN_ALL.value) registry.subscribe(post_v2t_migration_cleanups, constants.NSX_MIGRATE_V_T, shell.Operations.CLEAN_ALL.value) registry.subscribe(migration_tier0_redistribute, constants.NSX_MIGRATE_V_T, shell.Operations.NSX_REDISTRIBUTE.value) registry.subscribe(migration_validate_external_cidrs, constants.NSX_MIGRATE_V_T, 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)