c35d717a88
The external network extension's API definition was rehomed into neutron-lib with I9933b91d1e82db3891b3b72f06e94316e56a4f15. This patch consumes it, switch over to neutron-lib's modules in prep for I696b52265b9528082cd2524f05febe2338376488 Change-Id: I338af67ad05827d6a770bebe0600e9d08e37afae
328 lines
14 KiB
Python
328 lines
14 KiB
Python
# Copyright 2013 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 neutron_lib.api.definitions import external_net as extnet_apidef
|
|
from neutron_lib import constants as const
|
|
from neutron_lib import exceptions as n_exc
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from neutron.db import db_base_plugin_v2
|
|
from neutron.db import l3_db
|
|
|
|
from vmware_nsx._i18n import _
|
|
from vmware_nsx.common import exceptions as p_exc
|
|
from vmware_nsx.dhcp_meta import constants as d_const
|
|
from vmware_nsx.nsxlib.mh import lsn as lsn_api
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
dhcp_opts = [
|
|
cfg.ListOpt('extra_domain_name_servers',
|
|
deprecated_group='NVP_DHCP',
|
|
default=[],
|
|
help=_('Comma separated list of additional '
|
|
'domain name servers')),
|
|
cfg.StrOpt('domain_name',
|
|
deprecated_group='NVP_DHCP',
|
|
default='openstacklocal',
|
|
help=_('Domain to use for building the hostnames')),
|
|
cfg.IntOpt('default_lease_time', default=43200,
|
|
deprecated_group='NVP_DHCP',
|
|
help=_("Default DHCP lease time")),
|
|
]
|
|
|
|
|
|
metadata_opts = [
|
|
cfg.StrOpt('metadata_server_address',
|
|
deprecated_group='NVP_METADATA',
|
|
default='127.0.0.1',
|
|
help=_("IP address used by Metadata server.")),
|
|
cfg.PortOpt('metadata_server_port',
|
|
deprecated_group='NVP_METADATA',
|
|
default=8775,
|
|
help=_("TCP Port used by Metadata server.")),
|
|
cfg.StrOpt('metadata_shared_secret',
|
|
deprecated_group='NVP_METADATA',
|
|
default='',
|
|
help=_('When proxying metadata requests, Neutron signs the '
|
|
'Instance-ID header with a shared secret to prevent '
|
|
'spoofing. You may select any string for a secret, '
|
|
'but it MUST match with the configuration used by the '
|
|
'Metadata server.'),
|
|
secret=True)
|
|
]
|
|
|
|
|
|
def register_dhcp_opts(config):
|
|
config.CONF.register_opts(dhcp_opts, group="NSX_DHCP")
|
|
|
|
|
|
def register_metadata_opts(config):
|
|
config.CONF.register_opts(metadata_opts, group="NSX_METADATA")
|
|
|
|
|
|
class DhcpAgentNotifyAPI(object):
|
|
|
|
def __init__(self, plugin, lsn_manager):
|
|
self.plugin = plugin
|
|
self.lsn_manager = lsn_manager
|
|
self._handle_subnet_dhcp_access = {'create': self._subnet_create,
|
|
'update': self._subnet_update,
|
|
'delete': self._subnet_delete}
|
|
|
|
def notify(self, context, data, methodname):
|
|
[resource, action, _e] = methodname.split('.')
|
|
if resource == 'subnet':
|
|
self._handle_subnet_dhcp_access[action](context, data['subnet'])
|
|
elif resource == 'port' and action == 'update':
|
|
self._port_update(context, data['port'])
|
|
|
|
def _port_update(self, context, port):
|
|
# With no fixed IP's there's nothing that can be updated
|
|
if not port["fixed_ips"]:
|
|
return
|
|
network_id = port['network_id']
|
|
subnet_id = port["fixed_ips"][0]['subnet_id']
|
|
filters = {'network_id': [network_id]}
|
|
# Because NSX does not support updating a single host entry we
|
|
# got to build the whole list from scratch and update in bulk
|
|
ports = self.plugin.get_ports(context, filters)
|
|
if not ports:
|
|
return
|
|
dhcp_conf = [
|
|
{'mac_address': p['mac_address'],
|
|
'ip_address': p["fixed_ips"][0]['ip_address']}
|
|
for p in ports if is_user_port(p)
|
|
]
|
|
meta_conf = [
|
|
{'instance_id': p['device_id'],
|
|
'ip_address': p["fixed_ips"][0]['ip_address']}
|
|
for p in ports if is_user_port(p, check_dev_id=True)
|
|
]
|
|
self.lsn_manager.lsn_port_update(
|
|
context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf)
|
|
|
|
def _subnet_create(self, context, subnet, clean_on_err=True):
|
|
if subnet['enable_dhcp']:
|
|
network_id = subnet['network_id']
|
|
# Create port for DHCP service
|
|
dhcp_port = {
|
|
"name": "",
|
|
"admin_state_up": True,
|
|
"device_id": "",
|
|
"device_owner": const.DEVICE_OWNER_DHCP,
|
|
"network_id": network_id,
|
|
"tenant_id": subnet["tenant_id"],
|
|
"mac_address": const.ATTR_NOT_SPECIFIED,
|
|
"fixed_ips": [{"subnet_id": subnet['id']}]
|
|
}
|
|
try:
|
|
# This will end up calling handle_port_dhcp_access
|
|
# down below as well as handle_port_metadata_access
|
|
self.plugin.create_port(context, {'port': dhcp_port})
|
|
except p_exc.PortConfigurationError as e:
|
|
LOG.error("Error while creating subnet %(cidr)s for "
|
|
"network %(network)s. Please, contact "
|
|
"administrator",
|
|
{"cidr": subnet["cidr"],
|
|
"network": network_id})
|
|
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
|
|
self.plugin, context, e.port_id)
|
|
if clean_on_err:
|
|
self.plugin.delete_subnet(context, subnet['id'])
|
|
raise n_exc.Conflict()
|
|
|
|
def _subnet_update(self, context, subnet):
|
|
network_id = subnet['network_id']
|
|
try:
|
|
lsn_id, lsn_port_id = self.lsn_manager.lsn_port_get(
|
|
context, network_id, subnet['id'])
|
|
self.lsn_manager.lsn_port_dhcp_configure(
|
|
context, lsn_id, lsn_port_id, subnet)
|
|
except p_exc.LsnPortNotFound:
|
|
# It's possible that the subnet was created with dhcp off;
|
|
# check if the subnet was uplinked onto a router, and if so
|
|
# remove the patch attachment between the metadata port and
|
|
# the lsn port, in favor on the one we'll be creating during
|
|
# _subnet_create
|
|
self.lsn_manager.lsn_port_dispose(
|
|
context, network_id, d_const.METADATA_MAC)
|
|
# also, check that a dhcp port exists first and provision it
|
|
# accordingly
|
|
filters = dict(network_id=[network_id],
|
|
device_owner=[const.DEVICE_OWNER_DHCP])
|
|
ports = self.plugin.get_ports(context, filters=filters)
|
|
if ports:
|
|
handle_port_dhcp_access(
|
|
self.plugin, context, ports[0], 'create_port')
|
|
else:
|
|
self._subnet_create(context, subnet, clean_on_err=False)
|
|
|
|
def _subnet_delete(self, context, subnet):
|
|
# FIXME(armando-migliaccio): it looks like that a subnet filter
|
|
# is ineffective; so filter by network for now.
|
|
network_id = subnet['network_id']
|
|
filters = dict(network_id=[network_id],
|
|
device_owner=[const.DEVICE_OWNER_DHCP])
|
|
# FIXME(armando-migliaccio): this may be race-y
|
|
ports = self.plugin.get_ports(context, filters=filters)
|
|
if ports:
|
|
# This will end up calling handle_port_dhcp_access
|
|
# down below as well as handle_port_metadata_access
|
|
self.plugin.delete_port(context, ports[0]['id'])
|
|
|
|
|
|
def is_user_port(p, check_dev_id=False):
|
|
usable = p['fixed_ips'] and p['device_owner'] not in d_const.SPECIAL_OWNERS
|
|
return usable if not check_dev_id else usable and p['device_id']
|
|
|
|
|
|
def check_services_requirements(cluster):
|
|
ver = cluster.api_client.get_version()
|
|
# 4.1 is the first and only release where DHCP in NSX
|
|
# will have this feature, as experimental
|
|
if ver.major == 4 and ver.minor == 1:
|
|
cluster_id = cfg.CONF.default_service_cluster_uuid
|
|
if not lsn_api.service_cluster_exists(cluster, cluster_id):
|
|
raise p_exc.ServiceClusterUnavailable(cluster_id=cluster_id)
|
|
else:
|
|
raise p_exc.InvalidVersion(version=ver)
|
|
|
|
|
|
def handle_network_dhcp_access(plugin, context, network, action):
|
|
LOG.info("Performing DHCP %(action)s for resource: %(resource)s",
|
|
{"action": action, "resource": network})
|
|
if action == 'create_network':
|
|
network_id = network['id']
|
|
if network.get(extnet_apidef.EXTERNAL):
|
|
LOG.info("Network %s is external: no LSN to create",
|
|
network_id)
|
|
return
|
|
plugin.lsn_manager.lsn_create(context, network_id)
|
|
elif action == 'delete_network':
|
|
# NOTE(armando-migliaccio): on delete_network, network
|
|
# is just the network id
|
|
network_id = network
|
|
plugin.lsn_manager.lsn_delete_by_network(context, network_id)
|
|
LOG.info("Logical Services Node for network "
|
|
"%s configured successfully", network_id)
|
|
|
|
|
|
def handle_port_dhcp_access(plugin, context, port, action):
|
|
LOG.info("Performing DHCP %(action)s for resource: %(resource)s",
|
|
{"action": action, "resource": port})
|
|
if port["device_owner"] == const.DEVICE_OWNER_DHCP:
|
|
network_id = port["network_id"]
|
|
if action == "create_port":
|
|
# at this point the port must have a subnet and a fixed ip
|
|
subnet_id = port["fixed_ips"][0]['subnet_id']
|
|
subnet = plugin.get_subnet(context, subnet_id)
|
|
subnet_data = {
|
|
"mac_address": port["mac_address"],
|
|
"ip_address": subnet['cidr'],
|
|
"subnet_id": subnet['id']
|
|
}
|
|
try:
|
|
plugin.lsn_manager.lsn_port_dhcp_setup(
|
|
context, network_id, port['id'], subnet_data, subnet)
|
|
except p_exc.PortConfigurationError:
|
|
LOG.error("Error while configuring DHCP for "
|
|
"port %s", port['id'])
|
|
raise n_exc.NeutronException()
|
|
elif action == "delete_port":
|
|
plugin.lsn_manager.lsn_port_dispose(context, network_id,
|
|
port['mac_address'])
|
|
elif port["device_owner"] != const.DEVICE_OWNER_DHCP:
|
|
if port.get("fixed_ips"):
|
|
# do something only if there are IP's and dhcp is enabled
|
|
subnet_id = port["fixed_ips"][0]['subnet_id']
|
|
if not plugin.get_subnet(context, subnet_id)['enable_dhcp']:
|
|
LOG.info("DHCP is disabled for subnet %s: nothing "
|
|
"to do", subnet_id)
|
|
return
|
|
host_data = {
|
|
"mac_address": port["mac_address"],
|
|
"ip_address": port["fixed_ips"][0]['ip_address']
|
|
}
|
|
network_id = port["network_id"]
|
|
if action == "create_port":
|
|
handler = plugin.lsn_manager.lsn_port_dhcp_host_add
|
|
elif action == "delete_port":
|
|
handler = plugin.lsn_manager.lsn_port_dhcp_host_remove
|
|
try:
|
|
handler(context, network_id, subnet_id, host_data)
|
|
except p_exc.PortConfigurationError:
|
|
with excutils.save_and_reraise_exception():
|
|
if action == 'create_port':
|
|
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
|
|
plugin, context, port['id'])
|
|
LOG.info("DHCP for port %s configured successfully", port['id'])
|
|
|
|
|
|
def handle_port_metadata_access(plugin, context, port, is_delete=False):
|
|
if is_user_port(port, check_dev_id=True):
|
|
network_id = port["network_id"]
|
|
network = plugin.get_network(context, network_id)
|
|
if network[extnet_apidef.EXTERNAL]:
|
|
LOG.info("Network %s is external: nothing to do",
|
|
network_id)
|
|
return
|
|
subnet_id = port["fixed_ips"][0]['subnet_id']
|
|
host_data = {
|
|
"instance_id": port["device_id"],
|
|
"tenant_id": port["tenant_id"],
|
|
"ip_address": port["fixed_ips"][0]['ip_address']
|
|
}
|
|
LOG.info("Configuring metadata entry for port %s", port)
|
|
if not is_delete:
|
|
handler = plugin.lsn_manager.lsn_port_meta_host_add
|
|
else:
|
|
handler = plugin.lsn_manager.lsn_port_meta_host_remove
|
|
try:
|
|
handler(context, network_id, subnet_id, host_data)
|
|
except p_exc.PortConfigurationError:
|
|
with excutils.save_and_reraise_exception():
|
|
if not is_delete:
|
|
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
|
|
plugin, context, port['id'])
|
|
LOG.info("Metadata for port %s configured successfully",
|
|
port['id'])
|
|
|
|
|
|
def handle_router_metadata_access(plugin, context, router_id, interface=None):
|
|
LOG.info("Handle metadata access via router: %(r)s and "
|
|
"interface %(i)s", {'r': router_id, 'i': interface})
|
|
if interface:
|
|
try:
|
|
plugin.get_port(context, interface['port_id'])
|
|
is_enabled = True
|
|
except n_exc.NotFound:
|
|
is_enabled = False
|
|
subnet_id = interface['subnet_id']
|
|
try:
|
|
plugin.lsn_manager.lsn_metadata_configure(
|
|
context, subnet_id, is_enabled)
|
|
except p_exc.NsxPluginException:
|
|
with excutils.save_and_reraise_exception():
|
|
if is_enabled:
|
|
l3_db.L3_NAT_db_mixin.remove_router_interface(
|
|
plugin, context, router_id, interface)
|
|
LOG.info("Metadata for router %s handled successfully", router_id)
|