4b09261f76
- Support NSX 3.2 which force all the ports of a network to be migrated togather - Improve documentation - Optionally skip enabling & disabling the migration coordinator service Change-Id: I91900f040e22c336e7b8cc13bc8ed2f30452c80e
342 lines
11 KiB
Python
342 lines
11 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.
|
|
|
|
import inspect
|
|
import re
|
|
|
|
from distutils import version
|
|
import functools
|
|
import hashlib
|
|
import xml.etree.ElementTree as et
|
|
|
|
import eventlet
|
|
import six
|
|
import tenacity
|
|
from tenacity import _utils as tenacity_utils
|
|
|
|
from neutron._i18n import _
|
|
from neutron import version as n_version
|
|
from neutron_lib.api.definitions import provider_net
|
|
from neutron_lib.api import validators
|
|
from neutron_lib import constants
|
|
from neutron_lib import exceptions as n_exc
|
|
from oslo_context import context as common_context
|
|
from oslo_log import log
|
|
|
|
from vmware_nsxlib.v3 import nsx_constants as v3_const
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
MAX_DISPLAY_NAME_LEN = 40
|
|
NEUTRON_VERSION = n_version.version_info.release_string()
|
|
|
|
|
|
# Allowed network types for the NSX Plugin
|
|
class NetworkTypes(object):
|
|
"""Allowed provider network types for the NSX Plugin."""
|
|
L3_EXT = 'l3_ext'
|
|
STT = 'stt'
|
|
GRE = 'gre'
|
|
FLAT = 'flat'
|
|
VLAN = 'vlan'
|
|
BRIDGE = 'bridge'
|
|
PORTGROUP = 'portgroup'
|
|
LOCAL = 'local'
|
|
|
|
|
|
# Allowed network types for the NSX-v Plugin
|
|
class NsxVNetworkTypes(object):
|
|
"""Allowed provider network types for the NSX-v Plugin."""
|
|
FLAT = 'flat'
|
|
VLAN = 'vlan'
|
|
VXLAN = 'vxlan'
|
|
PORTGROUP = 'portgroup'
|
|
|
|
|
|
# Allowed network types for the NSXv3 and NSX-Policy Plugin
|
|
class NsxV3NetworkTypes(object):
|
|
"""Allowed provider network types for the NSXv3 Plugin."""
|
|
FLAT = 'flat'
|
|
VLAN = 'vlan'
|
|
GENEVE = 'geneve'
|
|
NSX_NETWORK = 'nsx-net'
|
|
|
|
|
|
def is_nsx_version_1_1_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_1_1_0))
|
|
|
|
|
|
def is_nsx_version_2_0_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_2_0_0))
|
|
|
|
|
|
def is_nsx_version_2_1_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_2_1_0))
|
|
|
|
|
|
def is_nsx_version_2_4_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_2_4_0))
|
|
|
|
|
|
def is_nsx_version_2_5_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_2_5_0))
|
|
|
|
|
|
def is_nsx_version_3_0_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_3_0_0))
|
|
|
|
|
|
def is_nsx_version_3_1_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_3_1_0))
|
|
|
|
|
|
def is_nsx_version_3_2_0(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion(v3_const.NSX_VERSION_3_2_0))
|
|
|
|
|
|
def is_nsxv_version_6_2(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.2'))
|
|
|
|
|
|
def is_nsxv_version_6_3(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.3'))
|
|
|
|
|
|
def is_nsxv_version_6_4_6(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.4.6'))
|
|
|
|
|
|
def is_nsxv_version_6_4_9(nsx_version):
|
|
return (version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.4.9'))
|
|
|
|
|
|
def is_nsxv_dhcp_binding_supported(nsx_version):
|
|
return ((version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.3.3')) or
|
|
(version.LooseVersion(nsx_version) >=
|
|
version.LooseVersion('6.2.8') and
|
|
version.LooseVersion(nsx_version) <
|
|
version.LooseVersion('6.3')))
|
|
|
|
|
|
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 (validators.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 normalize_xml(data):
|
|
data = data.encode('ascii', 'ignore')
|
|
return et.fromstring(data)
|
|
|
|
|
|
def _get_bad_request_error_code(e):
|
|
"""Get the error code out of the exception"""
|
|
try:
|
|
desc = normalize_xml(e.response)
|
|
return int(desc.find('errorCode').text)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _log_before_retry(retry_state):
|
|
"""Before call strategy that logs to some logger the attempt."""
|
|
if retry_state.attempt_number > 1:
|
|
LOG.warning("Retrying call to '%(func)s' for the %(num)s time",
|
|
{'func': tenacity_utils.get_callback_name(
|
|
retry_state.fn),
|
|
'num': tenacity_utils.to_ordinal(
|
|
retry_state.attempt_number)})
|
|
|
|
|
|
def _get_args_from_frame(frames, frame_num):
|
|
if len(frames) > frame_num and frames[frame_num] and frames[frame_num][0]:
|
|
# pylint: disable=deprecated-method
|
|
argvalues = inspect.getargvalues(frames[frame_num][0])
|
|
# pylint: disable=deprecated-method
|
|
formated_args = inspect.formatargvalues(*argvalues)
|
|
# remove the first 'self' arg from the log as it adds no information
|
|
formated_args = re.sub(r'\(self=.*?, ', "(", formated_args)
|
|
return formated_args
|
|
|
|
|
|
def _log_after_retry(retry_state):
|
|
"""After call strategy that logs to some logger the finished attempt."""
|
|
# Using inspect to get arguments of the relevant call
|
|
frames = inspect.trace()
|
|
# Look at frame #2 first because of the internal functions _do_X
|
|
formated_args = _get_args_from_frame(frames, 2)
|
|
if not formated_args:
|
|
formated_args = _get_args_from_frame(frames, 1)
|
|
if not formated_args:
|
|
formated_args = "Unknown"
|
|
|
|
LOG.warning("Finished retry of %(func)s for the %(num)s time after "
|
|
"%(time)0.3f(s) with args: %(args)s",
|
|
{'func': tenacity_utils.get_callback_name(retry_state.fn),
|
|
'num': tenacity_utils.to_ordinal(retry_state.attempt_number),
|
|
'time': retry_state.seconds_since_start,
|
|
'args': formated_args})
|
|
|
|
|
|
def retry_upon_exception_exclude_error_codes(
|
|
exc, excluded_errors, delay, max_delay, max_attempts):
|
|
"""Retry with the configured exponential delay, unless the exception error
|
|
code is in the given list
|
|
"""
|
|
def retry_if_not_error_codes(e):
|
|
# return True only for BadRequests without error codes or with error
|
|
# codes not in the exclude list
|
|
if isinstance(e, exc):
|
|
error_code = _get_bad_request_error_code(e)
|
|
if error_code and error_code not in excluded_errors:
|
|
return True
|
|
return False
|
|
|
|
return tenacity.retry(reraise=True,
|
|
retry=tenacity.retry_if_exception(
|
|
retry_if_not_error_codes),
|
|
wait=tenacity.wait_exponential(
|
|
multiplier=delay, max=max_delay),
|
|
stop=tenacity.stop_after_attempt(max_attempts),
|
|
before=_log_before_retry, after=_log_after_retry)
|
|
|
|
|
|
def retry_upon_exception(exc, delay, max_delay, max_attempts):
|
|
return tenacity.retry(reraise=True,
|
|
retry=tenacity.retry_if_exception_type(exc),
|
|
wait=tenacity.wait_exponential(
|
|
multiplier=delay, max=max_delay),
|
|
stop=tenacity.stop_after_attempt(max_attempts),
|
|
before=_log_before_retry, after=_log_after_retry)
|
|
|
|
|
|
def read_file(path):
|
|
try:
|
|
with open(path) as file:
|
|
return file.read().strip()
|
|
except IOError as e:
|
|
LOG.error("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
|
|
|
|
|
|
def is_port_dhcp_configurable(port):
|
|
owner = port.get('device_owner')
|
|
return (owner and
|
|
not owner.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX))
|
|
|
|
|
|
def spawn_n(func, *args, **kwargs):
|
|
"""Passthrough method for eventlet.spawn_n.
|
|
|
|
This utility exists so that it can be stubbed for testing without
|
|
interfering with the service spawns.
|
|
|
|
It will also grab the context from the threadlocal store and add it to
|
|
the store on the new thread. This allows for continuity in logging the
|
|
context when using this method to spawn a new thread.
|
|
"""
|
|
_context = common_context.get_current()
|
|
|
|
@functools.wraps(func)
|
|
def context_wrapper(*args, **kwargs):
|
|
# NOTE: If update_store is not called after spawn_n it won't be
|
|
# available for the logger to pull from threadlocal storage.
|
|
if _context is not None:
|
|
_context.update_store()
|
|
func(*args, **kwargs)
|
|
|
|
eventlet.spawn_n(context_wrapper, *args, **kwargs)
|
|
|
|
|
|
def raise_if_updates_provider_attributes(attrs):
|
|
"""Raise exception if provider attributes are present.
|
|
|
|
This method is used for plugins that do not support updating provider
|
|
network attributes.
|
|
"""
|
|
if any(validators.is_attr_set(attrs.get(a))
|
|
for a in provider_net.ATTRIBUTES):
|
|
msg = _("Plugin does not support updating provider attributes")
|
|
raise n_exc.InvalidInput(error_message=msg)
|