diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index 31ead9074e..118489ba55 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -155,7 +155,7 @@ Metadata NSXv3 ----- -The following resources are supported: 'security-groups', 'routers', 'networks', 'nsx-security-groups', 'dhcp-binding' and 'ports'. +The following resources are supported: 'security-groups', 'routers', 'networks', 'nsx-security-groups', 'dhcp-binding', 'metadata-proxy', 'orphaned-dhcp-servers', and 'ports'. Networks ~~~~~~~~ @@ -235,6 +235,17 @@ DHCP Bindings nsxadmin -r dhcp-binding -o nsx-update --property dhcp_profile_uuid= +Orphaned DHCP Servers +~~~~~~~~~~~~~~~~~~~~~ + +- List orphaned DHCP servers (exist on NSXv3 backend but don't have a corresponding binding in Neutron DB):: + + nsxadmin -r orphaned-dhcp-servers -o nsx-list + +- Clean orphaned DHCP servers (delete logical DHCP servers from NSXv3 backend):: + + nsxadmin -r orphaned-dhcp-servers -o nsx-clean + Upgrade Steps (Version 1.0.0 to Version 1.1.0) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/vmware_nsx/shell/admin/plugins/common/constants.py b/vmware_nsx/shell/admin/plugins/common/constants.py index 49c5648b80..915997c94e 100644 --- a/vmware_nsx/shell/admin/plugins/common/constants.py +++ b/vmware_nsx/shell/admin/plugins/common/constants.py @@ -23,22 +23,23 @@ NSXV_PLUGIN = 'vmware_nsx.plugin.NsxVPlugin' # Common Resource Constants NETWORKS = 'networks' ROUTERS = 'routers' +DHCP_BINDING = 'dhcp-binding' # NSXV3 Resource Constants FIREWALL_SECTIONS = 'firewall-sections' FIREWALL_NSX_GROUPS = 'nsx-security-groups' SECURITY_GROUPS = 'security-groups' PORTS = 'ports' +METADATA_PROXY = 'metadata-proxy' +ORPHANED_DHCP_SERVERS = 'orphaned-dhcp-servers' # NSXV Resource Constants EDGES = 'edges' SPOOFGUARD_POLICY = 'spoofguard-policy' -DHCP_BINDING = 'dhcp-binding' BACKUP_EDGES = 'backup-edges' ORPHANED_EDGES = 'orphaned-edges' MISSING_EDGES = 'missing-edges' METADATA = 'metadata' -METADATA_PROXY = 'metadata-proxy' MISSING_NETWORKS = 'missing-networks' ORPHANED_NETWORKS = 'orphaned-networks' LBAAS = 'lbaas' diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py index d82ac420f3..9d0e4aaac8 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py @@ -31,6 +31,7 @@ from vmware_nsxlib.v3 import resources LOG = logging.getLogger(__name__) neutron_client = utils.NeutronDbClient() +nsxlib = utils.get_connected_nsxlib() @admin_utils.output_header @@ -47,11 +48,10 @@ def list_dhcp_bindings(resource, event, trigger, **kwargs): def nsx_update_dhcp_bindings(resource, event, trigger, **kwargs): """Resync DHCP bindings for NSXv3 CrossHairs.""" - nsxlib = utils.get_connected_nsxlib() nsx_version = nsxlib.get_version() if not nsx_utils.is_nsx_version_1_1_0(nsx_version): - LOG.info(_LI("This utility is not available for NSX version %s"), - nsx_version) + LOG.error(_LE("This utility is not available for NSX version %s"), + nsx_version) return dhcp_profile_uuid = None diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_servers.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_servers.py new file mode 100644 index 0000000000..2c0731ae56 --- /dev/null +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_servers.py @@ -0,0 +1,168 @@ +# 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. + +import logging + +from neutron.callbacks import registry +from neutron import context +from oslo_config import cfg + +from vmware_nsx._i18n import _LE, _LI +from vmware_nsx.common import utils as nsx_utils +from vmware_nsx.db import db as nsx_db +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.nsxv3.resources import utils +import vmware_nsx.shell.resources as shell +from vmware_nsxlib.v3 import nsx_constants +from vmware_nsxlib.v3 import resources + +LOG = logging.getLogger(__name__) +neutron_client = utils.NeutronDbClient() +nsx_client = utils.get_nsxv3_client() +nsxlib = utils.get_connected_nsxlib() +port_resource = resources.LogicalPort(nsx_client) +dhcp_server_resource = resources.LogicalDhcpServer(nsx_client) + + +def _get_dhcp_profile_uuid(**kwargs): + if kwargs.get('property'): + properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) + dhcp_profile_uuid = properties.get('dhcp_profile_uuid') + if dhcp_profile_uuid: + return dhcp_profile_uuid + if cfg.CONF.nsx_v3.dhcp_profile: + return nsxlib.native_dhcp_profile.get_id_by_name_or_id( + cfg.CONF.nsx_v3.dhcp_profile) + + +def _get_orphaned_dhcp_servers(dhcp_profile_uuid): + # An orphaned DHCP server means the associated neutron network + # does not exist or has no DHCP-enabled subnet. + + orphaned_servers = [] + server_net_pairs = [] + + # Find matching DHCP servers for a given dhcp_profile_uuid. + response = dhcp_server_resource.list() + for dhcp_server in response['results']: + if dhcp_server['dhcp_profile_id'] != dhcp_profile_uuid: + continue + found = False + for tag in dhcp_server['tags']: + if tag['scope'] == 'os-neutron-net-id': + server_net_pairs.append((dhcp_server, tag['tag'])) + found = True + break + if not found: + # The associated neutron network is not defined. + dhcp_server['neutron_net_id'] = None + orphaned_servers.append(dhcp_server) + + # Check if there is DHCP-enabled subnet in each network. + for dhcp_server, net_id in server_net_pairs: + try: + network = neutron_client.get_network(net_id) + except Exception: + # The associated neutron network is not found in DB. + dhcp_server['neutron_net_id'] = None + orphaned_servers.append(dhcp_server) + continue + dhcp_enabled = False + for subnet_id in network['subnets']: + subnet = neutron_client.get_subnet(subnet_id) + if subnet['enable_dhcp']: + dhcp_enabled = True + break + if not dhcp_enabled: + dhcp_server['neutron_net_id'] = net_id + orphaned_servers.append(dhcp_server) + + return orphaned_servers + + +@admin_utils.output_header +def nsx_list_orphaned_dhcp_servers(resource, event, trigger, **kwargs): + """List logical DHCP servers without associated DHCP-enabled subnet.""" + + nsx_version = nsxlib.get_version() + if not nsx_utils.is_nsx_version_1_1_0(nsx_version): + LOG.error(_LE("This utility is not available for NSX version %s"), + nsx_version) + return + + dhcp_profile_uuid = _get_dhcp_profile_uuid(**kwargs) + if not dhcp_profile_uuid: + LOG.error(_LE("dhcp_profile_uuid is not defined")) + return + + orphaned_servers = _get_orphaned_dhcp_servers(dhcp_profile_uuid) + LOG.info(formatters.output_formatter(constants.ORPHANED_DHCP_SERVERS, + orphaned_servers, + ['id', 'neutron_net_id'])) + + +@admin_utils.output_header +def nsx_clean_orphaned_dhcp_servers(resource, event, trigger, **kwargs): + """Remove logical DHCP servers without associated DHCP-enabled subnet.""" + + # For each orphaned DHCP server, + # (1) delete the attached logical DHCP port, + # (2) delete the logical DHCP server, + # (3) clean corresponding neutron DB entry. + + nsx_version = nsxlib.get_version() + if not nsx_utils.is_nsx_version_1_1_0(nsx_version): + LOG.error(_LE("This utility is not available for NSX version %s"), + nsx_version) + return + + dhcp_profile_uuid = _get_dhcp_profile_uuid(**kwargs) + if not dhcp_profile_uuid: + LOG.error(_LE("dhcp_profile_uuid is not defined")) + return + + cfg.CONF.set_override('dhcp_agent_notification', False) + cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3') + cfg.CONF.set_override('dhcp_profile', dhcp_profile_uuid, 'nsx_v3') + + orphaned_servers = _get_orphaned_dhcp_servers(dhcp_profile_uuid) + + for server in orphaned_servers: + try: + resource = ('?attachment_type=DHCP_SERVICE&attachment_id=%s' % + server['id']) + response = port_resource._client.url_get(resource) + if response and response['result_count'] > 0: + port_resource.delete(response['results'][0]['id']) + dhcp_server_resource.delete(server['id']) + net_id = server.get('neutron_net_id') + if net_id: + # Delete neutron_net_id -> dhcp_service_id mapping from the DB. + nsx_db.delete_neutron_nsx_service_binding( + context.get_admin_context().session, net_id, + nsx_constants.SERVICE_DHCP) + LOG.info(_LI("Removed orphaned DHCP server %s"), server['id']) + except Exception as e: + LOG.error(_LE("Failed to clean orphaned DHCP server %(id)s. " + "Exception: %(e)s"), {'id': server['id'], 'e': e}) + + +registry.subscribe(nsx_list_orphaned_dhcp_servers, + constants.ORPHANED_DHCP_SERVERS, + shell.Operations.NSX_LIST.value) +registry.subscribe(nsx_clean_orphaned_dhcp_servers, + constants.ORPHANED_DHCP_SERVERS, + shell.Operations.NSX_CLEAN.value) diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py index 424c8057f6..fdc832db4d 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py @@ -31,6 +31,7 @@ from vmware_nsxlib.v3 import resources LOG = logging.getLogger(__name__) neutron_client = utils.NeutronDbClient() +nsxlib = utils.get_connected_nsxlib() def _is_metadata_network(network): @@ -58,10 +59,10 @@ def list_metadata_networks(resource, event, trigger, **kwargs): def nsx_update_metadata_proxy(resource, event, trigger, **kwargs): """Update Metadata proxy for NSXv3 CrossHairs.""" - nsx_version = utils.get_connected_nsxlib().get_version() + nsx_version = nsxlib.get_version() if not nsx_utils.is_nsx_version_1_1_0(nsx_version): - LOG.info(_LI("This utility is not available for NSX version %s"), - nsx_version) + LOG.error(_LE("This utility is not available for NSX version %s"), + nsx_version) return metadata_proxy_uuid = None @@ -104,7 +105,7 @@ def nsx_update_metadata_proxy(resource, event, trigger, **kwargs): lswitch_id = neutron_client.net_id_to_lswitch_id(network['id']) if not lswitch_id: continue - tags = utils.get_connected_nsxlib().build_v3_tags_payload( + tags = nsxlib.build_v3_tags_payload( network, resource_type='os-neutron-net-id', project_name='admin') name = nsx_utils.get_name_and_uuid('%s-%s' % ( diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index a3d40f39d8..eefe5370d3 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -85,6 +85,9 @@ nsxv3_resources = { constants.METADATA_PROXY: Resource(constants.METADATA_PROXY, [Operations.LIST.value, Operations.NSX_UPDATE.value]), + constants.ORPHANED_DHCP_SERVERS: Resource(constants.ORPHANED_DHCP_SERVERS, + [Operations.NSX_LIST.value, + Operations.NSX_CLEAN.value]), } # Add supported NSX-V resources in this dictionary diff --git a/vmware_nsx/tests/unit/shell/test_admin_utils.py b/vmware_nsx/tests/unit/shell/test_admin_utils.py index e8e72643fb..a9a9e49de0 100644 --- a/vmware_nsx/tests/unit/shell/test_admin_utils.py +++ b/vmware_nsx/tests/unit/shell/test_admin_utils.py @@ -139,6 +139,8 @@ class TestNsxv3AdminUtils(AbstractTestAdminUtils, '__init__', return_value=None) self._patch_object(nsx_v3_resources.LogicalDhcpServer, '__init__', return_value=None) + self._patch_object(nsx_v3_resources.LogicalDhcpServer, + 'list', return_value={'results': []}) self._patch_object(nsx_v3_resources.LogicalRouter, '__init__', return_value=None) self._patch_object(nsx_v3_resources.SwitchingProfile,