A set of Neutron drivers for the VMware NSX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

662 lines
28 KiB

# Copyright 2012 VMware, Inc.
# 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 oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
import six
import six.moves.urllib.parse as urlparse
from vmware_nsx.neutron.plugins.vmware.api_client import exception as api_exc
LOG = logging.getLogger(__name__)
def _validate_name(name):
if name and len(name) > MAX_NAME_LEN:
raise Exception("Logical switch name exceeds %d characters",
def _validate_resource(body):
class FakeClient:
SECPROF_RESOURCE = 'securityprofile'
LSWITCH_STATUS = 'lswitchstatus'
LROUTER_STATUS = 'lrouterstatus'
LSWITCH_LPORT_RESOURCE = 'lswitch_lport'
LROUTER_LPORT_RESOURCE = 'lrouter_lport'
LROUTER_NAT_RESOURCE = 'lrouter_nat'
LSWITCH_LPORT_STATUS = 'lswitch_lportstatus'
LSWITCH_LPORT_ATT = 'lswitch_lportattachment'
LROUTER_LPORT_STATUS = 'lrouter_lportstatus'
LROUTER_LPORT_ATT = 'lrouter_lportattachment'
GWSERVICE_RESOURCE = 'gatewayservice'
LSWITCH_RESOURCE: "fake_get_lswitch.json",
LSWITCH_LPORT_RESOURCE: "fake_get_lswitch_lport.json",
LSWITCH_LPORT_STATUS: "fake_get_lswitch_lport_status.json",
LSWITCH_LPORT_ATT: "fake_get_lswitch_lport_att.json",
LROUTER_RESOURCE: "fake_get_lrouter.json",
LROUTER_LPORT_RESOURCE: "fake_get_lrouter_lport.json",
LROUTER_LPORT_STATUS: "fake_get_lrouter_lport_status.json",
LROUTER_LPORT_ATT: "fake_get_lrouter_lport_att.json",
LROUTER_STATUS: "fake_get_lrouter_status.json",
LROUTER_NAT_RESOURCE: "fake_get_lrouter_nat.json",
SECPROF_RESOURCE: "fake_get_security_profile.json",
LQUEUE_RESOURCE: "fake_get_lqueue.json",
GWSERVICE_RESOURCE: "fake_get_gwservice.json"
LSWITCH_RESOURCE: "fake_post_lswitch.json",
LROUTER_RESOURCE: "fake_post_lrouter.json",
LSWITCH_LPORT_RESOURCE: "fake_post_lswitch_lport.json",
LROUTER_LPORT_RESOURCE: "fake_post_lrouter_lport.json",
LROUTER_NAT_RESOURCE: "fake_post_lrouter_nat.json",
SECPROF_RESOURCE: "fake_post_security_profile.json",
LQUEUE_RESOURCE: "fake_post_lqueue.json",
GWSERVICE_RESOURCE: "fake_post_gwservice.json"
LSWITCH_RESOURCE: "fake_post_lswitch.json",
LROUTER_RESOURCE: "fake_post_lrouter.json",
LSWITCH_LPORT_RESOURCE: "fake_post_lswitch_lport.json",
LROUTER_LPORT_RESOURCE: "fake_post_lrouter_lport.json",
LROUTER_NAT_RESOURCE: "fake_post_lrouter_nat.json",
LSWITCH_LPORT_ATT: "fake_put_lswitch_lport_att.json",
LROUTER_LPORT_ATT: "fake_put_lrouter_lport_att.json",
SECPROF_RESOURCE: "fake_post_security_profile.json",
LQUEUE_RESOURCE: "fake_post_lqueue.json",
GWSERVICE_RESOURCE: "fake_post_gwservice.json"
LSWITCH_LPORT_RESOURCE: ['LogicalPortAttachment'],
LROUTER_LPORT_RESOURCE: ['LogicalPortAttachment'],
_validators = {
LSWITCH_RESOURCE: _validate_resource,
LSWITCH_LPORT_RESOURCE: _validate_resource,
LROUTER_LPORT_RESOURCE: _validate_resource,
SECPROF_RESOURCE: _validate_resource,
LQUEUE_RESOURCE: _validate_resource,
GWSERVICE_RESOURCE: _validate_resource
def __init__(self, fake_files_path):
self.fake_files_path = fake_files_path
self._fake_lswitch_dict = {}
self._fake_lrouter_dict = {}
self._fake_lswitch_lport_dict = {}
self._fake_lrouter_lport_dict = {}
self._fake_lrouter_nat_dict = {}
self._fake_lswitch_lportstatus_dict = {}
self._fake_lrouter_lportstatus_dict = {}
self._fake_securityprofile_dict = {}
self._fake_lqueue_dict = {}
self._fake_gatewayservice_dict = {}
def _get_tag(self, resource, scope):
tags = [tag['tag'] for tag in resource['tags']
if tag['scope'] == scope]
return len(tags) > 0 and tags[0]
def _get_filters(self, querystring):
if not querystring:
return (None, None, None, None)
params = urlparse.parse_qs(querystring)
tag_filter = None
attr_filter = None
if 'tag' in params and 'tag_scope' in params:
tag_filter = {'scope': params['tag_scope'][0],
'tag': params['tag'][0]}
elif 'uuid' in params:
attr_filter = {'uuid': params['uuid'][0]}
# Handle page length and page cursor parameter
page_len = params.get('_page_length')
page_cursor = params.get('_page_cursor')
if page_len:
page_len = int(page_len[0])
# Explicitly set it to None (avoid 0 or empty list)
page_len = None
return (tag_filter, attr_filter, page_len, page_cursor)
def _add_lswitch(self, body):
fake_lswitch = jsonutils.loads(body)
fake_lswitch['uuid'] = uuidutils.generate_uuid()
self._fake_lswitch_dict[fake_lswitch['uuid']] = fake_lswitch
# put the tenant_id and the zone_uuid in the main dict
# for simplyfying templating
zone_uuid = fake_lswitch['transport_zones'][0]['zone_uuid']
fake_lswitch['zone_uuid'] = zone_uuid
fake_lswitch['tenant_id'] = self._get_tag(fake_lswitch, 'os_tid')
fake_lswitch['lport_count'] = 0
# set status value
fake_lswitch['status'] = 'true'
return fake_lswitch
def _build_lrouter(self, body, uuid=None):
fake_lrouter = jsonutils.loads(body)
if uuid:
fake_lrouter['uuid'] = uuid
fake_lrouter['tenant_id'] = self._get_tag(fake_lrouter, 'os_tid')
default_nexthop = fake_lrouter['routing_config'].get(
if default_nexthop:
fake_lrouter['default_next_hop'] = default_nexthop.get(
'gateway_ip_address', '')
fake_lrouter['default_next_hop'] = ''
# NOTE(salv-orlando): We won't make the Fake NSX API client
# aware of NSX version. The long term plan is to replace it
# with behavioral mocking of NSX API requests
if 'distributed' not in fake_lrouter:
fake_lrouter['distributed'] = False
distributed_json = ('"distributed": %s,' %
fake_lrouter['distributed_json'] = distributed_json
return fake_lrouter
def _add_lrouter(self, body):
fake_lrouter = self._build_lrouter(body,
self._fake_lrouter_dict[fake_lrouter['uuid']] = fake_lrouter
fake_lrouter['lport_count'] = 0
# set status value
fake_lrouter['status'] = 'true'
return fake_lrouter
def _add_lqueue(self, body):
fake_lqueue = jsonutils.loads(body)
fake_lqueue['uuid'] = uuidutils.generate_uuid()
self._fake_lqueue_dict[fake_lqueue['uuid']] = fake_lqueue
return fake_lqueue
def _add_lswitch_lport(self, body, ls_uuid):
fake_lport = jsonutils.loads(body)
new_uuid = uuidutils.generate_uuid()
fake_lport['uuid'] = new_uuid
# put the tenant_id and the ls_uuid in the main dict
# for simplyfying templating
fake_lport['ls_uuid'] = ls_uuid
fake_lport['tenant_id'] = self._get_tag(fake_lport, 'os_tid')
fake_lport['neutron_port_id'] = self._get_tag(fake_lport,
fake_lport['neutron_device_id'] = self._get_tag(fake_lport, 'vm_id')
fake_lport['att_type'] = "NoAttachment"
fake_lport['att_info_json'] = ''
self._fake_lswitch_lport_dict[fake_lport['uuid']] = fake_lport
fake_lswitch = self._fake_lswitch_dict[ls_uuid]
fake_lswitch['lport_count'] += 1
fake_lport_status = fake_lport.copy()
fake_lport_status['ls_tenant_id'] = fake_lswitch['tenant_id']
fake_lport_status['ls_uuid'] = fake_lswitch['uuid']
fake_lport_status['ls_name'] = fake_lswitch['display_name']
fake_lport_status['ls_zone_uuid'] = fake_lswitch['zone_uuid']
# set status value
fake_lport['status'] = 'true'
self._fake_lswitch_lportstatus_dict[new_uuid] = fake_lport_status
return fake_lport
def _build_lrouter_lport(self, body, new_uuid=None, lr_uuid=None):
fake_lport = jsonutils.loads(body)
if new_uuid:
fake_lport['uuid'] = new_uuid
# put the tenant_id and the le_uuid in the main dict
# for simplyfying templating
if lr_uuid:
fake_lport['lr_uuid'] = lr_uuid
fake_lport['tenant_id'] = self._get_tag(fake_lport, 'os_tid')
fake_lport['neutron_port_id'] = self._get_tag(fake_lport,
# replace ip_address with its json dump
if 'ip_addresses' in fake_lport:
ip_addresses_json = jsonutils.dumps(fake_lport['ip_addresses'])
fake_lport['ip_addresses_json'] = ip_addresses_json
return fake_lport
def _add_lrouter_lport(self, body, lr_uuid):
new_uuid = uuidutils.generate_uuid()
fake_lport = self._build_lrouter_lport(body, new_uuid, lr_uuid)
self._fake_lrouter_lport_dict[fake_lport['uuid']] = fake_lport
fake_lrouter = self._fake_lrouter_dict[lr_uuid]
except KeyError:
raise api_exc.ResourceNotFound()
fake_lrouter['lport_count'] += 1
fake_lport_status = fake_lport.copy()
fake_lport_status['lr_tenant_id'] = fake_lrouter['tenant_id']
fake_lport_status['lr_uuid'] = fake_lrouter['uuid']
fake_lport_status['lr_name'] = fake_lrouter['display_name']
self._fake_lrouter_lportstatus_dict[new_uuid] = fake_lport_status
return fake_lport
def _add_securityprofile(self, body):
fake_securityprofile = jsonutils.loads(body)
fake_securityprofile['uuid'] = uuidutils.generate_uuid()
fake_securityprofile['tenant_id'] = self._get_tag(
fake_securityprofile, 'os_tid')
fake_securityprofile['nova_spid'] = self._get_tag(fake_securityprofile,
self._fake_securityprofile_dict[fake_securityprofile['uuid']] = (
return fake_securityprofile
def _add_lrouter_nat(self, body, lr_uuid):
fake_nat = jsonutils.loads(body)
new_uuid = uuidutils.generate_uuid()
fake_nat['uuid'] = new_uuid
fake_nat['lr_uuid'] = lr_uuid
self._fake_lrouter_nat_dict[fake_nat['uuid']] = fake_nat
if 'match' in fake_nat:
match_json = jsonutils.dumps(fake_nat['match'])
fake_nat['match_json'] = match_json
return fake_nat
def _add_gatewayservice(self, body):
fake_gwservice = jsonutils.loads(body)
fake_gwservice['uuid'] = str(uuidutils.generate_uuid())
fake_gwservice['tenant_id'] = self._get_tag(
fake_gwservice, 'os_tid')
# FIXME(salvatore-orlando): For simplicity we're managing only a
# single device. Extend the fake client for supporting multiple devices
first_gw = fake_gwservice['gateways'][0]
fake_gwservice['transport_node_uuid'] = first_gw['transport_node_uuid']
fake_gwservice['device_id'] = first_gw['device_id']
self._fake_gatewayservice_dict[fake_gwservice['uuid']] = (
return fake_gwservice
def _build_relation(self, src, dst, resource_type, relation):
if relation not in self.MANAGED_RELATIONS[resource_type]:
return # Relation is not desired in output
if '_relations' not in src or not src['_relations'].get(relation):
return # Item does not have relation
relation_data = src['_relations'].get(relation)
dst_relations = dst.get('_relations', {})
dst_relations[relation] = relation_data
dst['_relations'] = dst_relations
def _fill_attachment(self, att_data, ls_uuid=None,
lr_uuid=None, lp_uuid=None):
new_data = att_data.copy()
for k in ('ls_uuid', 'lr_uuid', 'lp_uuid'):
if locals().get(k):
new_data[k] = locals()[k]
def populate_field(field_name):
if field_name in att_data:
new_data['%s_field' % field_name] = ('"%s" : "%s",'
% (field_name,
del new_data[field_name]
new_data['%s_field' % field_name] = ""
for field in ['vif_uuid', 'peer_port_href', 'vlan_id',
'peer_port_uuid', 'l3_gateway_service_uuid']:
return new_data
def _get_resource_type(self, path):
"""Get resource type.
Identifies resource type and relevant uuids in the uri
# The first element will always be 'ws.v1' - so we just discard it
uri_split = path.split('/')[1:]
# parse uri_split backwards
suffix = ""
idx = len(uri_split) - 1
if 'status' in uri_split[idx]:
suffix = "status"
idx = idx - 1
elif 'attachment' in uri_split[idx]:
suffix = "attachment"
idx = idx - 1
# then check if we have an uuid
uuids = []
if uri_split[idx].replace('-', '') not in self.RESOURCES:
idx = idx - 1
resource_type = "%s%s" % (uri_split[idx], suffix)
if idx > 1:
uuids.insert(0, uri_split[idx - 1])
resource_type = "%s_%s" % (uri_split[idx - 2], resource_type)
return (resource_type.replace('-', ''), uuids)
def _list(self, resource_type, response_file,
parent_uuid=None, query=None, relations=None):
(tag_filter, attr_filter,
page_len, page_cursor) = self._get_filters(query)
# result_count attribute in response should appear only when
# page_cursor is not specified
do_result_count = not page_cursor
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type)
if parent_uuid == '*':
parent_uuid = None
# NSX raises ResourceNotFound if lswitch doesn't exist and is not *
elif not res_dict and resource_type == self.LSWITCH_LPORT_RESOURCE:
raise api_exc.ResourceNotFound()
def _attr_match(res_uuid):
if not attr_filter:
return True
item = res_dict[res_uuid]
for (attr, value) in six.iteritems(attr_filter):
if item.get(attr) != value:
return False
return True
def _tag_match(res_uuid):
if not tag_filter:
return True
return any([x['scope'] == tag_filter['scope'] and
x['tag'] == tag_filter['tag']
for x in res_dict[res_uuid]['tags']])
def _lswitch_match(res_uuid):
# verify that the switch exist
if parent_uuid and parent_uuid not in self._fake_lswitch_dict:
raise Exception(_("lswitch:%s not found") % parent_uuid)
if (not parent_uuid
or res_dict[res_uuid].get('ls_uuid') == parent_uuid):
return True
return False
def _lrouter_match(res_uuid):
# verify that the router exist
if parent_uuid and parent_uuid not in self._fake_lrouter_dict:
raise api_exc.ResourceNotFound()
if (not parent_uuid or
res_dict[res_uuid].get('lr_uuid') == parent_uuid):
return True
return False
def _cursor_match(res_uuid, page_cursor):
if not page_cursor:
return True
if page_cursor == res_uuid:
# always return True once page_cursor has been found
page_cursor = None
return True
return False
def _build_item(resource):
item = jsonutils.loads(response_template % resource)
if relations:
for relation in relations:
self._build_relation(resource, item,
resource_type, relation)
return item
for item in res_dict.values():
if 'tags' in item:
item['tags_json'] = jsonutils.dumps(item['tags'])
if resource_type in (self.LSWITCH_LPORT_RESOURCE,
parent_func = _lswitch_match
elif resource_type in (self.LROUTER_LPORT_RESOURCE,
parent_func = _lrouter_match
parent_func = lambda x: True
items = [_build_item(res_dict[res_uuid])
for res_uuid in res_dict
if (parent_func(res_uuid) and
_tag_match(res_uuid) and
_attr_match(res_uuid) and
_cursor_match(res_uuid, page_cursor))]
# Rather inefficient, but hey this is just a mock!
next_cursor = None
total_items = len(items)
if page_len:
next_cursor = items[page_len]['uuid']
except IndexError:
next_cursor = None
items = items[:page_len]
response_dict = {'results': items}
if next_cursor:
response_dict['page_cursor'] = next_cursor
if do_result_count:
response_dict['result_count'] = total_items
return jsonutils.dumps(response_dict)
def _show(self, resource_type, response_file,
uuid1, uuid2=None, relations=None):
target_uuid = uuid2 or uuid1
if resource_type.endswith('attachment'):
resource_type = resource_type[:resource_type.index('attachment')]
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type)
for item in res_dict.values():
if 'tags' in item:
item['tags_json'] = jsonutils.dumps(item['tags'])
# replace sec prof rules with their json dump
def jsonify_rules(rule_key):
if rule_key in item:
rules_json = jsonutils.dumps(item[rule_key])
item['%s_json' % rule_key] = rules_json
items = [jsonutils.loads(response_template % res_dict[res_uuid])
for res_uuid in res_dict if res_uuid == target_uuid]
if items:
return jsonutils.dumps(items[0])
raise api_exc.ResourceNotFound()
def handle_get(self, url):
#TODO(salvatore-orlando): handle field selection
parsedurl = urlparse.urlparse(url)
(res_type, uuids) = self._get_resource_type(parsedurl.path)
relations = urlparse.parse_qs(parsedurl.query).get('relations')
response_file = self.FAKE_GET_RESPONSES.get(res_type)
if not response_file:
raise api_exc.NsxApiException()
if 'lport' in res_type or 'nat' in res_type:
if len(uuids) > 1:
return self._show(res_type, response_file, uuids[0],
uuids[1], relations=relations)
return self._list(res_type, response_file, uuids[0],
query=parsedurl.query, relations=relations)
elif ('lswitch' in res_type or
'lrouter' in res_type or
self.SECPROF_RESOURCE in res_type or
self.LQUEUE_RESOURCE in res_type or
'gatewayservice' in res_type):
LOG.debug("UUIDS:%s", uuids)
if uuids:
return self._show(res_type, response_file, uuids[0],
return self._list(res_type, response_file,
raise Exception("unknown resource:%s" % res_type)
def handle_post(self, url, body):
parsedurl = urlparse.urlparse(url)
(res_type, uuids) = self._get_resource_type(parsedurl.path)
response_file = self.FAKE_POST_RESPONSES.get(res_type)
if not response_file:
raise Exception("resource not found")
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
add_resource = getattr(self, '_add_%s' % res_type)
body_json = jsonutils.loads(body)
val_func = self._validators.get(res_type)
if val_func:
args = [body]
if uuids:
response = response_template % add_resource(*args)
return response
def handle_put(self, url, body):
parsedurl = urlparse.urlparse(url)
(res_type, uuids) = self._get_resource_type(parsedurl.path)
response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file:
raise Exception("resource not found")
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
# Manage attachment operations
is_attachment = False
if res_type.endswith('attachment'):
is_attachment = True
res_type = res_type[:res_type.index('attachment')]
res_dict = getattr(self, '_fake_%s_dict' % res_type)
body_json = jsonutils.loads(body)
val_func = self._validators.get(res_type)
if val_func:
resource = res_dict[uuids[-1]]
except KeyError:
raise api_exc.ResourceNotFound()
if not is_attachment:
edit_resource = getattr(self, '_build_%s' % res_type, None)
if edit_resource:
body_json = edit_resource(body)
relations = resource.get("_relations", {})
body_2 = jsonutils.loads(body)
resource['att_type'] = body_2['type']
relations['LogicalPortAttachment'] = body_2
resource['_relations'] = relations
if body_2['type'] == "PatchAttachment":
# We need to do a trick here
if self.LROUTER_RESOURCE in res_type:
res_type_2 = res_type.replace(self.LROUTER_RESOURCE,
elif self.LSWITCH_RESOURCE in res_type:
res_type_2 = res_type.replace(self.LSWITCH_RESOURCE,
res_dict_2 = getattr(self, '_fake_%s_dict' % res_type_2)
body_2['peer_port_uuid'] = uuids[-1]
resource_2 = \
relations_2 = resource_2.get("_relations")
if not relations_2:
relations_2 = {}
relations_2['LogicalPortAttachment'] = body_2
resource_2['_relations'] = relations_2
resource['peer_port_uuid'] = body_2['peer_port_uuid']
resource['att_info_json'] = (
"\"peer_port_uuid\": \"%s\"," %
resource_2['att_info_json'] = (
"\"peer_port_uuid\": \"%s\"," %
elif body_2['type'] == "L3GatewayAttachment":
resource['attachment_gwsvc_uuid'] = (
resource['vlan_id'] = body_2.get('vlan_id')
elif body_2['type'] == "L2GatewayAttachment":
resource['attachment_gwsvc_uuid'] = (
elif body_2['type'] == "VifAttachment":
resource['vif_uuid'] = body_2['vif_uuid']
resource['att_info_json'] = (
"\"vif_uuid\": \"%s\"," % body_2['vif_uuid'])
if not is_attachment:
response = response_template % resource
if res_type == self.LROUTER_LPORT_RESOURCE:
lr_uuid = uuids[0]
ls_uuid = None
elif res_type == self.LSWITCH_LPORT_RESOURCE:
ls_uuid = uuids[0]
lr_uuid = None
lp_uuid = uuids[1]
response = response_template % self._fill_attachment(
jsonutils.loads(body), ls_uuid, lr_uuid, lp_uuid)
return response
def handle_delete(self, url):
parsedurl = urlparse.urlparse(url)
(res_type, uuids) = self._get_resource_type(parsedurl.path)
response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file:
raise Exception("resource not found")
res_dict = getattr(self, '_fake_%s_dict' % res_type)
del res_dict[uuids[-1]]
except KeyError:
raise api_exc.ResourceNotFound()
return ""
def fake_request(self, *args, **kwargs):
method = args[0]
handler = getattr(self, "handle_%s" % method.lower())
return handler(*args[1:])
def reset_all(self):