Erica Liu 846e7ea1a3 Support querying resources with tags plus attributes
Previously we can only query resources either by tags
or attributes. The patch support searching resources by tags
and user specified attributes

Change-Id: I43d9db2a53cd39f113abbdfc92694f6f9e66ce03
2020-02-06 17:16:13 -08:00

321 lines
13 KiB
Python

# 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
import six
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__)
@six.add_metaclass(abc.ABCMeta)
class NsxLibBase(object):
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(
keepalive_section=self.keepalive_section,
validate_connection_method=self.validate_connection_method,
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.abstractproperty
def keepalive_section(self):
pass
@abc.abstractproperty
def validate_connection_method(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"
return "search?query=%s"
# 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': <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 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_all_by_tags(self, tags, resource_type=None, **extra_attrs):
"""Return all the results searched based on tags."""
results = []
cursor = 0
while True:
response = self.search_by_tags(
resource_type=resource_type, tags=tags, cursor=cursor,
**extra_attrs)
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_resource_by_attributes(self, resource_type, **attributes):
"""Return all the results searched based on attributes."""
results = []
cursor = 0
while True:
response = self.search_resource_by_attributes(
resource_type=resource_type,
cursor=cursor, **attributes)
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 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)