
Add JSON Web Token provider abstract class. In addition to this, allow clients to configure the token provider instance such when it is set, the Authorization header of NSXT requests has the bearer token value inserted. Change-Id: Ieb701411413ec239276685f02ee1364bd2b05abd
303 lines
12 KiB
Python
303 lines
12 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 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 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
|
|
|
|
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
|
|
|
|
# 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):
|
|
"""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.
|
|
"""
|
|
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
|
|
url = self._add_pagination_parameters("search?query=%s" % query,
|
|
cursor, page_size)
|
|
|
|
# Retry the search on 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("search?query=%s" % query,
|
|
cursor, page_size)
|
|
|
|
# Retry the search on case of error
|
|
@utils.retry_upon_exception(exceptions.NsxIndexingInProgress,
|
|
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):
|
|
"""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)
|
|
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)
|