diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index 18b8af8ae7..0448f312fd 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -205,6 +205,17 @@ Security Groups nsx -r nsx-security-groups -o migrate-to-dynamic-criteria +Metadata Proxy +~~~~~~~~~~~~~~ + +- List version 1.0.0 metadata networks in Neutron:: + + nsxadmin -r metadata-proxy -o list + +- Resync metadata proxies for NSXv3 version 1.1.0 and above:: + + nsxadmin -r metadata-proxy -o nsx-update --property metadata_proxy_uuid= + DHCP Bindings ~~~~~~~~~~~~~ @@ -212,6 +223,30 @@ DHCP Bindings nsxadmin -r dhcp-binding -o list -- Resync DHCP bindings for NSXv3 CrossHairs:: +- Resync DHCP bindings for NSXv3 version 1.1.0 and above:: - nsxadmin -r dhcp-binding -o nsx-update + nsxadmin -r dhcp-binding -o nsx-update --property dhcp_profile_uuid= + +Upgrade Steps (Version 1.0.0 to Version 1.1.0) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Upgrade NSX backend from version 1.0.0 to version 1.1.0 + +2. Create a DHCP-Profile and a Metadata-Proxy in NSX backend + +3. Stop Neutron + +4. Install version 1.1.0 Neutron plugin + +5. Run admin tools to migrate version 1.0.0 objects to version 1.1.0 objects + + nsxadmin -r metadata-proxy -o nsx-update --property metadata_proxy_uuid= + + nsxadmin -r dhcp-binding -o nsx-update --property dhcp_profile_uuid= + +6. Start Neutron + +7. Make sure /etc/nova/nova.conf has + metadata_proxy_shared_secret = + +8. Restart VMs or ifdown/ifup their network interface to get new DHCP options diff --git a/vmware_nsx/shell/admin/README.rst b/vmware_nsx/shell/admin/README.rst index 12080c30b9..01faaaffb7 100644 --- a/vmware_nsx/shell/admin/README.rst +++ b/vmware_nsx/shell/admin/README.rst @@ -157,6 +157,31 @@ Example } +Upgrade Steps (Version 1.0.0 to Version 1.1.0) +---------------------------------------------- + +1. Upgrade NSX backend from version 1.0.0 to version 1.1.0 + +2. Create a DHCP-Profile and a Metadata-Proxy in NSX backend + +3. Stop Neutron + +4. Install version 1.1.0 Neutron plugin + +5. Run admin tools to migrate version 1.0.0 objects to version 1.1.0 objects + + * nsxadmin -r metadata-proxy -o nsx-update --property metadata_proxy_uuid= + + * nsxadmin -r dhcp-binding -o nsx-update --property dhcp_profile_uuid= + +6. Start Neutron + +7. Make sure /etc/nova/nova.conf has + metadata_proxy_shared_secret = + +8. Restart VMs or ifdown/ifup their network interface to get new DHCP options + + Help ---- :: 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 9e69466d55..03bfec6877 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/dhcp_binding.py @@ -13,14 +13,15 @@ # under the License. import logging +import netaddr from neutron.callbacks import registry from neutron_lib import constants as const from oslo_config import cfg -from vmware_nsx._i18n import _LI +from vmware_nsx._i18n import _LE, _LI from vmware_nsx.common import nsx_constants -from vmware_nsx.common import utils as comm_utils +from vmware_nsx.common import utils as nsx_utils from vmware_nsx.nsxlib.v3 import native_dhcp from vmware_nsx.nsxlib.v3 import resources from vmware_nsx.shell.admin.plugins.common import constants @@ -37,9 +38,9 @@ neutron_client = utils.NeutronDbClient() def list_dhcp_bindings(resource, event, trigger, **kwargs): """List DHCP bindings in Neutron.""" - ports = neutron_client.get_ports() - comp_ports = [port for port in ports if port['device_owner'].startswith( - const.DEVICE_OWNER_COMPUTE_PREFIX)] + comp_ports = [port for port in neutron_client.get_ports() + if port['device_owner'].startswith( + const.DEVICE_OWNER_COMPUTE_PREFIX)] LOG.info(formatters.output_formatter(constants.DHCP_BINDING, comp_ports, ['id', 'mac_address', 'fixed_ips'])) @@ -49,51 +50,81 @@ def nsx_update_dhcp_bindings(resource, event, trigger, **kwargs): """Resync DHCP bindings for NSXv3 CrossHairs.""" nsx_version = utils.get_connected_nsxlib().get_version() - if not comm_utils.is_nsx_version_1_1_0(nsx_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) return + dhcp_profile_uuid = None + if kwargs.get('property'): + properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) + dhcp_profile_uuid = properties.get('dhcp_profile_uuid') + 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_uuid', dhcp_profile_uuid, 'nsx_v3') + nsx_client = utils.get_nsxv3_client() port_resource = resources.LogicalPort(nsx_client) dhcp_server_resource = resources.LogicalDhcpServer(nsx_client) - port_bindings = {} # lswitch_id: [(mac, ip, prefix_length), ...] + port_bindings = {} # lswitch_id: [(port_id, mac, ip), ...] server_bindings = {} # lswitch_id: dhcp_server_id ports = neutron_client.get_ports() for port in ports: - network_id = port['network_id'] device_owner = port['device_owner'] - if device_owner == const.DEVICE_OWNER_DHCP: - # For each DHCP-enabled network, create a logical DHCP server - # and update the attachment type to DHCP on the corresponding - # logical port of the Neutron DHCP port. - subnet_id = port['fixed_ips'][0]['subnet_id'] - subnet = neutron_client.get_subnet(subnet_id) - network = neutron_client.get_network(port['network_id']) - if len(port['fixed_ips']) > 1: - LOG.info(_LI("Network %(network)s has multiple subnets - " - "only enable native DHCP on subnet %(subnet)s"), - {'network': port['network_id'], 'subnet': subnet_id}) - server_data = native_dhcp.build_dhcp_server_config( - network, subnet, port, 'NSX Neutron plugin upgrade') - dhcp_server = dhcp_server_resource.create(**server_data) - lswitch_id, lport_id = neutron_client.get_lswitch_and_lport_id( - port['id']) - port_resource.update(lport_id, dhcp_server['id'], - attachment_type=nsx_constants.ATTACHMENT_DHCP) - server_bindings[lswitch_id] = dhcp_server['id'] - elif device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX): - lswitch_id = neutron_client.net_id_to_lswitch_id(network_id) - bindings = port_bindings.get(lswitch_id, []) - bindings.append((port['mac_address'], - port['fixed_ips'][0]['ip_address'])) - port_bindings[lswitch_id] = bindings + if (device_owner != const.DEVICE_OWNER_DHCP and + not device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX)): + continue + for fixed_ip in port['fixed_ips']: + if netaddr.IPNetwork(fixed_ip['ip_address']).version == 6: + continue + network_id = port['network_id'] + subnet = neutron_client.get_subnet(fixed_ip['subnet_id']) + if device_owner == const.DEVICE_OWNER_DHCP: + # For each DHCP-enabled network, create a logical DHCP server + # and update the attachment type to DHCP on the corresponding + # logical port of the Neutron DHCP port. + network = neutron_client.get_network(port['network_id']) + server_data = native_dhcp.build_dhcp_server_config( + network, subnet, port, 'admin') + dhcp_server = dhcp_server_resource.create(**server_data) + LOG.info(_LI("Created logical DHCP server %(server)s for " + "network %(network)s"), + {'server': dhcp_server['id'], + 'network': port['network_id']}) + # Add DHCP service binding in neutron DB. + neutron_client.add_dhcp_service_binding( + network['id'], port['id'], dhcp_server['id']) + # Update logical port for DHCP purpose. + lswitch_id, lport_id = ( + neutron_client.get_lswitch_and_lport_id(port['id'])) + port_resource.update( + lport_id, dhcp_server['id'], + attachment_type=nsx_constants.ATTACHMENT_DHCP) + server_bindings[lswitch_id] = dhcp_server['id'] + LOG.info(_LI("Updated DHCP logical port %(port)s for " + "network %(network)s"), + {'port': lport_id, 'network': port['network_id']}) + elif subnet['enable_dhcp']: + # Store (mac, ip) binding of each compute port in a + # DHCP-enabled subnet. + lswitch_id = neutron_client.net_id_to_lswitch_id(network_id) + bindings = port_bindings.get(lswitch_id, []) + bindings.append((port['id'], port['mac_address'], + fixed_ip['ip_address'])) + port_bindings[lswitch_id] = bindings + break # process only the first IPv4 address # Populate mac/IP bindings in each logical DHCP server. for lswitch_id, bindings in port_bindings.items(): - dhcp_server_id = server_bindings[lswitch_id] - for (mac, ip) in bindings: + dhcp_server_id = server_bindings.get(lswitch_id) + if not dhcp_server_id: + continue + for (port_id, mac, ip) in bindings: hostname = 'host-%s' % ip.replace('.', '-') options = {'option121': {'static_routes': [ {'network': '%s' % cfg.CONF.nsx_v3.native_metadata_route, @@ -101,6 +132,9 @@ def nsx_update_dhcp_bindings(resource, event, trigger, **kwargs): dhcp_server_resource.create_binding( dhcp_server_id, mac, ip, hostname, cfg.CONF.nsx_v3.dhcp_lease_time, options) + LOG.info(_LI("Added DHCP binding (mac: %(mac)s, ip: %(ip)s) " + "for neutron port %(port)s"), + {'mac': mac, 'ip': ip, 'port': port_id}) registry.subscribe(list_dhcp_bindings, 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 68f46c283d..aa5e779aa0 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py @@ -15,13 +15,16 @@ import logging from neutron.callbacks import registry +from neutron_lib import constants as const from oslo_config import cfg -from vmware_nsx._i18n import _LI, _LE +from vmware_nsx._i18n import _LE, _LI from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils as nsx_utils +from vmware_nsx.dhcp_meta import rpc as nsx_rpc from vmware_nsx.nsxlib.v3 import resources 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 @@ -30,30 +33,92 @@ LOG = logging.getLogger(__name__) neutron_client = utils.NeutronDbClient() +def _is_metadata_network(network): + # If a Neutron network has only one subnet with 169.254.169.252/30 CIDR, + # then it is an internal metadata network. + if len(network['subnets']) == 1: + subnet = neutron_client.get_subnet(network['subnets'][0]) + if subnet['cidr'] == nsx_rpc.METADATA_SUBNET_CIDR: + return True + return False + + +@admin_utils.output_header +def list_metadata_networks(resource, event, trigger, **kwargs): + """List Metadata networks in Neutron.""" + + meta_networks = [network for network in neutron_client.get_networks() + if _is_metadata_network(network)] + LOG.info(formatters.output_formatter(constants.METADATA_PROXY, + meta_networks, + ['id', 'name', 'subnets'])) + + @admin_utils.output_header def nsx_update_metadata_proxy(resource, event, trigger, **kwargs): """Update Metadata proxy for NSXv3 CrossHairs.""" + nsx_version = utils.get_connected_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) + return + + metadata_proxy_uuid = None + if kwargs.get('property'): + properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) + metadata_proxy_uuid = properties.get('metadata_proxy_uuid') + if not metadata_proxy_uuid: + LOG.error(_LE("metadata_proxy_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('metadata_proxy_uuid', metadata_proxy_uuid, 'nsx_v3') + + plugin = utils.NsxV3PluginWrapper() nsx_client = utils.get_nsxv3_client() port_resource = resources.LogicalPort(nsx_client) + # For each Neutron network, check if it is an internal metadata network. + # If yes, delete the network and associated router interface. + # Otherwise, create a logical switch port with MD-Proxy attachment. for network in neutron_client.get_networks(): - # For each Neutron network, create a logical switch port with - # MD-Proxy attachment. - lswitch_id = neutron_client.net_id_to_lswitch_id(network['id']) - if lswitch_id: + if _is_metadata_network(network): + # It is a metadata network, find the attached router, + # remove the router interface and the network. + filters = {'device_owner': const.ROUTER_INTERFACE_OWNERS, + 'fixed_ips': { + 'subnet_id': [network['subnets'][0]], + 'ip_address': [nsx_rpc.METADATA_GATEWAY_IP]}} + ports = neutron_client.get_ports(filters=filters) + if not ports: + continue + router_id = ports[0]['device_id'] + interface = {'subnet_id': network['subnets'][0]} + plugin.remove_router_interface(router_id, interface) + LOG.info(_LI("Removed metadata interface on router %s"), router_id) + plugin.delete_network(network['id']) + LOG.info(_LI("Removed metadata network %s"), network['id']) + else: + lswitch_id = neutron_client.net_id_to_lswitch_id(network['id']) + if not lswitch_id: + continue tags = nsx_utils.build_v3_tags_payload( network, resource_type='os-neutron-net-id', - project_name='NSX Neutron plugin upgrade') + project_name='admin') + name = nsx_utils.get_name_and_uuid('%s-%s' % ( + 'mdproxy', network['name'] or 'network'), network['id']) port_resource.create( - lswitch_id, cfg.CONF.nsx_v3.metadata_proxy_uuid, tags=tags, + lswitch_id, metadata_proxy_uuid, tags=tags, name=name, attachment_type=nsx_constants.ATTACHMENT_MDPROXY) LOG.info(_LI("Enabled native metadata proxy for network %s"), network['id']) - else: - LOG.error(_LE("Unable to find logical switch for network %s"), - network['id']) + +registry.subscribe(list_metadata_networks, + constants.METADATA_PROXY, + shell.Operations.LIST.value) registry.subscribe(nsx_update_metadata_proxy, constants.METADATA_PROXY, shell.Operations.NSX_UPDATE.value) diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py index 0eec62f825..ab0d7e542d 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py @@ -17,6 +17,7 @@ from neutron import context from neutron.db import db_base_plugin_v2 from oslo_config import cfg +from vmware_nsx.common import nsx_constants from vmware_nsx.db import db as nsx_db from vmware_nsx.nsxlib import v3 from vmware_nsx.plugins.nsx_v3 import plugin @@ -72,8 +73,17 @@ class NeutronDbClient(db_base_plugin_v2.NeutronDbPluginV2): lswitch_ids = nsx_db.get_nsx_switch_ids(self.context.session, net_id) return lswitch_ids[0] if lswitch_ids else None + def add_dhcp_service_binding(self, network_id, port_id, server_id): + return nsx_db.add_neutron_nsx_service_binding( + self.context.session, network_id, port_id, + nsx_constants.SERVICE_DHCP, server_id) + class NsxV3PluginWrapper(plugin.NsxV3Plugin): + def __init__(self): + super(NsxV3PluginWrapper, self).__init__() + self.context = context.get_admin_context() + def _init_dhcp_metadata(self): pass @@ -90,3 +100,11 @@ class NsxV3PluginWrapper(plugin.NsxV3Plugin): self._extend_network_dict_provider(context, net) # skip getting the Qos policy ID because get_object calls # plugin init again on admin-util environment + + def delete_network(self, network_id): + return super(NsxV3PluginWrapper, self).delete_network( + self.context, network_id) + + def remove_router_interface(self, router_id, interface): + return super(NsxV3PluginWrapper, self).remove_router_interface( + self.context, router_id, interface) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index 2de67159de..ca0ae99d38 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -79,6 +79,9 @@ nsxv3_resources = { constants.DHCP_BINDING: Resource(constants.DHCP_BINDING, [Operations.LIST.value, Operations.NSX_UPDATE.value]), + constants.METADATA_PROXY: Resource(constants.METADATA_PROXY, + [Operations.LIST.value, + Operations.NSX_UPDATE.value]), } # Add supported NSX-V resources in this dictionary