# Copyright 2016 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. from vmware_nsx.shell.admin.plugins.common import constants from vmware_nsx.shell.admin.plugins.common import formatters import vmware_nsx.shell.admin.plugins.common.utils as admin_utils import vmware_nsx.shell.admin.plugins.nsxv.resources.utils as utils import vmware_nsx.shell.resources as shell from neutron_lib.callbacks import registry from neutron_lib import context as n_context from oslo_config import cfg from oslo_log import log as logging from vmware_nsx.common import locking from vmware_nsx.db import nsxv_db from vmware_nsx.extensions import routersize from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az from vmware_nsx.plugins.nsx_v import md_proxy from vmware_nsx.plugins.nsx_v.vshield import edge_utils from vmware_nsx.plugins.nsx_v.vshield import vcns_driver LOG = logging.getLogger(__name__) def delete_old_edge(context, old_edge_id): LOG.info("Deleting the old edge: %s", old_edge_id) # clean it up from the DB nsxv_db.clean_edge_router_binding(context.session, old_edge_id) nsxv_db.clean_edge_vnic_binding(context.session, old_edge_id) nsxv_db.cleanup_nsxv_edge_firewallrule_binding(context.session, old_edge_id) with locking.LockManager.get_lock(old_edge_id): # Delete from NSXv backend # Note - If we will not delete the edge, but free it - it will be # immediately used as the new one, So it is better to delete it. try: nsxv = utils.get_nsxv_client() nsxv.delete_edge(old_edge_id) except Exception as e: LOG.warning("Failed to delete the old edge %(id)s: %(e)s", {'id': old_edge_id, 'e': e}) # Continue the process anyway # The edge may have been already deleted at the backend def _get_router_az_from_plugin_router(router): # If the router edge was already deployed the availability_zones will # return the az az_name = router.get('availability_zones', [''])[0] if not az_name: # If it was not deployed - it may be in the creation hints az_name = router.get('availability_zones_hints', [''])[0] if not az_name: # If not - the default az was used. az_name = nsx_az.DEFAULT_NAME return az_name def nsx_recreate_router_edge(old_edge_id): # init the plugin and edge manager cfg.CONF.set_override('core_plugin', 'vmware_nsx.shell.admin.plugins.nsxv.resources' '.utils.NsxVPluginWrapper') with utils.NsxVPluginWrapper() as plugin: nsxv_manager = vcns_driver.VcnsDriver( edge_utils.NsxVCallbacks(plugin)) edge_manager = edge_utils.EdgeManager(nsxv_manager, plugin) context = n_context.get_admin_context() # verify that this is a Router edge router_ids = edge_manager.get_routers_on_edge(context, old_edge_id) if not router_ids: LOG.error("Edge %(edge_id)s is not a router edge", {'edge_id': old_edge_id}) return # all the routers on the same edge have the same type, so it # is ok to check the type once example_router = plugin.get_router(context, router_ids[0]) if example_router.get('distributed'): LOG.error("Recreating a distributed router edge is not " "supported") return router_driver = plugin._router_managers.get_tenant_router_driver( context, example_router['router_type']) # load all the routers before deleting their binding routers = [] for router_id in router_ids: routers.append(plugin.get_router(context, router_id)) # delete the backend edge and all the relevant DB entries delete_old_edge(context, old_edge_id) # Go over all the relevant routers for router in routers: router_id = router['id'] az_name = _get_router_az_from_plugin_router(router) # clean up other objects related to this router if plugin.metadata_proxy_handler: md_proxy_h = plugin.get_metadata_proxy_handler(az_name) md_proxy_h.cleanup_router_edge(context, router_id) # attach the router to a new edge appliance_size = router.get(routersize.ROUTER_SIZE) router_driver.attach_router(context, router_id, {'router': router}, appliance_size=appliance_size) # find out who is the new edge to print it new_edge_id = router_driver._get_edge_id_or_raise( context, router_id) LOG.info("Router %(router)s was attached to edge %(edge)s", {'router': router_id, 'edge': new_edge_id}) def nsx_recreate_router(router_id): # init the plugin and edge manager cfg.CONF.set_override('core_plugin', 'vmware_nsx.shell.admin.plugins.nsxv.resources' '.utils.NsxVPluginWrapper') with utils.NsxVPluginWrapper() as plugin: context = n_context.get_admin_context() router = plugin.get_router(context, router_id) if router.get('distributed'): LOG.error("Recreating a distributed router is not supported") return router_driver = plugin._router_managers.get_tenant_router_driver( context, router['router_type']) # Check if it is already attached to an edge binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) if binding: old_edge_id = binding['edge_id'] # detach the router from this edge LOG.info("Detaching the router from edge %s", old_edge_id) router_driver.detach_router(context, router_id, {'router': router}) # attach the router to a new edge appliance_size = router.get(routersize.ROUTER_SIZE) router_driver.attach_router(context, router_id, {'router': router}, appliance_size=appliance_size) # find out who is the new edge to print it new_edge_id = router_driver._get_edge_id_or_raise( context, router_id) LOG.info("Router %(router)s was attached to edge %(edge)s", {'router': router_id, 'edge': new_edge_id}) @admin_utils.output_header def nsx_recreate_router_or_edge(resource, event, trigger, **kwargs): """Recreate a router edge with all the data on a new NSXv edge""" if not kwargs.get('property'): LOG.error("Need to specify edge-id or router-id parameter") return # input validation properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) old_edge_id = properties.get('edge-id') router_id = properties.get('router-id') if (not old_edge_id and not router_id) or (old_edge_id and router_id): LOG.error("Need to specify edge-id or router-id parameter") return if old_edge_id: LOG.info("ReCreating NSXv Router Edge: %s", old_edge_id) return nsx_recreate_router_edge(old_edge_id) LOG.info("ReCreating NSXv Router: %s", router_id) return nsx_recreate_router(router_id) @admin_utils.output_header def migrate_distributed_routers_dhcp(resource, event, trigger, **kwargs): context = n_context.get_admin_context() nsxv = utils.get_nsxv_client() with utils.NsxVPluginWrapper() as plugin: nsxv_manager = vcns_driver.VcnsDriver( edge_utils.NsxVCallbacks(plugin)) edge_manager = edge_utils.EdgeManager(nsxv_manager, plugin) routers = plugin.get_routers(context) for router in routers: if router.get('distributed', False): binding = nsxv_db.get_nsxv_router_binding(context.session, router['id']) if binding: edge_id = binding['edge_id'] with locking.LockManager.get_lock(edge_id): route_obj = nsxv.get_routes(edge_id)[1] routes = route_obj.get('staticRoutes', {} ).get('staticRoutes', []) new_routes = [route for route in routes if route.get( 'network') != '169.254.169.254/32'] route_obj['staticRoutes']['staticRoutes'] = new_routes nsxv.update_routes(edge_id, route_obj) _update_vdr_fw_config(nsxv, edge_id) plr_id = edge_manager.get_plr_by_tlr_id(context, router['id']) if plr_id: binding = nsxv_db.get_nsxv_router_binding( context.session, plr_id) if binding: _update_vdr_fw_config(nsxv, binding['edge_id']) @admin_utils.output_header def update_edge_firewalls(resource, event, trigger, **kwargs): context = n_context.get_admin_context() updated_routers = [] with utils.NsxVPluginWrapper() as plugin: shared_dr = plugin._router_managers.get_tenant_router_driver( context, 'shared') routers = plugin.get_routers(context) for router in routers: if router['id'] in updated_routers: continue if router.get('distributed', False): # Distributes firewall - Update plr and tlr router_db = plugin._get_router(context, router['id']) plugin._update_subnets_and_dnat_firewall(context, router_db) plr_id = plugin.edge_manager.get_plr_by_tlr_id( context, router['id']) if plr_id: plugin._update_subnets_and_dnat_firewall( context, router, router_id=plr_id) updated_routers.append(router['id']) elif router.get('router_type') == 'shared': # Shared router router_ids = shared_dr.edge_manager.get_routers_on_same_edge( context, router['id']) shared_dr._update_subnets_and_dnat_firewall_on_routers( context, router['id'], router_ids) updated_routers.extend(router_ids) else: # Exclusive router router_db = plugin._get_router(context, router['id']) plugin._update_subnets_and_dnat_firewall(context, router_db) updated_routers.append(router['id']) LOG.info("Updated edge firewall rules for routers: %s", updated_routers) def _update_vdr_fw_config(nsxv, edge_id): fw_config = nsxv.get_firewall(edge_id)[1] md_rule_names = [rule['name'] for rule in md_proxy.get_router_fw_rules()] fw_rules = fw_config.get('firewallRules', {}).get('firewallRules', []) if fw_rules: fw_rules = [rule for rule in fw_rules if rule['name'] not in md_rule_names] fw_config['firewallRules']['firewallRules'] = fw_rules nsxv.update_firewall(edge_id, fw_config) def is_router_conflicting_on_edge(context, driver, router_id): edge_id = edge_utils.get_router_edge_id(context, router_id) if not edge_id: return False (available_routers, conflict_routers) = driver._get_available_and_conflicting_ids( context, router_id) for conf_router in conflict_routers: conf_edge_id = edge_utils.get_router_edge_id(context, conf_router) if conf_edge_id == edge_id: LOG.info("Router %(rtr)s on edge %(edge)s is conflicting with " "another router and will be moved", {'rtr': router_id, 'edge': edge_id}) return True return False @admin_utils.output_header def redistribute_routers(resource, event, trigger, **kwargs): """If any of the shared routers are on a conflicting edge move them""" context = n_context.get_admin_context() with utils.NsxVPluginWrapper() as plugin: router_driver = plugin._router_managers.get_tenant_router_driver( context, 'shared') routers = plugin.get_routers(context) for router in routers: if (not router.get('distributed', False) and router.get('router_type') == 'shared' and is_router_conflicting_on_edge( context, router_driver, router['id'])): router_driver.detach_router(context, router['id'], router) router_driver.attach_router(context, router['id'], router) @admin_utils.output_header def list_orphaned_vnics(resource, event, trigger, **kwargs): """List router orphaned router vnics where the port was deleted""" orphaned_vnics = get_orphaned_vnics() if not orphaned_vnics: LOG.info("No orphaned router vnics found") return headers = ['edge_id', 'vnic_index', 'tunnel_index', 'network_id'] LOG.info(formatters.output_formatter(constants.ORPHANED_VNICS, orphaned_vnics, headers)) def get_orphaned_vnics(): orphaned_vnics = [] context = n_context.get_admin_context() vnic_binds = nsxv_db.get_edge_vnic_bindings_with_networks( context.session) with utils.NsxVPluginWrapper() as plugin: for vnic_bind in vnic_binds: edge_id = vnic_bind['edge_id'] # check if this is a router edge by the router bindings table router_bindings = nsxv_db.get_nsxv_router_bindings_by_edge( context.session, edge_id) if not router_bindings: # Only log it. this is a different type of orphaned LOG.warning("Router bindings for vnic %s not found", vnic_bind) continue router_ids = [b['router_id'] for b in router_bindings] routers = plugin.get_routers(context, filters={'id': router_ids}) if routers: interface_found = False # check if any of those routers is attached to this network for router in routers: if plugin._get_router_interface_ports_by_network( context, router['id'], vnic_bind['network_id']): interface_found = True break if not interface_found: # for later deleting the interface we need to know if this # is a distributed router. # All the routers on the same edge are of the same type, # so we can check the first one. vnic_bind['distributed'] = routers[0].get('distributed') orphaned_vnics.append(vnic_bind) return orphaned_vnics @admin_utils.output_header def clean_orphaned_vnics(resource, event, trigger, **kwargs): """List router orphaned router vnics where the port was deleted""" orphaned_vnics = get_orphaned_vnics() if not orphaned_vnics: LOG.info("No orphaned router vnics found") return headers = ['edge_id', 'vnic_index', 'tunnel_index', 'network_id'] LOG.info(formatters.output_formatter(constants.ORPHANED_VNICS, orphaned_vnics, headers)) user_confirm = admin_utils.query_yes_no("Do you want to delete " "orphaned vnics", default="no") if not user_confirm: LOG.info("NSXv vnics deletion aborted by user") return context = n_context.get_admin_context() with utils.NsxVPluginWrapper() as plugin: nsxv_manager = vcns_driver.VcnsDriver( edge_utils.NsxVCallbacks(plugin)) for vnic in orphaned_vnics: if not vnic['distributed']: try: nsxv_manager.vcns.delete_interface( vnic['edge_id'], vnic['vnic_index']) except Exception as e: LOG.error("Failed to delete vnic from NSX: %s", e) nsxv_db.free_edge_vnic_by_network( context.session, vnic['edge_id'], vnic['network_id']) else: try: nsxv_manager.vcns.delete_vdr_internal_interface( vnic['edge_id'], vnic['vnic_index']) except Exception as e: LOG.error("Failed to delete vnic from NSX: %s", e) nsxv_db.delete_edge_vnic_binding_by_network( context.session, vnic['edge_id'], vnic['network_id']) registry.subscribe(nsx_recreate_router_or_edge, constants.ROUTERS, shell.Operations.NSX_RECREATE.value) registry.subscribe(migrate_distributed_routers_dhcp, constants.ROUTERS, shell.Operations.MIGRATE_VDR_DHCP.value) registry.subscribe(redistribute_routers, constants.ROUTERS, shell.Operations.NSX_REDISTRIBURE.value) registry.subscribe(update_edge_firewalls, constants.ROUTERS, shell.Operations.NSX_UPDATE_FW.value) registry.subscribe(list_orphaned_vnics, constants.ORPHANED_VNICS, shell.Operations.NSX_LIST.value) registry.subscribe(clean_orphaned_vnics, constants.ORPHANED_VNICS, shell.Operations.NSX_CLEAN.value)