# 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. import hashlib from neutron.api.v2 import attributes from neutron.common import exceptions from neutron import version from oslo_config import cfg from oslo_log import log import retrying import six from vmware_nsx._i18n import _, _LE LOG = log.getLogger(__name__) MAX_DISPLAY_NAME_LEN = 40 MAX_RESOURCE_TYPE_LEN = 20 MAX_TAG_LEN = 40 NEUTRON_VERSION = version.version_info.release_string() NSX_NEUTRON_PLUGIN = 'NSX Neutron plugin' OS_NEUTRON_ID_SCOPE = 'os-neutron-id' # Allowed network types for the NSX Plugin class NetworkTypes: """Allowed provider network types for the NSX Plugin.""" L3_EXT = 'l3_ext' STT = 'stt' GRE = 'gre' FLAT = 'flat' VLAN = 'vlan' BRIDGE = 'bridge' # Allowed network types for the NSX-v Plugin class NsxVNetworkTypes: """Allowed provider network types for the NSX-v Plugin.""" FLAT = 'flat' VLAN = 'vlan' VXLAN = 'vxlan' PORTGROUP = 'portgroup' # Allowed network types for the NSXv3 Plugin class NsxV3NetworkTypes: """Allowed provider network types for the NSXv3 Plugin.""" FLAT = 'flat' VLAN = 'vlan' VXLAN = 'vxlan' def get_tags(**kwargs): tags = ([dict(tag=value, scope=key) for key, value in six.iteritems(kwargs)]) tags.append({"tag": NEUTRON_VERSION, "scope": "quantum"}) return sorted(tags, key=lambda x: x['tag']) def device_id_to_vm_id(device_id, obfuscate=False): # device_id can be longer than 40 characters, for example # a device_id for a dhcp port is like the following: # # dhcp83b5fdeb-e3b4-5e18-ac5f-55161...80747326-47d7-46c2-a87a-cf6d5194877c # # To fit it into an NSX tag we need to hash it, however device_id # used for ports associated to VM's are small enough so let's skip the # hashing if len(device_id) > MAX_DISPLAY_NAME_LEN or obfuscate: return hashlib.sha1(device_id.encode()).hexdigest() else: return device_id or "N/A" def check_and_truncate(display_name): if (attributes.is_attr_set(display_name) and len(display_name) > MAX_DISPLAY_NAME_LEN): LOG.debug("Specified name:'%s' exceeds maximum length. " "It will be truncated on NSX", display_name) return display_name[:MAX_DISPLAY_NAME_LEN] return display_name or '' def is_internal_resource(nsx_resource): """ Indicates whether the passed nsx-resource is owned by the plugin for internal use. """ for tag in nsx_resource.get('tags', []): if tag['scope'] == OS_NEUTRON_ID_SCOPE: return tag['tag'] == NSX_NEUTRON_PLUGIN return False def build_v3_api_version_tag(): """ Some resources are created on the manager that do not have a corresponding Neutron resource. """ return [{'scope': OS_NEUTRON_ID_SCOPE, 'tag': NSX_NEUTRON_PLUGIN}, {'scope': "os-api-version", 'tag': version.version_info.release_string()}] def _validate_resource_type_length(resource_type): # Add in a validation to ensure that we catch this at build time if len(resource_type) > MAX_RESOURCE_TYPE_LEN: raise exceptions.InvalidInput( error_message=(_('Resource type cannot exceed %(max_len)s ' 'characters: %(resource_type)s') % {'max_len': MAX_RESOURCE_TYPE_LEN, 'resource_type': resource_type})) def build_v3_tags_payload(resource, resource_type, project_name): """ Construct the tags payload that will be pushed to NSX-v3 Add :, os-project-id:, os-project-name: os-api-version: """ _validate_resource_type_length(resource_type) # There may be cases when the plugin creates the port, for example DHCP if not project_name: project_name = 'NSX Neutron plugin' return [{'scope': resource_type, 'tag': resource.get('id', '')[:MAX_TAG_LEN]}, {'scope': 'os-project-id', 'tag': resource.get('tenant_id', '')[:MAX_TAG_LEN]}, {'scope': 'os-project-name', 'tag': project_name[:MAX_TAG_LEN]}, {'scope': 'os-api-version', 'tag': version.version_info.release_string()[:MAX_TAG_LEN]}] def add_v3_tag(tags, resource_type, tag): _validate_resource_type_length(resource_type) tags.append({'scope': resource_type, 'tag': tag[:MAX_TAG_LEN]}) return tags def update_v3_tags(tags, resources): port_tags = dict((t['scope'], t['tag']) for t in tags) resources = resources or [] # Update tags for resource in resources: tag = resource['tag'][:MAX_TAG_LEN] resource_type = resource['resource_type'] if resource_type in port_tags: if tag: port_tags[resource_type] = tag else: port_tags.pop(resource_type, None) else: port_tags[resource_type] = tag # Create the new set of tags return [{'scope': k, 'tag': v} for k, v in port_tags.items()] def retry_upon_exception_nsxv3(exc, delay=500, max_delay=2000, max_attempts=cfg.CONF.nsx_v3.retries): return retrying.retry(retry_on_exception=lambda e: isinstance(e, exc), wait_exponential_multiplier=delay, wait_exponential_max=max_delay, stop_max_attempt_number=max_attempts) def list_match(list1, list2): # Check if list1 and list2 have identical elements, but relaxed on # dict elements where list1's dict element can be a subset of list2's # corresponding element. if (not isinstance(list1, list) or not isinstance(list2, list) or len(list1) != len(list2)): return False list1 = sorted(list1) list2 = sorted(list2) for (v1, v2) in zip(list1, list2): if isinstance(v1, dict): if not dict_match(v1, v2): return False elif isinstance(v1, list): if not list_match(v1, v2): return False elif v1 != v2: return False return True def dict_match(dict1, dict2): # Check if dict1 is a subset of dict2. if not isinstance(dict1, dict) or not isinstance(dict2, dict): return False for k1, v1 in dict1.items(): if k1 not in dict2: return False v2 = dict2[k1] if isinstance(v1, dict): if not dict_match(v1, v2): return False elif isinstance(v1, list): if not list_match(v1, v2): return False elif v1 != v2: return False return True def read_file(path): try: with open(path) as file: return file.read().strip() except IOError as e: LOG.error(_LE("Error while opening file " "%(path)s: %(err)s"), {'path': path, 'err': str(e)}) def get_name_and_uuid(name, uuid, tag=None, maxlen=80): short_uuid = '_' + uuid[:5] + '...' + uuid[-5:] maxlen = maxlen - len(short_uuid) if tag: maxlen = maxlen - len(tag) - 1 return name[:maxlen] + '_' + tag + short_uuid else: return name[:maxlen] + short_uuid def is_ipv4_ip_address(addr): def _valid_part(part): try: int_part = int(part) if int_part < 0 or int_part > 255: return False return True except ValueError: return False parts = str(addr).split('.') if len(parts) != 4: return False for ip_part in parts: if not _valid_part(ip_part): return False return True