# 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 time import logging import paramiko import tenacity from networking_l2gw.db.l2gateway import l2gateway_models from neutron.extensions import securitygroup as ext_sg from neutron_fwaas.db.firewall.v2 import firewall_db_v2 from neutron_lib.callbacks import registry from neutron_lib import context from oslo_config import cfg from vmware_nsx.common import utils as nsx_utils from vmware_nsx.db import db from vmware_nsx.db import nsx_models from vmware_nsx.plugins.nsx_p import plugin as p_plugin from vmware_nsx.plugins.nsx_v3 import cert_utils from vmware_nsx.plugins.nsx_v3 import plugin as v3_plugin from vmware_nsx.plugins.nsx_v3 import utils as v3_plugin_utils from vmware_nsx.services.fwaas.nsx_p import fwaas_callbacks_v2 from vmware_nsx.services.lbaas import lb_const from vmware_nsx.services.lbaas.nsx_p.implementation import lb_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.nsxp.resources import utils as p_utils from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils from vmware_nsx.shell import resources as shell from vmware_nsxlib.v3 import core_resources as nsx_resources from vmware_nsxlib.v3 import exceptions as nsxlib_exc from vmware_nsxlib.v3 import load_balancer as nsxlib_lb from vmware_nsxlib.v3 import nsx_constants from vmware_nsxlib.v3.policy import constants as policy_constants from vmware_nsxlib.v3.policy import core_resources as policy_resources from vmware_nsxlib.v3.policy import utils as policy_utils from vmware_nsxlib.v3 import security LOG = logging.getLogger(__name__) # Migration statuses POLICY_API_STATUS_FAILED = 'FAILED' POLICY_API_STATUS_SUCCESS = 'SUCCESS' POLICY_API_STATUS_IN_PROGRESS = 'PAUSING' POLICY_API_STATUS_PAUSED = 'PAUSED' POLICY_API_STATUS_READY = 'NOT_STARTED' STATUS_ALLOW_MIGRATION_REQ = set([ POLICY_API_STATUS_SUCCESS, POLICY_API_STATUS_READY ]) # Limits to number of objects that can be migrated in a single payload MIGRATE_LIMIT_NO_LIMIT = 0 MIGRATE_LIMIT_TIER0 = 1 MIGRATE_LIMIT_TIER0_PORTS = 1000 MIGRATE_LIMIT_TIER1 = 1000 MIGRATE_LIMIT_TIER1_PORTS = 1000 MIGRATE_LIMIT_NAT = 1500 MIGRATE_LIMIT_DHCP_SERVER = 1500 MIGRATE_LIMIT_MD_PROXY = 1500 MIGRATE_LIMIT_SWITCH_PROFILE = 1500 MIGRATE_LIMIT_LOGICAL_SWITCH = 500 MIGRATE_LIMIT_LOGICAL_PORT = 1500 MIGRATE_LIMIT_NS_GROUP = 2000 MIGRATE_LIMIT_DFW_SECTION = 3000 MIGRATE_LIMIT_LB_SERVICE = 2000 MIGRATE_LIMIT_LB_VIRTUAL_SERVER = 2000 MIGRATE_LIMIT_LB_MONITOR = 1500 MIGRATE_LIMIT_LB_POOL = 1500 MIGRATE_LIMIT_LB_APP_PROFILE = 2000 MIGRATE_LIMIT_LB_PER_PROFILE = 2000 MIGRATE_LIMIT_CERT = 1500 COMPONENT_STATUS_ALREADY_MIGRATED = 1 COMPONENT_STATUS_OK = 2 ROLLBACK_DATA = [] DFW_SEQ = 1 NSX_ROUTER_SECTIONS = [] SERVICE_UP_RETRIES = 30 END_API_TIMEOUT = 30 * 60 def start_migration_process(nsxlib): """Notify the manager that the migration process is starting""" return nsxlib.client.url_post( "migration/mp-to-policy/workflow?action=INITIATE", None) def end_migration_process(): """Notify the manager that the migration process has ended""" # Using a new nsxlib instance to avoid retries, and add a long timeout nsxlib = _get_nsxlib_from_config(verbose=True, for_end_api=True) return nsxlib.client.url_post( "migration/mp-to-policy/workflow?action=DONE", None) def send_migration_request(nsxlib, body): return nsxlib.client.url_post("migration/mp-to-policy", body) def send_rollback_request(body): #TODO(asarfaty): Rollback can take very long, especially for firewall # sections. In this case backup-restore might be better # Using a new nsxlib instance to avoid retries, and add a long timeout nsxlib = _get_nsxlib_from_config(verbose=True, for_end_api=True) return nsxlib.client.url_post("migration/mp-to-policy/rollback", body) def send_migration_plan_action(nsxlib, action): return nsxlib.client.url_post("migration/plan?action=%s" % action, None) def get_migration_status(nsxlib, silent=False): return nsxlib.client.get("migration/status-summary", silent=silent) def change_migration_service_status(start=True, nsxlib=None): """Enable/Disable the migration service on the NSX manager using SSH command """ action = 'start' if start else 'stop' command = "%s service migration-coordinator" % action LOG.info("Going to %s the migration service on the NSX manager by " "SSHing the manager and running '%s'", action, command) host = cfg.CONF.nsx_v3.nsx_api_managers[0] user = cfg.CONF.nsx_v3.nsx_api_user[0] passwd = cfg.CONF.nsx_v3.nsx_api_password[0] ssh = paramiko.SSHClient() ssh.load_system_host_keys() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(host, username=user, password=passwd) ssh.exec_command(command) if start and nsxlib: LOG.info("Waiting for the service to be up...") start_time = time.time() @tenacity.retry(reraise=True, retry=tenacity.retry_if_exception_type(Exception), wait=tenacity.wait_exponential(multiplier=0.5, max=2), stop=tenacity.stop_after_attempt(SERVICE_UP_RETRIES)) def get_migration_status_with_retry(nsxlib): get_migration_status(nsxlib, silent=True) try: get_migration_status_with_retry(nsxlib) except Exception: raise Exception("The migration service did not get up after %s " "retries" % SERVICE_UP_RETRIES) elapsed_time = time.time() - start_time LOG.info("The service is up (waited %s seconds)", elapsed_time) def ensure_migration_state_ready(nsxlib, with_abort=False): try: status = get_migration_status(nsxlib, silent=True) except Exception as e: if with_abort: change_migration_service_status(start=True, nsxlib=nsxlib) return ensure_migration_state_ready(nsxlib) LOG.debug("Failed to get migration status: %s", e) return False if status["overall_migration_status"] not in STATUS_ALLOW_MIGRATION_REQ: LOG.error("Migration status not ready: %s", status) if with_abort: send_migration_plan_action(nsxlib, 'abort') return ensure_migration_state_ready( nsxlib, with_abort=with_abort) else: return False return True def verify_component_status(nsxlib, component_number): status = get_migration_status(nsxlib) if (status['component_status'][component_number]['status'] == POLICY_API_STATUS_FAILED): # If it's a duplicate migration request, pass the verification if ('is duplicate or already migrated' in status['component_status'][component_number]['details'] and component_number == 0): # Success that indicates resource migration is already done return COMPONENT_STATUS_ALREADY_MIGRATED # bad state. abort, mark as fail, and go to next request raise Exception("The migration server returned with FAILURE status. " "Details: %s", status) # Success return COMPONENT_STATUS_OK def wait_on_overall_migration_status_to_pause(nsxlib): while True: status = get_migration_status(nsxlib) migration_status = status.get('overall_migration_status') if (migration_status == POLICY_API_STATUS_PAUSED or migration_status == POLICY_API_STATUS_SUCCESS): break time.sleep(1) def printable_resource_name(resource): name = resource.get('display_name') if name: try: name = str(name) except UnicodeEncodeError: name = name.encode('ascii', 'ignore') res_id = resource.get('id') if name == res_id: return name return "%s (%s)" % (name, resource.get('id')) def get_resource_migration_data(nsxlib_resource, neutron_id_tags, resource_type, resource_condition=None, printable_name=None, policy_resource_get=None, policy_id_callback=None, metadata_callback=None, skip_policy_path_check=False, nsxlib_list_args=None): """Get the payload for a specific resource by reading all the MP objects, And comparing then to neutron objects, using callbacks to add required information """ if not printable_name: printable_name = resource_type LOG.debug("Getting data for MP %s", printable_name) if nsxlib_list_args: resources = nsxlib_resource.list(**nsxlib_list_args) else: resources = nsxlib_resource.list() if not isinstance(resources, list): # The nsxlib resources list return inconsistent type of result resources = resources.get('results', []) policy_ids = [] entries = [] for resource in resources: name_and_id = printable_resource_name(resource) policy_id = resource['id'] # Go over tags and find the neutron id neutron_id = None found_policy_path = False for tag in resource.get('tags', []): if tag['scope'] == 'policyPath': # This is already a policy resource found_policy_path = True if neutron_id_tags and tag['scope'] in neutron_id_tags: # Found the neutron id neutron_id = tag['tag'] if not skip_policy_path_check and found_policy_path: LOG.debug("Skipping %s %s as it is already a policy " "resource", printable_name, name_and_id) continue if neutron_id_tags: if not neutron_id: # Not a neutron resource LOG.debug("Skipping %s %s as it is not a neutron resource", printable_name, name_and_id) continue policy_id = neutron_id if resource_condition: if not resource_condition(resource): LOG.debug("Skipping %s %s as it does not match the neutron " "condition", printable_name, name_and_id) continue if policy_id_callback: # Callback to change the policy id policy_id = policy_id_callback(resource, policy_id) if policy_id and policy_resource_get: # filter out resources that already exist on policy try: policy_resource_get(policy_id, silent=True) except nsxlib_exc.ResourceNotFound: pass else: LOG.debug("Skipping %s %s as it already exists on the " "policy backend", printable_name, name_and_id) continue # Make sure not to migrate multiple resources to the same policy-id if policy_id: if policy_id in policy_ids: msg = (_("Cannot migrate %(res)s %(name)s to policy-id " "%(id)s: Another %(res)s has the same designated " "policy-id. One of those is probably a neutron " "orphaned. Please delete it and try migration again.") % {'res': printable_name, 'name': name_and_id, 'id': policy_id}) raise Exception(msg) policy_ids.append(policy_id) LOG.debug("Adding data for %s %s, policy-id %s", printable_name, name_and_id, policy_id) entry = {'manager_id': resource['id']} if policy_id: entry['policy_id'] = policy_id if metadata_callback: metadata_callback(entry, policy_id, resource) entries.append(entry) return entries def migrate_objects(nsxlib, data, use_admin=False): # Do the actual migration for a given payload with admin or non admin user if not ensure_migration_state_ready(nsxlib): LOG.error("The migration server is not ready") raise Exception("The migration server is not ready") migration_body = {"migration_data": [data]} # Update the principal identity for the policy resources # use 'admin' for predefined objects, and the openstack configured # user/identity for openstack resources if use_admin: user = 'admin' elif cfg.CONF.nsx_v3.nsx_use_client_auth: user = cert_utils.NSX_OPENSTACK_IDENTITY else: user = cfg.CONF.nsx_v3.nsx_api_user[0] migration_body['setup_details'] = { 'principal_identity': user} LOG.info("Migrating %d %s objects with principal_identity %s", len(data['resource_ids']), data['type'], user) LOG.debug("Migration body : %s", migration_body) send_migration_request(nsxlib, migration_body) # send the start action send_migration_plan_action(nsxlib, 'start') # wait until the overall_migration_status is SUCCESS wait_on_overall_migration_status_to_pause(nsxlib) # verify first component status success_code = verify_component_status(nsxlib, 0) if success_code == COMPONENT_STATUS_ALREADY_MIGRATED: return True # send the continue action send_migration_plan_action(nsxlib, 'continue') # wait until the overall_migration_status is SUCCESS wait_on_overall_migration_status_to_pause(nsxlib) # verify second component status (Will raise in case of error) try: verify_component_status(nsxlib, 1) except Exception as e: raise e else: global ROLLBACK_DATA # rollback should be done in the reverse order ROLLBACK_DATA = [data] + ROLLBACK_DATA return True def migrate_resource(nsxlib, resource_type, entries, limit=MIGRATE_LIMIT_NO_LIMIT, count_internals=False, use_admin=False): """Perform the migration of a specific resource with all its objects. In case there is a limit to the number of object in a single call, divide it to several calls. """ if not entries: LOG.info("No %s to migrate", resource_type) return LOG.info("Going to migrate %d %s objects in groups of max %s", len(entries), resource_type, limit if limit else "UNLIMITED") start_time = time.time() if limit == MIGRATE_LIMIT_NO_LIMIT: migrate_objects(nsxlib, {'type': resource_type, 'resource_ids': entries}, use_admin=use_admin) else: # Call migrate_objects with a partial list of objects depending on # the size of the limit if count_internals: # Limit the total number of resources, including internal ones counter = 0 entries_to_migrate = [] for index in range(0, len(entries)): addition_size = 1 + len(entries[index].get('linked_ids', [])) if addition_size > limit: # Unsupported size of resource raise Exception("%s size is over the allowed limit of " "%s" % (resource_type, limit)) if counter + addition_size > limit: # Migrate what was accumulated so far migrate_objects(nsxlib, {'type': resource_type, 'resource_ids': entries_to_migrate}, use_admin=use_admin) # Start a new accumulation counter = addition_size entries_to_migrate = [entries[index]] else: # Keep accumulating counter = counter + addition_size entries_to_migrate.append(entries[index]) if entries_to_migrate: # Migrate the left overs migrate_objects(nsxlib, {'type': resource_type, 'resource_ids': entries_to_migrate}, use_admin=use_admin) else: for index in range(0, len(entries), limit): migrate_objects(nsxlib, {'type': resource_type, 'resource_ids': entries[index:index + limit]}, use_admin=use_admin) elapsed_time = time.time() - start_time LOG.info("Migrating %d %s objects took %s seconds", len(entries), resource_type, elapsed_time) def get_configured_values(plugin, az_attribute): """Return all configured values of a config param from all the availability zones """ values = [] for az in plugin.get_azs_list(): az_values = getattr(az, az_attribute) if isinstance(az_values, list): values.extend(az_values) else: values.append(az_values) return values def is_neutron_resource(resource): # Return True if the resource has the neutron marking tag for tag in resource.get('tags', []): if tag.get('scope') == 'os-api-version': return True return False def get_neurton_tier0s(plugin): return get_configured_values(plugin, '_default_tier0_router') def migrate_tier0s(nsxlib, nsxpolicy, plugin): # First prepare a list of neutron related tier0s from the config neutron_t0s = get_neurton_tier0s(plugin) # Add tier0s used specifically in external networks ctx = context.get_admin_context() with ctx.session.begin(subtransactions=True): bindings = ctx.session.query( nsx_models.TzNetworkBinding).filter_by( binding_type='l3_ext').all() for bind in bindings: if bind.phy_uuid not in neutron_t0s: neutron_t0s.append(bind.phy_uuid) def cond(resource): # migration condition for Tier0-s return (resource.get('router_type', '') == 'TIER0' and resource.get('id') in neutron_t0s) entries = get_resource_migration_data( nsxlib.logical_router, None, 'TIER0', resource_condition=cond, policy_resource_get=nsxpolicy.tier0.get, nsxlib_list_args={'router_type': nsx_constants.ROUTER_TYPE_TIER0}) migrate_resource(nsxlib, 'TIER0', entries, MIGRATE_LIMIT_TIER0, use_admin=True) migrated_tier0s = [entry['manager_id'] for entry in entries] # Create a list of public switches connected to the tier0s to migrate later public_switches = [] for tier0 in neutron_t0s: uplink_port = nsxlib.logical_router_port.get_tier0_uplink_port(tier0) if uplink_port: # Get the external LS id from the uplink port linked_ls_id = uplink_port.get('linked_logical_switch_port_id') if linked_ls_id: try: port_id = linked_ls_id['target_id'] port = nsxlib.logical_port.get(port_id) public_switches.append(port['logical_switch_id']) except KeyError as e: LOG.info("Required attribute missing: %s. Cannot " "add public switch to objects to migrate", e) else: LOG.info("Uplink port %s for Tier0 router %s is not " "connected to a logical switch", tier0, uplink_port['id']) return public_switches, migrated_tier0s def migrate_switch_profiles(nsxlib, nsxpolicy, plugin): """Return all types of neutron switching profiles""" # Build a condition for each type of switching profiles. # Note(asarfaty): system owned profiles should also be migrated as they are # missing from policy # Include switch profiles that are in the nsx.ini conf_profiles = get_configured_values(plugin, 'switching_profiles') # Add other switch profiles that might be used by neutron ports in the past port_profiles = set() ports = nsxlib.logical_port.list()['results'] for port in ports: # Check that it is a neutron port if not is_neutron_resource(port): continue for prof in port.get('switching_profile_ids', []): port_profiles.add(prof['value']) # Black list neuron & system profiles that should not be migrated names_black_list = [v3_plugin_utils.NSX_V3_DHCP_PROFILE_NAME, 'ServiceInsertion_MacManagement_Profile'] def get_cond(resource_type): def cond(resource): return (resource.get('resource_type') == resource_type and resource.get('display_name') not in names_black_list and (resource.get('id') in conf_profiles or resource.get('id') in port_profiles or resource.get('_system_owned', True) or is_neutron_resource(resource))) return cond def get_policy_id_callback(res, policy_id): """Callback to decide the profile policy id""" # In case of plugin init profiles: give it the id the policy plugin # will use mapping = {v3_plugin.NSX_V3_MAC_LEARNING_PROFILE_NAME: p_plugin.MAC_DISCOVERY_PROFILE_ID, v3_plugin_utils.NSX_V3_PSEC_PROFILE_NAME: p_plugin.SPOOFGUARD_PROFILE_ID} if mapping.get(res.get('display_name')): return mapping[res['display_name']] # QoS profiles should get the neutron policy id for tag in res.get('tags', []): if tag['scope'] == 'os-neutron-qos-id': policy_id = tag['tag'] return policy_id # Migrate each type of profile entries = get_resource_migration_data( nsxlib.switching_profile, None, 'SPOOFGUARD_PROFILES', resource_condition=get_cond( nsx_resources.SwitchingProfileTypes.SPOOF_GUARD), policy_resource_get=nsxpolicy.spoofguard_profile.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'SPOOFGUARD_PROFILES', entries, MIGRATE_LIMIT_SWITCH_PROFILE) entries = get_resource_migration_data( nsxlib.switching_profile, None, 'MACDISCOVERY_PROFILES', resource_condition=get_cond( nsx_resources.SwitchingProfileTypes.MAC_LEARNING), policy_resource_get=nsxpolicy.mac_discovery_profile.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'MACDISCOVERY_PROFILES', entries, MIGRATE_LIMIT_SWITCH_PROFILE) entries = get_resource_migration_data( nsxlib.switching_profile, None, 'SEGMENT_SECURITY_PROFILES', resource_condition=get_cond( nsx_resources.SwitchingProfileTypes.SWITCH_SECURITY), policy_resource_get=nsxpolicy.segment_security_profile.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'SEGMENT_SECURITY_PROFILES', entries, MIGRATE_LIMIT_SWITCH_PROFILE) entries = get_resource_migration_data( nsxlib.switching_profile, None, 'QOS_PROFILES', resource_condition=get_cond( nsx_resources.SwitchingProfileTypes.QOS), policy_resource_get=nsxpolicy.qos_profile.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'QOS_PROFILES', entries, MIGRATE_LIMIT_SWITCH_PROFILE) entries = get_resource_migration_data( nsxlib.switching_profile, None, 'IPDISCOVERY_PROFILES', resource_condition=get_cond( nsx_resources.SwitchingProfileTypes.IP_DISCOVERY), policy_resource_get=nsxpolicy.ip_discovery_profile.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'IPDISCOVERY_PROFILES', entries, MIGRATE_LIMIT_SWITCH_PROFILE) def migrate_md_proxies(nsxlib, nsxpolicy, plugin): neutron_md = get_configured_values(plugin, '_native_md_proxy_uuid') # Add other mdproxies that might be used by neutron networks in the past ports = nsxlib.logical_port.list()['results'] for port in ports: # Check that it is a neutron port if not is_neutron_resource(port): continue if (port.get('attachment') and port['attachment'].get('attachment_type') == 'METADATA_PROXY'): mdproxy_id = port['attachment'].get('id') if mdproxy_id not in neutron_md: neutron_md.append(port['attachment'].get('id')) # make sure to migrate all certificates used by those MD proxies certificates = [] for md_id in neutron_md: md_resource = nsxlib.native_md_proxy.get(md_id) certificates.extend(md_resource.get('metadata_server_ca_ids', [])) if certificates: def cert_cond(resource): return resource.get('id') in certificates entries = get_resource_migration_data( nsxlib.trust_management, None, 'CERTIFICATE', resource_condition=cert_cond, policy_resource_get=nsxpolicy.certificate.get) migrate_resource(nsxlib, 'CERTIFICATE', entries, MIGRATE_LIMIT_CERT) # Now migrate the MD proxies def cond(resource): return resource.get('id') in neutron_md entries = get_resource_migration_data( nsxlib.native_md_proxy, None, 'METADATA_PROXY', resource_condition=cond, policy_resource_get=nsxpolicy.md_proxy.get) migrate_resource(nsxlib, 'METADATA_PROXY', entries, MIGRATE_LIMIT_MD_PROXY, use_admin=True) def migrate_networks(nsxlib, nsxpolicy, plugin, public_switches): # Get a list of nsx-net provider networks to migrate # Those networks have no tags, and should keep the same id in policy nsx_networks = [] ctx = context.get_admin_context() with ctx.session.begin(subtransactions=True): bindings = ctx.session.query( nsx_models.TzNetworkBinding).filter_by( binding_type=nsx_utils.NsxV3NetworkTypes.NSX_NETWORK).all() for bind in bindings: nsx_networks.append(bind.phy_uuid) def cond(resource): # migrate external network, nsx provider networks and neutron networks return (resource.get('id', '') in nsx_networks or resource.get('id', '') in public_switches or is_neutron_resource(resource)) def get_policy_id(resource, policy_id): if resource['id'] in nsx_networks: # Keep original ID return resource['id'] if resource['id'] in public_switches: # Keep original ID return resource['id'] for tag in resource.get('tags', []): # Use the neutron ID if tag['scope'] == 'os-neutron-net-id': return tag['tag'] def add_metadata(entry, policy_id, resource): # Add dhcp-v4 static bindings for each network network_id = None for tag in resource.get('tags', []): # Use the neutron ID if tag['scope'] == 'os-neutron-net-id': network_id = tag['tag'] break if not network_id: return metadata = [] ctx = context.get_admin_context() port_filters = {'network_id': [network_id]} network_ports = plugin.get_ports(ctx, filters=port_filters) for port in network_ports: bindings = db.get_nsx_dhcp_bindings(ctx.session, port['id']) if bindings: # Should be only 1 metadata.append({ 'key': 'v4-static-binding%s' % bindings[0].nsx_binding_id, 'value': port['id'] + '-ipv4'}) entry['metadata'] = metadata entries = get_resource_migration_data( nsxlib.logical_switch, [], 'LOGICAL_SWITCH', resource_condition=cond, policy_resource_get=nsxpolicy.segment.get, policy_id_callback=get_policy_id, metadata_callback=add_metadata) migrate_resource(nsxlib, 'LOGICAL_SWITCH', entries, MIGRATE_LIMIT_LOGICAL_SWITCH) migrated_networks = [entry['manager_id'] for entry in entries] return migrated_networks def migrate_ports(nsxlib, nsxpolicy, plugin, migrated_networks): """Migrate all the neutron ports by network""" # For nsx networks support, keep a mapping of neutron id and MP id nsx_networks = {} ctx = context.get_admin_context() with ctx.session.begin(subtransactions=True): bindings = ctx.session.query( nsx_models.TzNetworkBinding).filter_by( binding_type='nsx-net').all() for bind in bindings: nsx_networks[bind.network_id] = bind.phy_uuid def get_policy_port(port_id, silent=False): """Getter to check if the port already exists on the backend""" # First get the segment id from neutron ctx = context.get_admin_context() neutron_port = plugin.get_port(ctx, port_id) net_id = neutron_port['network_id'] if net_id in nsx_networks: segment_id = nsx_networks[net_id] else: segment_id = net_id # Now get the nsx port using the segment id return nsxpolicy.segment_port.get(segment_id, port_id, silent=silent) def add_metadata(entry, policy_id, resource): # Add binding maps with 'DEFAULT' key entry['metadata'] = [{'key': 'security-profile-binding-maps-id', 'value': policy_resources.DEFAULT_MAP_ID}, {'key': 'discovery-profile-binding-maps-id', 'value': policy_resources.DEFAULT_MAP_ID}, {'key': 'qos-profile-binding-maps-id', 'value': policy_resources.DEFAULT_MAP_ID}] nsx_version = nsxlib.get_version() if nsx_utils.is_nsx_version_3_2_0(nsx_version): # separate call per neutron network class portResource(object): def list(self, **kwargs): # Mock the list command to do list by network return nsxlib.logical_port.get_by_logical_switch( kwargs.get('logical_switch_id')) nsxlib_ls_mock = portResource() # Call migration per network for nsx_ls_id in migrated_networks: entries = get_resource_migration_data( nsxlib_ls_mock, ['os-neutron-port-id'], 'LOGICAL_PORT', policy_resource_get=get_policy_port, metadata_callback=add_metadata, nsxlib_list_args={'logical_switch_id': nsx_ls_id}) migrate_resource(nsxlib, 'LOGICAL_PORT', entries, MIGRATE_LIMIT_NO_LIMIT) else: # migrate all ports together split up by the limit entries = get_resource_migration_data( nsxlib.logical_port, ['os-neutron-port-id'], 'LOGICAL_PORT', policy_resource_get=get_policy_port, metadata_callback=add_metadata) migrate_resource(nsxlib, 'LOGICAL_PORT', entries, MIGRATE_LIMIT_LOGICAL_PORT) def migrate_routers(nsxlib, nsxpolicy): """Migrate neutron Tier-1 routers""" entries = get_resource_migration_data( nsxlib.logical_router, ['os-neutron-router-id'], 'TIER1', policy_resource_get=nsxpolicy.tier1.get, nsxlib_list_args={'router_type': nsx_constants.ROUTER_TYPE_TIER1}) migrate_resource(nsxlib, 'TIER1', entries, MIGRATE_LIMIT_TIER1) migrated_routers = [entry['manager_id'] for entry in entries] return migrated_routers def _get_subnet_id_by_cidr(subnets, cidr): for subnet in subnets: if subnet['cidr'] == cidr: return subnet['id'] def migrate_routers_config(nsxlib, nsxpolicy, plugin, migrated_routers): """Migrate advanced configuration of neutron Tier-1s This will use the list of Tier-1s migrated earlier """ # Migrate all the centralized router ports and static routes for tier1 # routers without specifying ids def get_policy_id(resource, policy_id): # No policy id needed here return def cond(resource): # Import ports only for the routers that were currently migrated # because there is no easy way to verify what was already migrated return resource['id'] in migrated_routers def add_metadata(entry, policy_id, resource): # Add router interfaces Ids ctx = context.get_admin_context() metadata = [] mp_rtr_id = resource['id'] router_ports = nsxlib.logical_router_port.get_by_router_id(mp_rtr_id) for port in router_ports: if 'linked_logical_switch_port_id' in port: lsp_id = port['linked_logical_switch_port_id']['target_id'] lsp = nsxlib.logical_port.get(lsp_id) ls_id = lsp['logical_switch_id'] if ls_id: neutron_net_ids = plugin._get_neutron_net_ids_by_nsx_id( ctx, ls_id) if neutron_net_ids: # Should be only 1 metadata.append({'key': port['id'], 'value': neutron_net_ids[0]}) # Add static routes ids static_routes = nsxlib.logical_router.list_static_routes( mp_rtr_id)['results'] for route in static_routes: policy_id = "%s-%s" % (route['network'].replace('/', '_'), route['next_hops'][0]['ip_address']) metadata.append({'key': route['id'], 'value': policy_id}) # Add locale-service id as -0 policy_id = None for tag in resource.get('tags', []): if tag['scope'] == 'os-neutron-router-id': policy_id = tag['tag'] if policy_id: metadata.append({'key': 'localeServiceId', 'value': "%s-0" % policy_id}) entry['metadata'] = metadata entries = get_resource_migration_data( nsxlib.logical_router, ['os-neutron-router-id'], 'TIER1_LOGICAL_ROUTER_PORT', policy_id_callback=get_policy_id, resource_condition=cond, metadata_callback=add_metadata, skip_policy_path_check=True, nsxlib_list_args={'router_type': nsx_constants.ROUTER_TYPE_TIER1}) migrate_resource(nsxlib, 'TIER1_LOGICAL_ROUTER_PORT', entries, MIGRATE_LIMIT_TIER1_PORTS) # Migrate NAT rules per neutron tier1 entries = [] tier1s = nsxlib.logical_router.list( router_type=nsx_constants.ROUTER_TYPE_TIER1)['results'] ctx = context.get_admin_context() for tier1 in tier1s: # skip routers that were not migrated in this script call tier1_mp_id = tier1['id'] if tier1_mp_id not in migrated_routers: continue # skip non-neutron routers tier1_neutron_id = None for tag in tier1.get('tags', []): if tag['scope'] == 'os-neutron-router-id': tier1_neutron_id = tag['tag'] break if not tier1_neutron_id: continue # Migrate each existing NAT rule, with the parameters the policy # plugin would have set router_subnets = plugin._load_router_subnet_cidrs_from_db( ctx, tier1_neutron_id) nat_rules = nsxlib.logical_router.list_nat_rules( tier1_mp_id)['results'] for rule in nat_rules: # NO_DNAT rules for subnets if rule['action'] == 'NO_DNAT': seq_num = p_plugin.NAT_RULE_PRIORITY_GW cidr = rule['match_destination_network'] subnet_id = _get_subnet_id_by_cidr(router_subnets, cidr) if not subnet_id: LOG.error("Could not find subnet with cidr %s matching " "NO_DNAT rule %s tier1 %s", cidr, rule['id'], tier1_neutron_id) continue policy_id = 'ND-' + subnet_id # SNAT rules for subnet or fip elif rule['action'] == 'SNAT': cidr = rule['match_source_network'] if '/' in cidr: seq_num = p_plugin.NAT_RULE_PRIORITY_GW subnet_id = _get_subnet_id_by_cidr(router_subnets, cidr) if not subnet_id: LOG.error("Could not find subnet with cidr %s " "matching SNAT rule %s tier1 %s", cidr, rule['id'], tier1_neutron_id) continue policy_id = 'S-' + subnet_id else: # FIP rule seq_num = p_plugin.NAT_RULE_PRIORITY_FIP fip_ip = rule['translated_network'] filters = {'floating_ip_address': [fip_ip]} fips = plugin.get_floatingips(ctx, filters) if not fips: LOG.error("Could not find FIP with ip %s matching " "SNAT rule %s tier1 %s", fip_ip, rule['id'], tier1_neutron_id) continue policy_id = 'S-' + fips[0]['id'] # DNAT rules for fip elif rule['action'] == 'DNAT': # FIP rule seq_num = p_plugin.NAT_RULE_PRIORITY_FIP fip_ip = rule['match_destination_network'] filters = {'floating_ip_address': [fip_ip]} fips = plugin.get_floatingips(ctx, filters) if not fips: LOG.error("Could not find FIP with ip %s matching DNAT " "rule %s tier1 %s", fip_ip, rule['id'], tier1_neutron_id) continue policy_id = 'D-' + fips[0]['id'] else: LOG.error("Unknown NAT action %s for rule %s tier1 %s", rule['action'], rule['id'], tier1_neutron_id) continue entry = {'manager_id': rule['id'], 'policy_id': policy_id, 'metadata': [{'key': 'SEQUENCE_NUMBER', 'value': seq_num}], 'linked_ids': [{'key': 'TIER1', 'value': tier1_mp_id}]} entries.append(entry) migrate_resource(nsxlib, 'NAT', entries, MIGRATE_LIMIT_NAT) def migrate_tier0_config(nsxlib, nsxpolicy, tier0s): """Migrate ports and config for the already migrated Tier0-s""" entries = [] for tier0 in tier0s: uplink_ports = nsxlib.logical_router_port.get_tier0_uplink_ports(tier0) for uplink_port in uplink_ports: entries.append({'manager_id': uplink_port['id']}) migrate_resource(nsxlib, 'TIER0_LOGICAL_ROUTER_PORT', entries, MIGRATE_LIMIT_TIER0_PORTS, use_admin=True) def get_policy_id(resource, policy_id): # No policy id needed here return def cond(resource): # Import config only for the routers that were currently migrated # because there is no easy way to verify what was already migrated return resource['id'] in tier0s entries = get_resource_migration_data( nsxlib.logical_router, [], 'TIER0_LOGICAL_ROUTER_CONFIG', policy_id_callback=get_policy_id, resource_condition=cond, skip_policy_path_check=True, nsxlib_list_args={'router_type': nsx_constants.ROUTER_TYPE_TIER0}) migrate_resource(nsxlib, 'TIER0_LOGICAL_ROUTER_CONFIG', entries, MIGRATE_LIMIT_TIER0, use_admin=True) def migrate_groups(nsxlib, nsxpolicy): """Migrate NS groups of neutron defined security groups and groups predefined at plugin init """ def get_policy_id_callback(res, policy_id): # In case of plugin init groups: give it the id the policy plugin # will use if res.get('display_name') == \ v3_plugin.NSX_V3_FW_DEFAULT_NS_GROUP: return p_plugin.NSX_P_DEFAULT_GROUP if res.get('display_name') == \ v3_plugin.NSX_V3_EXCLUDED_PORT_NSGROUP_NAME: return p_plugin.NSX_P_EXCLUDE_LIST_GROUP return policy_id def get_policy_group(group_id, silent=False): return nsxpolicy.group.get(policy_constants.DEFAULT_DOMAIN, group_id, silent=silent) entries = get_resource_migration_data( nsxlib.ns_group, ['os-neutron-secgr-id', 'os-neutron-id'], 'NS_GROUP', policy_resource_get=get_policy_group, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'NS_GROUP', entries, MIGRATE_LIMIT_NS_GROUP) def migrate_dfw_sections(nsxlib, nsxpolicy, plugin): def get_policy_id_callback(res, policy_id): # In case of plugin init section: give it the id the policy plugin # will use if res.get('display_name') == \ v3_plugin.NSX_V3_FW_DEFAULT_SECTION: return p_plugin.NSX_P_DEFAULT_SECTION return policy_id def add_metadata(entry, policy_id, resource): # Add category, sequence, domain, and rule ids ctx = context.get_admin_context() category = p_plugin.NSX_P_REGULAR_SECTION_CATEGORY if policy_id == p_plugin.NSX_P_DEFAULT_SECTION: category = p_plugin.NSX_P_DEFAULT_SECTION_CATEGORY else: try: sg = plugin.get_security_group(ctx, policy_id) except ext_sg.SecurityGroupNotFound: LOG.warning("Neutron SG %s was not found. Section %s may be " "an orphaned", policy_id, resource['id']) provider = False else: provider = sg.get('provider') if provider: category = p_plugin.NSX_P_PROVIDER_SECTION_CATEGORY global DFW_SEQ metadata = [{'key': "category", 'value': category}, {'key': "sequence", 'value': str(DFW_SEQ)}] DFW_SEQ = DFW_SEQ + 1 # Add the rules rules = nsxlib.firewall_section.get_rules(resource['id'])['results'] linked_ids = [] seq = 1 for rule in rules: linked_ids.append({'key': rule['id'], 'value': str(seq)}) if policy_id == p_plugin.NSX_P_DEFAULT_SECTION: # Default section rule ids are their seq numbers linked_ids.append({'key': "%s-policyid" % rule['id'], 'value': seq}) else: # The display name of the MP rule is the neutron id, and this # will become the policy id linked_ids.append({'key': "%s-policyid" % rule['id'], 'value': rule['display_name']}) seq = seq + 1 entry['metadata'] = metadata entry['linked_ids'] = linked_ids def get_policy_section(sec_id, silent=False): return nsxpolicy.comm_map.get(policy_constants.DEFAULT_DOMAIN, sec_id, silent=silent) def dfw_migration_cond(resource): return (resource.get('enforced_on') == 'VIF' and resource.get('category') == 'Default' and resource.get('section_type') == 'LAYER3' and not resource.get('is_default') and # Migrate only DFW sections only and no edge FW sections 'applied_tos' in resource and resource['applied_tos'][0].get('target_type', '') == 'NSGroup') entries = get_resource_migration_data( nsxlib.firewall_section, ['os-neutron-secgr-id', 'os-neutron-id'], 'DFW_SECTION', resource_condition=dfw_migration_cond, policy_resource_get=get_policy_section, policy_id_callback=get_policy_id_callback, metadata_callback=add_metadata) migrate_resource(nsxlib, 'DFW_SECTION', entries, MIGRATE_LIMIT_DFW_SECTION, count_internals=False) def migrate_edge_firewalls(nsxlib, nsxpolicy, plugin): # Migrate edge firewall sections: # The MP plugin uses the default MP edge firewall section, while the policy # plugin uses a non default one, so regular migration cannot be used. # Instead, create new edge firewall sections, and remove rules from the MP # default sections # This is a hack to use the v3 plugin with the policy fwaas driver class MigrationNsxpFwaasCallbacks(fwaas_callbacks_v2.NsxpFwaasCallbacksV2): def __init__(self, with_rpc): super(MigrationNsxpFwaasCallbacks, self).__init__(with_rpc) # Make sure fwaas is considered as enabled self.fwaas_enabled = True def _get_port_firewall_group_id(self, context, port_id): # Override this api because directory.get_plugin does not work from # admin utils context. driver_db = firewall_db_v2.FirewallPluginDb() return driver_db.get_fwg_attached_to_port(context, port_id) fwaas_callbacks = MigrationNsxpFwaasCallbacks(False) plugin.nsxpolicy = nsxpolicy ctx = context.get_admin_context() routers = plugin.get_routers(ctx) global NSX_ROUTER_SECTIONS for rtr in routers: nsx_router_id = db.get_nsx_router_id(ctx.session, rtr['id']) nsx_rtr = nsxlib.logical_router.get(nsx_router_id) for sec in nsx_rtr.get('firewall_sections', []): section_id = sec['target_id'] section = nsxlib.firewall_section.get(section_id) if section['display_name'] != 'Default LR Layer3 Section': continue rules = nsxlib.firewall_section.get_rules(section_id)['results'] if len(rules) <= 1: continue # Non default rules exist. need to migrate this section router_db = plugin._get_router(ctx, rtr['id']) ports = plugin._get_router_interfaces(ctx, rtr['id']) fwaas_callbacks.update_router_firewall( ctx, rtr['id'], router_db, ports) LOG.debug("Created GW policy for router %s", rtr['id']) # delete rule from the default mp section at the end of the loop # so the new section will have time to realize NSX_ROUTER_SECTIONS.append({'id': section_id, 'default_rule': rules[-1], 'router_id': rtr['id']}) def migrate_dhcp_servers(nsxlib, nsxpolicy): # Each MP DHCP server will be migrated to a policy DHCP server config # which will be used by a segment later. It will get the neutron network id entries = get_resource_migration_data( nsxlib.dhcp_server, ['os-neutron-net-id'], 'DHCP_SERVER', policy_resource_get=nsxpolicy.dhcp_server_config.get) migrate_resource(nsxlib, 'DHCP_SERVER', entries, MIGRATE_LIMIT_DHCP_SERVER) def migrate_lb_resources(nsxlib, nsxpolicy): migrate_lb_certificates(nsxlib, nsxpolicy) migrate_lb_monitors(nsxlib, nsxpolicy) migrate_lb_pools(nsxlib, nsxpolicy) migrate_lb_profiles(nsxlib, nsxpolicy) migrate_lb_listeners(nsxlib, nsxpolicy) migrate_lb_services(nsxlib, nsxpolicy) def migrate_lb_certificates(nsxlib, nsxpolicy): entries = get_resource_migration_data( nsxlib.trust_management, [lb_const.LB_LISTENER_TYPE], 'CERTIFICATE', policy_resource_get=nsxpolicy.certificate.get) migrate_resource(nsxlib, 'CERTIFICATE', entries, MIGRATE_LIMIT_CERT) def _migrate_lb_resource(nsxlib, nsxpolicy, neutron_tag, api_name, migration_name, limit, policy_api_name=None, policy_id_callback=None): if not policy_api_name: policy_api_name = api_name entries = get_resource_migration_data( getattr(nsxlib.load_balancer, api_name), [neutron_tag], migration_name, policy_resource_get=getattr(nsxpolicy.load_balancer, policy_api_name).get, policy_id_callback=policy_id_callback) migrate_resource(nsxlib, migration_name, entries, limit) def migrate_lb_listeners(nsxlib, nsxpolicy): _migrate_lb_resource(nsxlib, nsxpolicy, lb_const.LB_LISTENER_TYPE, 'virtual_server', 'LB_VIRTUAL_SERVER', MIGRATE_LIMIT_LB_VIRTUAL_SERVER) def migrate_lb_pools(nsxlib, nsxpolicy): _migrate_lb_resource(nsxlib, nsxpolicy, lb_const.LB_POOL_TYPE, 'pool', 'LB_POOL', MIGRATE_LIMIT_LB_POOL, policy_api_name='lb_pool') def migrate_lb_monitors(nsxlib, nsxpolicy): _migrate_lb_resource(nsxlib, nsxpolicy, lb_const.LB_HM_TYPE, 'monitor', 'LB_MONITOR', MIGRATE_LIMIT_LB_MONITOR, policy_api_name='lb_monitor_profile_http') def migrate_lb_profiles(nsxlib, nsxpolicy): _migrate_lb_resource(nsxlib, nsxpolicy, lb_const.LB_LISTENER_TYPE, 'application_profile', 'LB_APPLICATION_PROFILE', MIGRATE_LIMIT_LB_APP_PROFILE, policy_api_name='lb_http_profile') def get_policy_id_callback(res, policy_id): # The input policy id is the pool id # Need to add a suffix regarding the type of persistence if (res.get('resource_type') == nsxlib_lb.PersistenceProfileTypes.SOURCE_IP): return "%s_%s" % (policy_id, 'sourceip') else: return "%s_%s" % (policy_id, 'cookie') _migrate_lb_resource(nsxlib, nsxpolicy, lb_const.LB_POOL_TYPE, 'persistence_profile', 'LB_PERSISTENCE_PROFILE', MIGRATE_LIMIT_LB_PER_PROFILE, policy_api_name='lb_persistence_profile', policy_id_callback=get_policy_id_callback) def migrate_lb_services(nsxlib, nsxpolicy): def get_policy_id_callback(res, policy_id): # LB service is shared between few octavia loadbalancers # so the policy id is not the LB id, and those should be marked # in the tags of the policy resource. # Keep the same id as MP so later we can search the MP DB # and update the tags return res['id'] entries = get_resource_migration_data( nsxlib.load_balancer.service, ['os-api-version'], 'LB_SERVICE', policy_resource_get=nsxpolicy.load_balancer.lb_service.get, policy_id_callback=get_policy_id_callback) migrate_resource(nsxlib, 'LB_SERVICE', entries, MIGRATE_LIMIT_LB_SERVICE) def migrate_t_resources_2_p(nsxlib, nsxpolicy, plugin, start_migration_service=False): """Create policy resources for all MP resources used by neutron""" # Initialize the migration process if not ensure_migration_state_ready( nsxlib, with_abort=start_migration_service): LOG.error("The migration coordinator service is not ready. " "Please start it and try again.") return False try: LOG.info("Starting resources migration") start_migration_process(nsxlib) # Migration order derives from the dependencies between resources public_switches, tier0s = migrate_tier0s(nsxlib, nsxpolicy, plugin) migrate_md_proxies(nsxlib, nsxpolicy, plugin) migrate_switch_profiles(nsxlib, nsxpolicy, plugin) migrate_groups(nsxlib, nsxpolicy) migrate_dhcp_servers(nsxlib, nsxpolicy) mp_routers = migrate_routers(nsxlib, nsxpolicy) mp_networks = migrate_networks(nsxlib, nsxpolicy, plugin, public_switches) migrate_ports(nsxlib, nsxpolicy, plugin, mp_networks) migrate_routers_config(nsxlib, nsxpolicy, plugin, mp_routers) migrate_tier0_config(nsxlib, nsxpolicy, tier0s) migrate_lb_resources(nsxlib, nsxpolicy) # Migrate firewall sections last as those take the longest to rollback # in case of error migrate_dfw_sections(nsxlib, nsxpolicy, plugin) migrate_edge_firewalls(nsxlib, nsxpolicy, plugin) # Finalize the migration (cause policy realization) end_migration_process() # Stop the migration service if start_migration_service: change_migration_service_status(start=False) return True except Exception as e: # Migration failed - abort it LOG.error("Exception occurred while making the request: %s", e) try: LOG.info("Aborting the current request") try: send_migration_plan_action(nsxlib, 'abort') except Exception as e: LOG.error("Abort migration failed: %s", e) global ROLLBACK_DATA if ROLLBACK_DATA: LOG.info("Rolling migration back %s", ROLLBACK_DATA) send_rollback_request({'migration_data': ROLLBACK_DATA}) LOG.info("Roll back done.") # Finalize the migration (Also needed after rollback) end_migration_process() # Stop the migration service if start_migration_service: change_migration_service_status(start=False) except Exception as e: LOG.error("Rollback failed: %s", e) return False def _get_network_nsx_segment_id(ctx, net_id): bindings = db.get_network_bindings(ctx.session, net_id) if (bindings and bindings[0].binding_type == nsx_utils.NsxV3NetworkTypes.NSX_NETWORK): # return the ID of the NSX network return bindings[0].phy_uuid return net_id def _delete_segment_profiles_bindings(nsxpolicy, segment_id): found = False sec_profiles = nsxpolicy.segment_security_profile_maps.list(segment_id) for profile in sec_profiles: found = True nsxpolicy.segment_security_profile_maps.delete( segment_id, profile['id']) qos_profiles = nsxpolicy.segment_qos_profile_maps.list(segment_id) for profile in qos_profiles: found = True nsxpolicy.segment_qos_profile_maps.delete( segment_id, profile['id']) discovery_profiles = nsxpolicy.segment_discovery_profile_maps.list( segment_id) for profile in discovery_profiles: found = True nsxpolicy.segment_discovery_profile_maps.delete( segment_id, profile['id']) if found: LOG.debug("Removed profiles mappings from segment %s", segment_id) def post_migration_actions(nsxlib, nsxpolicy, nsxpolicy_admin, plugin): """Update created policy resources that does not match the policy plugins' expectations. """ LOG.info("Starting post-migration actions") ctx = context.get_admin_context() # -- Amend default security group criteria mp_scope_and_tag = "%s|%s" % (security.PORT_SG_SCOPE, v3_plugin.NSX_V3_DEFAULT_SECTION) p_scope_and_tag = "%s|" % (p_plugin.NSX_P_PORT_RESOURCE_TYPE) mp_condition = nsxpolicy.group.build_condition( cond_val=mp_scope_and_tag, cond_key=policy_constants.CONDITION_KEY_TAG, cond_member_type=policy_constants.CONDITION_MEMBER_PORT) p_condition = nsxpolicy.group.build_condition( cond_val=p_scope_and_tag, cond_key=policy_constants.CONDITION_KEY_TAG, cond_member_type=policy_constants.CONDITION_MEMBER_PORT) final_conditions = nsxpolicy.group.build_union_condition( conditions=[mp_condition, p_condition]) nsxpolicy.group.update_with_conditions( p_plugin.NSX_P_GLOBAL_DOMAIN_ID, p_plugin.NSX_P_DEFAULT_GROUP, conditions=final_conditions) LOG.info("Match criteria for default SG group updated") # -- Update Lb tags on loadbalancer service pol_lb_services = nsxpolicy.load_balancer.lb_service.list() for lb_srv in pol_lb_services: # Verify this is a neutron resource if not is_neutron_resource(lb_srv): continue # Check if it already has the LB id tag migrated = False for tag in lb_srv.get('tags', []): if tag['scope'] == lb_utils.SERVICE_LB_TAG_SCOPE: migrated = True break if migrated: continue # Find the loadbalancers using this service from the DB lb_mapping = db.get_nsx_lbaas_loadbalancer_binding_by_service( ctx.session, lb_srv['id']) if lb_mapping: if 'tags' not in lb_srv: lb_srv['tags'] = [] loadbalancers = [lb_map.loadbalancer_id for lb_map in lb_mapping] for lb_id in loadbalancers: lb_srv['tags'].append({'scope': lb_utils.SERVICE_LB_TAG_SCOPE, 'tag': lb_id}) nsxpolicy.load_balancer.lb_service.update( lb_srv['id'], tags=lb_srv['tags']) LOG.debug("Added tags to LB service %s", lb_srv['id']) # -- Update Lb L7 rules names mp_lb_rules = nsxlib.load_balancer.rule.list()['results'] for mp_rule in mp_lb_rules: l7pol_id = None listener_id = None for tag in mp_rule.get('tags', []): if tag['scope'] == lb_const.LB_L7POLICY_TYPE: l7pol_id = tag['tag'] if tag['scope'] == 'policyPath': listener_id = policy_utils.path_to_id(tag['tag']) if not l7pol_id or not listener_id: continue pol_vs = nsxpolicy.load_balancer.virtual_server.get(listener_id) pol_rules = pol_vs['rules'] for pol_rule in pol_rules: if pol_rule['display_name'] == mp_rule['id']: new_name = nsx_utils.get_name_and_uuid('policy', l7pol_id) pol_rule['display_name'] = new_name nsxpolicy.load_balancer.virtual_server.update_lb_rules( listener_id, pol_rules) LOG.debug("Updated L7 policy %s name on the virtual server", l7pol_id) break # -- Create DHCP server configs to be used in neutron config # (The migration did not migrate MP DHCP profiles) neutron_dhcp = get_configured_values(plugin, '_native_dhcp_profile_uuid') for mp_dhcp in neutron_dhcp: # check if it was already migrated try: nsxpolicy.dhcp_server_config.get(mp_dhcp, silent=True) except Exception: # Create it mp_obj = nsxlib.native_dhcp_profile.get(mp_dhcp) # This should be created with the admin principal identity nsxpolicy_admin.dhcp_server_config.create_or_overwrite( mp_obj['display_name'], config_id=mp_dhcp, description=mp_obj.get('description', ''), edge_cluster_path=nsxpolicy.edge_cluster.get_path( mp_obj['edge_cluster_id'])) LOG.debug("Created DHCP server config %s for plugin config", mp_dhcp) # -- Update Policy segments: # Set subnets GW for networks without linked routers # And remove unused segment profiles mappings networks = plugin.get_networks(ctx) for net in networks: if net.get('router:external'): continue seg_id = _get_network_nsx_segment_id(ctx, net['id']) if seg_id == net['id']: # This is not an nsx-net. Delete the bindings _delete_segment_profiles_bindings(nsxpolicy, seg_id) if plugin._get_network_router_ids(ctx, net['id']): continue # verify that this network has a dhcp subnet subnets = plugin.get_subnets_by_network(ctx, net['id']) for subnet in subnets: if subnet['ip_version'] == 4 and subnet['enable_dhcp']: # Update backend subnet segment = nsxpolicy.segment.get(seg_id) subnets = segment.get('subnets', []) if subnets and len(subnets) == 1 and subnet['gateway_ip']: cidr_prefix = int(subnet['cidr'].split('/')[1]) gw = "%s/%s" % (subnet['gateway_ip'], cidr_prefix) subnets[0]['gateway_address'] = gw nsxpolicy.segment.update(seg_id, subnets=subnets) LOG.debug("Updated gateway of network %s", net['id']) break # -- Delete MP edge firewall rules global NSX_ROUTER_SECTIONS for section in NSX_ROUTER_SECTIONS: # make sure the policy section was already realized # with runtime_status=SUCESS nsxpolicy.gateway_policy.wait_until_state_sucessful( policy_constants.DEFAULT_DOMAIN, section['router_id'], max_attempts=600, with_refresh=True) nsxlib.firewall_section.update( section['id'], rules=[section['default_rule']]) LOG.debug("Deleted MP edge FW section %s rules", section['id']) LOG.info("Post-migration actions done.") def pre_migration_checks(nsxlib, plugin): """Check for unsupported configuration that will fail the migration""" nsx_version = nsxlib.get_version() if not nsx_utils.is_nsx_version_3_1_0(nsx_version): LOG.error("Pre migration check failed: Migration not supported for " "NSX %s", nsx_version) return False # Cannot migrate with unsupported services service_plugins = cfg.CONF.service_plugins for srv_plugin in service_plugins: if 'vpnaas' in srv_plugin: LOG.error("Pre migration check failed: VPNaaS is not supported. " "Please delete its configuration and disable it, before " "running migration again.") return False if 'l2gw' in srv_plugin: # L2GW is not supported with the policy plugin admin_context = context.get_admin_context() l2gws = admin_context.session.query( l2gateway_models.L2Gateway).all() if len(l2gws): LOG.error("Pre migration check failed: L2GW is not supported. " "Please delete all its resources, before " "running migration again.") return False # Tier0 with disabled BGP config neutron_t0s = get_neurton_tier0s(plugin) for tier0 in neutron_t0s: bgp_conf = nsxlib.logical_router.get_bgp_config(tier0) if not bgp_conf['enabled']: # Verify there are no neighbors configured if nsxlib.logical_router.get_bgp_neighbors(tier0)['result_count']: LOG.error("Pre migration check failed: Tier0 %s has BGP " "neighbors but BGP is disabled. Please remove the " "neighbors or enable BGP and try again.", tier0) return False # DHCP relay is unsupported if plugin._availability_zones_data.dhcp_relay_configured(): LOG.error("Pre migration check failed: DHCP relay configuration " "cannot be migrated. Please remove it from the plugin " "configuration and from all NSX logical router ports and " "try again.") return False LOG.info("Pre migration check succeeded") return True @admin_utils.output_header def MP2Policy_pre_migration_check(resource, event, trigger, **kwargs): """Verify if the current configuration can be migrated to Policy""" nsxlib = utils.get_connected_nsxlib() with utils.NsxV3PluginWrapper() as plugin: if not pre_migration_checks(nsxlib, plugin): # Failed LOG.error("T2P migration cannot run. Please fix the configuration " "and try again\n\n") exit(1) def _get_nsxlib_from_config(verbose, for_end_api=False): """Update the current config and return a working nsxlib or exit with error """ if (not len(cfg.CONF.nsx_v3.nsx_api_user) or not len(cfg.CONF.nsx_v3.nsx_api_password)): LOG.error("T2P migration cannot run. Please provide nsx_api_user and " "nsx_api_password in the configuration.") exit(1) retriables = [nsxlib_exc.APITransactionAborted, nsxlib_exc.ServerBusy] if for_end_api: # Enlarge timeouts and disable retries cfg.CONF.set_override('http_read_timeout', END_API_TIMEOUT, 'nsx_v3') cfg.CONF.set_override('http_retries', 0, 'nsx_v3') cfg.CONF.set_override('retries', 0, 'nsx_v3') # Initialize the nsxlib objects using just one of the managers because # the migration will be enabled only on one nsx_api_managers = copy.copy(cfg.CONF.nsx_v3.nsx_api_managers) nsx_api_user = copy.copy(cfg.CONF.nsx_v3.nsx_api_user) nsx_api_password = copy.copy(cfg.CONF.nsx_v3.nsx_api_password) for ind in range(len(nsx_api_managers)): # update the config to use this one manager only cfg.CONF.set_override( 'nsx_api_managers', [nsx_api_managers[ind]], 'nsx_v3') if len(nsx_api_user) > ind: cfg.CONF.set_override( 'nsx_api_user', [nsx_api_user[ind]], 'nsx_v3') else: cfg.CONF.set_override( 'nsx_api_user', [nsx_api_user[0]], 'nsx_v3') if len(nsx_api_password) > ind: cfg.CONF.set_override( 'nsx_api_password', [nsx_api_password[ind]], 'nsx_v3') else: cfg.CONF.set_override( 'nsx_api_password', [nsx_api_password[0]], 'nsx_v3') utils.reset_global_nsxlib() nsxlib = utils.get_connected_nsxlib(verbose=verbose, allow_overwrite_header=True, retriable_exceptions=retriables) try: # test connectivity nsxlib.get_version() except Exception: LOG.warning("Failed to connect to NSX manager %s", nsx_api_managers[ind]) else: # Found a working manager return nsxlib LOG.error("T2P migration failed. Cannot connect to NSX with managers %s", nsx_api_managers) exit(1) @admin_utils.output_header def MP2Policy_migration(resource, event, trigger, **kwargs): """Migrate NSX resources and neutron DB from NSX-T (MP) to Policy""" verbose = kwargs.get('verbose', False) if verbose: # Add DEBUG logs as well LOG.setLevel(logging.DEBUG) else: LOG.setLevel(logging.INFO) start_migration_service = False handler = logging.StreamHandler() if kwargs.get('property'): properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) logfile = properties.get('logfile', None) if logfile: handler = logging.FileHandler(logfile) start_service_flag = properties.get('start-migration-service', 'False') if start_service_flag.lower() == 'true': start_migration_service = True formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') handler.setFormatter(formatter) LOG.addHandler(handler) # Remove handler from root logger to avoid duplication root_logger = logging.getLogger(None) for root_handler in root_logger.handlers[:]: root_logger.removeHandler(root_handler) nsxlib = _get_nsxlib_from_config(verbose) nsxpolicy = p_utils.get_connected_nsxpolicy( conf_path=cfg.CONF.nsx_v3, verbose=verbose) if cfg.CONF.nsx_v3.nsx_use_client_auth: # Also create a policy manager with admin user to manipulate # admin-defined resources which should not have neutron principal # identity nsxpolicy_admin = p_utils.get_connected_nsxpolicy( conf_path=cfg.CONF.nsx_v3, use_basic_auth=True, nsx_username=cfg.CONF.nsx_v3.nsx_api_user, nsx_password=cfg.CONF.nsx_v3.nsx_api_password, verbose=verbose) else: nsxpolicy_admin = nsxpolicy with utils.NsxV3PluginWrapper(verbose=verbose) as plugin: # Make sure FWaaS was initialized plugin.init_fwaas_for_admin_utils() start_time = time.time() if not pre_migration_checks(nsxlib, plugin): # Failed LOG.error("T2P migration cannot run. Please fix the configuration " "and try again\n\n") exit(1) elapsed_time = time.time() - start_time LOG.debug("Pre-migration took %s seconds", elapsed_time) start_time = time.time() if not migrate_t_resources_2_p(nsxlib, nsxpolicy, plugin, start_migration_service): # Failed LOG.error("T2P migration failed. Aborting\n\n") exit(1) elapsed_time = time.time() - start_time LOG.debug("Migration took %s seconds", elapsed_time) start_time = time.time() post_migration_actions(nsxlib, nsxpolicy, nsxpolicy_admin, plugin) elapsed_time = time.time() - start_time LOG.debug("Post-migration took %s seconds", elapsed_time) LOG.info("T2P migration completed successfully\n\n") @admin_utils.output_header def MP2Policy_cleanup_db_mappings(resource, event, trigger, **kwargs): """Delete all entries from nsx-t mapping tables in DB. This cleanup does not have to run, as all those tables have delete-cascade so manipulation on the migrated resources will not be blocked by this. """ confirm = admin_utils.query_yes_no( "Are you sure you want to delete all MP plugin mapping DB tables?", default="no") if not confirm: LOG.info("Deletion aborted by user") return ctx = context.get_admin_context() mp_mapping_tables = [nsx_models.NeutronNsxFirewallSectionMapping, nsx_models.NeutronNsxSecurityGroupMapping, nsx_models.NeutronNsxRuleMapping, nsx_models.NeutronNsxPortMapping, nsx_models.NeutronNsxRouterMapping, nsx_models.NeutronNsxServiceBinding, nsx_models.NeutronNsxDhcpBinding, nsx_models.QosPolicySwitchProfile, nsx_models.NsxLbaasLoadbalancer, nsx_models.NsxLbaasListener, nsx_models.NsxLbaasPool, nsx_models.NsxLbaasMonitor, nsx_models.NsxLbaasL7Rule, nsx_models.NsxLbaasL7Policy] for table in mp_mapping_tables: ctx.session.query(table).delete() LOG.info("Deleted all MP plugin mapping DB tables.") registry.subscribe(MP2Policy_migration, constants.NSX_MIGRATE_T_P, shell.Operations.IMPORT.value) registry.subscribe(MP2Policy_pre_migration_check, constants.NSX_MIGRATE_T_P, shell.Operations.VALIDATE.value) registry.subscribe(MP2Policy_cleanup_db_mappings, constants.NSX_MIGRATE_T_P, shell.Operations.CLEAN_ALL.value)