# 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, **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': , 'tag': } :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 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) 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': , '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)