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
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)
|
|
|