catch up neutronclient change
Change-Id: I1354fe5378566dec66e7cac311a394cb5498c734
This commit is contained in:
		@@ -22,11 +22,12 @@ import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from keystoneclient import access
 | 
			
		||||
from keystoneclient import adapter
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import exceptions
 | 
			
		||||
from tackerclient.common import utils
 | 
			
		||||
from tackerclient.openstack.common.gettextutils import _
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
_logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -39,12 +40,14 @@ else:
 | 
			
		||||
    _requests_log_level = logging.WARNING
 | 
			
		||||
 | 
			
		||||
logging.getLogger("requests").setLevel(_requests_log_level)
 | 
			
		||||
MAX_URI_LEN = 8192
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
    """Handles the REST calls and responses, include authn."""
 | 
			
		||||
 | 
			
		||||
    USER_AGENT = 'python-tackerclient'
 | 
			
		||||
    CONTENT_TYPE = 'application/json'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, username=None, user_id=None,
 | 
			
		||||
                 tenant_name=None, tenant_id=None,
 | 
			
		||||
@@ -69,7 +72,6 @@ class HTTPClient(object):
 | 
			
		||||
        self.auth_token = token
 | 
			
		||||
        self.auth_tenant_id = None
 | 
			
		||||
        self.auth_user_id = None
 | 
			
		||||
        self.content_type = 'application/json'
 | 
			
		||||
        self.endpoint_url = endpoint_url
 | 
			
		||||
        self.auth_strategy = auth_strategy
 | 
			
		||||
        self.log_credentials = log_credentials
 | 
			
		||||
@@ -83,17 +85,8 @@ class HTTPClient(object):
 | 
			
		||||
        kargs.setdefault('headers', kwargs.get('headers', {}))
 | 
			
		||||
        kargs['headers']['User-Agent'] = self.USER_AGENT
 | 
			
		||||
 | 
			
		||||
        if 'content_type' in kwargs:
 | 
			
		||||
            kargs['headers']['Content-Type'] = kwargs['content_type']
 | 
			
		||||
            kargs['headers']['Accept'] = kwargs['content_type']
 | 
			
		||||
        else:
 | 
			
		||||
            kargs['headers']['Content-Type'] = self.content_type
 | 
			
		||||
            kargs['headers']['Accept'] = self.content_type
 | 
			
		||||
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            kargs['body'] = kwargs['body']
 | 
			
		||||
        args = utils.safe_encode_list(args)
 | 
			
		||||
        kargs = utils.safe_encode_dict(kargs)
 | 
			
		||||
 | 
			
		||||
        if self.log_credentials:
 | 
			
		||||
            log_kargs = kargs
 | 
			
		||||
@@ -112,8 +105,7 @@ class HTTPClient(object):
 | 
			
		||||
            _logger.debug("throwing ConnectionFailed : %s", e)
 | 
			
		||||
            raise exceptions.ConnectionFailed(reason=e)
 | 
			
		||||
        utils.http_log_resp(_logger, resp, body)
 | 
			
		||||
        status_code = self.get_status_code(resp)
 | 
			
		||||
        if status_code == 401:
 | 
			
		||||
        if resp.status_code == 401:
 | 
			
		||||
            raise exceptions.Unauthorized(message=body)
 | 
			
		||||
        return resp, body
 | 
			
		||||
 | 
			
		||||
@@ -132,24 +124,40 @@ class HTTPClient(object):
 | 
			
		||||
        elif not self.endpoint_url:
 | 
			
		||||
            self.endpoint_url = self._get_endpoint_url()
 | 
			
		||||
 | 
			
		||||
    def request(self, url, method, **kwargs):
 | 
			
		||||
        kwargs.setdefault('headers', kwargs.get('headers', {}))
 | 
			
		||||
        kwargs['headers']['User-Agent'] = self.USER_AGENT
 | 
			
		||||
        kwargs['headers']['Accept'] = 'application/json'
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            kwargs['headers']['Content-Type'] = 'application/json'
 | 
			
		||||
            kwargs['data'] = kwargs['body']
 | 
			
		||||
            del kwargs['body']
 | 
			
		||||
    def request(self, url, method, body=None, headers=None, **kwargs):
 | 
			
		||||
        """Request without authentication."""
 | 
			
		||||
 | 
			
		||||
        content_type = kwargs.pop('content_type', None) or 'application/json'
 | 
			
		||||
        headers = headers or {}
 | 
			
		||||
        headers.setdefault('Accept', content_type)
 | 
			
		||||
 | 
			
		||||
        if body:
 | 
			
		||||
            headers.setdefault('Content-Type', content_type)
 | 
			
		||||
 | 
			
		||||
        headers['User-Agent'] = self.USER_AGENT
 | 
			
		||||
 | 
			
		||||
        resp = requests.request(
 | 
			
		||||
            method,
 | 
			
		||||
            url,
 | 
			
		||||
            data=body,
 | 
			
		||||
            headers=headers,
 | 
			
		||||
            verify=self.verify_cert,
 | 
			
		||||
            timeout=self.timeout,
 | 
			
		||||
            **kwargs)
 | 
			
		||||
 | 
			
		||||
        return resp, resp.text
 | 
			
		||||
 | 
			
		||||
    def _check_uri_length(self, action):
 | 
			
		||||
        uri_len = len(self.endpoint_url) + len(action)
 | 
			
		||||
        if uri_len > MAX_URI_LEN:
 | 
			
		||||
            raise exceptions.RequestURITooLong(
 | 
			
		||||
                excess=uri_len - MAX_URI_LEN)
 | 
			
		||||
 | 
			
		||||
    def do_request(self, url, method, **kwargs):
 | 
			
		||||
        # Ensure client always has correct uri - do not guesstimate anything
 | 
			
		||||
        self.authenticate_and_fetch_endpoint_url()
 | 
			
		||||
        self._check_uri_length(url)
 | 
			
		||||
 | 
			
		||||
        # Perform the request once. If we get a 401 back then it
 | 
			
		||||
        # might be because the auth token expired, so try to
 | 
			
		||||
        # re-authenticate and try again. If it still fails, bail.
 | 
			
		||||
@@ -206,8 +214,7 @@ class HTTPClient(object):
 | 
			
		||||
                                           body=json.dumps(body),
 | 
			
		||||
                                           content_type="application/json",
 | 
			
		||||
                                           allow_redirects=True)
 | 
			
		||||
        status_code = self.get_status_code(resp)
 | 
			
		||||
        if status_code != 200:
 | 
			
		||||
        if resp.status_code != 200:
 | 
			
		||||
            raise exceptions.Unauthorized(message=resp_body)
 | 
			
		||||
        if resp_body:
 | 
			
		||||
            try:
 | 
			
		||||
@@ -250,7 +257,7 @@ class HTTPClient(object):
 | 
			
		||||
        body = json.loads(body)
 | 
			
		||||
        for endpoint in body.get('endpoints', []):
 | 
			
		||||
            if (endpoint['type'] == 'servicevm' and
 | 
			
		||||
                endpoint.get('region') == self.region_name):
 | 
			
		||||
                    endpoint.get('region') == self.region_name):
 | 
			
		||||
                if self.endpoint_type not in endpoint:
 | 
			
		||||
                    raise exceptions.EndpointTypeNotFound(
 | 
			
		||||
                        type_=self.endpoint_type)
 | 
			
		||||
@@ -264,13 +271,122 @@ class HTTPClient(object):
 | 
			
		||||
                'auth_user_id': self.auth_user_id,
 | 
			
		||||
                'endpoint_url': self.endpoint_url}
 | 
			
		||||
 | 
			
		||||
    def get_status_code(self, response):
 | 
			
		||||
        """Returns the integer status code from the response.
 | 
			
		||||
 | 
			
		||||
        Either a Webob.Response (used in testing) or requests.Response
 | 
			
		||||
        is returned.
 | 
			
		||||
        """
 | 
			
		||||
        if hasattr(response, 'status_int'):
 | 
			
		||||
            return response.status_int
 | 
			
		||||
class SessionClient(adapter.Adapter):
 | 
			
		||||
 | 
			
		||||
    def request(self, *args, **kwargs):
 | 
			
		||||
        kwargs.setdefault('authenticated', False)
 | 
			
		||||
        kwargs.setdefault('raise_exc', False)
 | 
			
		||||
 | 
			
		||||
        content_type = kwargs.pop('content_type', None) or 'application/json'
 | 
			
		||||
 | 
			
		||||
        headers = kwargs.setdefault('headers', {})
 | 
			
		||||
        headers.setdefault('Accept', content_type)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            kwargs.setdefault('data', kwargs.pop('body'))
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if kwargs.get('data'):
 | 
			
		||||
            headers.setdefault('Content-Type', content_type)
 | 
			
		||||
 | 
			
		||||
        resp = super(SessionClient, self).request(*args, **kwargs)
 | 
			
		||||
        return resp, resp.text
 | 
			
		||||
 | 
			
		||||
    def _check_uri_length(self, url):
 | 
			
		||||
        uri_len = len(self.endpoint_url) + len(url)
 | 
			
		||||
        if uri_len > MAX_URI_LEN:
 | 
			
		||||
            raise exceptions.RequestURITooLong(
 | 
			
		||||
                excess=uri_len - MAX_URI_LEN)
 | 
			
		||||
 | 
			
		||||
    def do_request(self, url, method, **kwargs):
 | 
			
		||||
        kwargs.setdefault('authenticated', True)
 | 
			
		||||
        self._check_uri_length(url)
 | 
			
		||||
        return self.request(url, method, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def endpoint_url(self):
 | 
			
		||||
        # NOTE(jamielennox): This is used purely by the CLI and should be
 | 
			
		||||
        # removed when the CLI gets smarter.
 | 
			
		||||
        return self.get_endpoint()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def auth_token(self):
 | 
			
		||||
        # NOTE(jamielennox): This is used purely by the CLI and should be
 | 
			
		||||
        # removed when the CLI gets smarter.
 | 
			
		||||
        return self.get_token()
 | 
			
		||||
 | 
			
		||||
    def authenticate(self):
 | 
			
		||||
        # NOTE(jamielennox): This is used purely by the CLI and should be
 | 
			
		||||
        # removed when the CLI gets smarter.
 | 
			
		||||
        self.get_token()
 | 
			
		||||
 | 
			
		||||
    def get_auth_info(self):
 | 
			
		||||
        auth_info = {'auth_token': self.auth_token,
 | 
			
		||||
                     'endpoint_url': self.endpoint_url}
 | 
			
		||||
 | 
			
		||||
        # NOTE(jamielennox): This is the best we can do here. It will work
 | 
			
		||||
        # with identity plugins which is the primary case but we should
 | 
			
		||||
        # deprecate it's usage as much as possible.
 | 
			
		||||
        try:
 | 
			
		||||
            get_access = (self.auth or self.session.auth).get_access
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            return response.status_code
 | 
			
		||||
            auth_ref = get_access(self.session)
 | 
			
		||||
 | 
			
		||||
            auth_info['auth_tenant_id'] = auth_ref.project_id
 | 
			
		||||
            auth_info['auth_user_id'] = auth_ref.user_id
 | 
			
		||||
 | 
			
		||||
        return auth_info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# FIXME(bklei): Should refactor this to use kwargs and only
 | 
			
		||||
# explicitly list arguments that are not None.
 | 
			
		||||
def construct_http_client(username=None,
 | 
			
		||||
                          user_id=None,
 | 
			
		||||
                          tenant_name=None,
 | 
			
		||||
                          tenant_id=None,
 | 
			
		||||
                          password=None,
 | 
			
		||||
                          auth_url=None,
 | 
			
		||||
                          token=None,
 | 
			
		||||
                          region_name=None,
 | 
			
		||||
                          timeout=None,
 | 
			
		||||
                          endpoint_url=None,
 | 
			
		||||
                          insecure=False,
 | 
			
		||||
                          endpoint_type='publicURL',
 | 
			
		||||
                          log_credentials=None,
 | 
			
		||||
                          auth_strategy='keystone',
 | 
			
		||||
                          ca_cert=None,
 | 
			
		||||
                          service_type='servicevm',
 | 
			
		||||
                          session=None,
 | 
			
		||||
                          **kwargs):
 | 
			
		||||
 | 
			
		||||
    if session:
 | 
			
		||||
        kwargs.setdefault('user_agent', 'python-tackerclient')
 | 
			
		||||
        kwargs.setdefault('interface', endpoint_type)
 | 
			
		||||
        return SessionClient(session=session,
 | 
			
		||||
                             service_type=service_type,
 | 
			
		||||
                             region_name=region_name,
 | 
			
		||||
                             **kwargs)
 | 
			
		||||
    else:
 | 
			
		||||
        # FIXME(bklei): username and password are now optional. Need
 | 
			
		||||
        # to test that they were provided in this mode.  Should also
 | 
			
		||||
        # refactor to use kwargs.
 | 
			
		||||
        return HTTPClient(username=username,
 | 
			
		||||
                          password=password,
 | 
			
		||||
                          tenant_id=tenant_id,
 | 
			
		||||
                          tenant_name=tenant_name,
 | 
			
		||||
                          user_id=user_id,
 | 
			
		||||
                          auth_url=auth_url,
 | 
			
		||||
                          token=token,
 | 
			
		||||
                          endpoint_url=endpoint_url,
 | 
			
		||||
                          insecure=insecure,
 | 
			
		||||
                          timeout=timeout,
 | 
			
		||||
                          region_name=region_name,
 | 
			
		||||
                          endpoint_type=endpoint_type,
 | 
			
		||||
                          service_type=service_type,
 | 
			
		||||
                          ca_cert=ca_cert,
 | 
			
		||||
                          log_credentials=log_credentials,
 | 
			
		||||
                          auth_strategy=auth_strategy)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
# Copyright 2011 VMware, Inc.
 | 
			
		||||
# 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 gettext
 | 
			
		||||
 | 
			
		||||
t = gettext.translation('tackerclient', fallback=True)
 | 
			
		||||
try:
 | 
			
		||||
    ugettext = t.ugettext  # Python 2
 | 
			
		||||
except AttributeError:
 | 
			
		||||
    ugettext = t.gettext   # Python 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _(msg):
 | 
			
		||||
    return ugettext(msg)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,7 @@ LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientCache(object):
 | 
			
		||||
    """Descriptor class for caching created client handles.
 | 
			
		||||
    """
 | 
			
		||||
    """Descriptor class for caching created client handles."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, factory):
 | 
			
		||||
        self.factory = factory
 | 
			
		||||
@@ -42,8 +41,7 @@ class ClientCache(object):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientManager(object):
 | 
			
		||||
    """Manages access to API clients, including authentication.
 | 
			
		||||
    """
 | 
			
		||||
    """Manages access to API clients, including authentication."""
 | 
			
		||||
    tacker = ClientCache(tacker_client.make_client)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, token=None, url=None,
 | 
			
		||||
@@ -61,6 +59,11 @@ class ClientManager(object):
 | 
			
		||||
                 ca_cert=None,
 | 
			
		||||
                 log_credentials=False,
 | 
			
		||||
                 service_type=None,
 | 
			
		||||
                 timeout=None,
 | 
			
		||||
                 retries=0,
 | 
			
		||||
                 raise_errors=True,
 | 
			
		||||
                 session=None,
 | 
			
		||||
                 auth=None,
 | 
			
		||||
                 ):
 | 
			
		||||
        self._token = token
 | 
			
		||||
        self._url = url
 | 
			
		||||
@@ -79,11 +82,16 @@ class ClientManager(object):
 | 
			
		||||
        self._insecure = insecure
 | 
			
		||||
        self._ca_cert = ca_cert
 | 
			
		||||
        self._log_credentials = log_credentials
 | 
			
		||||
        self._timeout = timeout
 | 
			
		||||
        self._retries = retries
 | 
			
		||||
        self._raise_errors = raise_errors
 | 
			
		||||
        self._session = session
 | 
			
		||||
        self._auth = auth
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    def initialize(self):
 | 
			
		||||
        if not self._url:
 | 
			
		||||
            httpclient = client.HTTPClient(
 | 
			
		||||
            httpclient = client.construct_http_client(
 | 
			
		||||
                username=self._username,
 | 
			
		||||
                user_id=self._user_id,
 | 
			
		||||
                tenant_name=self._tenant_name,
 | 
			
		||||
@@ -95,6 +103,9 @@ class ClientManager(object):
 | 
			
		||||
                endpoint_type=self._endpoint_type,
 | 
			
		||||
                insecure=self._insecure,
 | 
			
		||||
                ca_cert=self._ca_cert,
 | 
			
		||||
                timeout=self._timeout,
 | 
			
		||||
                session=self._session,
 | 
			
		||||
                auth=self._auth,
 | 
			
		||||
                log_credentials=self._log_credentials)
 | 
			
		||||
            httpclient.authenticate()
 | 
			
		||||
            # Populate other password flow attributes
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,11 @@
 | 
			
		||||
#    under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
OpenStack base command
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from cliff import command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OpenStackCommand(command.Command):
 | 
			
		||||
    """Base class for OpenStack commands
 | 
			
		||||
    """
 | 
			
		||||
    """Base class for OpenStack commands."""
 | 
			
		||||
 | 
			
		||||
    api = None
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EXT_NS = '_extension_ns'
 | 
			
		||||
XML_NS_V10 = 'http://openstack.org/tacker/api/v1.0'
 | 
			
		||||
XML_NS_V20 = 'http://openstack.org/tacker/api/v1.0'
 | 
			
		||||
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
XSI_ATTR = "xsi:nil"
 | 
			
		||||
XSI_NIL_ATTR = "xmlns:xsi"
 | 
			
		||||
@@ -33,6 +33,7 @@ TYPE_FLOAT = "float"
 | 
			
		||||
TYPE_LIST = "list"
 | 
			
		||||
TYPE_DICT = "dict"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PLURALS = {'templates': 'template',
 | 
			
		||||
           'devices': 'device',
 | 
			
		||||
           'services': 'service'}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import _
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Tacker base exception handling.
 | 
			
		||||
@@ -30,12 +30,11 @@ Exceptions are classified into three categories:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TackerException(Exception):
 | 
			
		||||
    """Base Tacker Exception
 | 
			
		||||
    """Base Tacker Exception.
 | 
			
		||||
 | 
			
		||||
    To correctly use this class, inherit from it and define
 | 
			
		||||
    a 'message' property. That message will get printf'd
 | 
			
		||||
    with the keyword arguments provided to the constructor.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    message = _("An unknown exception occurred.")
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +59,8 @@ class TackerClientException(TackerException):
 | 
			
		||||
    blocks. The actual error message is the one generated on the server side.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    status_code = 0
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None, **kwargs):
 | 
			
		||||
        if 'status_code' in kwargs:
 | 
			
		||||
            self.status_code = kwargs['status_code']
 | 
			
		||||
@@ -139,6 +140,10 @@ class IpAddressInUseClient(Conflict):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidIpForNetworkClient(BadRequest):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OverQuotaClient(Conflict):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
@@ -153,6 +158,10 @@ class IpAddressGenerationFailureClient(Conflict):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MacAddressInUseClient(Conflict):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExternalIpAddressExhaustedClient(BadRequest):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
@@ -212,8 +221,8 @@ class CommandError(TackerCLIError):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedVersion(TackerCLIError):
 | 
			
		||||
    """Indicates that the user is trying to use an unsupported
 | 
			
		||||
       version of the API
 | 
			
		||||
    """Indicates that the user is trying to use an unsupported version of
 | 
			
		||||
    the API.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								tackerclient/common/extension.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tackerclient/common/extension.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
# Copyright 2015 Rackspace Hosting Inc.
 | 
			
		||||
# 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.
 | 
			
		||||
#
 | 
			
		||||
from stevedore import extension
 | 
			
		||||
 | 
			
		||||
from tackerclient.tacker import v1_0 as tackerV10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _discover_via_entry_points():
 | 
			
		||||
    emgr = extension.ExtensionManager('tackerclient.extension',
 | 
			
		||||
                                      invoke_on_load=False)
 | 
			
		||||
    return ((ext.name, ext.plugin) for ext in emgr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TackerClientExtension(tackerV10.TackerCommand):
 | 
			
		||||
    pagination_support = False
 | 
			
		||||
    _formatters = {}
 | 
			
		||||
    sorting_support = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientExtensionShow(TackerClientExtension, tackerV10.ShowCommand):
 | 
			
		||||
    def get_data(self, parsed_args):
 | 
			
		||||
        # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
 | 
			
		||||
        #               for any implementers adding extensions with
 | 
			
		||||
        #               regard to any other extension verb.
 | 
			
		||||
        return self.execute(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def execute(self, parsed_args):
 | 
			
		||||
        return super(ClientExtensionShow, self).get_data(parsed_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientExtensionList(TackerClientExtension, tackerV10.ListCommand):
 | 
			
		||||
 | 
			
		||||
    def get_data(self, parsed_args):
 | 
			
		||||
        # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
 | 
			
		||||
        #               for any implementers adding extensions with
 | 
			
		||||
        #               regard to any other extension verb.
 | 
			
		||||
        return self.execute(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def execute(self, parsed_args):
 | 
			
		||||
        return super(ClientExtensionList, self).get_data(parsed_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientExtensionDelete(TackerClientExtension, tackerV10.DeleteCommand):
 | 
			
		||||
    def run(self, parsed_args):
 | 
			
		||||
        # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
 | 
			
		||||
        #               for any implementers adding extensions with
 | 
			
		||||
        #               regard to any other extension verb.
 | 
			
		||||
        return self.execute(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def execute(self, parsed_args):
 | 
			
		||||
        return super(ClientExtensionDelete, self).run(parsed_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientExtensionCreate(TackerClientExtension, tackerV10.CreateCommand):
 | 
			
		||||
    def get_data(self, parsed_args):
 | 
			
		||||
        # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
 | 
			
		||||
        #               for any implementers adding extensions with
 | 
			
		||||
        #               regard to any other extension verb.
 | 
			
		||||
        return self.execute(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def execute(self, parsed_args):
 | 
			
		||||
        return super(ClientExtensionCreate, self).get_data(parsed_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientExtensionUpdate(TackerClientExtension, tackerV10.UpdateCommand):
 | 
			
		||||
    def run(self, parsed_args):
 | 
			
		||||
        # NOTE(mdietz): Calls 'execute' to provide a consistent pattern
 | 
			
		||||
        #               for any implementers adding extensions with
 | 
			
		||||
        #               regard to any other extension verb.
 | 
			
		||||
        return self.execute(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def execute(self, parsed_args):
 | 
			
		||||
        return super(ClientExtensionUpdate, self).run(parsed_args)
 | 
			
		||||
@@ -12,23 +12,23 @@
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
#
 | 
			
		||||
###
 | 
			
		||||
### Codes from tacker wsgi
 | 
			
		||||
###
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from xml.etree import ElementTree as etree
 | 
			
		||||
from xml.parsers import expat
 | 
			
		||||
 | 
			
		||||
from oslo.serialization import jsonutils
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import constants
 | 
			
		||||
from tackerclient.common import exceptions as exception
 | 
			
		||||
from tackerclient.openstack.common.gettextutils import _
 | 
			
		||||
from tackerclient.openstack.common import jsonutils
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
if six.PY3:
 | 
			
		||||
    long = int
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionDispatcher(object):
 | 
			
		||||
    """Maps method name to local methods through action name."""
 | 
			
		||||
@@ -58,7 +58,7 @@ class JSONDictSerializer(DictSerializer):
 | 
			
		||||
 | 
			
		||||
    def default(self, data):
 | 
			
		||||
        def sanitizer(obj):
 | 
			
		||||
            return unicode(obj)
 | 
			
		||||
            return six.text_type(obj)
 | 
			
		||||
        return jsonutils.dumps(data, default=sanitizer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -67,16 +67,16 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
    def __init__(self, metadata=None, xmlns=None):
 | 
			
		||||
        """XMLDictSerializer constructor.
 | 
			
		||||
 | 
			
		||||
        :param metadata: information needed to deserialize xml into
 | 
			
		||||
        :param metadata: information needed to deserialize XML into
 | 
			
		||||
                         a dictionary.
 | 
			
		||||
        :param xmlns: XML namespace to include with serialized xml
 | 
			
		||||
        :param xmlns: XML namespace to include with serialized XML
 | 
			
		||||
        """
 | 
			
		||||
        super(XMLDictSerializer, self).__init__()
 | 
			
		||||
        self.metadata = metadata or {}
 | 
			
		||||
        if not xmlns:
 | 
			
		||||
            xmlns = self.metadata.get('xmlns')
 | 
			
		||||
        if not xmlns:
 | 
			
		||||
            xmlns = constants.XML_NS_V10
 | 
			
		||||
            xmlns = constants.XML_NS_V20
 | 
			
		||||
        self.xmlns = xmlns
 | 
			
		||||
 | 
			
		||||
    def default(self, data):
 | 
			
		||||
@@ -93,13 +93,13 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
                root_key = constants.VIRTUAL_ROOT_KEY
 | 
			
		||||
                root_value = None
 | 
			
		||||
            else:
 | 
			
		||||
                link_keys = [k for k in data.iterkeys() or []
 | 
			
		||||
                link_keys = [k for k in six.iterkeys(data) or []
 | 
			
		||||
                             if k.endswith('_links')]
 | 
			
		||||
                if link_keys:
 | 
			
		||||
                    links = data.pop(link_keys[0], None)
 | 
			
		||||
                    has_atom = True
 | 
			
		||||
                root_key = (len(data) == 1 and
 | 
			
		||||
                            data.keys()[0] or constants.VIRTUAL_ROOT_KEY)
 | 
			
		||||
                            list(data.keys())[0] or constants.VIRTUAL_ROOT_KEY)
 | 
			
		||||
                root_value = data.get(root_key, data)
 | 
			
		||||
            doc = etree.Element("_temp_root")
 | 
			
		||||
            used_prefixes = []
 | 
			
		||||
@@ -122,8 +122,8 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
        self._add_xmlns(node, used_prefixes, has_atom)
 | 
			
		||||
        return etree.tostring(node, encoding='UTF-8')
 | 
			
		||||
 | 
			
		||||
    #NOTE (ameade): the has_atom should be removed after all of the
 | 
			
		||||
    # xml serializers and view builders have been updated to the current
 | 
			
		||||
    # NOTE(ameade): the has_atom should be removed after all of the
 | 
			
		||||
    # XML serializers and view builders have been updated to the current
 | 
			
		||||
    # spec that required all responses include the xmlns:atom, the has_atom
 | 
			
		||||
    # flag is to prevent current tests from breaking
 | 
			
		||||
    def _add_xmlns(self, node, used_prefixes, has_atom=False):
 | 
			
		||||
@@ -142,7 +142,7 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
        result = etree.SubElement(parent, nodename)
 | 
			
		||||
        if ":" in nodename:
 | 
			
		||||
            used_prefixes.append(nodename.split(":", 1)[0])
 | 
			
		||||
        #TODO(bcwaldon): accomplish this without a type-check
 | 
			
		||||
        # TODO(bcwaldon): accomplish this without a type-check
 | 
			
		||||
        if isinstance(data, list):
 | 
			
		||||
            if not data:
 | 
			
		||||
                result.set(
 | 
			
		||||
@@ -158,7 +158,7 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
            for item in data:
 | 
			
		||||
                self._to_xml_node(result, metadata, singular, item,
 | 
			
		||||
                                  used_prefixes)
 | 
			
		||||
        #TODO(bcwaldon): accomplish this without a type-check
 | 
			
		||||
        # TODO(bcwaldon): accomplish this without a type-check
 | 
			
		||||
        elif isinstance(data, dict):
 | 
			
		||||
            if not data:
 | 
			
		||||
                result.set(
 | 
			
		||||
@@ -191,13 +191,10 @@ class XMLDictSerializer(DictSerializer):
 | 
			
		||||
                result.set(
 | 
			
		||||
                    constants.TYPE_ATTR,
 | 
			
		||||
                    constants.TYPE_FLOAT)
 | 
			
		||||
            LOG.debug(_("Data %(data)s type is %(type)s"),
 | 
			
		||||
            LOG.debug("Data %(data)s type is %(type)s",
 | 
			
		||||
                      {'data': data,
 | 
			
		||||
                       'type': type(data)})
 | 
			
		||||
            if isinstance(data, str):
 | 
			
		||||
                result.text = unicode(data, 'utf-8')
 | 
			
		||||
            else:
 | 
			
		||||
                result.text = unicode(data)
 | 
			
		||||
            result.text = six.text_type(data)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def _create_link_nodes(self, xml_doc, links):
 | 
			
		||||
@@ -235,14 +232,14 @@ class XMLDeserializer(TextDeserializer):
 | 
			
		||||
    def __init__(self, metadata=None):
 | 
			
		||||
        """XMLDeserializer constructor.
 | 
			
		||||
 | 
			
		||||
        :param metadata: information needed to deserialize xml into
 | 
			
		||||
        :param metadata: information needed to deserialize XML into
 | 
			
		||||
                         a dictionary.
 | 
			
		||||
        """
 | 
			
		||||
        super(XMLDeserializer, self).__init__()
 | 
			
		||||
        self.metadata = metadata or {}
 | 
			
		||||
        xmlns = self.metadata.get('xmlns')
 | 
			
		||||
        if not xmlns:
 | 
			
		||||
            xmlns = constants.XML_NS_V10
 | 
			
		||||
            xmlns = constants.XML_NS_V20
 | 
			
		||||
        self.xmlns = xmlns
 | 
			
		||||
 | 
			
		||||
    def _get_key(self, tag):
 | 
			
		||||
@@ -290,7 +287,7 @@ class XMLDeserializer(TextDeserializer):
 | 
			
		||||
            parseError = False
 | 
			
		||||
            # Python2.7
 | 
			
		||||
            if (hasattr(etree, 'ParseError') and
 | 
			
		||||
                isinstance(e, getattr(etree, 'ParseError'))):
 | 
			
		||||
                    isinstance(e, getattr(etree, 'ParseError'))):
 | 
			
		||||
                parseError = True
 | 
			
		||||
            # Python2.6
 | 
			
		||||
            elif isinstance(e, expat.ExpatError):
 | 
			
		||||
@@ -340,9 +337,9 @@ class XMLDeserializer(TextDeserializer):
 | 
			
		||||
            result = dict()
 | 
			
		||||
            for attr in node.keys():
 | 
			
		||||
                if (attr == 'xmlns' or
 | 
			
		||||
                    attr.startswith('xmlns:') or
 | 
			
		||||
                    attr == constants.XSI_ATTR or
 | 
			
		||||
                    attr == constants.TYPE_ATTR):
 | 
			
		||||
                        attr.startswith('xmlns:') or
 | 
			
		||||
                        attr == constants.XSI_ATTR or
 | 
			
		||||
                        attr == constants.TYPE_ATTR):
 | 
			
		||||
                    continue
 | 
			
		||||
                result[self._get_key(attr)] = node.get(attr)
 | 
			
		||||
            children = list(node)
 | 
			
		||||
@@ -392,7 +389,6 @@ class Serializer(object):
 | 
			
		||||
        """Deserialize a string to a dictionary.
 | 
			
		||||
 | 
			
		||||
        The string must be in the format of a supported MIME type.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        return self.get_deserialize_handler(content_type).deserialize(
 | 
			
		||||
            datastring)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,21 +17,22 @@
 | 
			
		||||
 | 
			
		||||
"""Utilities and helper functions."""
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import json
 | 
			
		||||
import argparse
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import _
 | 
			
		||||
from oslo.utils import encodeutils
 | 
			
		||||
from oslo.utils import importutils
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import exceptions
 | 
			
		||||
from tackerclient.openstack.common import strutils
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def env(*vars, **kwargs):
 | 
			
		||||
    """Returns the first environment variable set.
 | 
			
		||||
 | 
			
		||||
    if none are non-empty, defaults to '' or keyword arg default.
 | 
			
		||||
    If none are non-empty, defaults to '' or keyword arg default.
 | 
			
		||||
    """
 | 
			
		||||
    for v in vars:
 | 
			
		||||
        value = os.environ.get(v)
 | 
			
		||||
@@ -40,52 +41,8 @@ def env(*vars, **kwargs):
 | 
			
		||||
    return kwargs.get('default', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_primitive(value):
 | 
			
		||||
    if isinstance(value, list) or isinstance(value, tuple):
 | 
			
		||||
        o = []
 | 
			
		||||
        for v in value:
 | 
			
		||||
            o.append(to_primitive(v))
 | 
			
		||||
        return o
 | 
			
		||||
    elif isinstance(value, dict):
 | 
			
		||||
        o = {}
 | 
			
		||||
        for k, v in value.iteritems():
 | 
			
		||||
            o[k] = to_primitive(v)
 | 
			
		||||
        return o
 | 
			
		||||
    elif isinstance(value, datetime.datetime):
 | 
			
		||||
        return str(value)
 | 
			
		||||
    elif hasattr(value, 'iteritems'):
 | 
			
		||||
        return to_primitive(dict(value.iteritems()))
 | 
			
		||||
    elif hasattr(value, '__iter__'):
 | 
			
		||||
        return to_primitive(list(value))
 | 
			
		||||
    else:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dumps(value, indent=None):
 | 
			
		||||
    try:
 | 
			
		||||
        return json.dumps(value, indent=indent)
 | 
			
		||||
    except TypeError:
 | 
			
		||||
        pass
 | 
			
		||||
    return json.dumps(to_primitive(value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def loads(s):
 | 
			
		||||
    return json.loads(s)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_class(import_str):
 | 
			
		||||
    """Returns a class from a string including module and class.
 | 
			
		||||
 | 
			
		||||
    :param import_str: a string representation of the class name
 | 
			
		||||
    :rtype: the requested class
 | 
			
		||||
    """
 | 
			
		||||
    mod_str, _sep, class_str = import_str.rpartition('.')
 | 
			
		||||
    __import__(mod_str)
 | 
			
		||||
    return getattr(sys.modules[mod_str], class_str)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_client_class(api_name, version, version_map):
 | 
			
		||||
    """Returns the client class for the requested API version
 | 
			
		||||
    """Returns the client class for the requested API version.
 | 
			
		||||
 | 
			
		||||
    :param api_name: the name of the API, e.g. 'compute', 'image', etc
 | 
			
		||||
    :param version: the requested API version
 | 
			
		||||
@@ -101,10 +58,10 @@ def get_client_class(api_name, version, version_map):
 | 
			
		||||
                     'map_keys': ', '.join(version_map.keys())}
 | 
			
		||||
        raise exceptions.UnsupportedVersion(msg)
 | 
			
		||||
 | 
			
		||||
    return import_class(client_path)
 | 
			
		||||
    return importutils.import_class(client_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
 | 
			
		||||
def get_item_properties(item, fields, mixed_case_fields=(), formatters=None):
 | 
			
		||||
    """Return a tuple containing the item properties.
 | 
			
		||||
 | 
			
		||||
    :param item: a single item resource (e.g. Server, Tenant, etc)
 | 
			
		||||
@@ -113,6 +70,9 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
 | 
			
		||||
    :param formatters: dictionary mapping field names to callables
 | 
			
		||||
       to format the values
 | 
			
		||||
    """
 | 
			
		||||
    if formatters is None:
 | 
			
		||||
        formatters = {}
 | 
			
		||||
 | 
			
		||||
    row = []
 | 
			
		||||
 | 
			
		||||
    for field in fields:
 | 
			
		||||
@@ -136,22 +96,17 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
 | 
			
		||||
def str2bool(strbool):
 | 
			
		||||
    if strbool is None:
 | 
			
		||||
        return None
 | 
			
		||||
    else:
 | 
			
		||||
        return strbool.lower() == 'true'
 | 
			
		||||
    return strbool.lower() == 'true'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def str2dict(strdict):
 | 
			
		||||
        '''Convert key1=value1,key2=value2,... string into dictionary.
 | 
			
		||||
    """Convert key1=value1,key2=value2,... string into dictionary.
 | 
			
		||||
 | 
			
		||||
        :param strdict: key1=value1,key2=value2
 | 
			
		||||
        '''
 | 
			
		||||
        _info = {}
 | 
			
		||||
        if not strdict:
 | 
			
		||||
            return _info
 | 
			
		||||
        for kv_str in strdict.split(","):
 | 
			
		||||
            k, v = kv_str.split("=", 1)
 | 
			
		||||
            _info.update({k: v})
 | 
			
		||||
        return _info
 | 
			
		||||
    :param strdict: key1=value1,key2=value2
 | 
			
		||||
    """
 | 
			
		||||
    if not strdict:
 | 
			
		||||
        return {}
 | 
			
		||||
    return dict([kv.split('=', 1) for kv in strdict.split(',')])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def http_log_req(_logger, args, kwargs):
 | 
			
		||||
@@ -171,27 +126,27 @@ def http_log_req(_logger, args, kwargs):
 | 
			
		||||
 | 
			
		||||
    if 'body' in kwargs and kwargs['body']:
 | 
			
		||||
        string_parts.append(" -d '%s'" % (kwargs['body']))
 | 
			
		||||
    string_parts = safe_encode_list(string_parts)
 | 
			
		||||
    _logger.debug(_("\nREQ: %s\n"), "".join(string_parts))
 | 
			
		||||
    req = encodeutils.safe_encode("".join(string_parts))
 | 
			
		||||
    _logger.debug("\nREQ: %s\n", req)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def http_log_resp(_logger, resp, body):
 | 
			
		||||
    if not _logger.isEnabledFor(logging.DEBUG):
 | 
			
		||||
        return
 | 
			
		||||
    _logger.debug(_("RESP:%(code)s %(headers)s %(body)s\n"),
 | 
			
		||||
    _logger.debug("RESP:%(code)s %(headers)s %(body)s\n",
 | 
			
		||||
                  {'code': resp.status_code,
 | 
			
		||||
                   'headers': resp.headers,
 | 
			
		||||
                   'body': body})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _safe_encode_without_obj(data):
 | 
			
		||||
    if isinstance(data, basestring):
 | 
			
		||||
        return strutils.safe_encode(data)
 | 
			
		||||
    if isinstance(data, six.string_types):
 | 
			
		||||
        return encodeutils.safe_encode(data)
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_encode_list(data):
 | 
			
		||||
    return map(_safe_encode_without_obj, data)
 | 
			
		||||
    return list(map(_safe_encode_without_obj, data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_encode_dict(data):
 | 
			
		||||
@@ -203,4 +158,16 @@ def safe_encode_dict(data):
 | 
			
		||||
            return (k, safe_encode_dict(v))
 | 
			
		||||
        return (k, _safe_encode_without_obj(v))
 | 
			
		||||
 | 
			
		||||
    return dict(map(_encode_item, data.items()))
 | 
			
		||||
    return dict(list(map(_encode_item, data.items())))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_boolean_argument(parser, name, **kwargs):
 | 
			
		||||
    for keyword in ('metavar', 'choices'):
 | 
			
		||||
        kwargs.pop(keyword, None)
 | 
			
		||||
    default = kwargs.pop('default', argparse.SUPPRESS)
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        name,
 | 
			
		||||
        metavar='{True,False}',
 | 
			
		||||
        choices=['True', 'true', 'False', 'false'],
 | 
			
		||||
        default=default,
 | 
			
		||||
        **kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
import netaddr
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import exceptions
 | 
			
		||||
from tackerclient.openstack.common.gettextutils import _
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_int_range(parsed_args, attr_name, min_value=None, max_value=None):
 | 
			
		||||
@@ -29,7 +29,7 @@ def validate_int_range(parsed_args, attr_name, min_value=None, max_value=None):
 | 
			
		||||
        else:
 | 
			
		||||
            int_val = val
 | 
			
		||||
        if ((min_value is None or min_value <= int_val) and
 | 
			
		||||
            (max_value is None or int_val <= max_value)):
 | 
			
		||||
                (max_value is None or int_val <= max_value)):
 | 
			
		||||
            return
 | 
			
		||||
    except (ValueError, TypeError):
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								tackerclient/i18n.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								tackerclient/i18n.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
from oslo import i18n
 | 
			
		||||
 | 
			
		||||
_translators = i18n.TranslatorFactory(domain='tackerclient')
 | 
			
		||||
 | 
			
		||||
# The primary translation function using the well-known name "_"
 | 
			
		||||
_ = _translators.primary
 | 
			
		||||
 | 
			
		||||
# Translators for log levels.
 | 
			
		||||
#
 | 
			
		||||
# The abbreviated names are meant to reflect the usual use of a short
 | 
			
		||||
# name like '_'. The "L" is for "log" and the other letter comes from
 | 
			
		||||
# the level.
 | 
			
		||||
_LI = _translators.log_info
 | 
			
		||||
_LW = _translators.log_warning
 | 
			
		||||
_LE = _translators.log_error
 | 
			
		||||
_LC = _translators.log_critical
 | 
			
		||||
@@ -21,18 +21,30 @@ Command-line interface to the Tacker APIs
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import getpass
 | 
			
		||||
import inspect
 | 
			
		||||
import itertools
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from keystoneclient.auth.identity import v2 as v2_auth
 | 
			
		||||
from keystoneclient.auth.identity import v3 as v3_auth
 | 
			
		||||
from keystoneclient import discover
 | 
			
		||||
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
 | 
			
		||||
from keystoneclient import session
 | 
			
		||||
from oslo.utils import encodeutils
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
from cliff import app
 | 
			
		||||
from cliff import commandmanager
 | 
			
		||||
 | 
			
		||||
from tackerclient.common import clientmanager
 | 
			
		||||
from tackerclient.common import command as openstack_command
 | 
			
		||||
from tackerclient.common import exceptions as exc
 | 
			
		||||
from tackerclient.common import extension as client_extension
 | 
			
		||||
from tackerclient.common import utils
 | 
			
		||||
from tackerclient.openstack.common.gettextutils import _
 | 
			
		||||
from tackerclient.openstack.common import strutils
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
from tackerclient.tacker.v1_0 import extension
 | 
			
		||||
from tackerclient.tacker.v1_0.vm import device
 | 
			
		||||
from tackerclient.tacker.v1_0.vm import device_template
 | 
			
		||||
@@ -70,7 +82,23 @@ def env(*_vars, **kwargs):
 | 
			
		||||
    return kwargs.get('default', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_non_negative_int(value):
 | 
			
		||||
    try:
 | 
			
		||||
        value = int(value)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        raise argparse.ArgumentTypeError(_("invalid int value: %r") % value)
 | 
			
		||||
    if value < 0:
 | 
			
		||||
        raise argparse.ArgumentTypeError(_("input value %d is negative") %
 | 
			
		||||
                                         value)
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BashCompletionCommand(openstack_command.OpenStackCommand):
 | 
			
		||||
    """Prints all of the commands and options for bash-completion."""
 | 
			
		||||
    resource = "bash_completion"
 | 
			
		||||
 | 
			
		||||
COMMAND_V1 = {
 | 
			
		||||
    'bash-completion': BashCompletionCommand,
 | 
			
		||||
    'ext-list': extension.ListExt,
 | 
			
		||||
    'ext-show': extension.ShowExt,
 | 
			
		||||
    'device-template-create': device_template.CreateDeviceTemplate,
 | 
			
		||||
@@ -134,6 +162,11 @@ class TackerShell(app.App):
 | 
			
		||||
        for k, v in self.commands[apiversion].items():
 | 
			
		||||
            self.command_manager.add_command(k, v)
 | 
			
		||||
 | 
			
		||||
        self._register_extensions(VERSION)
 | 
			
		||||
 | 
			
		||||
        # Pop the 'complete' to correct the outputs of 'tacker help'.
 | 
			
		||||
        self.command_manager.commands.pop('complete')
 | 
			
		||||
 | 
			
		||||
        # This is instantiated in initialize_app() only when using
 | 
			
		||||
        # password flow auth
 | 
			
		||||
        self.auth_client = None
 | 
			
		||||
@@ -169,20 +202,64 @@ class TackerShell(app.App):
 | 
			
		||||
            action='store_const',
 | 
			
		||||
            dest='verbose_level',
 | 
			
		||||
            const=0,
 | 
			
		||||
            help=_('Suppress output except warnings and errors'))
 | 
			
		||||
            help=_('Suppress output except warnings and errors.'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-h', '--help',
 | 
			
		||||
            action=HelpAction,
 | 
			
		||||
            nargs=0,
 | 
			
		||||
            default=self,  # tricky
 | 
			
		||||
            help=_("Show this help message and exit"))
 | 
			
		||||
        # Global arguments
 | 
			
		||||
            help=_("Show this help message and exit."))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-r', '--retries',
 | 
			
		||||
            metavar="NUM",
 | 
			
		||||
            type=check_non_negative_int,
 | 
			
		||||
            default=0,
 | 
			
		||||
            help=_("How many times the request to the Tacker server should "
 | 
			
		||||
                   "be retried if it fails."))
 | 
			
		||||
        # FIXME(bklei): this method should come from python-keystoneclient
 | 
			
		||||
        self._append_global_identity_args(parser)
 | 
			
		||||
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def _append_global_identity_args(self, parser):
 | 
			
		||||
        # FIXME(bklei): these are global identity (Keystone) arguments which
 | 
			
		||||
        # should be consistent and shared by all service clients. Therefore,
 | 
			
		||||
        # they should be provided by python-keystoneclient. We will need to
 | 
			
		||||
        # refactor this code once this functionality is available in
 | 
			
		||||
        # python-keystoneclient.
 | 
			
		||||
        #
 | 
			
		||||
        # Note: At that time we'll need to decide if we can just abandon
 | 
			
		||||
        #       the deprecated args (--service-type and --endpoint-type).
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-service-type', metavar='<os-service-type>',
 | 
			
		||||
            default=env('OS_SERVICEVM_SERVICE_TYPE', default='servicevm'),
 | 
			
		||||
            help=_('Defaults to env[OS_SERVICEVM_SERVICE_TYPE] or servicevm.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-endpoint-type', metavar='<os-endpoint-type>',
 | 
			
		||||
            default=env('OS_ENDPOINT_TYPE', default='publicURL'),
 | 
			
		||||
            help=_('Defaults to env[OS_ENDPOINT_TYPE] or publicURL.'))
 | 
			
		||||
 | 
			
		||||
        # FIXME(bklei): --service-type is deprecated but kept in for
 | 
			
		||||
        # backward compatibility.
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--service-type', metavar='<service-type>',
 | 
			
		||||
            default=env('OS_SERVICEVM_SERVICE_TYPE', default='servicevm'),
 | 
			
		||||
            help=_('DEPRECATED! Use --os-service-type.'))
 | 
			
		||||
 | 
			
		||||
        # FIXME(bklei): --endpoint-type is deprecated but kept in for
 | 
			
		||||
        # backward compatibility.
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--endpoint-type', metavar='<endpoint-type>',
 | 
			
		||||
            default=env('OS_ENDPOINT_TYPE', default='publicURL'),
 | 
			
		||||
            help=_('DEPRECATED! Use --os-endpoint-type.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-auth-strategy', metavar='<auth-strategy>',
 | 
			
		||||
            default=env('OS_AUTH_STRATEGY', default='keystone'),
 | 
			
		||||
            help=_('Authentication strategy (Env: OS_AUTH_STRATEGY'
 | 
			
		||||
                   ', default keystone). For now, any other value will'
 | 
			
		||||
                   ' disable the authentication'))
 | 
			
		||||
            help=_('DEPRECATED! Only keystone is supported.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_auth_strategy',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
@@ -190,28 +267,49 @@ class TackerShell(app.App):
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-auth-url', metavar='<auth-url>',
 | 
			
		||||
            default=env('OS_AUTH_URL'),
 | 
			
		||||
            help=_('Authentication URL (Env: OS_AUTH_URL)'))
 | 
			
		||||
            help=_('Authentication URL, defaults to env[OS_AUTH_URL].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_auth_url',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
        project_name_group = parser.add_mutually_exclusive_group()
 | 
			
		||||
        project_name_group.add_argument(
 | 
			
		||||
            '--os-tenant-name', metavar='<auth-tenant-name>',
 | 
			
		||||
            default=env('OS_TENANT_NAME'),
 | 
			
		||||
            help=_('Authentication tenant name (Env: OS_TENANT_NAME)'))
 | 
			
		||||
            help=_('Authentication tenant name, defaults to '
 | 
			
		||||
                   'env[OS_TENANT_NAME].'))
 | 
			
		||||
        project_name_group.add_argument(
 | 
			
		||||
            '--os-project-name',
 | 
			
		||||
            metavar='<auth-project-name>',
 | 
			
		||||
            default=utils.env('OS_PROJECT_NAME'),
 | 
			
		||||
            help='Another way to specify tenant name. '
 | 
			
		||||
                 'This option is mutually exclusive with '
 | 
			
		||||
                 ' --os-tenant-name. '
 | 
			
		||||
                 'Defaults to env[OS_PROJECT_NAME].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_tenant_name',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
        project_id_group = parser.add_mutually_exclusive_group()
 | 
			
		||||
        project_id_group.add_argument(
 | 
			
		||||
            '--os-tenant-id', metavar='<auth-tenant-id>',
 | 
			
		||||
            default=env('OS_TENANT_ID'),
 | 
			
		||||
            help=_('Authentication tenant ID (Env: OS_TENANT_ID)'))
 | 
			
		||||
            help=_('Authentication tenant ID, defaults to '
 | 
			
		||||
                   'env[OS_TENANT_ID].'))
 | 
			
		||||
        project_id_group.add_argument(
 | 
			
		||||
            '--os-project-id',
 | 
			
		||||
            metavar='<auth-project-id>',
 | 
			
		||||
            default=utils.env('OS_PROJECT_ID'),
 | 
			
		||||
            help='Another way to specify tenant ID. '
 | 
			
		||||
            'This option is mutually exclusive with '
 | 
			
		||||
            ' --os-tenant-id. '
 | 
			
		||||
            'Defaults to env[OS_PROJECT_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-username', metavar='<auth-username>',
 | 
			
		||||
            default=utils.env('OS_USERNAME'),
 | 
			
		||||
            help=_('Authentication username (Env: OS_USERNAME)'))
 | 
			
		||||
            help=_('Authentication username, defaults to env[OS_USERNAME].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_username',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
@@ -222,54 +320,115 @@ class TackerShell(app.App):
 | 
			
		||||
            help=_('Authentication user ID (Env: OS_USER_ID)'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-password', metavar='<auth-password>',
 | 
			
		||||
            default=utils.env('OS_PASSWORD'),
 | 
			
		||||
            help=_('Authentication password (Env: OS_PASSWORD)'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_password',
 | 
			
		||||
            '--os_user_id',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-region-name', metavar='<auth-region-name>',
 | 
			
		||||
            default=env('OS_REGION_NAME'),
 | 
			
		||||
            help=_('Authentication region name (Env: OS_REGION_NAME)'))
 | 
			
		||||
            '--os-user-domain-id',
 | 
			
		||||
            metavar='<auth-user-domain-id>',
 | 
			
		||||
            default=utils.env('OS_USER_DOMAIN_ID'),
 | 
			
		||||
            help='OpenStack user domain ID. '
 | 
			
		||||
            'Defaults to env[OS_USER_DOMAIN_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_region_name',
 | 
			
		||||
            '--os_user_domain_id',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-token', metavar='<token>',
 | 
			
		||||
            default=env('OS_TOKEN'),
 | 
			
		||||
            help=_('Defaults to env[OS_TOKEN]'))
 | 
			
		||||
            '--os-user-domain-name',
 | 
			
		||||
            metavar='<auth-user-domain-name>',
 | 
			
		||||
            default=utils.env('OS_USER_DOMAIN_NAME'),
 | 
			
		||||
            help='OpenStack user domain name. '
 | 
			
		||||
                 'Defaults to env[OS_USER_DOMAIN_NAME].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_token',
 | 
			
		||||
            '--os_user_domain_name',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--service-type', metavar='<service-type>',
 | 
			
		||||
            default=env('OS_SERVICEVM_SERVICE_TYPE', default='servicevm'),
 | 
			
		||||
            help=_('Defaults to env[OS_SERVICEVM_SERVICE_TYPE] or servicevm.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--endpoint-type', metavar='<endpoint-type>',
 | 
			
		||||
            default=env('OS_ENDPOINT_TYPE', default='publicURL'),
 | 
			
		||||
            help=_('Defaults to env[OS_ENDPOINT_TYPE] or publicURL.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-url', metavar='<url>',
 | 
			
		||||
            default=env('OS_URL'),
 | 
			
		||||
            help=_('Defaults to env[OS_URL]'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_url',
 | 
			
		||||
            '--os_project_id',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_project_name',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-project-domain-id',
 | 
			
		||||
            metavar='<auth-project-domain-id>',
 | 
			
		||||
            default=utils.env('OS_PROJECT_DOMAIN_ID'),
 | 
			
		||||
            help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-project-domain-name',
 | 
			
		||||
            metavar='<auth-project-domain-name>',
 | 
			
		||||
            default=utils.env('OS_PROJECT_DOMAIN_NAME'),
 | 
			
		||||
            help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-cert',
 | 
			
		||||
            metavar='<certificate>',
 | 
			
		||||
            default=utils.env('OS_CERT'),
 | 
			
		||||
            help=_("Path of certificate file to use in SSL "
 | 
			
		||||
                   "connection. This file can optionally be "
 | 
			
		||||
                   "prepended with the private key. Defaults "
 | 
			
		||||
                   "to env[OS_CERT]."))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-cacert',
 | 
			
		||||
            metavar='<ca-certificate>',
 | 
			
		||||
            default=env('OS_CACERT', default=None),
 | 
			
		||||
            help=_("Specify a CA bundle file to use in "
 | 
			
		||||
                   "verifying a TLS (https) server certificate. "
 | 
			
		||||
                   "Defaults to env[OS_CACERT]"))
 | 
			
		||||
                   "Defaults to env[OS_CACERT]."))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-key',
 | 
			
		||||
            metavar='<key>',
 | 
			
		||||
            default=utils.env('OS_KEY'),
 | 
			
		||||
            help=_("Path of client key to use in SSL "
 | 
			
		||||
                   "connection. This option is not necessary "
 | 
			
		||||
                   "if your key is prepended to your certificate "
 | 
			
		||||
                   "file. Defaults to env[OS_KEY]."))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-password', metavar='<auth-password>',
 | 
			
		||||
            default=utils.env('OS_PASSWORD'),
 | 
			
		||||
            help=_('Authentication password, defaults to env[OS_PASSWORD].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_password',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-region-name', metavar='<auth-region-name>',
 | 
			
		||||
            default=env('OS_REGION_NAME'),
 | 
			
		||||
            help=_('Authentication region name, defaults to '
 | 
			
		||||
                   'env[OS_REGION_NAME].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_region_name',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-token', metavar='<token>',
 | 
			
		||||
            default=env('OS_TOKEN'),
 | 
			
		||||
            help=_('Authentication token, defaults to env[OS_TOKEN].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_token',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--http-timeout', metavar='<seconds>',
 | 
			
		||||
            default=env('OS_NETWORK_TIMEOUT', default=None), type=float,
 | 
			
		||||
            help=_('Timeout in seconds to wait for an HTTP response. Defaults '
 | 
			
		||||
                   'to env[OS_NETWORK_TIMEOUT] or None if not specified.'))
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os-url', metavar='<url>',
 | 
			
		||||
            default=env('OS_URL'),
 | 
			
		||||
            help=_('Defaults to env[OS_URL].'))
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--os_url',
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--insecure',
 | 
			
		||||
@@ -280,8 +439,6 @@ class TackerShell(app.App):
 | 
			
		||||
                   "not be verified against any certificate authorities. "
 | 
			
		||||
                   "This option should be used with caution."))
 | 
			
		||||
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def _bash_completion(self):
 | 
			
		||||
        """Prints all of the commands and options for bash-completion."""
 | 
			
		||||
        commands = set()
 | 
			
		||||
@@ -297,6 +454,26 @@ class TackerShell(app.App):
 | 
			
		||||
                options.add(option)
 | 
			
		||||
        print(' '.join(commands | options))
 | 
			
		||||
 | 
			
		||||
    def _register_extensions(self, version):
 | 
			
		||||
        for name, module in itertools.chain(
 | 
			
		||||
                client_extension._discover_via_entry_points()):
 | 
			
		||||
            self._extend_shell_commands(module, version)
 | 
			
		||||
 | 
			
		||||
    def _extend_shell_commands(self, module, version):
 | 
			
		||||
        classes = inspect.getmembers(module, inspect.isclass)
 | 
			
		||||
        for cls_name, cls in classes:
 | 
			
		||||
            if (issubclass(cls, client_extension.TackerClientExtension) and
 | 
			
		||||
                    hasattr(cls, 'shell_command')):
 | 
			
		||||
                cmd = cls.shell_command
 | 
			
		||||
                if hasattr(cls, 'versions'):
 | 
			
		||||
                    if version not in cls.versions:
 | 
			
		||||
                        continue
 | 
			
		||||
                try:
 | 
			
		||||
                    self.command_manager.add_command(cmd, cls)
 | 
			
		||||
                    self.commands[version][cmd] = cls
 | 
			
		||||
                except TypeError:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
    def run(self, argv):
 | 
			
		||||
        """Equivalent to the main program for the application.
 | 
			
		||||
 | 
			
		||||
@@ -309,7 +486,7 @@ class TackerShell(app.App):
 | 
			
		||||
            help_pos = -1
 | 
			
		||||
            help_command_pos = -1
 | 
			
		||||
            for arg in argv:
 | 
			
		||||
                if arg == 'bash-completion':
 | 
			
		||||
                if arg == 'bash-completion' and help_command_pos == -1:
 | 
			
		||||
                    self._bash_completion()
 | 
			
		||||
                    return 0
 | 
			
		||||
                if arg in self.commands[self.api_version]:
 | 
			
		||||
@@ -331,27 +508,22 @@ class TackerShell(app.App):
 | 
			
		||||
            self.interactive_mode = not remainder
 | 
			
		||||
            self.initialize_app(remainder)
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            if self.options.verbose_level == self.DEBUG_LEVEL:
 | 
			
		||||
                self.log.exception(unicode(err))
 | 
			
		||||
            if self.options.verbose_level >= self.DEBUG_LEVEL:
 | 
			
		||||
                self.log.exception(err)
 | 
			
		||||
                raise
 | 
			
		||||
            else:
 | 
			
		||||
                self.log.error(unicode(err))
 | 
			
		||||
                self.log.error(err)
 | 
			
		||||
            return 1
 | 
			
		||||
        result = 1
 | 
			
		||||
        if self.interactive_mode:
 | 
			
		||||
            _argv = [sys.argv[0]]
 | 
			
		||||
            sys.argv = _argv
 | 
			
		||||
            result = self.interact()
 | 
			
		||||
        else:
 | 
			
		||||
            result = self.run_subcommand(remainder)
 | 
			
		||||
        return result
 | 
			
		||||
            return self.interact()
 | 
			
		||||
        return self.run_subcommand(remainder)
 | 
			
		||||
 | 
			
		||||
    def run_subcommand(self, argv):
 | 
			
		||||
        subcommand = self.command_manager.find_command(argv)
 | 
			
		||||
        cmd_factory, cmd_name, sub_argv = subcommand
 | 
			
		||||
        cmd = cmd_factory(self, self.options)
 | 
			
		||||
        err = None
 | 
			
		||||
        result = 1
 | 
			
		||||
        try:
 | 
			
		||||
            self.prepare_to_run_command(cmd)
 | 
			
		||||
            full_name = (cmd_name
 | 
			
		||||
@@ -360,29 +532,12 @@ class TackerShell(app.App):
 | 
			
		||||
                         )
 | 
			
		||||
            cmd_parser = cmd.get_parser(full_name)
 | 
			
		||||
            return run_command(cmd, cmd_parser, sub_argv)
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            if self.options.verbose_level == self.DEBUG_LEVEL:
 | 
			
		||||
                self.log.exception(unicode(err))
 | 
			
		||||
            else:
 | 
			
		||||
                self.log.error(unicode(err))
 | 
			
		||||
            try:
 | 
			
		||||
                self.clean_up(cmd, result, err)
 | 
			
		||||
            except Exception as err2:
 | 
			
		||||
                if self.options.verbose_level == self.DEBUG_LEVEL:
 | 
			
		||||
                    self.log.exception(unicode(err2))
 | 
			
		||||
                else:
 | 
			
		||||
                    self.log.error(_('Could not clean up: %s'), unicode(err2))
 | 
			
		||||
            if self.options.verbose_level == self.DEBUG_LEVEL:
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            if self.options.verbose_level >= self.DEBUG_LEVEL:
 | 
			
		||||
                self.log.exception("%s", e)
 | 
			
		||||
                raise
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                self.clean_up(cmd, result, None)
 | 
			
		||||
            except Exception as err3:
 | 
			
		||||
                if self.options.verbose_level == self.DEBUG_LEVEL:
 | 
			
		||||
                    self.log.exception(unicode(err3))
 | 
			
		||||
                else:
 | 
			
		||||
                    self.log.error(_('Could not clean up: %s'), unicode(err3))
 | 
			
		||||
        return result
 | 
			
		||||
            self.log.error("%s", e)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    def authenticate_user(self):
 | 
			
		||||
        """Make sure the user has provided all of the authentication
 | 
			
		||||
@@ -394,43 +549,74 @@ class TackerShell(app.App):
 | 
			
		||||
                if not self.options.os_token:
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide a token via"
 | 
			
		||||
                          " either --os-token or env[OS_TOKEN]"))
 | 
			
		||||
                          " either --os-token or env[OS_TOKEN]"
 | 
			
		||||
                          " when providing a service URL"))
 | 
			
		||||
 | 
			
		||||
                if not self.options.os_url:
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide a service URL via"
 | 
			
		||||
                          " either --os-url or env[OS_URL]"))
 | 
			
		||||
                          " either --os-url or env[OS_URL]"
 | 
			
		||||
                          " when providing a token"))
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                # Validate password flow auth
 | 
			
		||||
                project_info = (self.options.os_tenant_name or
 | 
			
		||||
                                self.options.os_tenant_id or
 | 
			
		||||
                                (self.options.os_project_name and
 | 
			
		||||
                                    (self.options.os_project_domain_name or
 | 
			
		||||
                                     self.options.os_project_domain_id)) or
 | 
			
		||||
                                self.options.os_project_id)
 | 
			
		||||
 | 
			
		||||
                if (not self.options.os_username
 | 
			
		||||
                    and not self.options.os_user_id):
 | 
			
		||||
                        and not self.options.os_user_id):
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide a username or user ID via"
 | 
			
		||||
                          "  --os-username, env[OS_USERNAME] or"
 | 
			
		||||
                          "  --os-user_id, env[OS_USER_ID]"))
 | 
			
		||||
                          "  --os-user-id, env[OS_USER_ID]"))
 | 
			
		||||
 | 
			
		||||
                if not self.options.os_password:
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide a password via"
 | 
			
		||||
                          " either --os-password or env[OS_PASSWORD]"))
 | 
			
		||||
                    # No password, If we've got a tty, try prompting for it
 | 
			
		||||
                    if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
 | 
			
		||||
                        # Check for Ctl-D
 | 
			
		||||
                        try:
 | 
			
		||||
                            self.options.os_password = getpass.getpass(
 | 
			
		||||
                                'OS Password: ')
 | 
			
		||||
                        except EOFError:
 | 
			
		||||
                            pass
 | 
			
		||||
                    # No password because we didn't have a tty or the
 | 
			
		||||
                    # user Ctl-D when prompted.
 | 
			
		||||
                    if not self.options.os_password:
 | 
			
		||||
                        raise exc.CommandError(
 | 
			
		||||
                            _("You must provide a password via"
 | 
			
		||||
                              " either --os-password or env[OS_PASSWORD]"))
 | 
			
		||||
 | 
			
		||||
                if (not self.options.os_tenant_name
 | 
			
		||||
                    and not self.options.os_tenant_id):
 | 
			
		||||
                if (not project_info):
 | 
			
		||||
                    # tenent is deprecated in Keystone v3. Use the latest
 | 
			
		||||
                    # terminology instead.
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide a tenant_name or tenant_id via"
 | 
			
		||||
                          "  --os-tenant-name, env[OS_TENANT_NAME]"
 | 
			
		||||
                          "  --os-tenant-id, or via env[OS_TENANT_ID]"))
 | 
			
		||||
                        _("You must provide a project_id or project_name ("
 | 
			
		||||
                          "with project_domain_name or project_domain_id) "
 | 
			
		||||
                          "via "
 | 
			
		||||
                          "  --os-project-id (env[OS_PROJECT_ID])"
 | 
			
		||||
                          "  --os-project-name (env[OS_PROJECT_NAME]),"
 | 
			
		||||
                          "  --os-project-domain-id "
 | 
			
		||||
                          "(env[OS_PROJECT_DOMAIN_ID])"
 | 
			
		||||
                          "  --os-project-domain-name "
 | 
			
		||||
                          "(env[OS_PROJECT_DOMAIN_NAME])"))
 | 
			
		||||
 | 
			
		||||
                if not self.options.os_auth_url:
 | 
			
		||||
                    raise exc.CommandError(
 | 
			
		||||
                        _("You must provide an auth url via"
 | 
			
		||||
                          " either --os-auth-url or via env[OS_AUTH_URL]"))
 | 
			
		||||
            auth_session = self._get_keystone_session()
 | 
			
		||||
            auth = auth_session.auth
 | 
			
		||||
        else:   # not keystone
 | 
			
		||||
            if not self.options.os_url:
 | 
			
		||||
                raise exc.CommandError(
 | 
			
		||||
                    _("You must provide a service URL via"
 | 
			
		||||
                      " either --os-url or env[OS_URL]"))
 | 
			
		||||
            auth_session = None
 | 
			
		||||
            auth = None
 | 
			
		||||
 | 
			
		||||
        self.client_manager = clientmanager.ClientManager(
 | 
			
		||||
            token=self.options.os_token,
 | 
			
		||||
@@ -444,10 +630,18 @@ class TackerShell(app.App):
 | 
			
		||||
            region_name=self.options.os_region_name,
 | 
			
		||||
            api_version=self.api_version,
 | 
			
		||||
            auth_strategy=self.options.os_auth_strategy,
 | 
			
		||||
            service_type=self.options.service_type,
 | 
			
		||||
            endpoint_type=self.options.endpoint_type,
 | 
			
		||||
            # FIXME (bklei) honor deprecated service_type and
 | 
			
		||||
            # endpoint type until they are removed
 | 
			
		||||
            service_type=self.options.os_service_type or
 | 
			
		||||
            self.options.service_type,
 | 
			
		||||
            endpoint_type=self.options.os_endpoint_type or self.endpoint_type,
 | 
			
		||||
            insecure=self.options.insecure,
 | 
			
		||||
            ca_cert=self.options.os_cacert,
 | 
			
		||||
            timeout=self.options.http_timeout,
 | 
			
		||||
            retries=self.options.retries,
 | 
			
		||||
            raise_errors=False,
 | 
			
		||||
            session=auth_session,
 | 
			
		||||
            auth=auth,
 | 
			
		||||
            log_credentials=True)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
@@ -471,11 +665,6 @@ class TackerShell(app.App):
 | 
			
		||||
        if self.interactive_mode or cmd_name != 'help':
 | 
			
		||||
            self.authenticate_user()
 | 
			
		||||
 | 
			
		||||
    def clean_up(self, cmd, result, err):
 | 
			
		||||
        self.log.debug('clean_up %s', cmd.__class__.__name__)
 | 
			
		||||
        if err:
 | 
			
		||||
            self.log.debug(_('Got an error: %s'), unicode(err))
 | 
			
		||||
 | 
			
		||||
    def configure_logging(self):
 | 
			
		||||
        """Create logging handlers for any log output."""
 | 
			
		||||
        root_logger = logging.getLogger('')
 | 
			
		||||
@@ -489,24 +678,118 @@ class TackerShell(app.App):
 | 
			
		||||
                         self.INFO_LEVEL: logging.INFO,
 | 
			
		||||
                         self.DEBUG_LEVEL: logging.DEBUG,
 | 
			
		||||
                         }.get(self.options.verbose_level, logging.DEBUG)
 | 
			
		||||
        console.setLevel(console_level)
 | 
			
		||||
        # The default log level is INFO, in this situation, set the
 | 
			
		||||
        # log level of the console to WARNING, to avoid displaying
 | 
			
		||||
        # useless messages. This equals using "--quiet"
 | 
			
		||||
        if console_level == logging.INFO:
 | 
			
		||||
            console.setLevel(logging.WARNING)
 | 
			
		||||
        else:
 | 
			
		||||
            console.setLevel(console_level)
 | 
			
		||||
        if logging.DEBUG == console_level:
 | 
			
		||||
            formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT)
 | 
			
		||||
        else:
 | 
			
		||||
            formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT)
 | 
			
		||||
        logging.getLogger('iso8601.iso8601').setLevel(logging.WARNING)
 | 
			
		||||
        logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
 | 
			
		||||
        console.setFormatter(formatter)
 | 
			
		||||
        root_logger.addHandler(console)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    def get_v2_auth(self, v2_auth_url):
 | 
			
		||||
        return v2_auth.Password(
 | 
			
		||||
            v2_auth_url,
 | 
			
		||||
            username=self.options.os_username,
 | 
			
		||||
            password=self.options.os_password,
 | 
			
		||||
            tenant_id=self.options.os_tenant_id,
 | 
			
		||||
            tenant_name=self.options.os_tenant_name)
 | 
			
		||||
 | 
			
		||||
    def get_v3_auth(self, v3_auth_url):
 | 
			
		||||
        project_id = self.options.os_project_id or self.options.os_tenant_id
 | 
			
		||||
        project_name = (self.options.os_project_name or
 | 
			
		||||
                        self.options.os_tenant_name)
 | 
			
		||||
 | 
			
		||||
        return v3_auth.Password(
 | 
			
		||||
            v3_auth_url,
 | 
			
		||||
            username=self.options.os_username,
 | 
			
		||||
            password=self.options.os_password,
 | 
			
		||||
            user_id=self.options.os_user_id,
 | 
			
		||||
            user_domain_name=self.options.os_user_domain_name,
 | 
			
		||||
            user_domain_id=self.options.os_user_domain_id,
 | 
			
		||||
            project_id=project_id,
 | 
			
		||||
            project_name=project_name,
 | 
			
		||||
            project_domain_name=self.options.os_project_domain_name,
 | 
			
		||||
            project_domain_id=self.options.os_project_domain_id
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _discover_auth_versions(self, session, auth_url):
 | 
			
		||||
        # discover the API versions the server is supporting base on the
 | 
			
		||||
        # given URL
 | 
			
		||||
        try:
 | 
			
		||||
            ks_discover = discover.Discover(session=session, auth_url=auth_url)
 | 
			
		||||
            return (ks_discover.url_for('2.0'), ks_discover.url_for('3.0'))
 | 
			
		||||
        except ks_exc.ClientException:
 | 
			
		||||
            # Identity service may not support discover API version.
 | 
			
		||||
            # Lets try to figure out the API version from the original URL.
 | 
			
		||||
            url_parts = urlparse.urlparse(auth_url)
 | 
			
		||||
            (scheme, netloc, path, params, query, fragment) = url_parts
 | 
			
		||||
            path = path.lower()
 | 
			
		||||
            if path.startswith('/v3'):
 | 
			
		||||
                return (None, auth_url)
 | 
			
		||||
            elif path.startswith('/v2'):
 | 
			
		||||
                return (auth_url, None)
 | 
			
		||||
            else:
 | 
			
		||||
                # not enough information to determine the auth version
 | 
			
		||||
                msg = _('Unable to determine the Keystone version '
 | 
			
		||||
                        'to authenticate with using the given '
 | 
			
		||||
                        'auth_url. Identity service may not support API '
 | 
			
		||||
                        'version discovery. Please provide a versioned '
 | 
			
		||||
                        'auth_url instead.')
 | 
			
		||||
                raise exc.CommandError(msg)
 | 
			
		||||
 | 
			
		||||
    def _get_keystone_session(self):
 | 
			
		||||
        # first create a Keystone session
 | 
			
		||||
        cacert = self.options.os_cacert or None
 | 
			
		||||
        cert = self.options.os_cert or None
 | 
			
		||||
        key = self.options.os_key or None
 | 
			
		||||
        insecure = self.options.insecure or False
 | 
			
		||||
        ks_session = session.Session.construct(dict(cacert=cacert,
 | 
			
		||||
                                                    cert=cert,
 | 
			
		||||
                                                    key=key,
 | 
			
		||||
                                                    insecure=insecure))
 | 
			
		||||
        # discover the supported keystone versions using the given url
 | 
			
		||||
        (v2_auth_url, v3_auth_url) = self._discover_auth_versions(
 | 
			
		||||
            session=ks_session,
 | 
			
		||||
            auth_url=self.options.os_auth_url)
 | 
			
		||||
 | 
			
		||||
        # Determine which authentication plugin to use. First inspect the
 | 
			
		||||
        # auth_url to see the supported version. If both v3 and v2 are
 | 
			
		||||
        # supported, then use the highest version if possible.
 | 
			
		||||
        user_domain_name = self.options.os_user_domain_name or None
 | 
			
		||||
        user_domain_id = self.options.os_user_domain_id or None
 | 
			
		||||
        project_domain_name = self.options.os_project_domain_name or None
 | 
			
		||||
        project_domain_id = self.options.os_project_domain_id or None
 | 
			
		||||
        domain_info = (user_domain_name or user_domain_id or
 | 
			
		||||
                       project_domain_name or project_domain_id)
 | 
			
		||||
 | 
			
		||||
        if (v2_auth_url and not domain_info) or not v3_auth_url:
 | 
			
		||||
            ks_session.auth = self.get_v2_auth(v2_auth_url)
 | 
			
		||||
        else:
 | 
			
		||||
            ks_session.auth = self.get_v3_auth(v3_auth_url)
 | 
			
		||||
 | 
			
		||||
        return ks_session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argv=sys.argv[1:]):
 | 
			
		||||
    try:
 | 
			
		||||
        return TackerShell(TACKER_API_VERSION).run(map(strutils.safe_decode,
 | 
			
		||||
                                                   argv))
 | 
			
		||||
        return TackerShell(TACKER_API_VERSION).run(
 | 
			
		||||
            list(map(encodeutils.safe_decode, argv)))
 | 
			
		||||
    except KeyboardInterrupt:
 | 
			
		||||
        print("... terminating tacker client", file=sys.stderr)
 | 
			
		||||
        return 130
 | 
			
		||||
    except exc.TackerClientException:
 | 
			
		||||
        return 1
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(unicode(e))
 | 
			
		||||
        print(e)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,10 +43,15 @@ def make_client(instance):
 | 
			
		||||
                               region_name=instance._region_name,
 | 
			
		||||
                               auth_url=instance._auth_url,
 | 
			
		||||
                               endpoint_url=url,
 | 
			
		||||
                               endpoint_type=instance._endpoint_type,
 | 
			
		||||
                               token=instance._token,
 | 
			
		||||
                               auth_strategy=instance._auth_strategy,
 | 
			
		||||
                               insecure=instance._insecure,
 | 
			
		||||
                               ca_cert=instance._ca_cert)
 | 
			
		||||
                               ca_cert=instance._ca_cert,
 | 
			
		||||
                               retries=instance._retries,
 | 
			
		||||
                               raise_errors=instance._raise_errors,
 | 
			
		||||
                               session=instance._session,
 | 
			
		||||
                               auth=instance._auth)
 | 
			
		||||
        return client
 | 
			
		||||
    else:
 | 
			
		||||
        raise exceptions.UnsupportedVersion(_("API version %s is not "
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# Copyright 2012 OpenStack Foundation.
 | 
			
		||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
# All Rights Reserved
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
@@ -16,31 +17,29 @@
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import urllib
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
from tackerclient import client
 | 
			
		||||
from tackerclient.common import _
 | 
			
		||||
from tackerclient.common import constants
 | 
			
		||||
from tackerclient.common import exceptions
 | 
			
		||||
from tackerclient.common import serializer
 | 
			
		||||
from tackerclient.common import utils
 | 
			
		||||
from tackerclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exception_handler_v10(status_code, error_content):
 | 
			
		||||
    """Exception handler for API v1.0 client
 | 
			
		||||
    """Exception handler for API v1.0 client.
 | 
			
		||||
 | 
			
		||||
       This routine generates the appropriate
 | 
			
		||||
       Tacker exception according to the contents of the
 | 
			
		||||
       response body
 | 
			
		||||
    This routine generates the appropriate Tacker exception according to
 | 
			
		||||
    the contents of the response body.
 | 
			
		||||
 | 
			
		||||
       :param status_code: HTTP error status code
 | 
			
		||||
       :param error_content: deserialized body of error response
 | 
			
		||||
    :param status_code: HTTP error status code
 | 
			
		||||
    :param error_content: deserialized body of error response
 | 
			
		||||
    """
 | 
			
		||||
    error_dict = None
 | 
			
		||||
    if isinstance(error_content, dict):
 | 
			
		||||
@@ -87,8 +86,7 @@ def exception_handler_v10(status_code, error_content):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIParamsCall(object):
 | 
			
		||||
    """A Decorator to add support for format and tenant overriding
 | 
			
		||||
       and filters
 | 
			
		||||
    """A Decorator to add support for format and tenant overriding and filters.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, function):
 | 
			
		||||
        self.function = function
 | 
			
		||||
@@ -104,7 +102,7 @@ class APIParamsCall(object):
 | 
			
		||||
        return with_params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client(object):
 | 
			
		||||
class ClientBase(object):
 | 
			
		||||
    """Client for the OpenStack Tacker v1.0 API.
 | 
			
		||||
 | 
			
		||||
    :param string username: Username for authentication. (optional)
 | 
			
		||||
@@ -113,6 +111,8 @@ class Client(object):
 | 
			
		||||
    :param string token: Token for authentication. (optional)
 | 
			
		||||
    :param string tenant_name: Tenant name. (optional)
 | 
			
		||||
    :param string tenant_id: Tenant id. (optional)
 | 
			
		||||
    :param string auth_strategy: 'keystone' by default, 'noauth' for no
 | 
			
		||||
                                 authentication against keystone. (optional)
 | 
			
		||||
    :param string auth_url: Keystone service endpoint for authorization.
 | 
			
		||||
    :param string service_type: Network service type to pull from the
 | 
			
		||||
                                keystone catalog (e.g. 'network') (optional)
 | 
			
		||||
@@ -128,7 +128,17 @@ class Client(object):
 | 
			
		||||
    :param integer timeout: Allows customization of the timeout for client
 | 
			
		||||
                            http requests. (optional)
 | 
			
		||||
    :param bool insecure: SSL certificate validation. (optional)
 | 
			
		||||
    :param bool log_credentials: Allow for logging of passwords or not.
 | 
			
		||||
                                 Defaults to False. (optional)
 | 
			
		||||
    :param string ca_cert: SSL CA bundle file to use. (optional)
 | 
			
		||||
    :param integer retries: How many times idempotent (GET, PUT, DELETE)
 | 
			
		||||
                            requests to Tacker server should be retried if
 | 
			
		||||
                            they fail (default: 0).
 | 
			
		||||
    :param bool raise_errors: If True then exceptions caused by connection
 | 
			
		||||
                              failure are propagated to the caller.
 | 
			
		||||
                              (default: True)
 | 
			
		||||
    :param session: Keystone client auth session to use. (optional)
 | 
			
		||||
    :param auth: Keystone auth plugin to use. (optional)
 | 
			
		||||
 | 
			
		||||
    Example::
 | 
			
		||||
 | 
			
		||||
@@ -143,20 +153,84 @@ class Client(object):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    extensions_path = "/extensions"
 | 
			
		||||
    extension_path = "/extensions/%s"
 | 
			
		||||
 | 
			
		||||
    device_templates_path = '/device-templates'
 | 
			
		||||
    device_template_path = '/device-templates/%s'
 | 
			
		||||
    devices_path = '/devices'
 | 
			
		||||
    device_path = '/devices/%s'
 | 
			
		||||
    interface_attach_path = '/devices/%s/attach_interface'
 | 
			
		||||
    interface_detach_path = '/devices/%s/detach_interface'
 | 
			
		||||
 | 
			
		||||
    # API has no way to report plurals, so we have to hard code them
 | 
			
		||||
    # This variable should be overridden by a child class.
 | 
			
		||||
    EXTED_PLURALS = {}
 | 
			
		||||
    # 8192 Is the default max URI len for eventlet.wsgi.server
 | 
			
		||||
    MAX_URI_LEN = 8192
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        """Initialize a new client for the Tacker v1.0 API."""
 | 
			
		||||
        super(ClientBase, self).__init__()
 | 
			
		||||
        self.retries = kwargs.pop('retries', 0)
 | 
			
		||||
        self.raise_errors = kwargs.pop('raise_errors', True)
 | 
			
		||||
        self.httpclient = client.construct_http_client(**kwargs)
 | 
			
		||||
        self.version = '1.0'
 | 
			
		||||
        self.format = 'json'
 | 
			
		||||
        self.action_prefix = "/v%s" % (self.version)
 | 
			
		||||
        self.retry_interval = 1
 | 
			
		||||
 | 
			
		||||
    def _handle_fault_response(self, status_code, response_body):
 | 
			
		||||
        # Create exception with HTTP status code and message
 | 
			
		||||
        _logger.debug("Error message: %s", response_body)
 | 
			
		||||
        # Add deserialized error message to exception arguments
 | 
			
		||||
        try:
 | 
			
		||||
            des_error_body = self.deserialize(response_body, status_code)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            # If unable to deserialized body it is probably not a
 | 
			
		||||
            # Tacker error
 | 
			
		||||
            des_error_body = {'message': response_body}
 | 
			
		||||
        # Raise the appropriate exception
 | 
			
		||||
        exception_handler_v10(status_code, des_error_body)
 | 
			
		||||
 | 
			
		||||
    def do_request(self, method, action, body=None, headers=None, params=None):
 | 
			
		||||
        # Add format and tenant_id
 | 
			
		||||
        action += ".%s" % self.format
 | 
			
		||||
        action = self.action_prefix + action
 | 
			
		||||
        if type(params) is dict and params:
 | 
			
		||||
            params = utils.safe_encode_dict(params)
 | 
			
		||||
            action += '?' + urlparse.urlencode(params, doseq=1)
 | 
			
		||||
 | 
			
		||||
        if body:
 | 
			
		||||
            body = self.serialize(body)
 | 
			
		||||
 | 
			
		||||
        resp, replybody = self.httpclient.do_request(
 | 
			
		||||
            action, method, body=body,
 | 
			
		||||
            content_type=self.content_type())
 | 
			
		||||
 | 
			
		||||
        status_code = resp.status_code
 | 
			
		||||
        if status_code in (requests.codes.ok,
 | 
			
		||||
                           requests.codes.created,
 | 
			
		||||
                           requests.codes.accepted,
 | 
			
		||||
                           requests.codes.no_content):
 | 
			
		||||
            return self.deserialize(replybody, status_code)
 | 
			
		||||
        else:
 | 
			
		||||
            if not replybody:
 | 
			
		||||
                replybody = resp.reason
 | 
			
		||||
            self._handle_fault_response(status_code, replybody)
 | 
			
		||||
 | 
			
		||||
    def get_auth_info(self):
 | 
			
		||||
        return self.httpclient.get_auth_info()
 | 
			
		||||
 | 
			
		||||
    def serialize(self, data):
 | 
			
		||||
        """Serializes a dictionary into either XML or JSON.
 | 
			
		||||
 | 
			
		||||
        A dictionary with a single key can be passed and it can contain any
 | 
			
		||||
        structure.
 | 
			
		||||
        """
 | 
			
		||||
        if data is None:
 | 
			
		||||
            return None
 | 
			
		||||
        elif type(data) is dict:
 | 
			
		||||
            return serializer.Serializer(
 | 
			
		||||
                self.get_attr_metadata()).serialize(data, self.content_type())
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(_("Unable to serialize object of type = '%s'") %
 | 
			
		||||
                            type(data))
 | 
			
		||||
 | 
			
		||||
    def deserialize(self, data, status_code):
 | 
			
		||||
        """Deserializes an XML or JSON string into a dictionary."""
 | 
			
		||||
        if status_code == 204:
 | 
			
		||||
            return data
 | 
			
		||||
        return serializer.Serializer(self.get_attr_metadata()).deserialize(
 | 
			
		||||
            data, self.content_type())['body']
 | 
			
		||||
 | 
			
		||||
    def get_attr_metadata(self):
 | 
			
		||||
        if self.format == 'json':
 | 
			
		||||
@@ -168,9 +242,107 @@ class Client(object):
 | 
			
		||||
        ns = dict([(ext['alias'], ext['namespace']) for ext in exts])
 | 
			
		||||
        self.EXTED_PLURALS.update(constants.PLURALS)
 | 
			
		||||
        return {'plurals': self.EXTED_PLURALS,
 | 
			
		||||
                'xmlns': constants.XML_NS_V10,
 | 
			
		||||
                'xmlns': constants.XML_NS_V20,
 | 
			
		||||
                constants.EXT_NS: ns}
 | 
			
		||||
 | 
			
		||||
    def content_type(self, _format=None):
 | 
			
		||||
        """Returns the mime-type for either 'xml' or 'json'.
 | 
			
		||||
 | 
			
		||||
        Defaults to the currently set format.
 | 
			
		||||
        """
 | 
			
		||||
        _format = _format or self.format
 | 
			
		||||
        return "application/%s" % (_format)
 | 
			
		||||
 | 
			
		||||
    def retry_request(self, method, action, body=None,
 | 
			
		||||
                      headers=None, params=None):
 | 
			
		||||
        """Call do_request with the default retry configuration.
 | 
			
		||||
 | 
			
		||||
        Only idempotent requests should retry failed connection attempts.
 | 
			
		||||
        :raises: ConnectionFailed if the maximum # of retries is exceeded
 | 
			
		||||
        """
 | 
			
		||||
        max_attempts = self.retries + 1
 | 
			
		||||
        for i in range(max_attempts):
 | 
			
		||||
            try:
 | 
			
		||||
                return self.do_request(method, action, body=body,
 | 
			
		||||
                                       headers=headers, params=params)
 | 
			
		||||
            except exceptions.ConnectionFailed:
 | 
			
		||||
                # Exception has already been logged by do_request()
 | 
			
		||||
                if i < self.retries:
 | 
			
		||||
                    _logger.debug('Retrying connection to Tacker service')
 | 
			
		||||
                    time.sleep(self.retry_interval)
 | 
			
		||||
                elif self.raise_errors:
 | 
			
		||||
                    raise
 | 
			
		||||
 | 
			
		||||
        if self.retries:
 | 
			
		||||
            msg = (_("Failed to connect to Tacker server after %d attempts")
 | 
			
		||||
                   % max_attempts)
 | 
			
		||||
        else:
 | 
			
		||||
            msg = _("Failed to connect Tacker server")
 | 
			
		||||
 | 
			
		||||
        raise exceptions.ConnectionFailed(reason=msg)
 | 
			
		||||
 | 
			
		||||
    def delete(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("DELETE", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def get(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("GET", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def post(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        # Do not retry POST requests to avoid the orphan objects problem.
 | 
			
		||||
        return self.do_request("POST", action, body=body,
 | 
			
		||||
                               headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def put(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("PUT", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def list(self, collection, path, retrieve_all=True, **params):
 | 
			
		||||
        if retrieve_all:
 | 
			
		||||
            res = []
 | 
			
		||||
            for r in self._pagination(collection, path, **params):
 | 
			
		||||
                res.extend(r[collection])
 | 
			
		||||
            return {collection: res}
 | 
			
		||||
        else:
 | 
			
		||||
            return self._pagination(collection, path, **params)
 | 
			
		||||
 | 
			
		||||
    def _pagination(self, collection, path, **params):
 | 
			
		||||
        if params.get('page_reverse', False):
 | 
			
		||||
            linkrel = 'previous'
 | 
			
		||||
        else:
 | 
			
		||||
            linkrel = 'next'
 | 
			
		||||
        next = True
 | 
			
		||||
        while next:
 | 
			
		||||
            res = self.get(path, params=params)
 | 
			
		||||
            yield res
 | 
			
		||||
            next = False
 | 
			
		||||
            try:
 | 
			
		||||
                for link in res['%s_links' % collection]:
 | 
			
		||||
                    if link['rel'] == linkrel:
 | 
			
		||||
                        query_str = urlparse.urlparse(link['href']).query
 | 
			
		||||
                        params = urlparse.parse_qs(query_str)
 | 
			
		||||
                        next = True
 | 
			
		||||
                        break
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client(ClientBase):
 | 
			
		||||
 | 
			
		||||
    extensions_path = "/extensions"
 | 
			
		||||
    extension_path = "/extensions/%s"
 | 
			
		||||
 | 
			
		||||
    device_templates_path = '/device-templates'
 | 
			
		||||
    device_template_path = '/device-templates/%s'
 | 
			
		||||
    devices_path = '/devices'
 | 
			
		||||
    device_path = '/devices/%s'
 | 
			
		||||
    interface_attach_path = '/devices/%s/attach_interface'
 | 
			
		||||
    interface_detach_path = '/devices/%s/detach_interface'
 | 
			
		||||
 | 
			
		||||
    # API has no way to report plurals, so we have to hard code them
 | 
			
		||||
    # EXTED_PLURALS = {}
 | 
			
		||||
 | 
			
		||||
    @APIParamsCall
 | 
			
		||||
    def list_extensions(self, **_params):
 | 
			
		||||
        """Fetch a list of all exts on server side."""
 | 
			
		||||
@@ -229,168 +401,3 @@ class Client(object):
 | 
			
		||||
    @APIParamsCall
 | 
			
		||||
    def detach_interface(self, device, body=None):
 | 
			
		||||
        return self.put(self.detach_interface_path % device, body)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        """Initialize a new client for the Tacker v1.0 API."""
 | 
			
		||||
        super(Client, self).__init__()
 | 
			
		||||
        self.httpclient = client.HTTPClient(**kwargs)
 | 
			
		||||
        self.version = '1.0'
 | 
			
		||||
        self.format = 'json'
 | 
			
		||||
        self.action_prefix = "/v%s" % (self.version)
 | 
			
		||||
        self.retries = 0
 | 
			
		||||
        self.retry_interval = 1
 | 
			
		||||
 | 
			
		||||
    def _handle_fault_response(self, status_code, response_body):
 | 
			
		||||
        # Create exception with HTTP status code and message
 | 
			
		||||
        _logger.debug(_("Error message: %s"), response_body)
 | 
			
		||||
        # Add deserialized error message to exception arguments
 | 
			
		||||
        try:
 | 
			
		||||
            des_error_body = self.deserialize(response_body, status_code)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            # If unable to deserialized body it is probably not a
 | 
			
		||||
            # Tacker error
 | 
			
		||||
            des_error_body = {'message': response_body}
 | 
			
		||||
        # Raise the appropriate exception
 | 
			
		||||
        exception_handler_v10(status_code, des_error_body)
 | 
			
		||||
 | 
			
		||||
    def _check_uri_length(self, action):
 | 
			
		||||
        uri_len = len(self.httpclient.endpoint_url) + len(action)
 | 
			
		||||
        if uri_len > self.MAX_URI_LEN:
 | 
			
		||||
            raise exceptions.RequestURITooLong(
 | 
			
		||||
                excess=uri_len - self.MAX_URI_LEN)
 | 
			
		||||
 | 
			
		||||
    def do_request(self, method, action, body=None, headers=None, params=None):
 | 
			
		||||
        # Add format and tenant_id
 | 
			
		||||
        action += ".%s" % self.format
 | 
			
		||||
        action = self.action_prefix + action
 | 
			
		||||
        if type(params) is dict and params:
 | 
			
		||||
            params = utils.safe_encode_dict(params)
 | 
			
		||||
            action += '?' + urllib.urlencode(params, doseq=1)
 | 
			
		||||
        # Ensure client always has correct uri - do not guesstimate anything
 | 
			
		||||
        self.httpclient.authenticate_and_fetch_endpoint_url()
 | 
			
		||||
        self._check_uri_length(action)
 | 
			
		||||
 | 
			
		||||
        if body:
 | 
			
		||||
            body = self.serialize(body)
 | 
			
		||||
        self.httpclient.content_type = self.content_type()
 | 
			
		||||
        resp, replybody = self.httpclient.do_request(action, method, body=body)
 | 
			
		||||
        status_code = self.get_status_code(resp)
 | 
			
		||||
        if status_code in (requests.codes.ok,
 | 
			
		||||
                           requests.codes.created,
 | 
			
		||||
                           requests.codes.accepted,
 | 
			
		||||
                           requests.codes.no_content):
 | 
			
		||||
            return self.deserialize(replybody, status_code)
 | 
			
		||||
        else:
 | 
			
		||||
            if not replybody:
 | 
			
		||||
                replybody = resp.reason
 | 
			
		||||
            self._handle_fault_response(status_code, replybody)
 | 
			
		||||
 | 
			
		||||
    def get_auth_info(self):
 | 
			
		||||
        return self.httpclient.get_auth_info()
 | 
			
		||||
 | 
			
		||||
    def get_status_code(self, response):
 | 
			
		||||
        """Returns the integer status code from the response.
 | 
			
		||||
 | 
			
		||||
        Either a Webob.Response (used in testing) or requests.Response
 | 
			
		||||
        is returned.
 | 
			
		||||
        """
 | 
			
		||||
        if hasattr(response, 'status_int'):
 | 
			
		||||
            return response.status_int
 | 
			
		||||
        else:
 | 
			
		||||
            return response.status_code
 | 
			
		||||
 | 
			
		||||
    def serialize(self, data):
 | 
			
		||||
        """Serializes a dictionary into either xml or json.
 | 
			
		||||
 | 
			
		||||
        A dictionary with a single key can be passed and
 | 
			
		||||
        it can contain any structure.
 | 
			
		||||
        """
 | 
			
		||||
        if data is None:
 | 
			
		||||
            return None
 | 
			
		||||
        elif type(data) is dict:
 | 
			
		||||
            return serializer.Serializer(
 | 
			
		||||
                self.get_attr_metadata()).serialize(data, self.content_type())
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(_("Unable to serialize object of type = '%s'") %
 | 
			
		||||
                            type(data))
 | 
			
		||||
 | 
			
		||||
    def deserialize(self, data, status_code):
 | 
			
		||||
        """Deserializes an xml or json string into a dictionary."""
 | 
			
		||||
        if status_code == 204:
 | 
			
		||||
            return data
 | 
			
		||||
        return serializer.Serializer(self.get_attr_metadata()).deserialize(
 | 
			
		||||
            data, self.content_type())['body']
 | 
			
		||||
 | 
			
		||||
    def content_type(self, _format=None):
 | 
			
		||||
        """Returns the mime-type for either 'xml' or 'json'.
 | 
			
		||||
 | 
			
		||||
        Defaults to the currently set format.
 | 
			
		||||
        """
 | 
			
		||||
        _format = _format or self.format
 | 
			
		||||
        return "application/%s" % (_format)
 | 
			
		||||
 | 
			
		||||
    def retry_request(self, method, action, body=None,
 | 
			
		||||
                      headers=None, params=None):
 | 
			
		||||
        """Call do_request with the default retry configuration.
 | 
			
		||||
 | 
			
		||||
        Only idempotent requests should retry failed connection attempts.
 | 
			
		||||
        :raises: ConnectionFailed if the maximum # of retries is exceeded
 | 
			
		||||
        """
 | 
			
		||||
        max_attempts = self.retries + 1
 | 
			
		||||
        for i in range(max_attempts):
 | 
			
		||||
            try:
 | 
			
		||||
                return self.do_request(method, action, body=body,
 | 
			
		||||
                                       headers=headers, params=params)
 | 
			
		||||
            except exceptions.ConnectionFailed:
 | 
			
		||||
                # Exception has already been logged by do_request()
 | 
			
		||||
                if i < self.retries:
 | 
			
		||||
                    _logger.debug(_('Retrying connection to Tacker service'))
 | 
			
		||||
                    time.sleep(self.retry_interval)
 | 
			
		||||
 | 
			
		||||
        raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached"))
 | 
			
		||||
 | 
			
		||||
    def delete(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("DELETE", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def get(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("GET", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def post(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        # Do not retry POST requests to avoid the orphan objects problem.
 | 
			
		||||
        return self.do_request("POST", action, body=body,
 | 
			
		||||
                               headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def put(self, action, body=None, headers=None, params=None):
 | 
			
		||||
        return self.retry_request("PUT", action, body=body,
 | 
			
		||||
                                  headers=headers, params=params)
 | 
			
		||||
 | 
			
		||||
    def list(self, collection, path, retrieve_all=True, **params):
 | 
			
		||||
        if retrieve_all:
 | 
			
		||||
            res = []
 | 
			
		||||
            for r in self._pagination(collection, path, **params):
 | 
			
		||||
                res.extend(r[collection])
 | 
			
		||||
            return {collection: res}
 | 
			
		||||
        else:
 | 
			
		||||
            return self._pagination(collection, path, **params)
 | 
			
		||||
 | 
			
		||||
    def _pagination(self, collection, path, **params):
 | 
			
		||||
        if params.get('page_reverse', False):
 | 
			
		||||
            linkrel = 'previous'
 | 
			
		||||
        else:
 | 
			
		||||
            linkrel = 'next'
 | 
			
		||||
        next = True
 | 
			
		||||
        while next:
 | 
			
		||||
            res = self.get(path, params=params)
 | 
			
		||||
            yield res
 | 
			
		||||
            next = False
 | 
			
		||||
            try:
 | 
			
		||||
                for link in res['%s_links' % collection]:
 | 
			
		||||
                    if link['rel'] == linkrel:
 | 
			
		||||
                        query_str = urlparse.urlparse(link['href']).query
 | 
			
		||||
                        params = urlparse.parse_qs(query_str)
 | 
			
		||||
                        next = True
 | 
			
		||||
                        break
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                break
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user