95809f59c5
This patch completes the process of moving code from nvplib to nsxlib. Utility methods such as do_request and get_all_query_pages are transferred into nsxlib, so that nvplib can be safely removed. Partially implements blueprint nicira-plugin-renaming Change-Id: I43be18c89ac908ebae32b39a8508a7006b5fe328
389 lines
16 KiB
Python
389 lines
16 KiB
Python
# Copyright 2014 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 json
|
|
|
|
from neutron.common import constants
|
|
from neutron.common import exceptions as exception
|
|
from neutron.openstack.common import log
|
|
from neutron.plugins.vmware.api_client import exception as api_exc
|
|
from neutron.plugins.vmware.common import exceptions as nsx_exc
|
|
from neutron.plugins.vmware.common import utils
|
|
from neutron.plugins.vmware.nsxlib import _build_uri_path
|
|
from neutron.plugins.vmware.nsxlib import do_request
|
|
from neutron.plugins.vmware.nsxlib import get_all_query_pages
|
|
|
|
HTTP_GET = "GET"
|
|
HTTP_POST = "POST"
|
|
HTTP_DELETE = "DELETE"
|
|
HTTP_PUT = "PUT"
|
|
|
|
LSWITCH_RESOURCE = "lswitch"
|
|
LSWITCHPORT_RESOURCE = "lport/%s" % LSWITCH_RESOURCE
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def _configure_extensions(lport_obj, mac_address, fixed_ips,
|
|
port_security_enabled, security_profiles,
|
|
queue_id, mac_learning_enabled,
|
|
allowed_address_pairs):
|
|
lport_obj['allowed_address_pairs'] = []
|
|
if port_security_enabled:
|
|
for fixed_ip in fixed_ips:
|
|
ip_address = fixed_ip.get('ip_address')
|
|
if ip_address:
|
|
lport_obj['allowed_address_pairs'].append(
|
|
{'mac_address': mac_address, 'ip_address': ip_address})
|
|
# add address pair allowing src_ip 0.0.0.0 to leave
|
|
# this is required for outgoing dhcp request
|
|
lport_obj["allowed_address_pairs"].append(
|
|
{"mac_address": mac_address,
|
|
"ip_address": "0.0.0.0"})
|
|
lport_obj['security_profiles'] = list(security_profiles or [])
|
|
lport_obj['queue_uuid'] = queue_id
|
|
if mac_learning_enabled is not None:
|
|
lport_obj["mac_learning"] = mac_learning_enabled
|
|
lport_obj["type"] = "LogicalSwitchPortConfig"
|
|
for address_pair in list(allowed_address_pairs or []):
|
|
lport_obj['allowed_address_pairs'].append(
|
|
{'mac_address': address_pair['mac_address'],
|
|
'ip_address': address_pair['ip_address']})
|
|
|
|
|
|
def get_lswitch_by_id(cluster, lswitch_id):
|
|
try:
|
|
lswitch_uri_path = _build_uri_path(
|
|
LSWITCH_RESOURCE, lswitch_id,
|
|
relations="LogicalSwitchStatus")
|
|
return do_request(HTTP_GET, lswitch_uri_path, cluster=cluster)
|
|
except exception.NotFound:
|
|
# FIXME(salv-orlando): this should not raise a neutron exception
|
|
raise exception.NetworkNotFound(net_id=lswitch_id)
|
|
|
|
|
|
def get_lswitches(cluster, neutron_net_id):
|
|
|
|
def lookup_switches_by_tag():
|
|
# Fetch extra logical switches
|
|
lswitch_query_path = _build_uri_path(
|
|
LSWITCH_RESOURCE,
|
|
fields="uuid,display_name,tags,lport_count",
|
|
relations="LogicalSwitchStatus",
|
|
filters={'tag': neutron_net_id,
|
|
'tag_scope': 'quantum_net_id'})
|
|
return get_all_query_pages(lswitch_query_path, cluster)
|
|
|
|
lswitch_uri_path = _build_uri_path(LSWITCH_RESOURCE, neutron_net_id,
|
|
relations="LogicalSwitchStatus")
|
|
results = []
|
|
try:
|
|
ls = do_request(HTTP_GET, lswitch_uri_path, cluster=cluster)
|
|
results.append(ls)
|
|
for tag in ls['tags']:
|
|
if (tag['scope'] == "multi_lswitch" and
|
|
tag['tag'] == "True"):
|
|
results.extend(lookup_switches_by_tag())
|
|
except exception.NotFound:
|
|
# This is legit if the neutron network was created using
|
|
# a post-Havana version of the plugin
|
|
results.extend(lookup_switches_by_tag())
|
|
if results:
|
|
return results
|
|
else:
|
|
raise exception.NetworkNotFound(net_id=neutron_net_id)
|
|
|
|
|
|
def create_lswitch(cluster, neutron_net_id, tenant_id, display_name,
|
|
transport_zones_config,
|
|
shared=None,
|
|
**kwargs):
|
|
# The tag scope adopts a slightly different naming convention for
|
|
# historical reasons
|
|
lswitch_obj = {"display_name": utils.check_and_truncate(display_name),
|
|
"transport_zones": transport_zones_config,
|
|
"tags": utils.get_tags(os_tid=tenant_id,
|
|
quantum_net_id=neutron_net_id)}
|
|
# TODO(salv-orlando): Now that we have async status synchronization
|
|
# this tag is perhaps not needed anymore
|
|
if shared:
|
|
lswitch_obj["tags"].append({"tag": "true",
|
|
"scope": "shared"})
|
|
if "tags" in kwargs:
|
|
lswitch_obj["tags"].extend(kwargs["tags"])
|
|
uri = _build_uri_path(LSWITCH_RESOURCE)
|
|
lswitch = do_request(HTTP_POST, uri, json.dumps(lswitch_obj),
|
|
cluster=cluster)
|
|
LOG.debug(_("Created logical switch: %s"), lswitch['uuid'])
|
|
return lswitch
|
|
|
|
|
|
def update_lswitch(cluster, lswitch_id, display_name,
|
|
tenant_id=None, **kwargs):
|
|
uri = _build_uri_path(LSWITCH_RESOURCE, resource_id=lswitch_id)
|
|
lswitch_obj = {"display_name": utils.check_and_truncate(display_name),
|
|
"tags": utils.get_tags(os_tid=tenant_id)}
|
|
if "tags" in kwargs:
|
|
lswitch_obj["tags"].extend(kwargs["tags"])
|
|
try:
|
|
return do_request(HTTP_PUT, uri, json.dumps(lswitch_obj),
|
|
cluster=cluster)
|
|
except exception.NotFound as e:
|
|
LOG.error(_("Network not found, Error: %s"), str(e))
|
|
raise exception.NetworkNotFound(net_id=lswitch_id)
|
|
|
|
|
|
def delete_network(cluster, net_id, lswitch_id):
|
|
delete_networks(cluster, net_id, [lswitch_id])
|
|
|
|
|
|
#TODO(salvatore-orlando): Simplify and harmonize
|
|
def delete_networks(cluster, net_id, lswitch_ids):
|
|
for ls_id in lswitch_ids:
|
|
path = "/ws.v1/lswitch/%s" % ls_id
|
|
try:
|
|
do_request(HTTP_DELETE, path, cluster=cluster)
|
|
except exception.NotFound as e:
|
|
LOG.error(_("Network not found, Error: %s"), str(e))
|
|
raise exception.NetworkNotFound(net_id=ls_id)
|
|
|
|
|
|
def query_lswitch_lports(cluster, ls_uuid, fields="*",
|
|
filters=None, relations=None):
|
|
# Fix filter for attachments
|
|
if filters and "attachment" in filters:
|
|
filters['attachment_vif_uuid'] = filters["attachment"]
|
|
del filters['attachment']
|
|
uri = _build_uri_path(LSWITCHPORT_RESOURCE, parent_resource_id=ls_uuid,
|
|
fields=fields, filters=filters, relations=relations)
|
|
return do_request(HTTP_GET, uri, cluster=cluster)['results']
|
|
|
|
|
|
def delete_port(cluster, switch, port):
|
|
uri = "/ws.v1/lswitch/" + switch + "/lport/" + port
|
|
try:
|
|
do_request(HTTP_DELETE, uri, cluster=cluster)
|
|
except exception.NotFound:
|
|
LOG.exception(_("Port or Network not found"))
|
|
raise exception.PortNotFoundOnNetwork(
|
|
net_id=switch, port_id=port)
|
|
except api_exc.NsxApiException:
|
|
raise exception.NeutronException()
|
|
|
|
|
|
def get_ports(cluster, networks=None, devices=None, tenants=None):
|
|
vm_filter_obsolete = ""
|
|
vm_filter = ""
|
|
tenant_filter = ""
|
|
# This is used when calling delete_network. Neutron checks to see if
|
|
# the network has any ports.
|
|
if networks:
|
|
# FIXME (Aaron) If we get more than one network_id this won't work
|
|
lswitch = networks[0]
|
|
else:
|
|
lswitch = "*"
|
|
if devices:
|
|
for device_id in devices:
|
|
vm_filter_obsolete = '&'.join(
|
|
["tag_scope=vm_id",
|
|
"tag=%s" % utils.device_id_to_vm_id(device_id,
|
|
obfuscate=True),
|
|
vm_filter_obsolete])
|
|
vm_filter = '&'.join(
|
|
["tag_scope=vm_id",
|
|
"tag=%s" % utils.device_id_to_vm_id(device_id),
|
|
vm_filter])
|
|
if tenants:
|
|
for tenant in tenants:
|
|
tenant_filter = '&'.join(
|
|
["tag_scope=os_tid",
|
|
"tag=%s" % tenant,
|
|
tenant_filter])
|
|
|
|
nsx_lports = {}
|
|
lport_fields_str = ("tags,admin_status_enabled,display_name,"
|
|
"fabric_status_up")
|
|
try:
|
|
lport_query_path_obsolete = (
|
|
"/ws.v1/lswitch/%s/lport?fields=%s&%s%stag_scope=q_port_id"
|
|
"&relations=LogicalPortStatus" %
|
|
(lswitch, lport_fields_str, vm_filter_obsolete, tenant_filter))
|
|
lport_query_path = (
|
|
"/ws.v1/lswitch/%s/lport?fields=%s&%s%stag_scope=q_port_id"
|
|
"&relations=LogicalPortStatus" %
|
|
(lswitch, lport_fields_str, vm_filter, tenant_filter))
|
|
try:
|
|
# NOTE(armando-migliaccio): by querying with obsolete tag first
|
|
# current deployments won't take the performance hit of a double
|
|
# call. In release L-** or M-**, we might want to swap the calls
|
|
# as it's likely that ports with the new tag would outnumber the
|
|
# ones with the old tag
|
|
ports = get_all_query_pages(lport_query_path_obsolete, cluster)
|
|
if not ports:
|
|
ports = get_all_query_pages(lport_query_path, cluster)
|
|
except exception.NotFound:
|
|
LOG.warn(_("Lswitch %s not found in NSX"), lswitch)
|
|
ports = None
|
|
|
|
if ports:
|
|
for port in ports:
|
|
for tag in port["tags"]:
|
|
if tag["scope"] == "q_port_id":
|
|
nsx_lports[tag["tag"]] = port
|
|
except Exception:
|
|
err_msg = _("Unable to get ports")
|
|
LOG.exception(err_msg)
|
|
raise nsx_exc.NsxPluginException(err_msg=err_msg)
|
|
return nsx_lports
|
|
|
|
|
|
def get_port_by_neutron_tag(cluster, lswitch_uuid, neutron_port_id):
|
|
"""Get port by neutron tag.
|
|
|
|
Returns the NSX UUID of the logical port with tag q_port_id equal to
|
|
neutron_port_id or None if the port is not Found.
|
|
"""
|
|
uri = _build_uri_path(LSWITCHPORT_RESOURCE,
|
|
parent_resource_id=lswitch_uuid,
|
|
fields='uuid',
|
|
filters={'tag': neutron_port_id,
|
|
'tag_scope': 'q_port_id'})
|
|
LOG.debug(_("Looking for port with q_port_id tag '%(neutron_port_id)s' "
|
|
"on: '%(lswitch_uuid)s'"),
|
|
{'neutron_port_id': neutron_port_id,
|
|
'lswitch_uuid': lswitch_uuid})
|
|
res = do_request(HTTP_GET, uri, cluster=cluster)
|
|
num_results = len(res["results"])
|
|
if num_results >= 1:
|
|
if num_results > 1:
|
|
LOG.warn(_("Found '%(num_ports)d' ports with "
|
|
"q_port_id tag: '%(neutron_port_id)s'. "
|
|
"Only 1 was expected."),
|
|
{'num_ports': num_results,
|
|
'neutron_port_id': neutron_port_id})
|
|
return res["results"][0]
|
|
|
|
|
|
def get_port(cluster, network, port, relations=None):
|
|
LOG.info(_("get_port() %(network)s %(port)s"),
|
|
{'network': network, 'port': port})
|
|
uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "?"
|
|
if relations:
|
|
uri += "relations=%s" % relations
|
|
try:
|
|
return do_request(HTTP_GET, uri, cluster=cluster)
|
|
except exception.NotFound as e:
|
|
LOG.error(_("Port or Network not found, Error: %s"), str(e))
|
|
raise exception.PortNotFoundOnNetwork(
|
|
port_id=port, net_id=network)
|
|
|
|
|
|
def update_port(cluster, lswitch_uuid, lport_uuid, neutron_port_id, tenant_id,
|
|
display_name, device_id, admin_status_enabled,
|
|
mac_address=None, fixed_ips=None, port_security_enabled=None,
|
|
security_profiles=None, queue_id=None,
|
|
mac_learning_enabled=None, allowed_address_pairs=None):
|
|
lport_obj = dict(
|
|
admin_status_enabled=admin_status_enabled,
|
|
display_name=utils.check_and_truncate(display_name),
|
|
tags=utils.get_tags(os_tid=tenant_id,
|
|
q_port_id=neutron_port_id,
|
|
vm_id=utils.device_id_to_vm_id(device_id)))
|
|
|
|
_configure_extensions(lport_obj, mac_address, fixed_ips,
|
|
port_security_enabled, security_profiles,
|
|
queue_id, mac_learning_enabled,
|
|
allowed_address_pairs)
|
|
|
|
path = "/ws.v1/lswitch/" + lswitch_uuid + "/lport/" + lport_uuid
|
|
try:
|
|
result = do_request(HTTP_PUT, path, json.dumps(lport_obj),
|
|
cluster=cluster)
|
|
LOG.debug(_("Updated logical port %(result)s "
|
|
"on logical switch %(uuid)s"),
|
|
{'result': result['uuid'], 'uuid': lswitch_uuid})
|
|
return result
|
|
except exception.NotFound as e:
|
|
LOG.error(_("Port or Network not found, Error: %s"), str(e))
|
|
raise exception.PortNotFoundOnNetwork(
|
|
port_id=lport_uuid, net_id=lswitch_uuid)
|
|
|
|
|
|
def create_lport(cluster, lswitch_uuid, tenant_id, neutron_port_id,
|
|
display_name, device_id, admin_status_enabled,
|
|
mac_address=None, fixed_ips=None, port_security_enabled=None,
|
|
security_profiles=None, queue_id=None,
|
|
mac_learning_enabled=None, allowed_address_pairs=None):
|
|
"""Creates a logical port on the assigned logical switch."""
|
|
display_name = utils.check_and_truncate(display_name)
|
|
lport_obj = dict(
|
|
admin_status_enabled=admin_status_enabled,
|
|
display_name=display_name,
|
|
tags=utils.get_tags(os_tid=tenant_id,
|
|
q_port_id=neutron_port_id,
|
|
vm_id=utils.device_id_to_vm_id(device_id))
|
|
)
|
|
|
|
_configure_extensions(lport_obj, mac_address, fixed_ips,
|
|
port_security_enabled, security_profiles,
|
|
queue_id, mac_learning_enabled,
|
|
allowed_address_pairs)
|
|
|
|
path = _build_uri_path(LSWITCHPORT_RESOURCE,
|
|
parent_resource_id=lswitch_uuid)
|
|
result = do_request(HTTP_POST, path, json.dumps(lport_obj),
|
|
cluster=cluster)
|
|
|
|
LOG.debug(_("Created logical port %(result)s on logical switch %(uuid)s"),
|
|
{'result': result['uuid'], 'uuid': lswitch_uuid})
|
|
return result
|
|
|
|
|
|
def get_port_status(cluster, lswitch_id, port_id):
|
|
"""Retrieve the operational status of the port."""
|
|
try:
|
|
r = do_request(HTTP_GET,
|
|
"/ws.v1/lswitch/%s/lport/%s/status" %
|
|
(lswitch_id, port_id), cluster=cluster)
|
|
except exception.NotFound as e:
|
|
LOG.error(_("Port not found, Error: %s"), str(e))
|
|
raise exception.PortNotFoundOnNetwork(
|
|
port_id=port_id, net_id=lswitch_id)
|
|
if r['link_status_up'] is True:
|
|
return constants.PORT_STATUS_ACTIVE
|
|
else:
|
|
return constants.PORT_STATUS_DOWN
|
|
|
|
|
|
def plug_interface(cluster, lswitch_id, lport_id, att_obj):
|
|
return do_request(HTTP_PUT,
|
|
_build_uri_path(LSWITCHPORT_RESOURCE,
|
|
lport_id, lswitch_id,
|
|
is_attachment=True),
|
|
json.dumps(att_obj),
|
|
cluster=cluster)
|
|
|
|
|
|
def plug_vif_interface(
|
|
cluster, lswitch_id, port_id, port_type, attachment=None):
|
|
"""Plug a VIF Attachment object in a logical port."""
|
|
lport_obj = {}
|
|
if attachment:
|
|
lport_obj["vif_uuid"] = attachment
|
|
|
|
lport_obj["type"] = port_type
|
|
return plug_interface(cluster, lswitch_id, port_id, lport_obj)
|