diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index f3ac1aa498..82399623ab 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -79,6 +79,14 @@ Edges nsxadmin -o nsx-update -r edges --property hostgroup=clean +- Disconnect edges interfaces (for V2T migration):: + + nsxadmin -o nsx-disconnect -r edges + +- Reconnect edges interfaces (for V2T migration rollback):: + + nsxadmin -o nsx-reconnect -r edges + Orphaned Edges ~~~~~~~~~~~~~~ diff --git a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py index d0338a95dd..998b85a609 100644 --- a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py +++ b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py @@ -90,13 +90,16 @@ def get_lb_interface(context, plugin, lb_id, subnet_id): def create_lb_interface(context, plugin, lb_id, subnet_id, tenant_id, - vip_addr=None, subnet=None): + vip_addr=None, subnet=None, internal=False): if not subnet: subnet = plugin.get_subnet(context, subnet_id) network_id = subnet.get('network_id') network = plugin.get_network(context.elevated(), network_id) - port_dict = {'name': 'lb_if-' + lb_id, + name = 'lb_if-' + lb_id + if internal: + name = "internal for V2T migration" + port_dict = {'name': name, 'admin_state_up': True, 'network_id': network_id, 'tenant_id': tenant_id, @@ -131,13 +134,14 @@ def create_lb_interface(context, plugin, lb_id, subnet_id, tenant_id, network_id, address_groups) -def delete_lb_interface(context, plugin, lb_id, subnet_id): +def delete_lb_interface(context, plugin, lb_id, subnet_id, internal=False): resource_id = get_lb_edge_name(context, lb_id) subnet = plugin.get_subnet(context, subnet_id) network_id = subnet.get('network_id') lb_ports = get_lb_interface(context, plugin, lb_id, subnet_id) for lb_port in lb_ports: - plugin.delete_port(context, lb_port['id'], allow_delete_lb_if=True) + plugin.delete_port(context, lb_port['id'], allow_delete_lb_if=True, + allow_delete_internal=internal) edge_utils.delete_interface(plugin.nsx_v, context, resource_id, network_id, dist=False) diff --git a/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py b/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py index 8071fbb340..f3f2a5fb5a 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py +++ b/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py @@ -16,34 +16,34 @@ import pprint import textwrap +from neutron_lib.callbacks import registry +from neutron_lib import context as n_context +from neutron_lib import exceptions +from neutron_lib.exceptions import l3 as l3_exc +from oslo_config import cfg +from oslo_log import log as logging + from vmware_nsx.common import config +from vmware_nsx.common import nsxv_constants +from vmware_nsx.db import nsxv_db from vmware_nsx.dvs import dvs +from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az +from vmware_nsx.plugins.nsx_v.vshield.common import ( + constants as vcns_const) +from vmware_nsx.plugins.nsx_v.vshield.common import ( + exceptions as nsxv_exceptions) from vmware_nsx.plugins.nsx_v.vshield import edge_utils from vmware_nsx.plugins.nsx_v.vshield import vcns_driver from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common 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 neutron_lib import exceptions -from oslo_config import cfg -from oslo_log import log as logging - -from vmware_nsx.common import nsxv_constants -from vmware_nsx.db import nsxv_db -from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az -from vmware_nsx.plugins.nsx_v.vshield.common import ( - constants as vcns_const) -import vmware_nsx.plugins.nsx_v.vshield.common.exceptions as nsxv_exceptions - +from vmware_nsx.shell.admin.plugins.common import utils as admin_utils +from vmware_nsx.shell.admin.plugins.nsxv.resources import utils +from vmware_nsx.shell import resources as shell LOG = logging.getLogger(__name__) nsxv = utils.get_nsxv_client() +INTERNAL_SUBNET = "169.254" @admin_utils.output_header @@ -644,6 +644,160 @@ def nsx_update_edges(resource, event, trigger, **kwargs): "to update.", {'result': result, 'total': total}) +def _update_edge_interface_conectivity(edge_id, interface, disconnect, + nsxv_manager, distributed, + router_admin_state_up): + # keep the 169.254 interfaces connected and disable all the rest + primary_ip = None + addr_grp = interface.get('addressGroups', {}).get('addressGroups', []) + if len(addr_grp): + primary_ip = addr_grp[0]['primaryAddress'] + else: + # Skip interfaces with no IPs + if (not interface.get('subInterfaces') or + not interface['subInterfaces'].get('subInterfaces')): + LOG.debug("Interface %s has no addressGroups and will not be " + "modified", interface.get('name')) + return + + # skip interfaces on 169.254 subnet + if primary_ip and primary_ip.startswith(INTERNAL_SUBNET): + LOG.debug("Interface %s is on the %s.0.0 network and will not be " + "modified", interface.get('name'), INTERNAL_SUBNET) + return + + # skip interfaces that are already connected/disconnected + if ((disconnect and not interface['isConnected']) or + (not disconnect and interface['isConnected'])): + LOG.debug("Interface %s already %s: %s", interface.get('name'), + 'disconnected' if disconnect else 'connected', interface) + return + + # if the router admin state is down, skip internal interfaces + if not router_admin_state_up and interface.get('type') == 'internal': + LOG.debug("Skipping internal interface %s as router admin state " + "is down", interface.get('name')) + return + + if interface.get('portgroupId') or interface.get('connectedToId'): + # Its an active interface + interface['isConnected'] = not disconnect + try: + if distributed: + nsxv_manager.vcns.update_vdr_internal_interface( + edge_id, interface['index'], {'interface': interface}) + else: + nsxv_manager.vcns.update_interface(edge_id, interface) + LOG.info("%s interface %s of %s %s", + 'Disconneted' if disconnect else 'Connected', + interface['name'], edge_id, + '(distributed)' if distributed else '') + except Exception as e: + LOG.error("Failed with %s", e) + + +def _update_edges_connectivity(disconnect=True): + edges = utils.get_nsxv_backend_edges() + context = n_context.get_admin_context() + internal_subnet = None + + with utils.NsxVPluginWrapper() as plugin: + nsxv_manager = vcns_driver.VcnsDriver( + edge_utils.NsxVCallbacks(plugin)) + for edge in edges: + edge_id = edge.get('id') + # check that this is a neutron edge + bindings = nsxv_db.get_nsxv_router_binding_by_edge( + context.session, edge_id) + if not bindings: + LOG.info("Skipping non-neutron %s %s", + edge_id, edge.get('name')) + continue + router_id = bindings.router_id + router_admin_state_up = True + try: + rtr_obj = plugin.get_router(context, router_id) + except l3_exc.RouterNotFound: + pass + else: + router_admin_state_up = rtr_obj.get('admin_state_up', True) + + # skip backup edges + if not edge.get('name') or edge['name'].startswith('backup-'): + LOG.info("Skipping backup %s %s", + edge_id, edge['name']) + continue + + LOG.error("%s interfaces of %s %s", + 'Disconnecting' if disconnect else 'Connecting', + edge_id, edge['name']) + + if edge.get('type') == 'distributedRouter': + header, response = nsxv_manager.vcns.get_edge_interfaces( + edge_id) + for interface in response['interfaces']: + _update_edge_interface_conectivity( + edge_id, interface, disconnect, nsxv_manager, + True, router_admin_state_up) + elif edge['name'].startswith('lbaas-'): + # if the uplink interface has no ip - keep it, and disconnect + # all the rest. + # else - add/delete a dummy interface and disconnect all others + uplink_ip = None + h, uplink_if = nsxv_manager.vcns.query_interface(edge_id, 0) + if uplink_if: + addr_grp = uplink_if.get('addressGroups', {}).get( + 'addressGroups', []) + uplink_ip = None + if len(addr_grp): + uplink_ip = addr_grp[0]['primaryAddress'] + if uplink_ip: + # create/delete an interface on the inter-edge network + if not internal_subnet: + int_net = nsxv_db.get_nsxv_internal_network_for_az( + context.session, "inter_edge_net", "default") + subnets = plugin.get_subnets( + context, fields=['id'], + filters={'network_id': [int_net.network_id]}) + internal_subnet = subnets[0]['id'] + lb_id = edge['name'][6:] + if disconnect: + lb_common.create_lb_interface( + context, plugin, lb_id, internal_subnet, + "internal", internal=True) + + header, response = nsxv_manager.vcns.get_interfaces(edge_id) + for interface in response['vnics']: + if not uplink_ip and interface['index'] == 0: + # change the connectivity of the uplink only if it + # does not have ip + continue + _update_edge_interface_conectivity( + edge_id, interface, disconnect, nsxv_manager, + False, router_admin_state_up) + if uplink_ip and not disconnect: + # Remove the dummy interface + lb_common.delete_lb_interface(context, plugin, lb_id, + internal_subnet, + internal=True) + else: + header, response = nsxv_manager.vcns.get_interfaces(edge_id) + for interface in response['vnics']: + _update_edge_interface_conectivity( + edge_id, interface, disconnect, nsxv_manager, + False, router_admin_state_up) + + +@admin_utils.output_header +def nsx_disconnect_edges(resource, event, trigger, **kwargs): + return _update_edges_connectivity(disconnect=True) + + +@admin_utils.output_header +def nsx_reconnect_edges(resource, event, trigger, **kwargs): + return _update_edges_connectivity(disconnect=False) + + registry.subscribe(nsx_list_edges, constants.EDGES, shell.Operations.NSX_LIST.value) @@ -671,3 +825,9 @@ registry.subscribe(list_orphaned_router_bindings, registry.subscribe(clean_orphaned_router_bindings, constants.ORPHANED_BINDINGS, shell.Operations.CLEAN.value) +registry.subscribe(nsx_disconnect_edges, + constants.EDGES, + shell.Operations.NSX_DISCONNECT.value) +registry.subscribe(nsx_reconnect_edges, + constants.EDGES, + shell.Operations.NSX_RECONNECT.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index c2f37c6896..c011089876 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -60,6 +60,8 @@ class Operations(enum.Enum): NSX_RECREATE = 'nsx-recreate' NSX_REDISTRIBURE = 'nsx-redistribute' NSX_REORDER = 'nsx-reorder' + NSX_DISCONNECT = 'nsx-disconnect' + NSX_RECONNECT = 'nsx-reconnect' NSX_TAG_DEFAULT = 'nsx-tag-default' NSX_MIGRATE_V_V3 = 'nsx-migrate-v-v3' MIGRATE_TO_POLICY = 'migrate-to-policy' @@ -168,7 +170,9 @@ nsxv_resources = { [Operations.NSX_LIST.value, Operations.NEUTRON_LIST.value, Operations.NSX_UPDATE.value, - Operations.NSX_UPDATE_ALL.value]), + Operations.NSX_UPDATE_ALL.value, + Operations.NSX_DISCONNECT.value, + Operations.NSX_RECONNECT.value]), constants.BACKUP_EDGES: Resource(constants.BACKUP_EDGES, [Operations.LIST.value, Operations.CLEAN.value, diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py index c7d680d3d7..c3fe28b2d6 100644 --- a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py +++ b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py @@ -204,7 +204,7 @@ class FakeVcns(object): header = { 'status': 200 } - response = '' + response = {'vnics': {}} return (header, response) def update_interface(self, edge_id, vnic):