The keystoneclient.openstack.common directory is where we sync files from oslo incubator. It is not a public directory and should not be being consumed by openstackclient. Change-Id: I011bb95c2c824e2dbc4b822ca922ae77b8d9b955
		
			
				
	
	
		
			350 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#   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.
 | 
						|
#
 | 
						|
 | 
						|
"""Base API Library"""
 | 
						|
 | 
						|
import simplejson as json
 | 
						|
 | 
						|
from keystoneclient import exceptions as ksc_exceptions
 | 
						|
from keystoneclient import session as ksc_session
 | 
						|
from openstackclient.common import exceptions
 | 
						|
 | 
						|
 | 
						|
class KeystoneSession(object):
 | 
						|
    """Wrapper for the Keystone Session
 | 
						|
 | 
						|
    Restore some requests.session.Session compatibility;
 | 
						|
    keystoneclient.session.Session.request() has the method and url
 | 
						|
    arguments swapped from the rest of the requests-using world.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        session=None,
 | 
						|
        endpoint=None,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        """Base object that contains some common API objects and methods
 | 
						|
 | 
						|
        :param Session session:
 | 
						|
            The default session to be used for making the HTTP API calls.
 | 
						|
        :param string endpoint:
 | 
						|
            The URL from the Service Catalog to be used as the base for API
 | 
						|
            requests on this API.
 | 
						|
        """
 | 
						|
 | 
						|
        super(KeystoneSession, self).__init__()
 | 
						|
 | 
						|
        # a requests.Session-style interface
 | 
						|
        self.session = session
 | 
						|
        self.endpoint = endpoint
 | 
						|
 | 
						|
    def _request(self, method, url, session=None, **kwargs):
 | 
						|
        """Perform call into session
 | 
						|
 | 
						|
        All API calls are funneled through this method to provide a common
 | 
						|
        place to finalize the passed URL and other things.
 | 
						|
 | 
						|
        :param string method:
 | 
						|
            The HTTP method name, i.e. ``GET``, ``PUT``, etc
 | 
						|
        :param string url:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param Session session:
 | 
						|
            HTTP client session
 | 
						|
        :param kwargs:
 | 
						|
            keyword arguments passed to requests.request().
 | 
						|
        :return: the requests.Response object
 | 
						|
        """
 | 
						|
 | 
						|
        if not session:
 | 
						|
            session = self.session
 | 
						|
        if not session:
 | 
						|
            session = ksc_session.Session()
 | 
						|
 | 
						|
        if self.endpoint:
 | 
						|
            if url:
 | 
						|
                url = '/'.join([self.endpoint.rstrip('/'), url.lstrip('/')])
 | 
						|
            else:
 | 
						|
                url = self.endpoint.rstrip('/')
 | 
						|
 | 
						|
        # Why is ksc session backwards???
 | 
						|
        return session.request(url, method, **kwargs)
 | 
						|
 | 
						|
 | 
						|
class BaseAPI(KeystoneSession):
 | 
						|
    """Base API"""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        session=None,
 | 
						|
        service_type=None,
 | 
						|
        endpoint=None,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        """Base object that contains some common API objects and methods
 | 
						|
 | 
						|
        :param Session session:
 | 
						|
            The default session to be used for making the HTTP API calls.
 | 
						|
        :param string service_type:
 | 
						|
            API name, i.e. ``identity`` or ``compute``
 | 
						|
        :param string endpoint:
 | 
						|
            The URL from the Service Catalog to be used as the base for API
 | 
						|
            requests on this API.
 | 
						|
        """
 | 
						|
 | 
						|
        super(BaseAPI, self).__init__(session=session, endpoint=endpoint)
 | 
						|
 | 
						|
        self.service_type = service_type
 | 
						|
 | 
						|
    # The basic action methods all take a Session and return dict/lists
 | 
						|
 | 
						|
    def create(
 | 
						|
        self,
 | 
						|
        url,
 | 
						|
        session=None,
 | 
						|
        method=None,
 | 
						|
        **params
 | 
						|
    ):
 | 
						|
        """Create a new resource
 | 
						|
 | 
						|
        :param string url:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param Session session:
 | 
						|
            HTTP client session
 | 
						|
        :param string method:
 | 
						|
            HTTP method (default POST)
 | 
						|
        """
 | 
						|
 | 
						|
        if not method:
 | 
						|
            method = 'POST'
 | 
						|
        ret = self._request(method, url, session=session, **params)
 | 
						|
        # Should this move into _requests()?
 | 
						|
        try:
 | 
						|
            return ret.json()
 | 
						|
        except json.JSONDecodeError:
 | 
						|
            return ret
 | 
						|
 | 
						|
    def delete(
 | 
						|
        self,
 | 
						|
        url,
 | 
						|
        session=None,
 | 
						|
        **params
 | 
						|
    ):
 | 
						|
        """Delete a resource
 | 
						|
 | 
						|
        :param string url:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param Session session:
 | 
						|
            HTTP client session
 | 
						|
        """
 | 
						|
 | 
						|
        return self._request('DELETE', url, **params)
 | 
						|
 | 
						|
    def list(
 | 
						|
        self,
 | 
						|
        path,
 | 
						|
        session=None,
 | 
						|
        body=None,
 | 
						|
        detailed=False,
 | 
						|
        **params
 | 
						|
    ):
 | 
						|
        """Return a list of resources
 | 
						|
 | 
						|
        GET ${ENDPOINT}/${PATH}
 | 
						|
 | 
						|
        path is often the object's plural resource type
 | 
						|
 | 
						|
        :param string path:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param Session session:
 | 
						|
            HTTP client session
 | 
						|
        :param body: data that will be encoded as JSON and passed in POST
 | 
						|
            request (GET will be sent by default)
 | 
						|
        :param bool detailed:
 | 
						|
            Adds '/details' to path for some APIs to return extended attributes
 | 
						|
        :returns:
 | 
						|
            JSON-decoded response, could be a list or a dict-wrapped-list
 | 
						|
        """
 | 
						|
 | 
						|
        if detailed:
 | 
						|
            path = '/'.join([path.rstrip('/'), 'details'])
 | 
						|
 | 
						|
        if body:
 | 
						|
            ret = self._request(
 | 
						|
                'POST',
 | 
						|
                path,
 | 
						|
                # service=self.service_type,
 | 
						|
                json=body,
 | 
						|
                params=params,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            ret = self._request(
 | 
						|
                'GET',
 | 
						|
                path,
 | 
						|
                # service=self.service_type,
 | 
						|
                params=params,
 | 
						|
            )
 | 
						|
        try:
 | 
						|
            return ret.json()
 | 
						|
        except json.JSONDecodeError:
 | 
						|
            return ret
 | 
						|
 | 
						|
    # Layered actions built on top of the basic action methods do not
 | 
						|
    # explicitly take a Session but one may still be passed in kwargs
 | 
						|
 | 
						|
    def find_attr(
 | 
						|
        self,
 | 
						|
        path,
 | 
						|
        value=None,
 | 
						|
        attr=None,
 | 
						|
        resource=None,
 | 
						|
    ):
 | 
						|
        """Find a resource via attribute or ID
 | 
						|
 | 
						|
        Most APIs return a list wrapped by a dict with the resource
 | 
						|
        name as key.  Some APIs (Identity) return a dict when a query
 | 
						|
        string is present and there is one return value.  Take steps to
 | 
						|
        unwrap these bodies and return a single dict without any resource
 | 
						|
        wrappers.
 | 
						|
 | 
						|
        :param string path:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param string value:
 | 
						|
            value to search for
 | 
						|
        :param string attr:
 | 
						|
            attribute to use for resource search
 | 
						|
        :param string resource:
 | 
						|
            plural of the object resource name; defaults to path
 | 
						|
 | 
						|
        For example:
 | 
						|
            n = find(netclient, 'network', 'networks', 'matrix')
 | 
						|
        """
 | 
						|
 | 
						|
        # Default attr is 'name'
 | 
						|
        if attr is None:
 | 
						|
            attr = 'name'
 | 
						|
 | 
						|
        # Default resource is path - in many APIs they are the same
 | 
						|
        if resource is None:
 | 
						|
            resource = path
 | 
						|
 | 
						|
        def getlist(kw):
 | 
						|
            """Do list call, unwrap resource dict if present"""
 | 
						|
            ret = self.list(path, **kw)
 | 
						|
            if type(ret) == dict and resource in ret:
 | 
						|
                ret = ret[resource]
 | 
						|
            return ret
 | 
						|
 | 
						|
        # Search by attribute
 | 
						|
        kwargs = {attr: value}
 | 
						|
        data = getlist(kwargs)
 | 
						|
        if type(data) == dict:
 | 
						|
            return data
 | 
						|
        if len(data) == 1:
 | 
						|
            return data[0]
 | 
						|
        if len(data) > 1:
 | 
						|
            msg = "Multiple %s exist with %s='%s'"
 | 
						|
            raise ksc_exceptions.CommandError(
 | 
						|
                msg % (resource, attr, value),
 | 
						|
            )
 | 
						|
 | 
						|
        # Search by id
 | 
						|
        kwargs = {'id': value}
 | 
						|
        data = getlist(kwargs)
 | 
						|
        if len(data) == 1:
 | 
						|
            return data[0]
 | 
						|
        msg = "No %s with a %s or ID of '%s' found"
 | 
						|
        raise exceptions.CommandError(msg % (resource, attr, value))
 | 
						|
 | 
						|
    def find_bulk(
 | 
						|
        self,
 | 
						|
        path,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        """Bulk load and filter locally
 | 
						|
 | 
						|
        :param string path:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param kwargs:
 | 
						|
            A dict of AVPs to match - logical AND
 | 
						|
        :returns: list of resource dicts
 | 
						|
        """
 | 
						|
 | 
						|
        items = self.list(path)
 | 
						|
        if type(items) == dict:
 | 
						|
            # strip off the enclosing dict
 | 
						|
            key = list(items.keys())[0]
 | 
						|
            items = items[key]
 | 
						|
 | 
						|
        ret = []
 | 
						|
        for o in items:
 | 
						|
            try:
 | 
						|
                if all(o[attr] == kwargs[attr] for attr in kwargs.keys()):
 | 
						|
                    ret.append(o)
 | 
						|
            except KeyError:
 | 
						|
                continue
 | 
						|
 | 
						|
        return ret
 | 
						|
 | 
						|
    def find_one(
 | 
						|
        self,
 | 
						|
        path,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        """Find a resource by name or ID
 | 
						|
 | 
						|
        :param string path:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :returns:
 | 
						|
            resource dict
 | 
						|
        """
 | 
						|
 | 
						|
        bulk_list = self.find_bulk(path, **kwargs)
 | 
						|
        num_bulk = len(bulk_list)
 | 
						|
        if num_bulk == 0:
 | 
						|
            msg = "none found"
 | 
						|
            raise ksc_exceptions.NotFound(msg)
 | 
						|
        elif num_bulk > 1:
 | 
						|
            msg = "many found"
 | 
						|
            raise RuntimeError(msg)
 | 
						|
        return bulk_list[0]
 | 
						|
 | 
						|
    def find(
 | 
						|
        self,
 | 
						|
        path,
 | 
						|
        value=None,
 | 
						|
        attr=None,
 | 
						|
    ):
 | 
						|
        """Find a single resource by name or ID
 | 
						|
 | 
						|
        :param string path:
 | 
						|
            The API-specific portion of the URL path
 | 
						|
        :param string search:
 | 
						|
            search expression
 | 
						|
        :param string attr:
 | 
						|
            name of attribute for secondary search
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            ret = self._request('GET', "/%s/%s" % (path, value)).json()
 | 
						|
        except ksc_exceptions.NotFound:
 | 
						|
            kwargs = {attr: value}
 | 
						|
            try:
 | 
						|
                ret = self.find_one("/%s/detail" % (path), **kwargs)
 | 
						|
            except ksc_exceptions.NotFound:
 | 
						|
                msg = "%s not found" % value
 | 
						|
                raise ksc_exceptions.NotFound(msg)
 | 
						|
 | 
						|
        return ret
 |