A common library that interfaces with 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.
 
 

380 lines
16 KiB

# Copyright 2016 OpenStack Foundation
# 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 abc
from distutils import version
from oslo_log import log
from vmware_nsxlib._i18n import _
from vmware_nsxlib.v3 import client
from vmware_nsxlib.v3 import cluster
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import utils
LOG = log.getLogger(__name__)
class NsxLibBase(object, metaclass=abc.ABCMeta):
def __init__(self, nsxlib_config):
self.nsx_version = None
self.nsx_api = None
self.default_headers = None
self.set_config(nsxlib_config)
self.set_default_headers(nsxlib_config)
# create the Cluster
self.cluster = cluster.NSXClusteredAPI(self.nsxlib_config)
# create the Client
self.client = client.NSX3Client(
self.cluster,
nsx_api_managers=self.nsxlib_config.nsx_api_managers,
max_attempts=self.nsxlib_config.max_attempts,
url_path_base=self.client_url_prefix,
rate_limit_retry=self.nsxlib_config.rate_limit_retry,
default_headers=self.default_headers)
self.general_apis = utils.NsxLibApiBase(
self.client, self.nsxlib_config)
self.init_api()
super(NsxLibBase, self).__init__()
def set_config(self, nsxlib_config):
"""Set config user provided and extend it according to application"""
self.nsxlib_config = nsxlib_config
self.nsxlib_config.extend(None, url_base=self.client_url_prefix)
def set_default_headers(self, nsxlib_config):
"""Set the default headers with token information"""
if nsxlib_config.token_provider:
try:
token_value = nsxlib_config.token_provider.get_token()
except exceptions.BadJSONWebTokenProviderRequest as e:
LOG.error("Error in retrieving JSON Web Token: %s", e)
return
bearer_token = "Bearer %s" % token_value
self.default_headers = self.default_headers or {}
self.default_headers["Authorization"] = bearer_token
@abc.abstractproperty
def client_url_prefix(self):
pass
@abc.abstractmethod
def init_api(self):
pass
@abc.abstractmethod
def feature_supported(self, feature):
pass
@abc.abstractmethod
def get_version(self):
pass
def build_v3_api_version_tag(self):
return self.general_apis.build_v3_api_version_tag()
def is_internal_resource(self, nsx_resource):
return self.general_apis.is_internal_resource(nsx_resource)
def build_v3_api_version_project_tag(self, project_name, project_id=None):
return self.general_apis.build_v3_api_version_project_tag(
project_name, project_id=project_id)
def build_v3_tags_payload(self, resource, resource_type, project_name):
return self.general_apis.build_v3_tags_payload(
resource, resource_type, project_name)
def reinitialize_cluster(self, resource, event, trigger, payload=None):
self.cluster._reinit_cluster()
def subscribe(self, callback, event):
self.cluster.subscribe(callback, event)
def _add_pagination_parameters(self, url, cursor, page_size):
if cursor:
url += "&cursor=%d" % cursor
if page_size:
url += "&page_size=%d" % page_size
return url
def _get_search_url(self):
if (version.LooseVersion(self.get_version()) >=
version.LooseVersion(nsx_constants.NSX_VERSION_3_0_0)):
return "search/query?query=%s&sort_by=id"
return "search?query=%s&sort_by=id"
# TODO(abhiraut): Revisit this method to generate complex boolean
# queries to search resources.
def search_by_tags(self, tags, resource_type=None, cursor=None,
page_size=None, silent=False, **extra_attrs):
"""Return the list of resources searched based on tags.
Currently the query only supports AND boolean operator.
:param tags: List of dictionaries containing tags. Each
NSX tag dictionary is of the form:
{'scope': <scope_key>, 'tag': <tag_value>}
:param resource_type: Optional string parameter to limit the
scope of the search to the given ResourceType.
:param cursor: Opaque cursor to be used for getting next page of
records (supplied by current result page).
:param page_size: Maximum number of results to return in this page.
:param silent: Silence the logging if True.
:param extra_attrs: Support querying by user specified attributes.
Multiple attributes will be ANDed.
"""
if not tags:
reason = _("Missing required argument 'tags'")
raise exceptions.NsxSearchInvalidQuery(reason=reason)
# Query will return nothing if the same scope is repeated.
query_tags = self._build_query(tags)
query = 'resource_type:%s' % resource_type if resource_type else None
if query:
query += " AND %s" % query_tags
else:
query = query_tags
if extra_attrs:
query += " AND %s" % " AND ".join(
['%s:%s' % (k, v) for (k, v) in extra_attrs.items()])
url = self._add_pagination_parameters(self._get_search_url() % query,
cursor, page_size)
# Retry the search in case of error
@utils.retry_upon_exception(exceptions.NsxSearchError,
max_attempts=self.client.max_attempts)
def do_search(url):
return self.client.url_get(url, silent=silent)
return do_search(url)
def search_resource_by_attributes(self, resource_type, cursor=None,
page_size=None, **attributes):
"""Search resources of a given type matching specific attributes.
It is optional to specify attributes. If multiple attributes are
specified they are ANDed together to form the search query.
:param resource_type: String parameter specifying the desired
resource_type
:param cursor: Opaque cursor to be used for getting next page of
records (supplied by current result page).
:param page_size: Maximum number of results to return in this page.
:param **attributes: an optional set of keyword arguments
specifying filters for the search query.
Wildcards will not be interpeted.
:returns: a list of resources of the requested type matching
specified filters.
"""
if not resource_type:
raise exceptions.NsxSearchInvalidQuery(
reason=_("Resource type was not specified"))
attributes_query = " AND ".join(['%s:%s' % (k, v) for (k, v)
in attributes.items()])
query = 'resource_type:%s' % resource_type + (
" AND %s" % attributes_query if attributes_query else "")
url = self._add_pagination_parameters(self._get_search_url() % query,
cursor, page_size)
# Retry the search in case of error
@utils.retry_upon_exception(exceptions.NsxSearchError,
max_attempts=self.client.max_attempts)
def do_search(url):
return self.client.url_get(url)
return do_search(url)
def search_resource_by_attribute_values(self, resource_type, name, values,
cursor=None, page_size=None):
"""Search resources of a given type matching values of an attribute.
:param resource_type: String parameter specifying the desired
resource_type.
:param name: Attribute name to match.
:param values: List of attribute values to search for.
:param cursor: Opaque cursor to be used for getting next page of
records (supplied by current result page).
:param page_size: Maximum number of results to return in this page.
:returns: a list of resources of the requested type matching
specified attribute values.
"""
attribute_query = " OR ".join(values)
query = 'resource_type:%s' % resource_type + (
" AND %s:(%s)" % (name, attribute_query)
if attribute_query else "")
body = {"query_pipeline": [{"query": query}]}
url = self._add_pagination_parameters(
"search/querypipeline?sort_by=id", cursor, page_size)
# Retry the search in case of error
@utils.retry_upon_exception(exceptions.NsxSearchError,
max_attempts=self.client.max_attempts)
def do_search(url):
return self.client.url_post(url, body)
return do_search(url)
def search_resource_by_filters(self, resource_type, filters,
cursor=None, page_size=None, **extra_attrs):
"""Search resources of a given type matching specific filters.
:param resource_type: String parameter specifying the desired
resource_type.
:param filters: List of dictionaries containing filters. Each
filter dictionary is of the form:
{'field_names': <filter_key>, 'value': <filter_value>}
:param cursor: Opaque cursor to be used for getting next page of
records (supplied by current result page).
:param page_size: Maximum number of results to return in this page.
:param extra_attrs: Support querying by user specified attributes.
:returns: a list of resources of the requested type matching
specified filters.
"""
body = {"primary": {"resource_type": resource_type,
"filters": filters}}
related = extra_attrs.get("related")
if related:
body["related"] = related
url = self._add_pagination_parameters(
"search/aggregate?sort_by=id", cursor, page_size)
# Retry the search in case of error
@utils.retry_upon_exception(exceptions.NsxSearchError,
max_attempts=self.client.max_attempts)
def do_search(url):
return self.client.url_post(url, body)
return do_search(url)
def _search_all(self, search_func, *args, **kwargs):
results = []
cursor = 0
while True:
response = search_func(*args, cursor=cursor, **kwargs)
if not response['results']:
return results
results.extend(response['results'])
cursor = int(response['cursor'])
result_count = int(response['result_count'])
if cursor >= result_count:
return results
def search_all_by_tags(self, tags, resource_type=None, **extra_attrs):
"""Return all the results searched based on tags."""
return self._search_all(self.search_by_tags,
resource_type=resource_type, tags=tags,
**extra_attrs)
def search_all_resource_by_attributes(self, resource_type, **attributes):
"""Return all resources of a given type matching specific attributes.
"""
return self._search_all(self.search_resource_by_attributes,
resource_type=resource_type, **attributes)
def search_all_resource_by_attribute_values(self, resource_type, name,
values):
"""Return all resources of a given type matching an attribute value.
"""
return self._search_all(self.search_resource_by_attribute_values,
resource_type, name, values)
def search_all_resource_by_filters(self, resource_type, filters,
**extra_attrs):
"""Return all resources of a given type matching specific filters."""
return self._search_all(self.search_resource_by_filters, resource_type,
filters, **extra_attrs)
def get_id_by_resource_and_tag(self, resource_type, scope, tag,
alert_not_found=False,
alert_multiple=False):
"""Search a resource type by 1 scope&tag.
Return the id of the result only if it is single.
"""
query_tags = [{'scope': utils.escape_tag_data(scope),
'tag': utils.escape_tag_data(tag)}]
query_result = self.search_by_tags(
tags=query_tags, resource_type=resource_type)
if not query_result['result_count']:
if alert_not_found:
msg = _("No %(type)s found for tag '%(scope)s:%(tag)s'") % {
'type': resource_type,
'scope': scope,
'tag': tag}
LOG.warning(msg)
raise exceptions.ResourceNotFound(
manager=self.nsxlib_config.nsx_api_managers,
operation=msg)
elif query_result['result_count'] == 1:
return query_result['results'][0]['id']
else:
# multiple results
if alert_multiple:
msg = _("Multiple %(type)s found for tag '%(scope)s:"
"%(tag)s'") % {
'type': resource_type,
'scope': scope,
'tag': tag}
LOG.warning(msg)
raise exceptions.ManagerError(
manager=self.nsxlib_config.nsx_api_managers,
operation=msg,
details='')
def _build_tag_query(self, tag):
# Validate that the correct keys are used
if set(tag.keys()) - set(('scope', 'tag')):
reason = _("Only 'scope' and 'tag' keys are supported")
raise exceptions.NsxSearchInvalidQuery(reason=reason)
_scope = tag.get('scope')
_tag = tag.get('tag')
if _scope and _tag:
return 'tags.scope:%s AND tags.tag:%s' % (_scope, _tag)
elif _scope:
return 'tags.scope:%s' % _scope
else:
return 'tags.tag:%s' % _tag
def _build_query(self, tags):
return " AND ".join([self._build_tag_query(item) for item in tags])
def get_tag_limits(self):
try:
result = self.client.url_get('spec/vmware/types/Tag')
scope_length = result['properties']['scope'].get(
'maxLength', utils.MAX_RESOURCE_TYPE_LEN)
tag_length = result['properties']['tag'].get(
'maxLength', utils.MAX_TAG_LEN)
except Exception as e:
LOG.error("Unable to read tag limits. Reason: %s", e)
scope_length = utils.MAX_RESOURCE_TYPE_LEN
tag_length = utils.MAX_TAG_LEN
try:
result = self.client.url_get('spec/vmware/types/ManagedResource')
max_tags = result['properties']['tags']['maxItems']
except Exception as e:
LOG.error("Unable to read maximum tags. Reason: %s", e)
max_tags = utils.MAX_TAGS
return utils.TagLimits(scope_length, tag_length, max_tags)