Integrate client with osc-lib
osc-lib library is foundation on which a CLI client for openstack can be built. It is meant to facilitate several aspects, that were previously hard-coded in client: * keystone communication handling * supporting multiple authentication methods (not only password) * common authentication parameters (i.e. environmental OS_*) * communicating over http with service endpoint * interactive CLI mode Thanks to those items, it was possible not only to drop nearly 3k lines of code and replace them with osc-lib but also increase reliabity of the client in terms of new openstack releases. Also it allowed to greatly simpify existing set of unit-tests. They are now testing only actual logic instead of mocking entire process of calling shell (i.e. MonascaShell.run(args)) or mocking HTTP communication. Both items are handled by osc-lib thus not they are not subject of monascaclient unit tests layers. Note: This change is partial integration with osc-lib and its main purpose is to move the responsibility of: * keystone communication * rest-ful communication with service endpoint to underlying library thus allowing client to implement only necessary functionality and not supporting boilerplate code, mentioned above. Story: 2000995 Task: 4172 Change-Id: I1712a24739438e2d8331a495f18f357749a633c5
This commit is contained in:
parent
c46b781405
commit
94c5223f02
|
@ -1,4 +1,4 @@
|
|||
.coverage
|
||||
.coverage*
|
||||
.venv
|
||||
cover
|
||||
*.pyc
|
||||
|
|
|
@ -1,18 +1,70 @@
|
|||
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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 pbr.version
|
||||
"""
|
||||
|
||||
__version__ = pbr.version.VersionInfo('python-monascaclient').version_string()
|
||||
Patches method that transforms error responses.
|
||||
That is required to handle different format monasca follows.
|
||||
|
||||
"""
|
||||
|
||||
from keystoneauth1 import exceptions as exc
|
||||
from keystoneauth1.exceptions import http
|
||||
|
||||
|
||||
def mon_exc_from_response(response, method, url):
|
||||
req_id = response.headers.get('x-openstack-request-id')
|
||||
|
||||
kwargs = {
|
||||
'http_status': response.status_code,
|
||||
'response': response,
|
||||
'method': method,
|
||||
'url': url,
|
||||
'request_id': req_id,
|
||||
}
|
||||
if 'retry-after' in response.headers:
|
||||
kwargs['retry_after'] = response.headers['retry-after']
|
||||
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
if content_type.startswith('application/json'):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(body, dict):
|
||||
if isinstance(body.get('error'), dict):
|
||||
error = body['error']
|
||||
kwargs['message'] = error.get('message')
|
||||
kwargs['details'] = error.get('details')
|
||||
elif {'description', 'title'} <= set(body):
|
||||
# monasca-api error response structure
|
||||
kwargs['message'] = body.get('title')
|
||||
kwargs['details'] = body.get('description')
|
||||
elif content_type.startswith('text/'):
|
||||
kwargs['details'] = response.text
|
||||
|
||||
try:
|
||||
cls = http._code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = exc.HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = exc.HTTPClientError
|
||||
else:
|
||||
cls = exc.HttpError
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
exc.from_response = mon_exc_from_response
|
||||
|
|
|
@ -1,496 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2012 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
# E1102: %s is not callable
|
||||
# pylint: disable=E1102
|
||||
|
||||
import abc
|
||||
import copy
|
||||
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from monascaclient.apiclient import exceptions
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""Return id if argument is a Resource.
|
||||
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
if obj.uuid:
|
||||
return obj.uuid
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
|
||||
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
|
||||
class HookableMixin(object):
|
||||
"""Mixin so classes can register and run hooks."""
|
||||
_hooks_map = {}
|
||||
|
||||
@classmethod
|
||||
def add_hook(cls, hook_type, hook_func):
|
||||
"""Add a new hook of specified type.
|
||||
|
||||
:param cls: class that registers hooks
|
||||
:param hook_type: hook type, e.g., '__pre_parse_args__'
|
||||
:param hook_func: hook function
|
||||
"""
|
||||
if hook_type not in cls._hooks_map:
|
||||
cls._hooks_map[hook_type] = []
|
||||
|
||||
cls._hooks_map[hook_type].append(hook_func)
|
||||
|
||||
@classmethod
|
||||
def run_hooks(cls, hook_type, *args, **kwargs):
|
||||
"""Run all hooks of specified type.
|
||||
|
||||
:param cls: class that registers hooks
|
||||
:param hook_type: hook type, e.g., '__pre_parse_args__'
|
||||
:param **args: args to be passed to every hook function
|
||||
:param **kwargs: kwargs to be passed to every hook function
|
||||
"""
|
||||
hook_funcs = cls._hooks_map.get(hook_type) or []
|
||||
for hook_func in hook_funcs:
|
||||
hook_func(*args, **kwargs)
|
||||
|
||||
|
||||
class BaseManager(HookableMixin):
|
||||
"""Basic manager type providing common operations.
|
||||
|
||||
Managers interact with a particular type of API (servers, flavors, images,
|
||||
etc.) and provide CRUD operations for them.
|
||||
"""
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, client):
|
||||
"""Initializes BaseManager with `client`.
|
||||
|
||||
:param client: instance of BaseClient descendant for HTTP requests
|
||||
"""
|
||||
super(BaseManager, self).__init__()
|
||||
self.client = client
|
||||
|
||||
def _list(self, url, response_key, obj_class=None, json=None):
|
||||
"""List the collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param obj_class: class for constructing the returned objects
|
||||
(self.resource_class will be used by default)
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
"""
|
||||
if json:
|
||||
body = self.client.post(url, json=json).json()
|
||||
else:
|
||||
body = self.client.get(url).json()
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
data = body[response_key]
|
||||
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
||||
# unlike other services which just return the list...
|
||||
try:
|
||||
data = data['values']
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def _get(self, url, response_key):
|
||||
"""Get an object from collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'server'
|
||||
"""
|
||||
body = self.client.get(url).json()
|
||||
return self.resource_class(self, body[response_key], loaded=True)
|
||||
|
||||
def _head(self, url):
|
||||
"""Retrieve request headers for an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
"""
|
||||
resp = self.client.head(url)
|
||||
return resp.status_code == 204
|
||||
|
||||
def _post(self, url, json, response_key, return_raw=False):
|
||||
"""Create an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
:param return_raw: flag to force returning raw JSON instead of
|
||||
Python object of self.resource_class
|
||||
"""
|
||||
body = self.client.post(url, json=json).json()
|
||||
if return_raw:
|
||||
return body[response_key]
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
def _put(self, url, json=None, response_key=None):
|
||||
"""Update an object with PUT method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
"""
|
||||
resp = self.client.put(url, json=json)
|
||||
# PUT requests may not return a body
|
||||
if resp.content:
|
||||
body = resp.json()
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _patch(self, url, json=None, response_key=None):
|
||||
"""Update an object with PATCH method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'
|
||||
"""
|
||||
body = self.client.patch(url, json=json).json()
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _delete(self, url):
|
||||
"""Delete an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers/my-server'
|
||||
"""
|
||||
return self.client.delete(url)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ManagerWithFind(BaseManager):
|
||||
"""Manager with additional `find()`/`findall()` methods."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list(self):
|
||||
pass
|
||||
|
||||
def find(self, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
matches = self.findall(**kwargs)
|
||||
num_matches = len(matches)
|
||||
if num_matches == 0:
|
||||
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
|
||||
raise exceptions.NotFound(msg)
|
||||
elif num_matches > 1:
|
||||
raise exceptions.NoUniqueMatch()
|
||||
else:
|
||||
return matches[0]
|
||||
|
||||
def findall(self, **kwargs):
|
||||
"""Find all items with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
found = []
|
||||
searches = kwargs.items()
|
||||
|
||||
for obj in self.list():
|
||||
try:
|
||||
if all(getattr(obj, attr) == value
|
||||
for (attr, value) in searches):
|
||||
found.append(obj)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
return found
|
||||
|
||||
|
||||
class CrudManager(BaseManager):
|
||||
"""Base manager class for manipulating entities.
|
||||
|
||||
Children of this class are expected to define a `collection_key` and `key`.
|
||||
|
||||
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
|
||||
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
|
||||
objects containing a list of member resources (e.g. `{'entities': [{},
|
||||
{}, {}]}`).
|
||||
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
|
||||
refer to an individual member of the collection.
|
||||
|
||||
"""
|
||||
collection_key = None
|
||||
key = None
|
||||
|
||||
def build_url(self, base_url=None, **kwargs):
|
||||
"""Builds a resource URL for the given kwargs.
|
||||
|
||||
Given an example collection where `collection_key = 'entities'` and
|
||||
`key = 'entity'`, the following URL's could be generated.
|
||||
|
||||
By default, the URL will represent a collection of entities, e.g.::
|
||||
|
||||
/entities
|
||||
|
||||
If kwargs contains an `entity_id`, then the URL will represent a
|
||||
specific member, e.g.::
|
||||
|
||||
/entities/{entity_id}
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
url = base_url if base_url is not None else ''
|
||||
|
||||
url += '/%s' % self.collection_key
|
||||
|
||||
# do we have a specific entity?
|
||||
entity_id = kwargs.get('%s_id' % self.key)
|
||||
if entity_id is not None:
|
||||
url += '/%s' % entity_id
|
||||
|
||||
return url
|
||||
|
||||
def _filter_kwargs(self, kwargs):
|
||||
"""Drop null values and handle ids."""
|
||||
for key, ref in six.iteritems(kwargs.copy()):
|
||||
if ref is None:
|
||||
kwargs.pop(key)
|
||||
else:
|
||||
if isinstance(ref, Resource):
|
||||
kwargs.pop(key)
|
||||
kwargs['%s_id' % key] = getid(ref)
|
||||
return kwargs
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._post(
|
||||
self.build_url(**kwargs),
|
||||
{self.key: kwargs},
|
||||
self.key)
|
||||
|
||||
def get(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._get(
|
||||
self.build_url(**kwargs),
|
||||
self.key)
|
||||
|
||||
def head(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._head(self.build_url(**kwargs))
|
||||
|
||||
def list(self, base_url=None, **kwargs):
|
||||
"""List the collection.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._list(
|
||||
'%(base_url)s%(query)s' % {
|
||||
'base_url': self.build_url(base_url=base_url, **kwargs),
|
||||
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
|
||||
},
|
||||
self.collection_key)
|
||||
|
||||
def put(self, base_url=None, **kwargs):
|
||||
"""Update an element.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._put(self.build_url(base_url=base_url, **kwargs))
|
||||
|
||||
def update(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
params = kwargs.copy()
|
||||
params.pop('%s_id' % self.key)
|
||||
|
||||
return self._patch(
|
||||
self.build_url(**kwargs),
|
||||
{self.key: params},
|
||||
self.key)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._delete(
|
||||
self.build_url(**kwargs))
|
||||
|
||||
def find(self, base_url=None, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
rl = self._list(
|
||||
'%(base_url)s%(query)s' % {
|
||||
'base_url': self.build_url(base_url=base_url, **kwargs),
|
||||
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
|
||||
},
|
||||
self.collection_key)
|
||||
num = len(rl)
|
||||
|
||||
if num == 0:
|
||||
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
|
||||
raise exceptions.NotFound(404, msg)
|
||||
elif num > 1:
|
||||
raise exceptions.NoUniqueMatch
|
||||
else:
|
||||
return rl[0]
|
||||
|
||||
|
||||
class Extension(HookableMixin):
|
||||
"""Extension descriptor."""
|
||||
|
||||
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
|
||||
manager_class = None
|
||||
|
||||
def __init__(self, name, module):
|
||||
super(Extension, self).__init__()
|
||||
self.name = name
|
||||
self.module = module
|
||||
self._parse_extension_module()
|
||||
|
||||
def _parse_extension_module(self):
|
||||
self.manager_class = None
|
||||
for attr_name, attr_value in self.module.__dict__.items():
|
||||
if attr_name in self.SUPPORTED_HOOKS:
|
||||
self.add_hook(attr_name, attr_value)
|
||||
else:
|
||||
try:
|
||||
if issubclass(attr_value, BaseManager):
|
||||
self.manager_class = attr_value
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "<Extension '%s'>" % self.name
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""Base class for OpenStack resources (tenant, user, etc.).
|
||||
|
||||
This is pretty much just a bag for attributes.
|
||||
"""
|
||||
|
||||
HUMAN_ID = False
|
||||
NAME_ATTR = 'name'
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
"""Populate and bind to a manager.
|
||||
|
||||
:param manager: BaseManager object
|
||||
:param info: dictionary representing resource attributes
|
||||
:param loaded: prevent lazy-loading if set to True
|
||||
"""
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k
|
||||
for k in self.__dict__.keys()
|
||||
if k[0] != '_' and k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
||||
|
||||
@property
|
||||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion."""
|
||||
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
|
||||
return strutils.to_slug(getattr(self, self.NAME_ATTR))
|
||||
return None
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in six.iteritems(info):
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
self._info[k] = v
|
||||
except AttributeError:
|
||||
# In this case we already defined the attribute on the class
|
||||
pass
|
||||
|
||||
def __getattr__(self, k):
|
||||
if k not in self.__dict__:
|
||||
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
|
||||
if not self.is_loaded:
|
||||
self._get()
|
||||
return self.__getattr__(k)
|
||||
|
||||
raise AttributeError(k)
|
||||
else:
|
||||
return self.__dict__[k]
|
||||
|
||||
def _get(self):
|
||||
# set _loaded first ... so if we have to bail, we know we tried.
|
||||
self._loaded = True
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
|
||||
new = self.manager.get(self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Resource):
|
||||
return NotImplemented
|
||||
# two resources of different types are not equal
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
if hasattr(self, 'id') and hasattr(other, 'id'):
|
||||
return self.id == other.id
|
||||
return self._info == other._info
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
@property
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
|
@ -1,438 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
class MissingArgs(ClientException):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = "Missing argument(s): %s" % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
"Authentication failed. Missing options: %s" %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified a AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
"AuthSystemNotFound: %s" % repr(auth_system))
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
"AmbiguousEndpoints: %s" % repr(endpoints))
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions."""
|
||||
http_status = 0
|
||||
message = "HTTP Error"
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HttpError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPClientError(HttpError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = "HTTP Client Error"
|
||||
|
||||
|
||||
class HttpServerError(HttpError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = "HTTP Server Error"
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = "Bad Request"
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = "Unauthorized"
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = "Payment Required"
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = "Forbidden"
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = "Not Found"
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = "Method Not Allowed"
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = "Not Acceptable"
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = "Proxy Authentication Required"
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = "Request Timeout"
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = "Conflict"
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = "Gone"
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = "Length Required"
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = "Precondition Failed"
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = "Request Entity Too Large"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = "Request-URI Too Long"
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = "Unsupported Media Type"
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
The client has asked for a portion of the file, but the server cannot
|
||||
supply that portion.
|
||||
"""
|
||||
http_status = 416
|
||||
message = "Requested Range Not Satisfiable"
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = "Expectation Failed"
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = "Unprocessable Entity"
|
||||
|
||||
|
||||
class InternalServerError(HttpServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = "Internal Server Error"
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HttpNotImplemented(HttpServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = "Not Implemented"
|
||||
|
||||
|
||||
class BadGateway(HttpServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
The server was acting as a gateway or proxy and received an invalid
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 502
|
||||
message = "Bad Gateway"
|
||||
|
||||
|
||||
class ServiceUnavailable(HttpServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = "Service Unavailable"
|
||||
|
||||
|
||||
class GatewayTimeout(HttpServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
The server was acting as a gateway or proxy and did not receive a timely
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 504
|
||||
message = "Gateway Timeout"
|
||||
|
||||
|
||||
class HttpVersionNotSupported(HttpServerError):
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = "HTTP Version Not Supported"
|
||||
|
||||
|
||||
# _code_map contains all the classes that have http_status attribute.
|
||||
_code_map = dict(
|
||||
(getattr(obj, 'http_status', None), obj)
|
||||
for name, obj in six.iteritems(vars(sys.modules[__name__]))
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": response.headers.get("x-compute-request-id"),
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if hasattr(body, "keys"):
|
||||
error = body[body.keys()[0]]
|
||||
kwargs["message"] = error.get("message")
|
||||
kwargs["details"] = error.get("details")
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = response.text
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HttpError
|
||||
return cls(**kwargs)
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 Fujitsu LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,12 +14,63 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from monascaclient.common import utils
|
||||
from keystoneauth1 import identity
|
||||
from keystoneauth1 import session
|
||||
|
||||
from monascaclient.osc import migration
|
||||
from monascaclient import version
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
if 'use_environment_variables' in kwargs and kwargs['use_environment_variables']:
|
||||
utils.set_env_variables(kwargs)
|
||||
return client_class(*args, **kwargs)
|
||||
def Client(api_version, **kwargs):
|
||||
|
||||
auth = _get_auth_handler(kwargs)
|
||||
sess = _get_session(auth, kwargs)
|
||||
|
||||
client = migration.make_client(
|
||||
api_version=api_version,
|
||||
session=sess,
|
||||
endpoint=kwargs.get('endpoint'),
|
||||
service_type=kwargs.get('service_type', 'monitoring')
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def _get_session(auth, kwargs):
|
||||
return session.Session(auth=auth,
|
||||
app_name='monascaclient',
|
||||
app_version=version.version_string,
|
||||
cert=kwargs.get('cert', None),
|
||||
timeout=kwargs.get('timeout', None),
|
||||
verify=kwargs.get('verify',
|
||||
not kwargs.get('insecure',
|
||||
False)))
|
||||
|
||||
|
||||
def _get_auth_handler(kwargs):
|
||||
if 'token' in kwargs:
|
||||
auth = identity.Token(
|
||||
auth_url=kwargs.get('auth_url', None),
|
||||
token=kwargs.get('token', None),
|
||||
project_id=kwargs.get('project_id', None),
|
||||
project_name=kwargs.get('project_name', None),
|
||||
project_domain_id=kwargs.get('project_domain_id', None),
|
||||
project_domain_name=kwargs.get('project_domain_name', None)
|
||||
)
|
||||
elif {'username', 'password'} <= set(kwargs):
|
||||
auth = identity.Password(
|
||||
auth_url=kwargs.get('auth_url', None),
|
||||
username=kwargs.get('username', None),
|
||||
password=kwargs.get('password', None),
|
||||
project_id=kwargs.get('project_id', None),
|
||||
project_name=kwargs.get('project_name', None),
|
||||
project_domain_id=kwargs.get('project_domain_id', None),
|
||||
project_domain_name=kwargs.get('project_domain_name', None),
|
||||
user_domain_id=kwargs.get('user_domain_id', None),
|
||||
user_domain_name=kwargs.get('user_domain_name', None)
|
||||
)
|
||||
else:
|
||||
raise Exception('monascaclient can be configured with either '
|
||||
'"token" or "username:password" but neither of '
|
||||
'them was found in passed arguments.')
|
||||
return auth
|
||||
|
|
|
@ -1,323 +0,0 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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 copy
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from monascaclient import exc
|
||||
from monascaclient import ksclient
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
if not LOG.handlers:
|
||||
LOG.addHandler(logging.StreamHandler())
|
||||
USER_AGENT = 'python-monascaclient'
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
|
||||
def get_system_ca_file():
|
||||
"""Return path to system default CA file."""
|
||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
||||
# Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca
|
||||
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
'/etc/ssl/ca-bundle.pem',
|
||||
'/etc/ssl/cert.pem',
|
||||
'/System/Library/OpenSSL/certs/cacert.pem',
|
||||
requests.certs.where()]
|
||||
for ca in ca_path:
|
||||
LOG.debug("Looking for ca file %s", ca)
|
||||
if os.path.exists(ca):
|
||||
LOG.debug("Using ca file %s", ca)
|
||||
return ca
|
||||
LOG.warning("System ca file could not be found.")
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
|
||||
def __init__(self, endpoint, write_timeout=None, read_timeout=None, **kwargs):
|
||||
if endpoint.endswith('/'):
|
||||
endpoint = endpoint[:-1]
|
||||
self.endpoint = endpoint
|
||||
self.write_timeout = write_timeout
|
||||
self.read_timeout = read_timeout
|
||||
self.auth_url = kwargs.get('auth_url')
|
||||
self.auth_token = kwargs.get('token')
|
||||
self.username = kwargs.get('username')
|
||||
self.password = kwargs.get('password')
|
||||
self.user_domain_id = kwargs.get('user_domain_id')
|
||||
self.user_domain_name = kwargs.get('user_domain_name')
|
||||
self.region_name = kwargs.get('region_name')
|
||||
self.endpoint_url = endpoint
|
||||
# adding for re-authenticate
|
||||
self.project_name = kwargs.get('project_name')
|
||||
self.region_name = kwargs.get('region_name')
|
||||
self.project_id = kwargs.get('project_id')
|
||||
self.domain_id = kwargs.get('domain_id')
|
||||
self.domain_name = kwargs.get('domain_name')
|
||||
self.endpoint_type = kwargs.get('endpoint_type')
|
||||
self.service_type = kwargs.get('service_type')
|
||||
self.keystone_timeout = kwargs.get('keystone_timeout')
|
||||
|
||||
self.cert_file = kwargs.get('cert_file')
|
||||
self.key_file = kwargs.get('key_file')
|
||||
|
||||
self.ssl_connection_params = {
|
||||
'os_cacert': kwargs.get('os_cacert'),
|
||||
'cert_file': kwargs.get('cert_file'),
|
||||
'key_file': kwargs.get('key_file'),
|
||||
'insecure': kwargs.get('insecure'),
|
||||
}
|
||||
|
||||
self.verify_cert = None
|
||||
if parse.urlparse(endpoint).scheme == "https":
|
||||
if kwargs.get('insecure'):
|
||||
self.verify_cert = False
|
||||
else:
|
||||
self.verify_cert = kwargs.get(
|
||||
'os_cacert', get_system_ca_file())
|
||||
|
||||
def replace_token(self, token):
|
||||
self.auth_token = token
|
||||
|
||||
def re_authenticate(self):
|
||||
ks_args = {
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'user_domain_id': self.user_domain_id,
|
||||
'user_domain_name': self.user_domain_name,
|
||||
'token': '',
|
||||
'auth_url': self.auth_url,
|
||||
'service_type': self.service_type,
|
||||
'endpoint_type': self.endpoint_type,
|
||||
'os_cacert': self.ssl_connection_params['os_cacert'],
|
||||
'project_id': self.project_id,
|
||||
'project_name': self.project_name,
|
||||
'domain_id': self.domain_id,
|
||||
'domain_name': self.domain_name,
|
||||
'insecure': self.ssl_connection_params['insecure'],
|
||||
'region_name': self.region_name,
|
||||
'keystone_timeout': self.keystone_timeout
|
||||
}
|
||||
try:
|
||||
_ksclient = ksclient.KSClient(**ks_args)
|
||||
self.auth_token = _ksclient.token
|
||||
except Exception as e:
|
||||
raise exc.KeystoneException(e)
|
||||
|
||||
def log_curl_request(self, method, url, kwargs):
|
||||
curl = ['curl -i -X %s' % method]
|
||||
|
||||
for (key, value) in kwargs['headers'].items():
|
||||
if key in ('X-Auth-Token'):
|
||||
value = '*****'
|
||||
header = '-H \'%s: %s\'' % (encodeutils.safe_decode(key),
|
||||
encodeutils.safe_decode(value))
|
||||
curl.append(header)
|
||||
|
||||
conn_params_fmt = [
|
||||
('key_file', '--key %s'),
|
||||
('cert_file', '--cert %s'),
|
||||
('os_cacert', '--cacert %s'),
|
||||
]
|
||||
for (key, fmt) in conn_params_fmt:
|
||||
value = self.ssl_connection_params.get(key)
|
||||
if value:
|
||||
curl.append(fmt % value)
|
||||
|
||||
if self.ssl_connection_params.get('insecure'):
|
||||
curl.append('-k')
|
||||
|
||||
if 'data' in kwargs:
|
||||
curl.append('-d \'%s\'' % kwargs['data'])
|
||||
|
||||
curl.append('%s%s' % (self.endpoint, url))
|
||||
LOG.debug(' '.join(curl))
|
||||
|
||||
@staticmethod
|
||||
def log_http_response(resp):
|
||||
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
|
||||
dump.append('')
|
||||
if resp.content:
|
||||
content = resp.content
|
||||
if isinstance(content, six.binary_type):
|
||||
content = content.decode('utf-8', 'strict')
|
||||
dump.extend([content, ''])
|
||||
LOG.debug('\n'.join(dump))
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around requests.request to handle tasks such as
|
||||
setting headers and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
||||
if self.auth_token:
|
||||
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
||||
|
||||
self.log_curl_request(method, url, kwargs)
|
||||
|
||||
if self.cert_file and self.key_file:
|
||||
kwargs['cert'] = (self.cert_file, self.key_file)
|
||||
|
||||
if self.verify_cert is not None:
|
||||
kwargs['verify'] = self.verify_cert
|
||||
|
||||
# Since requests does not follow the RFC when doing redirection to sent
|
||||
# back the same method on a redirect we are simply bypassing it. For
|
||||
# example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says
|
||||
# that we should follow that URL with the same method as before,
|
||||
# requests doesn't follow that and send a GET instead for the method.
|
||||
# Hopefully this could be fixed as they say in a comment in a future
|
||||
# point version i.e: 3.x
|
||||
# See issue: https://github.com/kennethreitz/requests/issues/1704
|
||||
allow_redirects = False
|
||||
timeout = None
|
||||
if method in ['POST', 'DELETE', 'PUT', 'PATCH']:
|
||||
timeout = self.write_timeout
|
||||
elif method is 'GET':
|
||||
timeout = self.read_timeout
|
||||
|
||||
resp = self._make_request(method, url, allow_redirects, timeout,
|
||||
**kwargs)
|
||||
if self._unauthorized(resp):
|
||||
try:
|
||||
# re-authenticate and attempt one more request
|
||||
self.re_authenticate()
|
||||
kwargs['headers']['X-Auth-Token'] = self.auth_token
|
||||
resp = self._make_request(method, url, allow_redirects,
|
||||
timeout, **kwargs)
|
||||
self._check_status_code(resp, method, **kwargs)
|
||||
except exc.KeystoneException:
|
||||
raise
|
||||
else:
|
||||
self._check_status_code(resp, method, **kwargs)
|
||||
return resp
|
||||
|
||||
def _unauthorized(self, resp):
|
||||
status401 = (resp.status_code == 401)
|
||||
status500 = (resp.status_code == 500 and "(HTTP 401)" in resp.content)
|
||||
return status401 or status500
|
||||
|
||||
def _check_status_code(self, resp, method, **kwargs):
|
||||
if self._unauthorized(resp):
|
||||
message = "Unauthorized error"
|
||||
raise exc.HTTPUnauthorized(message=message)
|
||||
elif 400 <= resp.status_code < 600:
|
||||
raise exc.from_response(resp)
|
||||
elif resp.status_code in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
location = resp.headers.get('location')
|
||||
if location is None:
|
||||
message = "Location not returned with 302"
|
||||
raise exc.InvalidEndpoint(message=message)
|
||||
elif location.startswith(self.endpoint):
|
||||
# shave off the endpoint, it will be prepended when we recurse
|
||||
location = location[len(self.endpoint):]
|
||||
else:
|
||||
message = "Prohibited endpoint redirect %s" % location
|
||||
raise exc.InvalidEndpoint(message=message)
|
||||
return self._http_request(location, method, **kwargs)
|
||||
elif resp.status_code == 300:
|
||||
raise exc.from_response(resp)
|
||||
|
||||
def _make_request(self, method, url, allow_redirects, timeout, **kwargs):
|
||||
try:
|
||||
resp = requests.request(
|
||||
method,
|
||||
self.endpoint_url + url,
|
||||
allow_redirects=allow_redirects,
|
||||
timeout=timeout,
|
||||
**kwargs)
|
||||
except socket.gaierror as e:
|
||||
message = ("Error finding address for %(url)s: %(e)s" %
|
||||
{'url': self.endpoint_url + url, 'e': e})
|
||||
raise exc.InvalidEndpoint(message=message)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
endpoint = self.endpoint
|
||||
message = ("Error communicating with %(endpoint)s %(e)s" %
|
||||
{'endpoint': endpoint, 'e': e})
|
||||
raise exc.CommunicationError(message=message)
|
||||
except requests.Timeout as e:
|
||||
endpoint = self.endpoint
|
||||
message = ("Error %(method)s timeout request to %(endpoint)s %(e)s" %
|
||||
{'method': method, 'endpoint': endpoint, 'e': e})
|
||||
raise exc.RequestTimeoutError(message=message)
|
||||
except requests.ConnectionError as ex:
|
||||
endpoint = self.endpoint
|
||||
message = ("Failed to connect to %s, error was %s" % (endpoint, ex.message))
|
||||
raise exc.CommunicationError(message=message)
|
||||
self.log_http_response(resp)
|
||||
return resp
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
|
||||
if 'data' in kwargs:
|
||||
kwargs['data'] = jsonutils.dumps(kwargs['data'])
|
||||
|
||||
resp = self._http_request(url, method, **kwargs)
|
||||
body = resp.content
|
||||
if 'application/json' in resp.headers.get('content-type', ''):
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
resp, body = self.json_request(method, url, **kwargs)
|
||||
return resp
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.raw_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014, 2015 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 Fujitsu LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,16 +14,15 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
|
||||
from six.moves.urllib import parse
|
||||
from six.moves.urllib_parse import unquote
|
||||
|
||||
|
||||
class MonascaManager(base.BaseManager):
|
||||
class MonascaManager(object):
|
||||
base_url = None
|
||||
|
||||
def __init__(self, client, **kwargs):
|
||||
super(MonascaManager, self).__init__(client)
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def _parse_body(self, body):
|
||||
if type(body) is dict:
|
||||
|
@ -38,18 +38,22 @@ class MonascaManager(base.BaseManager):
|
|||
"""Get a list of metrics."""
|
||||
url_str = self.base_url + path
|
||||
if dim_key and dim_key in kwargs:
|
||||
dimstr = self.get_dimensions_url_string(kwargs[dim_key])
|
||||
kwargs[dim_key] = dimstr
|
||||
dim_str = self.get_dimensions_url_string(kwargs[dim_key])
|
||||
kwargs[dim_key] = dim_str
|
||||
|
||||
if kwargs:
|
||||
url_str += '?%s' % parse.urlencode(kwargs, True)
|
||||
resp, body = self.client.json_request(
|
||||
'GET', url_str)
|
||||
|
||||
body = self.client.list(
|
||||
path=url_str
|
||||
)
|
||||
|
||||
return self._parse_body(body)
|
||||
|
||||
def get_dimensions_url_string(self, dimdict):
|
||||
@staticmethod
|
||||
def get_dimensions_url_string(dimensions):
|
||||
dim_list = list()
|
||||
for k, v in dimdict.items():
|
||||
for k, v in dimensions.items():
|
||||
# In case user specifies a dimension multiple times
|
||||
if isinstance(v, (list, tuple)):
|
||||
v = v[-1]
|
||||
|
@ -59,10 +63,3 @@ class MonascaManager(base.BaseManager):
|
|||
dim_str = k
|
||||
dim_list.append(dim_str)
|
||||
return ','.join(dim_list)
|
||||
|
||||
def list_next(self):
|
||||
if hasattr(self, 'next') and self.next:
|
||||
self.next = unquote(self.next)
|
||||
path = self.next.split(self.base_url, 1)[-1]
|
||||
return self._list(path)
|
||||
return None
|
||||
|
|
|
@ -16,18 +16,13 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import numbers
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
import uuid
|
||||
|
||||
import prettytable
|
||||
import yaml
|
||||
|
||||
from monascaclient import exc
|
||||
from osc_lib import exceptions as exc
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
supported_formats = {
|
||||
"json": lambda x: jsonutils.dumps(x, indent=2),
|
||||
|
@ -45,23 +40,14 @@ def arg(*args, **kwargs):
|
|||
return _decorator
|
||||
|
||||
|
||||
def link_formatter(links):
|
||||
return '\n'.join([l.get('href', '') for l in links or []])
|
||||
|
||||
|
||||
def json_formatter(js):
|
||||
return (jsonutils.dumps(js, indent=2, ensure_ascii=False)).encode('utf-8')
|
||||
|
||||
|
||||
def text_wrap_formatter(d):
|
||||
return '\n'.join(textwrap.wrap(d or '', 55))
|
||||
def print_list(objs, fields, field_labels=None, formatters=None, sortby=None):
|
||||
if formatters is None:
|
||||
formatters = {}
|
||||
|
||||
|
||||
def newline_list_formatter(r):
|
||||
return '\n'.join(r or [])
|
||||
|
||||
|
||||
def print_list(objs, fields, field_labels=None, formatters={}, sortby=None):
|
||||
field_labels = field_labels or fields
|
||||
pt = prettytable.PrettyTable([f for f in field_labels],
|
||||
caching=False, print_empty=False)
|
||||
|
@ -84,7 +70,9 @@ def print_list(objs, fields, field_labels=None, formatters={}, sortby=None):
|
|||
print(pt.get_string(sortby=field_labels[sortby]).encode('utf-8'))
|
||||
|
||||
|
||||
def print_dict(d, formatters={}):
|
||||
def print_dict(d, formatters=None):
|
||||
if formatters is None:
|
||||
formatters = {}
|
||||
pt = prettytable.PrettyTable(['Property', 'Value'],
|
||||
caching=False, print_empty=False)
|
||||
pt.align = 'l'
|
||||
|
@ -97,57 +85,6 @@ def print_dict(d, formatters={}):
|
|||
print(pt.get_string(sortby='Property').encode('utf-8'))
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id):
|
||||
"""Helper for the _find_* methods."""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
if isinstance(name_or_id, int) or name_or_id.isdigit():
|
||||
return manager.get(int(name_or_id))
|
||||
except exc.NotFound:
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
uuid.UUID(str(name_or_id))
|
||||
return manager.get(name_or_id)
|
||||
except (ValueError, exc.NotFound):
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
return manager.find(name=name_or_id)
|
||||
except exc.NotFound:
|
||||
msg = ("No %s with a name or ID of '%s' exists." %
|
||||
(manager.resource_class.__name__.lower(), name_or_id))
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
"""Search for the first defined of possibly many env vars
|
||||
|
||||
Returns the first environment variable defined in vars, or
|
||||
returns the default defined in kwargs.
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', None)
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'monascaclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print(msg.encode('utf-8'), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def format_parameters(params):
|
||||
'''Reformat parameters into dict of format expected by the API.'''
|
||||
|
||||
|
@ -165,7 +102,7 @@ def format_parameters(params):
|
|||
parameters = {}
|
||||
for p in params:
|
||||
try:
|
||||
(n, v) = p.split(('='), 1)
|
||||
(n, v) = p.split('=', 1)
|
||||
except ValueError:
|
||||
msg = '%s(%s). %s.' % ('Malformed parameter', p,
|
||||
'Use the key=value format')
|
||||
|
@ -206,24 +143,14 @@ def format_dimensions_query(dims):
|
|||
return dimensions
|
||||
|
||||
|
||||
def format_output(output, format='yaml'):
|
||||
"""Format the supplied dict as specified."""
|
||||
output_format = format.lower()
|
||||
try:
|
||||
return supported_formats[output_format](output)
|
||||
except KeyError:
|
||||
raise exc.HTTPUnsupported("The format(%s) is unsupported."
|
||||
% output_format)
|
||||
|
||||
|
||||
def format_dimensions(dict):
|
||||
return ('dimensions: {\n' + format_dict(dict) + '\n}')
|
||||
return 'dimensions: {\n' + format_dict(dict) + '\n}'
|
||||
|
||||
|
||||
def format_expression_data(dict):
|
||||
def format_expression_data(data):
|
||||
# takes an dictionary containing a dict
|
||||
string_list = list()
|
||||
for k, v in dict.items():
|
||||
for k, v in data.items():
|
||||
if k == 'dimensions':
|
||||
dim_str = format_dimensions(v)
|
||||
string_list.append(dim_str)
|
||||
|
@ -271,25 +198,3 @@ def format_list(in_list):
|
|||
key = k
|
||||
string_list.append(key)
|
||||
return '\n'.join(string_list)
|
||||
|
||||
|
||||
def set_env_variables(kwargs):
|
||||
environment_variables = {
|
||||
'username': 'OS_USERNAME',
|
||||
'password': 'OS_PASSWORD',
|
||||
'token': 'OS_AUTH_TOKEN',
|
||||
'auth_url': 'OS_AUTH_URL',
|
||||
'service_type': 'OS_SERVICE_TYPE',
|
||||
'endpoint_type': 'OS_ENDPOINT_TYPE',
|
||||
'os_cacert': 'OS_CACERT',
|
||||
'user_domain_id': 'OS_USER_DOMAIN_ID',
|
||||
'user_domain_name': 'OS_USER_DOMAIN_NAME',
|
||||
'project_id': 'OS_PROJECT_ID',
|
||||
'project_name': 'OS_PROJECT_NAME',
|
||||
'domain_id': 'OS_DOMAIN_ID',
|
||||
'domain_name': 'OS_DOMAIN_NAME',
|
||||
'region_name': 'OS_REGION_NAME'
|
||||
}
|
||||
for k, v in environment_variables.items():
|
||||
if k not in kwargs:
|
||||
kwargs[k] = env(v)
|
||||
|
|
|
@ -1,233 +0,0 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 logging
|
||||
import sys
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
verbose = 0
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseException(Exception):
|
||||
|
||||
"""An error occurred."""
|
||||
|
||||
def __init__(self, message=None):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message or self.__class__.__doc__
|
||||
|
||||
|
||||
class CommandError(BaseException):
|
||||
|
||||
"""Invalid usage of CLI."""
|
||||
|
||||
|
||||
class InvalidEndpoint(BaseException):
|
||||
|
||||
"""The provided endpoint is invalid."""
|
||||
|
||||
|
||||
class CommunicationError(BaseException):
|
||||
|
||||
"""Unable to communicate with server."""
|
||||
|
||||
|
||||
class RequestTimeoutError(BaseException):
|
||||
|
||||
"""Timeout making a POST, GET, PATCH, DELETE, or PUT request to the server."""
|
||||
|
||||
|
||||
class KeystoneException(Exception):
|
||||
|
||||
"""Base exception for all Keystone-derived exceptions."""
|
||||
# This is initialized with the exception raised by the Keystone client so
|
||||
# deriving this class from Exception instead of BaseException allows that to
|
||||
# be handled without any additional code
|
||||
pass
|
||||
|
||||
|
||||
class HTTPException(BaseException):
|
||||
|
||||
"""Base exception for all HTTP-derived exceptions."""
|
||||
code = 'N/A'
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(HTTPException, self).__init__(message)
|
||||
try:
|
||||
log.error("exception: {}".format(message))
|
||||
self.error = jsonutils.loads(message)
|
||||
except Exception:
|
||||
self.error = {'error':
|
||||
{'message': self.message or self.__class__.__doc__}}
|
||||
|
||||
def __str__(self):
|
||||
|
||||
if 'description' in self.error:
|
||||
# Python API:
|
||||
# Expected message format:
|
||||
# {
|
||||
# "title": "Foo",
|
||||
# "description": "Bar"
|
||||
# }
|
||||
message = self.error['description']
|
||||
else:
|
||||
# Java API:
|
||||
# Expected message format:
|
||||
# {
|
||||
# "conflict":{"code":409,
|
||||
# "message":"Bar",
|
||||
# "details":"",
|
||||
# "internal_code":"Baz"}
|
||||
# }
|
||||
for key in self.error:
|
||||
message = self.error[key].get('message', 'Internal Error')
|
||||
|
||||
if verbose:
|
||||
traceback = self.error['error'].get('traceback', '')
|
||||
return '%s\n%s' % (message, traceback)
|
||||
else:
|
||||
return '%s' % message
|
||||
|
||||
|
||||
class HTTPMultipleChoices(HTTPException):
|
||||
code = 300
|
||||
|
||||
def __str__(self):
|
||||
self.details = ("Requested version of Monasca API is not"
|
||||
"available.")
|
||||
return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code,
|
||||
self.details)
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
code = 400
|
||||
|
||||
|
||||
class HTTPBadRequest(BadRequest):
|
||||
pass
|
||||
|
||||
|
||||
class Unauthorized(HTTPException):
|
||||
code = 401
|
||||
|
||||
|
||||
class HTTPUnauthorized(Unauthorized):
|
||||
pass
|
||||
|
||||
|
||||
class Forbidden(HTTPException):
|
||||
|
||||
"""DEPRECATED."""
|
||||
code = 403
|
||||
|
||||
|
||||
class HTTPForbidden(Forbidden):
|
||||
pass
|
||||
|
||||
|
||||
class NotFound(HTTPException):
|
||||
|
||||
"""DEPRECATED."""
|
||||
code = 404
|
||||
|
||||
|
||||
class HTTPNotFound(NotFound):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPMethodNotAllowed(HTTPException):
|
||||
code = 405
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
|
||||
"""DEPRECATED."""
|
||||
code = 409
|
||||
|
||||
|
||||
class HTTPConflict(Conflict):
|
||||
pass
|
||||
|
||||
|
||||
class OverLimit(HTTPException):
|
||||
|
||||
"""DEPRECATED."""
|
||||
code = 413
|
||||
|
||||
|
||||
class HTTPOverLimit(OverLimit):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPUnsupported(HTTPException):
|
||||
code = 415
|
||||
|
||||
|
||||
class HTTPUnProcessable(HTTPException):
|
||||
code = 422
|
||||
|
||||
|
||||
class HTTPInternalServerError(HTTPException):
|
||||
code = 500
|
||||
|
||||
|
||||
class HTTPNotImplemented(HTTPException):
|
||||
code = 501
|
||||
|
||||
|
||||
class HTTPBadGateway(HTTPException):
|
||||
code = 502
|
||||
|
||||
|
||||
class ServiceUnavailable(HTTPException):
|
||||
|
||||
"""DEPRECATED."""
|
||||
code = 503
|
||||
|
||||
|
||||
class HTTPServiceUnavailable(ServiceUnavailable):
|
||||
pass
|
||||
|
||||
|
||||
# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
||||
# classes
|
||||
_code_map = {}
|
||||
for obj_name in dir(sys.modules[__name__]):
|
||||
if obj_name.startswith('HTTP'):
|
||||
obj = getattr(sys.modules[__name__], obj_name)
|
||||
_code_map[obj.code] = obj
|
||||
|
||||
|
||||
def from_response(response):
|
||||
"""Return an instance of an HTTPException based on requests response."""
|
||||
cls = _code_map.get(response.status_code, HTTPException)
|
||||
return cls(response.content)
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
|
||||
"""DEPRECATED."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(Exception):
|
||||
|
||||
"""DEPRECATED."""
|
||||
pass
|
|
@ -1,105 +0,0 @@
|
|||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Wrapper around python keystone client to assist in getting a properly scoped token and the registered service
|
||||
endpoint for Monasca.
|
||||
"""
|
||||
|
||||
from keystoneclient.v3 import client
|
||||
|
||||
from monascaclient import exc
|
||||
|
||||
|
||||
class KSClient(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Get an endpoint and auth token from Keystone.
|
||||
|
||||
:param username: name of user
|
||||
:param password: user's password
|
||||
:param user_domain_id: unique identifier of domain username resides in (optional)
|
||||
:param user_domain_name: name of domain for username (optional), if user_domain_id not specified
|
||||
:param project_id: unique identifier of project
|
||||
:param project_name: name of project
|
||||
:param project_domain_name: name of domain project is in
|
||||
:param project_domain_id: id of domain project is in
|
||||
:param auth_url: endpoint to authenticate against
|
||||
:param token: token to use instead of username/password
|
||||
"""
|
||||
kc_args = {'auth_url': kwargs.get('auth_url'),
|
||||
'insecure': kwargs.get('insecure'),
|
||||
'timeout': kwargs.get('keystone_timeout')}
|
||||
|
||||
if kwargs.get('os_cacert'):
|
||||
kc_args['cacert'] = kwargs.get('os_cacert')
|
||||
if kwargs.get('project_id'):
|
||||
kc_args['project_id'] = kwargs.get('project_id')
|
||||
elif kwargs.get('project_name'):
|
||||
kc_args['project_name'] = kwargs.get('project_name')
|
||||
if kwargs.get('project_domain_name'):
|
||||
kc_args['project_domain_name'] = kwargs.get('project_domain_name')
|
||||
elif kwargs.get('domain_name'):
|
||||
kc_args['project_domain_name'] = kwargs.get('domain_name') # backwards compat to 1.0.30 API
|
||||
if kwargs.get('project_domain_id'):
|
||||
kc_args['project_domain_id'] = kwargs.get('project_domain_id')
|
||||
elif kwargs.get('domain_id'):
|
||||
kc_args['project_domain_id'] = kwargs.get('domain_id') # backwards compat to 1.0.30 API
|
||||
|
||||
if kwargs.get('token'):
|
||||
kc_args['token'] = kwargs.get('token')
|
||||
else:
|
||||
kc_args['username'] = kwargs.get('username')
|
||||
kc_args['password'] = kwargs.get('password')
|
||||
# when username not in the default domain (id='default'), supply user domain (as namespace)
|
||||
if kwargs.get('user_domain_name'):
|
||||
kc_args['user_domain_name'] = kwargs.get('user_domain_name')
|
||||
if kwargs.get('user_domain_id'):
|
||||
kc_args['user_domain_id'] = kwargs.get('user_domain_id')
|
||||
|
||||
self._kwargs = kwargs
|
||||
self._keystone = client.Client(**kc_args)
|
||||
self._token = None
|
||||
self._monasca_url = None
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
"""Token property
|
||||
|
||||
Validate token is project scoped and return it if it is
|
||||
project_id and auth_token were fetched when keystone client was created
|
||||
"""
|
||||
if self._token is None:
|
||||
if self._keystone.project_id:
|
||||
self._token = self._keystone.auth_token
|
||||
else:
|
||||
raise exc.CommandError("No project id or project name.")
|
||||
return self._token
|
||||
|
||||
@property
|
||||
def monasca_url(self):
|
||||
"""Return the monasca publicURL registered in keystone."""
|
||||
if self._monasca_url is None:
|
||||
if self._kwargs.get('region_name'):
|
||||
self._monasca_url = self._keystone.service_catalog.url_for(
|
||||
service_type=self._kwargs.get('service_type') or 'monitoring',
|
||||
attr='region',
|
||||
filter_value=self._kwargs.get('region_name'),
|
||||
endpoint_type=self._kwargs.get('endpoint_type') or 'publicURL')
|
||||
else:
|
||||
self._monasca_url = self._keystone.service_catalog.url_for(
|
||||
service_type=self._kwargs.get('service_type') or 'monitoring',
|
||||
endpoint_type=self._kwargs.get('endpoint_type') or 'publicURL')
|
||||
return self._monasca_url
|
|
@ -0,0 +1,169 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 logging
|
||||
import six
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils
|
||||
|
||||
from monascaclient import version
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# NOTE(trebskit) this will be moved to another module
|
||||
# once initial migration is up
|
||||
# the point is to show how many code can we spare
|
||||
# in order to get the client working with minimum effort needed
|
||||
VERSION_MAP = {
|
||||
'2_0': 'monascaclient.v2_0.client.Client'
|
||||
}
|
||||
|
||||
|
||||
def make_client(api_version, session=None,
|
||||
endpoint=None, service_type='monitoring'):
|
||||
"""Returns an monitoring API client."""
|
||||
|
||||
client_cls = utils.get_client_class('monitoring', api_version, VERSION_MAP)
|
||||
c = client_cls(
|
||||
session=session,
|
||||
service_type=service_type,
|
||||
endpoint=endpoint,
|
||||
app_name='monascaclient',
|
||||
app_version=version.version_string,
|
||||
)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def create_command_class(name, func_module):
|
||||
"""Dynamically creates subclass of MigratingCommand.
|
||||
|
||||
Method takes name of the function, module it is part of
|
||||
and builds the subclass of :py:class:`MigratingCommand`.
|
||||
Having a subclass of :py:class:`cliff.command.Command` is mandatory
|
||||
for the osc-lib integration.
|
||||
|
||||
:param name: name of the function
|
||||
:type name: basestring
|
||||
:param func_module: the module function is part of
|
||||
:type func_module: module
|
||||
:return: command name, subclass of :py:class:`MigratingCommand`
|
||||
:rtype: tuple(basestring, class)
|
||||
|
||||
"""
|
||||
|
||||
cmd_name = name[3:].replace('_', '-')
|
||||
callback = getattr(func_module, name)
|
||||
desc = callback.__doc__ or ''
|
||||
help = desc.strip().split('\n')[0]
|
||||
|
||||
arguments = getattr(callback, 'arguments', [])
|
||||
|
||||
body = {
|
||||
'_args': arguments,
|
||||
'_callback': staticmethod(callback),
|
||||
'_description': desc,
|
||||
'_epilog': desc,
|
||||
'_help': help
|
||||
}
|
||||
|
||||
claz = type('%sCommand' % cmd_name.title().replace('-', ''),
|
||||
(MigratingCommand,), body)
|
||||
|
||||
return cmd_name, claz
|
||||
|
||||
|
||||
class MigratingCommandMeta(command.CommandMeta):
|
||||
"""Overwrite module name based on osc_lib.CommandMeta requirements."""
|
||||
|
||||
def __new__(mcs, name, bases, cls_dict):
|
||||
# NOTE(trebskit) little dirty, but should suffice for migration period
|
||||
cls_dict['__module__'] = 'monascaclient.v2_0.shell'
|
||||
return super(MigratingCommandMeta, mcs).__new__(mcs, name,
|
||||
bases, cls_dict)
|
||||
|
||||
|
||||
@six.add_metaclass(MigratingCommandMeta)
|
||||
class MigratingCommand(command.Command):
|
||||
"""MigratingCommand is temporary command.
|
||||
|
||||
MigratingCommand allows to map function defined
|
||||
shell commands from :py:module:`monascaclient.v2_0.shell`
|
||||
into :py:class:`command.Command` instances.
|
||||
|
||||
Note:
|
||||
This class is temporary solution during migrating
|
||||
to osc_lib and will be removed when all
|
||||
shell commands are migrated to cliff commands.
|
||||
|
||||
"""
|
||||
|
||||
_help = None
|
||||
_args = None
|
||||
_callback = None
|
||||
|
||||
def __init__(self, app, app_args, cmd_name=None):
|
||||
super(MigratingCommand, self).__init__(app, app_args, cmd_name)
|
||||
self._client = None
|
||||
self._endpoint = None
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
return self._callback(self.mon_client, parsed_args)
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(MigratingCommand, self).get_parser(prog_name)
|
||||
for (args, kwargs) in self._args:
|
||||
parser.add_argument(*args, **kwargs)
|
||||
parser.add_argument('-j', '--json',
|
||||
action='store_true',
|
||||
help='output raw json response')
|
||||
return parser
|
||||
|
||||
@property
|
||||
def mon_client(self):
|
||||
if not self._client:
|
||||
self.log.debug('Initializing mon-client')
|
||||
self._client = make_client(api_version=self.mon_version,
|
||||
endpoint=self.mon_url,
|
||||
session=self.app.client_manager.session)
|
||||
return self._client
|
||||
|
||||
@property
|
||||
def mon_version(self):
|
||||
return self.app_args.monasca_api_version
|
||||
|
||||
@property
|
||||
def mon_url(self):
|
||||
if self._endpoint:
|
||||
return self._endpoint
|
||||
|
||||
app_args = self.app_args
|
||||
cm = self.app.client_manager
|
||||
|
||||
endpoint = app_args.monasca_api_url
|
||||
|
||||
if not endpoint:
|
||||
req_data = {
|
||||
'service_type': 'monitoring',
|
||||
'region_name': cm.region_name,
|
||||
'interface': cm.interface,
|
||||
}
|
||||
LOG.debug('Discovering monasca endpoint using %s' % req_data)
|
||||
endpoint = cm.get_endpoint_for_service_type(**req_data)
|
||||
else:
|
||||
LOG.debug('Using supplied endpoint=%s' % endpoint)
|
||||
|
||||
self._endpoint = endpoint
|
||||
return self._endpoint
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 Fujitsu LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,499 +18,100 @@
|
|||
Command-line interface to the monasca-client API.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import string
|
||||
import locale
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from six.moves.urllib.parse import urljoin
|
||||
from osc_lib.api import auth
|
||||
from osc_lib.cli import client_config as cloud_config
|
||||
from osc_lib import shell
|
||||
from osc_lib import utils as utils
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
import monascaclient
|
||||
from monascaclient import client as monasca_client
|
||||
from monascaclient.common import utils
|
||||
from monascaclient import exc
|
||||
from monascaclient import ksclient
|
||||
from monascaclient.osc import migration
|
||||
from monascaclient import version as mc_version
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeprecatedStore(argparse._StoreAction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeprecatedStore, self).__init__(*args, **kwargs)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
warnings.filterwarnings(action='default', category=DeprecationWarning, module='.*monascaclient.*')
|
||||
warnings.warn("{} is deprecated".format(",".join(self.option_strings)),
|
||||
DeprecationWarning)
|
||||
setattr(namespace, self.dest, values)
|
||||
|
||||
|
||||
class MonascaShell(object):
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='monasca',
|
||||
class MonascaShell(shell.OpenStackShell):
|
||||
def __init__(self):
|
||||
super(MonascaShell, self).__init__(
|
||||
description=__doc__.strip(),
|
||||
epilog='See "monasca help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
# formatter_class=HelpFormatter,
|
||||
formatter_class=lambda prog: argparse.HelpFormatter(
|
||||
prog,
|
||||
max_help_position=29)
|
||||
version=mc_version.version_string
|
||||
)
|
||||
self.cloud_config = None
|
||||
|
||||
def initialize_app(self, argv):
|
||||
super(MonascaShell, self).initialize_app(argv)
|
||||
self.cloud_config = cloud_config.OSC_Config(
|
||||
override_defaults={
|
||||
'interface': None,
|
||||
'auth_type': self._auth_type,
|
||||
},
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('-j', '--json',
|
||||
action='store_true',
|
||||
help='output raw json response')
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=monascaclient.__version__,
|
||||
help="Shows the client version and exits.")
|
||||
|
||||
parser.add_argument('-d', '--debug',
|
||||
default=bool(utils.env('MONASCA_DEBUG')),
|
||||
action='store_true',
|
||||
help='Defaults to env[MONASCA_DEBUG].')
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help="Print more verbose output.")
|
||||
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Explicitly allow the client to perform "
|
||||
"\"insecure\" SSL (https) requests. The server's "
|
||||
"certificate will not be verified against any "
|
||||
"certificate authorities. "
|
||||
"This option should be used with caution.")
|
||||
|
||||
parser.add_argument('--cert-file',
|
||||
help='Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.')
|
||||
|
||||
parser.add_argument(
|
||||
'--key-file',
|
||||
help='Path of client key to use in SSL connection. '
|
||||
'This option is not necessary if your key is'
|
||||
' prepended to your cert file.')
|
||||
|
||||
parser.add_argument('--os-cacert',
|
||||
default=utils.env('OS_CACERT'),
|
||||
help='Specify a CA bundle file to use in verifying'
|
||||
' a TLS (https) server certificate. Defaults to'
|
||||
' env[OS_CACERT]. Without either of these, the'
|
||||
' client looks for the default system CA'
|
||||
' certificates.')
|
||||
|
||||
parser.add_argument('--keystone_timeout',
|
||||
default=20,
|
||||
help='Number of seconds to wait for a response from keystone.')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
default=utils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
default=utils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
|
||||
parser.add_argument('--os-project-id',
|
||||
default=utils.env('OS_PROJECT_ID'),
|
||||
help='Defaults to env[OS_PROJECT_ID].')
|
||||
|
||||
parser.add_argument('--os_project_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-name',
|
||||
default=utils.env('OS_PROJECT_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_NAME].')
|
||||
|
||||
parser.add_argument('--os_project_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
dest='os_project_name',
|
||||
action=DeprecatedStore,
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='(Deprecated, use --os-project_name) '
|
||||
'Defaults to env[OS_TENANT_NAME].')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
dest='os_project_name',
|
||||
action=DeprecatedStore,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
dest='os_project_id',
|
||||
action=DeprecatedStore,
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='(Deprecated, use --os-project_id) '
|
||||
'Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
dest='os_project_id',
|
||||
action=DeprecatedStore,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-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',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-version',
|
||||
default=utils.env('OS_AUTH_VERSION'),
|
||||
help='Defaults to env[OS_AUTH_VERSION].')
|
||||
|
||||
parser.add_argument('--os_auth_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-no-client-auth',
|
||||
default=utils.env('OS_NO_CLIENT_AUTH'),
|
||||
action='store_true',
|
||||
help="Do not contact keystone for a token. "
|
||||
"Defaults to env[OS_NO_CLIENT_AUTH].")
|
||||
def build_option_parser(self, description, version):
|
||||
parser = super(MonascaShell, self).build_option_parser(
|
||||
description,
|
||||
version
|
||||
)
|
||||
parser = auth.build_auth_plugins_option_parser(parser)
|
||||
parser = self._append_monasca_args(parser)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def _append_monasca_args(parser):
|
||||
parser.add_argument('--monasca-api-url',
|
||||
default=utils.env('MONASCA_API_URL'),
|
||||
help='Defaults to env[MONASCA_API_URL].')
|
||||
|
||||
parser.add_argument('--monasca_api_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--monasca-api-version',
|
||||
default=utils.env(
|
||||
'MONASCA_API_VERSION',
|
||||
default='2_0'),
|
||||
help='Defaults to env[MONASCA_API_VERSION] or 2_0')
|
||||
|
||||
parser.add_argument('--monasca_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=utils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE].')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE].')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# In OpenStack, the parameters below are intended for domain-scoping, i.e. authorize
|
||||
# the user against a domain instead of a project. Previous versions of the agent used these
|
||||
# to qualify the project name, leading to confusion and preventing reuse of typical RC files.
|
||||
# Since domain scoping is not supported by Monasca, we can still support the old variable
|
||||
# names for the time being. If the project-name is not scoped using the correct project
|
||||
# domain name parameter, the code falls back to the domain scoping parameters.
|
||||
parser.add_argument('--os-domain-id',
|
||||
default=utils.env('OS_DOMAIN_ID'),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-domain-name',
|
||||
default=utils.env('OS_DOMAIN_NAME'),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
def _load_commands(self):
|
||||
version = self.options.monasca_api_version
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
self._find_actions(subparsers, submodule)
|
||||
self._find_actions(subparsers, self)
|
||||
self._add_bash_completion_subparser(subparsers)
|
||||
submodule = importutils.import_versioned_module('monascaclient',
|
||||
version,
|
||||
'shell')
|
||||
|
||||
return parser
|
||||
self._find_actions(submodule)
|
||||
|
||||
def _add_bash_completion_subparser(self, subparsers):
|
||||
subparser = subparsers.add_parser(
|
||||
'bash_completion',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter
|
||||
)
|
||||
self.subcommands['bash_completion'] = subparser
|
||||
subparser.set_defaults(func=self.do_bash_completion)
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
def _find_actions(self, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hyphen-separated instead of underscores.
|
||||
command = attr[3:].replace('_', '-')
|
||||
callback = getattr(actions_module, attr)
|
||||
desc = callback.__doc__ or ''
|
||||
help = desc.strip().split('\n')[0]
|
||||
arguments = getattr(callback, 'arguments', [])
|
||||
name, clazz = migration.create_command_class(attr, actions_module)
|
||||
|
||||
subparser = subparsers.add_parser(command,
|
||||
help=help,
|
||||
description=desc,
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter)
|
||||
subparser.add_argument('-h', '--help',
|
||||
action='help',
|
||||
help=argparse.SUPPRESS)
|
||||
self.subcommands[command] = subparser
|
||||
for (args, kwargs) in arguments:
|
||||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
if 'help' == name:
|
||||
# help requires no auth
|
||||
clazz.auth_required = False
|
||||
|
||||
def _setup_logging(self, debug):
|
||||
log_lvl = logging.DEBUG if debug else logging.ERROR
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=log_lvl)
|
||||
|
||||
def _setup_verbose(self, verbose):
|
||||
if verbose:
|
||||
exc.verbose = 1
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_logging(options.debug)
|
||||
self._setup_verbose(options.verbose)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.monasca_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if not args and options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with help command right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion(args)
|
||||
return 0
|
||||
|
||||
if not args.os_username and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if not args.os_password and not args.os_auth_token:
|
||||
raise exc.CommandError("You must provide a password via"
|
||||
" either --os-password or env[OS_PASSWORD]"
|
||||
" or a token via --os-auth-token or"
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if args.os_no_client_auth:
|
||||
if not args.monasca_api_url:
|
||||
raise exc.CommandError("If you specify --os-no-client-auth"
|
||||
" you must specify a Monasca API URL"
|
||||
" via either --monasca-api-url or"
|
||||
" env[MONASCA_API_URL]")
|
||||
else:
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError("You must provide an auth url via"
|
||||
" either --os-auth-url or via"
|
||||
" env[OS_AUTH_URL]")
|
||||
|
||||
auth_vars_present = args.os_auth_url and args.os_auth_version
|
||||
versioned = 'v2.0' in args.os_auth_url or 'v3' in args.os_auth_url
|
||||
if auth_vars_present and not versioned:
|
||||
args.os_auth_url = urljoin(args.os_auth_url, args.os_auth_version)
|
||||
|
||||
if args.os_auth_url and 'v2.0' in args.os_auth_url:
|
||||
args.os_auth_url = string.replace(args.os_auth_url, 'v2.0', 'v3')
|
||||
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'token': args.os_auth_token,
|
||||
'auth_url': args.os_auth_url,
|
||||
'service_type': args.os_service_type,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'os_cacert': args.os_cacert,
|
||||
'user_domain_id': args.os_user_domain_id,
|
||||
'user_domain_name': args.os_user_domain_name,
|
||||
'project_id': args.os_project_id,
|
||||
'project_name': args.os_project_name,
|
||||
# if project name is not scoped, fall back to previous behaviour (see above)
|
||||
'project_domain_id': args.os_project_domain_id if args.os_project_domain_id else args.os_domain_id,
|
||||
'project_domain_name': args.os_project_domain_name if args.os_project_domain_name else args.os_domain_name,
|
||||
'insecure': args.insecure,
|
||||
'region_name': args.os_region_name,
|
||||
'keystone_timeout': args.keystone_timeout
|
||||
}
|
||||
|
||||
endpoint = args.monasca_api_url
|
||||
|
||||
if not args.os_no_client_auth:
|
||||
_ksclient = ksclient.KSClient(**kwargs)
|
||||
if args.os_auth_token:
|
||||
token = args.os_auth_token
|
||||
else:
|
||||
try:
|
||||
token = _ksclient.token
|
||||
except exc.CommandError:
|
||||
raise exc.CommandError(
|
||||
"User does not have a default project. "
|
||||
"You must provide a project id using "
|
||||
"--os-project-id or via env[OS_PROJECT_ID], "
|
||||
"or you must provide a project name using "
|
||||
"--os-project-name or via env[OS_PROJECT_NAME] "
|
||||
"and a project domain using --os-project-domain-name, via "
|
||||
"env[OS_PROJECT_DOMAIN_NAME], using --os-project-domain-id or "
|
||||
"via env[OS_PROJECT_DOMAIN_ID]")
|
||||
|
||||
kwargs = {
|
||||
'token': token,
|
||||
'insecure': args.insecure,
|
||||
'os_cacert': args.os_cacert,
|
||||
'cert_file': args.cert_file,
|
||||
'key_file': args.key_file,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
'service_type': args.os_service_type,
|
||||
'endpoint_type': args.os_endpoint_type,
|
||||
'auth_url': args.os_auth_url,
|
||||
'keystone_timeout': args.keystone_timeout
|
||||
}
|
||||
|
||||
if args.os_user_domain_name:
|
||||
kwargs['user_domain_name'] = args.os_user_domain_name
|
||||
if args.os_user_domain_id:
|
||||
kwargs['user_domain_id'] = args.os_user_domain_id
|
||||
if args.os_region_name:
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
if args.os_project_name:
|
||||
kwargs['project_name'] = args.os_project_name
|
||||
if args.os_project_id:
|
||||
kwargs['project_id'] = args.os_project_id
|
||||
# Monasca API uses domain_id/name for project_domain_id/name
|
||||
# We cannot change this and therefore still use the misleading parameter names
|
||||
if args.os_domain_name:
|
||||
kwargs['domain_name'] = args.os_project_domain_name if args.os_project_domain_name \
|
||||
else args.os_domain_name
|
||||
if args.os_domain_id:
|
||||
kwargs['domain_id'] = args.os_project_domain_id if args.os_project_domain_id \
|
||||
else args.os_domain_id
|
||||
|
||||
if not endpoint:
|
||||
endpoint = _ksclient.monasca_url
|
||||
|
||||
client = monasca_client.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
args.func(client, args)
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""Prints all of the commands and options to stdout.
|
||||
|
||||
The monasca.bash_completion script doesn't have to hard code them.
|
||||
"""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in list(sc._optionals._option_string_actions):
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
commands.remove('bash_completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>.')
|
||||
def do_help(self, args):
|
||||
"""Display help about this program or one of its subcommands."""
|
||||
if getattr(args, 'command', None):
|
||||
if args.command in self.subcommands:
|
||||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
raise exc.CommandError("'%s' is not a valid subcommand" %
|
||||
args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
self.command_manager.add_command(name, clazz)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
try:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
MonascaShell().main(args)
|
||||
if six.PY2:
|
||||
# Emulate Py3, decode argv into Unicode based on locale so that
|
||||
# commands always see arguments as text instead of binary data
|
||||
encoding = locale.getpreferredencoding()
|
||||
if encoding:
|
||||
args = map(lambda arg: arg.decode(encoding), args)
|
||||
MonascaShell().run(args)
|
||||
except Exception as e:
|
||||
if '--debug' in args or '-d' in args:
|
||||
raise
|
||||
else:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# (C) Copyright 2014,2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 keystoneclient.v3 import client as ksclient
|
||||
|
||||
|
||||
def script_keystone_client(token=None):
|
||||
if token:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
tenant_id='tenant_id',
|
||||
token=token).AndReturn(FakeKeystone(token, None))
|
||||
else:
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
password='password',
|
||||
project_name='project_name',
|
||||
timeout=20,
|
||||
username='username').AndReturn(FakeKeystone(
|
||||
'abcd1234', 'test'))
|
||||
|
||||
|
||||
class FakeServiceCatalog(object):
|
||||
|
||||
def url_for(self, endpoint_type, service_type):
|
||||
return 'http://192.168.1.5:8004/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone(object):
|
||||
service_catalog = FakeServiceCatalog()
|
||||
|
||||
def __init__(self, auth_token, project_id):
|
||||
self.auth_token = auth_token
|
||||
self.project_id = project_id
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,440 +14,48 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
import mock
|
||||
|
||||
import fixtures
|
||||
from keystoneclient.v3 import client as ksclient
|
||||
from mox3 import mox
|
||||
from oslotest import base
|
||||
from requests_mock.contrib import fixture as requests_mock_fixture
|
||||
import six
|
||||
|
||||
from monascaclient import exc
|
||||
import monascaclient.shell
|
||||
from monascaclient.tests import fakes
|
||||
from monascaclient import shell
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
class TestMonascaShell(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
@mock.patch('monascaclient.shell.auth')
|
||||
def test_should_use_auth_plugin_option_parser(self, auth):
|
||||
auth.build_auth_plugins_option_parser = apop = mock.Mock()
|
||||
shell.MonascaShell().run([])
|
||||
apop.assert_called_once()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestCase, self).tearDown()
|
||||
|
||||
def set_fake_env(self, fake_env):
|
||||
client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_USER_DOMAIN_ID',
|
||||
'OS_USER_DOMAIN_NAME', 'OS_PROJECT_ID',
|
||||
'OS_PROJECT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME',
|
||||
'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE',
|
||||
'OS_DOMAIN_NAME', 'OS_DOMAIN_ID',
|
||||
'OS_ENDPOINT_TYPE', 'MONASCA_API_URL')
|
||||
|
||||
for key in client_env:
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(key, fake_env.get(key)))
|
||||
|
||||
# required for testing with Python 2.6
|
||||
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||
"""Fail the test unless the text matches the regular expression."""
|
||||
if isinstance(expected_regexp, six.string_types):
|
||||
expected_regexp = re.compile(expected_regexp)
|
||||
if not expected_regexp.search(text):
|
||||
msg = msg or "Regexp didn't match"
|
||||
msg = '%s: %r not found in %r' % (
|
||||
msg, expected_regexp.pattern, text)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def shell_error(self, argstr, error_match):
|
||||
orig = sys.stderr
|
||||
sys.stderr = six.StringIO()
|
||||
_shell = monascaclient.shell.MonascaShell()
|
||||
e = self.assertRaises(Exception, _shell.main, argstr.split()) # noqa
|
||||
self.assertRegexpMatches(e.__str__(), error_match)
|
||||
err = sys.stderr.getvalue()
|
||||
sys.stderr.close()
|
||||
sys.stderr = orig
|
||||
return err
|
||||
|
||||
|
||||
class ShellBase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShellBase, self).setUp()
|
||||
self.requests_mock = self.useFixture(requests_mock_fixture.Fixture())
|
||||
|
||||
self.m = mox.Mox()
|
||||
self.m.StubOutWithMock(ksclient, 'Client')
|
||||
self.addCleanup(self.m.VerifyAll)
|
||||
self.addCleanup(self.m.UnsetStubs)
|
||||
|
||||
# Some tests set exc.verbose = 1, so reset on cleanup
|
||||
def unset_exc_verbose():
|
||||
exc.verbose = 0
|
||||
|
||||
self.addCleanup(unset_exc_verbose)
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = monascaclient.shell.MonascaShell()
|
||||
_shell.main(argstr.split())
|
||||
self.subcommands = _shell.subcommands.keys()
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ShellTestCommon(ShellBase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTestCommon, self).setUp()
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'^usage: monasca',
|
||||
'(?m)^See "monasca help COMMAND" for help on a specific command',
|
||||
def test_should_specify_monasca_args(self):
|
||||
expected_args = [
|
||||
'--monasca-api-url',
|
||||
'--monasca-api-version',
|
||||
'--monasca_api_url',
|
||||
'--monasca_api_version',
|
||||
]
|
||||
for argstr in ['--help', 'help']:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertRegexpMatches(help_text, r)
|
||||
|
||||
def test_command_help(self):
|
||||
output = self.shell('help help')
|
||||
self.assertIn('usage: monasca help [<subcommand>]', output)
|
||||
subcommands = list(self.subcommands)
|
||||
for command in subcommands:
|
||||
if command.replace('_', '-') == 'bash-completion':
|
||||
continue
|
||||
output1 = self.shell('help %s' % command)
|
||||
output2 = self.shell('%s --help' % command)
|
||||
self.assertEqual(output1, output2)
|
||||
self.assertRegexpMatches(output1, '^usage: monasca %s' % command)
|
||||
parser = mock.Mock()
|
||||
parser.add_argument = aa = mock.Mock()
|
||||
shell.MonascaShell._append_monasca_args(parser)
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'^usage: monasca metric-create',
|
||||
"(?m)^Create metric",
|
||||
]
|
||||
argstrings = [
|
||||
'help metric-create',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertRegexpMatches(help_text, r)
|
||||
aa.assert_called()
|
||||
for mc in aa.mock_calls:
|
||||
name = mc[1][0]
|
||||
self.assertIn(name, expected_args)
|
||||
|
||||
def test_deprecated_warning(self):
|
||||
argrequired = [('--help --os-tenant-name=this', '--os-tenant-name is deprecated'),
|
||||
('--help --os-tenant-id=this', '--os-tenant-id is deprecated')]
|
||||
for argstr, required in argrequired:
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
self.shell(argstr)
|
||||
self.assertEqual(str(w[0].message), required)
|
||||
self.assertEqual(w[0].category, DeprecationWarning)
|
||||
@mock.patch('monascaclient.shell.importutils')
|
||||
def test_should_load_commands_based_on_api_version(self, iu):
|
||||
iu.import_versioned_module = ivm = mock.Mock()
|
||||
|
||||
instance = shell.MonascaShell()
|
||||
instance.options = mock.Mock()
|
||||
instance.options.monasca_api_version = version = mock.Mock()
|
||||
|
||||
class ShellTestMonascaCommands(ShellBase):
|
||||
instance._find_actions = mock.Mock()
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTestMonascaCommands, self).setUp()
|
||||
self._set_fake_env()
|
||||
instance._load_commands()
|
||||
|
||||
def assertHeaders(self, req=None, **kwargs):
|
||||
if not req:
|
||||
req = self.requests_mock.last_request
|
||||
|
||||
self.assertEqual('abcd1234', req.headers['X-Auth-Token'])
|
||||
self.assertEqual('python-monascaclient', req.headers['User-Agent'])
|
||||
|
||||
for k, v in kwargs.items():
|
||||
self.assertEqual(v, req.headers[k])
|
||||
|
||||
def _set_fake_env(self):
|
||||
fake_env = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_NAME': 'project_name',
|
||||
'OS_AUTH_URL': 'http://no.where',
|
||||
}
|
||||
self.set_fake_env(fake_env)
|
||||
|
||||
def _script_keystone_client(self):
|
||||
fakes.script_keystone_client()
|
||||
|
||||
def test_bad_metrics_create_subcommand(self):
|
||||
argstrings = [
|
||||
'metric-create metric1',
|
||||
'metric-create 123',
|
||||
'metric-create',
|
||||
]
|
||||
_shell = monascaclient.shell.MonascaShell()
|
||||
for argstr in argstrings:
|
||||
self.assertRaises(SystemExit, _shell.main, argstr.split())
|
||||
|
||||
def test_good_metrics_create_subcommand(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
headers = {'location': 'http://no.where/v2.0/metrics'}
|
||||
self.requests_mock.post('http://192.168.1.5:8004/v1/f14b41234/metrics',
|
||||
status_code=204,
|
||||
headers=headers)
|
||||
|
||||
argstrings = [
|
||||
'metric-create metric1 123 --time 1395691090',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
retvalue = self.shell(argstr)
|
||||
self.assertRegexpMatches(retvalue, "^Success")
|
||||
|
||||
data = {'timestamp': 1395691090,
|
||||
'name': 'metric1',
|
||||
'value': 123.0}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_good_metrics_create_subcommand_with_tenant_id(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
headers = {'location': 'http://no.where/v2.0/metrics'}
|
||||
self.requests_mock.post('http://192.168.1.5:8004/v1/f14b41234/metrics',
|
||||
status_code=204,
|
||||
headers=headers)
|
||||
|
||||
proj = 'd48e63e76a5c4e05ba26a1185f31d4aa'
|
||||
argstrings = [
|
||||
'metric-create metric1 123 --time 1395691090 --project-id ' + proj,
|
||||
]
|
||||
for argstr in argstrings:
|
||||
retvalue = self.shell(argstr)
|
||||
self.assertRegexpMatches(retvalue, "^Success")
|
||||
|
||||
data = {'timestamp': 1395691090,
|
||||
'name': 'metric1',
|
||||
'value': 123.0}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
request_url = self.requests_mock.last_request.url
|
||||
query_arg = request_url[request_url.index('?') + 1:]
|
||||
self.assertEqual('tenant_id=' + proj, query_arg)
|
||||
|
||||
def test_bad_notifications_create_missing_args_subcommand(self):
|
||||
argstrings = [
|
||||
'notification-create email1 metric1@hp.com',
|
||||
]
|
||||
_shell = monascaclient.shell.MonascaShell()
|
||||
for argstr in argstrings:
|
||||
self.assertRaises(SystemExit, _shell.main, argstr.split())
|
||||
|
||||
def test_good_notifications_create_subcommand(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/notification-methods'
|
||||
headers = {'location': 'http://no.where/v2.0/notification-methods',
|
||||
'Content-Type': 'application/json'}
|
||||
self.requests_mock.post(url,
|
||||
status_code=201,
|
||||
headers=headers,
|
||||
json='id')
|
||||
|
||||
argstrings = [
|
||||
'notification-create email1 EMAIL john.doe@hp.com',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
retvalue = self.shell(argstr)
|
||||
self.assertRegexpMatches(retvalue, "id")
|
||||
|
||||
data = {'name': 'email1',
|
||||
'type': 'EMAIL',
|
||||
'address': 'john.doe@hp.com'}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_good_notifications_create_subcommand_webhook(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/notification-methods'
|
||||
headers = {'location': 'http://no.where/v2.0/notification-methods',
|
||||
'Content-Type': 'application/json'}
|
||||
self.requests_mock.post(url,
|
||||
status_code=201,
|
||||
headers=headers,
|
||||
json='id')
|
||||
|
||||
argstrings = [
|
||||
'notification-create mypost WEBHOOK http://localhost:8080',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
retvalue = self.shell(argstr)
|
||||
self.assertRegexpMatches(retvalue, "id")
|
||||
|
||||
data = {'name': 'mypost',
|
||||
'type': 'WEBHOOK',
|
||||
'address': 'http://localhost:8080'}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_good_notifications_patch(self):
|
||||
args = '--type EMAIL --address john.doe@hpe.com --period 0'
|
||||
data = {'type': 'EMAIL',
|
||||
'address': 'john.doe@hpe.com',
|
||||
'period': 0}
|
||||
self.run_notification_patch_test(args, data)
|
||||
|
||||
def test_good_notifications_patch_just_name(self):
|
||||
name = 'fred'
|
||||
args = '--name ' + name
|
||||
data = {'name': name}
|
||||
self.run_notification_patch_test(args, data)
|
||||
|
||||
def test_good_notifications_patch_just_address(self):
|
||||
address = 'fred@fl.com'
|
||||
args = '--address ' + address
|
||||
data = {'address': address}
|
||||
self.run_notification_patch_test(args, data)
|
||||
|
||||
def test_good_notifications_patch_just_period(self):
|
||||
period = 0
|
||||
args = '--period ' + str(period)
|
||||
data = {'period': period}
|
||||
self.run_notification_patch_test(args, data)
|
||||
|
||||
def run_notification_patch_test(self, args, data):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/notification-methods/'
|
||||
headers = {'location': 'http://no.where/v2.0/notification-methods',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
self.requests_mock.patch(url + id_str,
|
||||
status_code=201,
|
||||
headers=headers,
|
||||
json='id')
|
||||
|
||||
argstring = 'notification-patch {0} {1}'.format(id_str, args)
|
||||
retvalue = self.shell(argstring)
|
||||
self.assertRegexpMatches(retvalue, "id")
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_bad_notifications_patch(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
argstring = 'notification-patch {0} --type EMAIL --address' \
|
||||
' john.doe@hpe.com --period 60'.format(id_str)
|
||||
|
||||
retvalue = self.shell(argstring)
|
||||
self.assertRegexpMatches(retvalue, "^Invalid")
|
||||
|
||||
def test_good_notifications_update(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96491'
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/notification-methods/'
|
||||
headers = {'location': 'http://no.where/v2.0/notification-methods',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
self.requests_mock.put(url + id_str,
|
||||
status_code=201,
|
||||
headers=headers,
|
||||
json='id')
|
||||
|
||||
argstring = 'notification-update {0} notification_updated_name ' \
|
||||
'EMAIL john.doe@hpe.com 0'.format(id_str)
|
||||
retvalue = self.shell(argstring)
|
||||
self.assertRegexpMatches(retvalue, "id")
|
||||
|
||||
data = {'name': 'notification_updated_name',
|
||||
'type': 'EMAIL',
|
||||
'address': 'john.doe@hpe.com',
|
||||
'period': 0}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_good_alarm_definition_update(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/alarm-definitions/'
|
||||
headers = {'location': 'http://no.where/v2.0/notification-methods',
|
||||
'Content-Type': 'application/json'}
|
||||
|
||||
self.requests_mock.put(url + id_str,
|
||||
status_code=201,
|
||||
headers=headers,
|
||||
json='id')
|
||||
|
||||
cmd = 'alarm-definition-update'
|
||||
name = 'alarm_name'
|
||||
description = 'test_alarm_definition'
|
||||
expression = 'avg(Test_Metric_1)>=10'
|
||||
notif_id = '16012650-0b62-4692-9103-2d04fe81cc93'
|
||||
enabled = 'True'
|
||||
match_by = 'hostname'
|
||||
severity = 'CRITICAL'
|
||||
|
||||
args = [cmd, id_str, name, description, expression, notif_id,
|
||||
notif_id, notif_id, enabled, match_by, severity]
|
||||
argstring = " ".join(args)
|
||||
retvalue = self.shell(argstring)
|
||||
self.assertRegexpMatches(retvalue, "id")
|
||||
|
||||
data = {'name': name,
|
||||
'description': description,
|
||||
'expression': expression,
|
||||
'alarm_actions': [notif_id],
|
||||
'undetermined_actions': [notif_id],
|
||||
'ok_actions': [notif_id],
|
||||
'match_by': [match_by],
|
||||
'actions_enabled': bool(enabled),
|
||||
'severity': severity}
|
||||
|
||||
self.assertHeaders()
|
||||
self.assertEqual(data, self.requests_mock.last_request.json())
|
||||
|
||||
def test_notifications_types_list(self):
|
||||
self._script_keystone_client()
|
||||
self.m.ReplayAll()
|
||||
|
||||
url = 'http://192.168.1.5:8004/v1/f14b41234/notification-methods/'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
body = [{"type": "WEBHOOK"}, {"type": "EMAIL"}, {"type": "PAGERDUTY"}]
|
||||
self.requests_mock.get(url + 'types', headers=headers, json=body)
|
||||
|
||||
argstrings = ["notification-type-list"]
|
||||
|
||||
retvalue = self.shell("".join(argstrings))
|
||||
self.assertRegexpMatches(retvalue, "types")
|
||||
|
||||
self.assertHeaders()
|
||||
ivm.assert_called_once_with('monascaclient', version, 'shell')
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from monascaclient.osc import migration as migr
|
||||
from monascaclient.v2_0 import alarm_definitions as ad
|
||||
from monascaclient.v2_0 import shell
|
||||
|
||||
|
||||
class FakeV2Client(object):
|
||||
def __init__(self):
|
||||
super(FakeV2Client, self).__init__()
|
||||
self.alarm_definitions = mock.Mock(
|
||||
spec=ad.AlarmDefinitionsManager)
|
||||
|
||||
|
||||
class TestAlarmDefinitionShellV2(base.BaseTestCase):
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_should_update(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
ad_id = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
ad_name = 'alarm_name'
|
||||
ad_desc = 'test_alarm_definition'
|
||||
ad_expr = 'avg(Test_Metric_1)>=10'
|
||||
ad_action_id = '16012650-0b62-4692-9103-2d04fe81cc93'
|
||||
ad_action_enabled = 'True'
|
||||
ad_match_by = 'hostname'
|
||||
ad_severity = 'CRITICAL'
|
||||
|
||||
raw_args = [
|
||||
ad_id, ad_name, ad_desc, ad_expr,
|
||||
ad_action_id, ad_action_id, ad_action_id, ad_action_enabled,
|
||||
ad_match_by, ad_severity
|
||||
]
|
||||
name, cmd_clazz = migr.create_command_class(
|
||||
'do_alarm_definition_update',
|
||||
shell
|
||||
)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
c.alarm_definitions.update.assert_called_once_with(
|
||||
actions_enabled=True,
|
||||
alarm_actions=[ad_action_id],
|
||||
alarm_id=ad_id,
|
||||
description=ad_desc,
|
||||
expression=ad_expr,
|
||||
match_by=[ad_match_by],
|
||||
name=ad_name,
|
||||
ok_actions=[ad_action_id],
|
||||
severity=ad_severity,
|
||||
undetermined_actions=[ad_action_id]
|
||||
)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_should_patch_name(self, mc):
|
||||
ad_id = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
ad_name = 'patch_name'
|
||||
|
||||
raw_args = '{0} --name {1}'.format(ad_id, ad_name).split(' ')
|
||||
self._patch_test(mc, raw_args, alarm_id=ad_id, name=ad_name)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_should_patch_actions(self, mc):
|
||||
ad_id = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
ad_action_id = '16012650-0b62-4692-9103-2d04fe81cc93'
|
||||
|
||||
actions = ['alarm-actions', 'ok-actions',
|
||||
'undetermined-actions']
|
||||
for action in actions:
|
||||
raw_args = ('{0} --{1} {2}'.format(ad_id, action, ad_action_id)
|
||||
.split(' '))
|
||||
self._patch_test(mc, raw_args, **{
|
||||
'alarm_id': ad_id,
|
||||
action.replace('-', '_'): [ad_action_id]
|
||||
})
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_should_patch_severity(self, mc):
|
||||
ad_id = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
|
||||
severity_types = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
|
||||
for st in severity_types:
|
||||
raw_args = ('{0} --severity {1}'.format(ad_id, st)
|
||||
.split(' '))
|
||||
self._patch_test(mc, raw_args, alarm_id=ad_id, severity=st)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_should_not_patch_unknown_severity(self, mc):
|
||||
ad_id = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
|
||||
st = 'foo'
|
||||
raw_args = ('{0} --severity {1}'.format(ad_id, st)
|
||||
.split(' '))
|
||||
self._patch_test(mc, raw_args, called=False)
|
||||
|
||||
@staticmethod
|
||||
def _patch_test(mc, args, called=True, **kwargs):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
name, cmd_clazz = migr.create_command_class(
|
||||
'do_alarm_definition_patch',
|
||||
shell
|
||||
)
|
||||
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
if called:
|
||||
c.alarm_definitions.patch.assert_called_once_with(**kwargs)
|
||||
else:
|
||||
c.alarm_definitions.patch.assert_not_called()
|
|
@ -0,0 +1,85 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from monascaclient.osc import migration as migr
|
||||
from monascaclient.v2_0 import metrics
|
||||
from monascaclient.v2_0 import shell
|
||||
|
||||
|
||||
class FakeV2Client(object):
|
||||
def __init__(self):
|
||||
super(FakeV2Client, self).__init__()
|
||||
self.metrics = mock.Mock(spec=metrics.MetricsManager)
|
||||
|
||||
|
||||
class TestMetricsShellV2(base.BaseTestCase):
|
||||
|
||||
def test_bad_metrics(self):
|
||||
raw_args_list = [
|
||||
['metric1'],
|
||||
['123'],
|
||||
['']
|
||||
]
|
||||
name, cmd_clazz = migr.create_command_class('do_metric_create',
|
||||
shell)
|
||||
|
||||
for raw_args in raw_args_list:
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
parser = cmd.get_parser(name)
|
||||
self.assertRaises(SystemExit, parser.parse_args, raw_args)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_metric_create(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
raw_args = 'metric1 123 --time 1395691090'.split(' ')
|
||||
name, cmd_clazz = migr.create_command_class('do_metric_create',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
data = {'timestamp': 1395691090,
|
||||
'name': 'metric1',
|
||||
'value': 123.0}
|
||||
|
||||
c.metrics.create.assert_called_once_with(**data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_metric_create_with_project_id(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
project_id = 'd48e63e76a5c4e05ba26a1185f31d4aa'
|
||||
raw_args = ('metric1 123 --time 1395691090 --project-id %s'
|
||||
% project_id).split(' ')
|
||||
name, cmd_clazz = migr.create_command_class('do_metric_create',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
data = {'timestamp': 1395691090,
|
||||
'name': 'metric1',
|
||||
'tenant_id': project_id,
|
||||
'value': 123.0}
|
||||
|
||||
c.metrics.create.assert_called_once_with(**data)
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from monascaclient.osc import migration as migr
|
||||
from monascaclient.v2_0 import notificationtypes
|
||||
from monascaclient.v2_0 import shell
|
||||
|
||||
|
||||
class FakeV2Client(object):
|
||||
|
||||
def __init__(self):
|
||||
super(FakeV2Client, self).__init__()
|
||||
self.notificationtypes = mock.Mock(
|
||||
spec=notificationtypes.NotificationTypesManager)
|
||||
|
||||
|
||||
class TestNotificationsTypesShellV2(base.BaseTestCase):
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_notification_types_list(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
c.notificationtypes.list.return_value = [
|
||||
{"type": "WEBHOOK"},
|
||||
{"type": "EMAIL"},
|
||||
{"type": "PAGERDUTY"}
|
||||
]
|
||||
|
||||
raw_args = []
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_type_list',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
c.notificationtypes.list.assert_called_once()
|
|
@ -0,0 +1,160 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from monascaclient.osc import migration as migr
|
||||
from monascaclient.v2_0 import notifications
|
||||
from monascaclient.v2_0 import shell
|
||||
|
||||
|
||||
class FakeV2Client(object):
|
||||
|
||||
def __init__(self):
|
||||
super(FakeV2Client, self).__init__()
|
||||
self.notifications = mock.Mock(spec=notifications.NotificationsManager)
|
||||
|
||||
|
||||
class TestNotificationsShellV2(base.BaseTestCase):
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_notification_create_email(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
raw_args = ['email1', 'EMAIL', 'john.doe@hp.com']
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_create',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
data = {'name': 'email1',
|
||||
'type': 'EMAIL',
|
||||
'address': 'john.doe@hp.com'}
|
||||
|
||||
c.notifications.create.assert_called_once_with(**data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_notification_create_webhook(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
raw_args = ['mypost', 'WEBHOOK', 'http://localhost:8080']
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_create',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
data = {'name': 'mypost',
|
||||
'type': 'WEBHOOK',
|
||||
'address': 'http://localhost:8080'}
|
||||
|
||||
c.notifications.create.assert_called_once_with(**data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_good_notifications_patch(self, mc):
|
||||
args = '--type EMAIL --address john.doe@hpe.com --period 0'
|
||||
data = {'type': 'EMAIL',
|
||||
'address': 'john.doe@hpe.com',
|
||||
'period': 0}
|
||||
self._patch_test(mc, args, data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_good_notifications_patch_just_name(self, mc):
|
||||
name = 'fred'
|
||||
args = '--name ' + name
|
||||
data = {'name': name}
|
||||
self._patch_test(mc, args, data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_good_notifications_patch_just_address(self, mc):
|
||||
address = 'fred@fl.com'
|
||||
args = '--address ' + address
|
||||
data = {'address': address}
|
||||
self._patch_test(mc, args, data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_good_notifications_patch_just_period(self, mc):
|
||||
period = 0
|
||||
args = '--period ' + str(period)
|
||||
data = {'period': period}
|
||||
self._patch_test(mc, args, data)
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_bad_notifications_patch(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
raw_args = ('{0} --type EMAIL --address john.doe@hpe.com '
|
||||
'--period 60').format(id_str).split(' ')
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_patch',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
c.notifications.patch.assert_not_called()
|
||||
|
||||
@mock.patch('monascaclient.osc.migration.make_client')
|
||||
def test_good_notifications_update(self, mc):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96491'
|
||||
raw_args = ('{0} notification_updated_name '
|
||||
'EMAIL john.doe@hpe.com 0').format(id_str).split(' ')
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_update',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
|
||||
cmd.run(parsed_args)
|
||||
|
||||
data = {
|
||||
'name': 'notification_updated_name',
|
||||
'type': 'EMAIL',
|
||||
'address': 'john.doe@hpe.com',
|
||||
'period': 0,
|
||||
'notification_id': id_str
|
||||
}
|
||||
|
||||
c.notifications.update.assert_called_once_with(**data)
|
||||
|
||||
@staticmethod
|
||||
def _patch_test(mc, args, data):
|
||||
mc.return_value = c = FakeV2Client()
|
||||
|
||||
id_str = '0495340b-58fd-4e1c-932b-5e6f9cc96490'
|
||||
raw_args = '{0} {1}'.format(id_str, args).split(' ')
|
||||
name, cmd_clazz = migr.create_command_class('do_notification_patch',
|
||||
shell)
|
||||
cmd = cmd_clazz(mock.Mock(), mock.Mock())
|
||||
|
||||
parser = cmd.get_parser(name)
|
||||
parsed_args = parser.parse_args(raw_args)
|
||||
cmd.run(parsed_args)
|
||||
|
||||
# add notification_id to data
|
||||
data['notification_id'] = id_str
|
||||
|
||||
c.notifications.patch.assert_called_once_with(**data)
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,31 +14,27 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
from monascaclient.common import monasca_manager
|
||||
|
||||
|
||||
class AlarmDefinitions(base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<AlarmDefinitions %s>" % self._info
|
||||
|
||||
|
||||
class AlarmDefinitionsManager(monasca_manager.MonascaManager):
|
||||
resource_class = AlarmDefinitions
|
||||
base_url = '/alarm-definitions'
|
||||
|
||||
def create(self, **kwargs):
|
||||
"""Create an alarm definition."""
|
||||
resp, body = self.client.json_request('POST', self.base_url,
|
||||
data=kwargs)
|
||||
return body
|
||||
resp = self.client.create(url=self.base_url,
|
||||
json=kwargs)
|
||||
return resp
|
||||
|
||||
def get(self, **kwargs):
|
||||
"""Get the details for a specific alarm definition."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
return body
|
||||
|
||||
# NOTE(trebskit) should actually be find_one, but
|
||||
# monasca does not support expected response format
|
||||
|
||||
url = '%s/%s' % (self.base_url, kwargs['alarm_id'])
|
||||
resp = self.client.list(path=url)
|
||||
return resp
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of alarm definitions."""
|
||||
|
@ -46,19 +43,27 @@ class AlarmDefinitionsManager(monasca_manager.MonascaManager):
|
|||
def delete(self, **kwargs):
|
||||
"""Delete a specific alarm definition."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('DELETE', url_str)
|
||||
resp = self.client.delete(url_str)
|
||||
return resp
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update a specific alarm definition."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
del kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('PUT', url_str, data=kwargs)
|
||||
return body
|
||||
|
||||
resp = self.client.create(url=url_str,
|
||||
method='PUT',
|
||||
json=kwargs)
|
||||
|
||||
return resp
|
||||
|
||||
def patch(self, **kwargs):
|
||||
"""Patch a specific alarm definition."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
del kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('PATCH', url_str, data=kwargs)
|
||||
return body
|
||||
|
||||
resp = self.client.create(url=url_str,
|
||||
method='PATCH',
|
||||
json=kwargs)
|
||||
|
||||
return resp
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -15,25 +16,21 @@
|
|||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
from monascaclient.common import monasca_manager
|
||||
|
||||
|
||||
class Alarms(base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Alarms %s>" % self._info
|
||||
|
||||
|
||||
class AlarmsManager(monasca_manager.MonascaManager):
|
||||
resource_class = Alarms
|
||||
base_url = '/alarms'
|
||||
|
||||
def get(self, **kwargs):
|
||||
"""Get the details for a specific alarm."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
return body
|
||||
|
||||
# NOTE(trebskit) should actually be find_one, but
|
||||
# monasca does not support expected response format
|
||||
|
||||
url = '%s/%s' % (self.base_url, kwargs['alarm_id'])
|
||||
resp = self.client.list(path=url)
|
||||
return resp
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of alarms."""
|
||||
|
@ -42,34 +39,41 @@ class AlarmsManager(monasca_manager.MonascaManager):
|
|||
def delete(self, **kwargs):
|
||||
"""Delete a specific alarm."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('DELETE', url_str)
|
||||
resp = self.client.delete(url_str)
|
||||
return resp
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update a specific alarm."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
del kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('PUT', url_str,
|
||||
data=kwargs)
|
||||
|
||||
body = self.client.create(url=url_str,
|
||||
method='PUT',
|
||||
json=kwargs)
|
||||
|
||||
return body
|
||||
|
||||
def patch(self, **kwargs):
|
||||
"""Patch a specific alarm."""
|
||||
url_str = self.base_url + '/%s' % kwargs['alarm_id']
|
||||
del kwargs['alarm_id']
|
||||
resp, body = self.client.json_request('PATCH', url_str,
|
||||
data=kwargs)
|
||||
return body
|
||||
|
||||
resp = self.client.create(url=url_str,
|
||||
method='PATCH',
|
||||
json=kwargs)
|
||||
|
||||
return resp
|
||||
|
||||
def count(self, **kwargs):
|
||||
url_str = self.base_url + '/count'
|
||||
if 'metric_dimensions' in kwargs:
|
||||
dimstr = self.get_dimensions_url_string(kwargs['metric_dimensions'])
|
||||
dimstr = self.get_dimensions_url_string(
|
||||
kwargs['metric_dimensions'])
|
||||
kwargs['metric_dimensions'] = dimstr
|
||||
|
||||
if kwargs:
|
||||
url_str = url_str + '?%s' % parse.urlencode(kwargs, True)
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
body = self.client.list(url_str)
|
||||
return body
|
||||
|
||||
def history(self, **kwargs):
|
||||
|
@ -78,8 +82,8 @@ class AlarmsManager(monasca_manager.MonascaManager):
|
|||
del kwargs['alarm_id']
|
||||
if kwargs:
|
||||
url_str = url_str + '?%s' % parse.urlencode(kwargs, True)
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
return body['elements'] if type(body) is dict else body
|
||||
resp = self.client.list(url_str)
|
||||
return resp['elements'] if type(resp) is dict else resp
|
||||
|
||||
def history_list(self, **kwargs):
|
||||
"""History list of alarm state."""
|
||||
|
@ -89,5 +93,5 @@ class AlarmsManager(monasca_manager.MonascaManager):
|
|||
kwargs['dimensions'] = dimstr
|
||||
if kwargs:
|
||||
url_str = url_str + '?%s' % parse.urlencode(kwargs, True)
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
return body['elements'] if type(body) is dict else body
|
||||
resp = self.client.list(url_str)
|
||||
return resp['elements'] if type(resp) is dict else resp
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -12,41 +13,28 @@
|
|||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import string
|
||||
|
||||
from monascaclient.common import http
|
||||
from monascaclient.v2_0 import alarm_definitions
|
||||
from osc_lib.api import api
|
||||
|
||||
from monascaclient.v2_0 import alarm_definitions as ad
|
||||
from monascaclient.v2_0 import alarms
|
||||
from monascaclient.v2_0 import metrics
|
||||
from monascaclient.v2_0 import notifications
|
||||
from monascaclient.v2_0 import notificationtypes
|
||||
from monascaclient.v2_0 import notificationtypes as nt
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
"""Client for the Monasca v2_0 API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the monasca api
|
||||
service.
|
||||
:param string token: Token for authentication.
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new http client for the monasca API."""
|
||||
if 'auth_url' in kwargs and 'v2.0' in kwargs['auth_url']:
|
||||
kwargs['auth_url'] = string.replace(
|
||||
kwargs['auth_url'], 'v2.0', 'v3')
|
||||
self.http_client = http.HTTPClient(*args, **kwargs)
|
||||
self.metrics = metrics.MetricsManager(self.http_client)
|
||||
self.notifications = notifications.NotificationsManager(
|
||||
self.http_client)
|
||||
self.alarms = alarms.AlarmsManager(self.http_client)
|
||||
self.alarm_definitions = alarm_definitions.AlarmDefinitionsManager(
|
||||
self.http_client)
|
||||
self.notificationtypes = notificationtypes.NotificationTypesManager(
|
||||
self.http_client)
|
||||
|
||||
def replace_token(self, token):
|
||||
self.http_client.replace_token(token)
|
||||
client = MonascaApi(*args, **kwargs)
|
||||
|
||||
self.metrics = metrics.MetricsManager(client)
|
||||
self.notifications = notifications.NotificationsManager(client)
|
||||
self.alarms = alarms.AlarmsManager(client)
|
||||
self.alarm_definitions = ad.AlarmDefinitionsManager(client)
|
||||
self.notificationtypes = nt.NotificationTypesManager(client)
|
||||
|
||||
|
||||
class MonascaApi(api.BaseAPI):
|
||||
SERVICE_TYPE = "monitoring"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,36 +14,22 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
from monascaclient.common import monasca_manager
|
||||
|
||||
|
||||
class Metrics(base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Metrics %s>" % self._info
|
||||
|
||||
|
||||
class MetricsManager(monasca_manager.MonascaManager):
|
||||
resource_class = Metrics
|
||||
base_url = '/metrics'
|
||||
|
||||
def create(self, **kwargs):
|
||||
local_kwargs = deepcopy(kwargs)
|
||||
"""Create a metric."""
|
||||
url_str = self.base_url
|
||||
if 'tenant_id' in local_kwargs:
|
||||
url_str = url_str + '?tenant_id=%s' % local_kwargs['tenant_id']
|
||||
del local_kwargs['tenant_id']
|
||||
if 'jsonbody' in local_kwargs:
|
||||
resp, body = self.client.json_request('POST', url_str,
|
||||
data=local_kwargs['jsonbody'])
|
||||
else:
|
||||
resp, body = self.client.json_request('POST', url_str,
|
||||
data=local_kwargs)
|
||||
return resp
|
||||
if 'tenant_id' in kwargs:
|
||||
url_str = url_str + '?tenant_id=%s' % kwargs['tenant_id']
|
||||
del kwargs['tenant_id']
|
||||
|
||||
data = kwargs['jsonbody'] if 'jsonbody' in kwargs else kwargs
|
||||
body = self.client.create(url=url_str, json=data)
|
||||
return body
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of metrics."""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,33 +14,27 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
from monascaclient.common import monasca_manager
|
||||
|
||||
|
||||
class Notifications(base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Notifications %s>" % self._info
|
||||
|
||||
|
||||
class NotificationsManager(monasca_manager.MonascaManager):
|
||||
resource_class = Notifications
|
||||
base_url = '/notification-methods'
|
||||
|
||||
def create(self, **kwargs):
|
||||
"""Create a notification."""
|
||||
resp, body = self.client.json_request('POST', self.base_url,
|
||||
data=kwargs)
|
||||
body = self.client.create(url=self.base_url,
|
||||
json=kwargs)
|
||||
return body
|
||||
|
||||
def get(self, **kwargs):
|
||||
"""Get the details for a specific notification."""
|
||||
url_str = self.base_url + '/%s' % kwargs['notification_id']
|
||||
resp, body = self.client.json_request('GET', url_str)
|
||||
return body
|
||||
|
||||
# NOTE(trebskit) should actually be find_one, but
|
||||
# monasca does not support expected response format
|
||||
|
||||
url = '%s/%s' % (self.base_url, kwargs['notification_id'])
|
||||
resp = self.client.list(path=url)
|
||||
return resp
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of notifications."""
|
||||
|
@ -47,24 +42,27 @@ class NotificationsManager(monasca_manager.MonascaManager):
|
|||
|
||||
def delete(self, **kwargs):
|
||||
"""Delete a notification."""
|
||||
url_str = self.base_url + '/%s' % kwargs['notification_id']
|
||||
resp, body = self.client.json_request('DELETE', url_str)
|
||||
url = self.base_url + '/%s' % kwargs['notification_id']
|
||||
resp = self.client.delete(url=url)
|
||||
return resp
|
||||
|
||||
def update(self, **kwargs):
|
||||
local_kwargs = deepcopy(kwargs)
|
||||
"""Update a notification."""
|
||||
url_str = self.base_url + '/%s' % local_kwargs['notification_id']
|
||||
del local_kwargs['notification_id']
|
||||
resp, body = self.client.json_request('PUT', url_str,
|
||||
data=local_kwargs)
|
||||
return body
|
||||
url_str = self.base_url + '/%s' % kwargs['notification_id']
|
||||
del kwargs['notification_id']
|
||||
|
||||
resp = self.client.create(url=url_str,
|
||||
method='PUT',
|
||||
json=kwargs)
|
||||
return resp
|
||||
|
||||
def patch(self, **kwargs):
|
||||
local_kwargs = deepcopy(kwargs)
|
||||
"""Patch a notification."""
|
||||
url_str = self.base_url + '/%s' % local_kwargs['notification_id']
|
||||
del local_kwargs['notification_id']
|
||||
resp, body = self.client.json_request('PATCH', url_str,
|
||||
data=local_kwargs)
|
||||
return body
|
||||
url_str = self.base_url + '/%s' % kwargs['notification_id']
|
||||
del kwargs['notification_id']
|
||||
|
||||
resp = self.client.create(url=url_str,
|
||||
method='PATCH',
|
||||
json=kwargs)
|
||||
|
||||
return resp
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,18 +14,10 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from monascaclient.apiclient import base
|
||||
from monascaclient.common import monasca_manager
|
||||
|
||||
|
||||
class NotificationTypes(base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<NotificationTypes %s>" % self._info
|
||||
|
||||
|
||||
class NotificationTypesManager(monasca_manager.MonascaManager):
|
||||
resource_class = NotificationTypes
|
||||
base_url = '/notification-methods/types'
|
||||
|
||||
def list(self, **kwargs):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -18,12 +19,12 @@ import json
|
|||
import numbers
|
||||
import time
|
||||
|
||||
from keystoneauth1 import exceptions as k_exc
|
||||
from osc_lib import exceptions as osc_exc
|
||||
|
||||
from monascaclient.common import utils
|
||||
import monascaclient.exc as exc
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from six.moves import xrange
|
||||
|
||||
# Alarm valid types
|
||||
severity_types = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
|
||||
|
@ -83,10 +84,8 @@ def do_metric_create(mc, args):
|
|||
fields['tenant_id'] = args.project_id
|
||||
try:
|
||||
mc.metrics.create(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print('Successfully created metric')
|
||||
|
||||
|
@ -96,14 +95,10 @@ def do_metric_create(mc, args):
|
|||
help='The raw JSON body in single quotes. See api doc.')
|
||||
def do_metric_create_raw(mc, args):
|
||||
'''Create metric from raw json body.'''
|
||||
fields = {}
|
||||
fields['jsonbody'] = args.jsonbody
|
||||
try:
|
||||
mc.metrics.create(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
mc.metrics.create(**args.jsonbody)
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print('Successfully created metric')
|
||||
|
||||
|
@ -136,17 +131,14 @@ def do_metric_name_list(mc, args):
|
|||
|
||||
try:
|
||||
metric_names = mc.metrics.list_names(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
|
||||
if args.json:
|
||||
print(utils.json_formatter(metric_names))
|
||||
return
|
||||
|
||||
if isinstance(metric_names, list):
|
||||
utils.print_list(metric_names, ['Name'], formatters={'Name': lambda x: x['name']})
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(metric_names))
|
||||
return
|
||||
if isinstance(metric_names, list):
|
||||
utils.print_list(metric_names, ['Name'], formatters={'Name': lambda x: x['name']})
|
||||
|
||||
|
||||
@utils.arg('--name', metavar='<METRIC_NAME>',
|
||||
|
@ -190,10 +182,8 @@ def do_metric_list(mc, args):
|
|||
|
||||
try:
|
||||
metric = mc.metrics.list(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(metric))
|
||||
|
@ -241,10 +231,8 @@ def do_dimension_name_list(mc, args):
|
|||
|
||||
try:
|
||||
dimension_names = mc.metrics.list_dimension_names(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
|
||||
if args.json:
|
||||
print(utils.json_formatter(dimension_names))
|
||||
|
@ -283,10 +271,8 @@ def do_dimension_value_list(mc, args):
|
|||
|
||||
try:
|
||||
dimension_values = mc.metrics.list_dimension_values(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
|
||||
if args.json:
|
||||
print(utils.json_formatter(dimension_values))
|
||||
|
@ -429,10 +415,8 @@ def do_measurement_list(mc, args):
|
|||
|
||||
try:
|
||||
metric = mc.metrics.list_measurements(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(metric))
|
||||
|
@ -496,8 +480,8 @@ def do_metric_statistics(mc, args):
|
|||
if stat.upper() not in statistic_types:
|
||||
errmsg = ('Invalid type, not one of [' +
|
||||
', '.join(statistic_types) + ']')
|
||||
print(errmsg)
|
||||
return
|
||||
raise osc_exc.CommandError(errmsg)
|
||||
|
||||
fields = {}
|
||||
fields['name'] = args.name
|
||||
if args.dimensions:
|
||||
|
@ -522,10 +506,8 @@ def do_metric_statistics(mc, args):
|
|||
|
||||
try:
|
||||
metric = mc.metrics.list_statistics(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(metric))
|
||||
|
@ -598,10 +580,8 @@ def do_notification_create(mc, args):
|
|||
fields['period'] = args.period
|
||||
try:
|
||||
notification = mc.notifications.create(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(notification, indent=2))
|
||||
|
||||
|
@ -614,10 +594,8 @@ def do_notification_show(mc, args):
|
|||
fields['notification_id'] = args.id
|
||||
try:
|
||||
notification = mc.notifications.get(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(notification))
|
||||
|
@ -665,9 +643,9 @@ def do_notification_list(mc, args):
|
|||
|
||||
try:
|
||||
notification = mc.notifications.list(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
except osc_exc.ClientException as he:
|
||||
raise osc_exc.CommandError(
|
||||
'ClientException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
else:
|
||||
if args.json:
|
||||
|
@ -701,10 +679,8 @@ def do_notification_delete(mc, args):
|
|||
fields['notification_id'] = args.id
|
||||
try:
|
||||
mc.notifications.delete(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print('Successfully deleted notification')
|
||||
|
||||
|
@ -732,10 +708,8 @@ def do_notification_update(mc, args):
|
|||
fields['period'] = args.period
|
||||
try:
|
||||
notification = mc.notifications.update(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(notification, indent=2))
|
||||
|
||||
|
@ -768,10 +742,8 @@ def do_notification_patch(mc, args):
|
|||
fields['period'] = args.period
|
||||
try:
|
||||
notification = mc.notifications.patch(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(notification, indent=2))
|
||||
|
||||
|
@ -832,10 +804,8 @@ def do_alarm_definition_create(mc, args):
|
|||
fields['match_by'] = args.match_by.split(',')
|
||||
try:
|
||||
alarm = mc.alarm_definitions.create(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(alarm, indent=2))
|
||||
|
||||
|
@ -848,10 +818,8 @@ def do_alarm_definition_show(mc, args):
|
|||
fields['alarm_id'] = args.id
|
||||
try:
|
||||
alarm = mc.alarm_definitions.get(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(alarm))
|
||||
|
@ -923,10 +891,8 @@ def do_alarm_definition_list(mc, args):
|
|||
fields['offset'] = args.offset
|
||||
try:
|
||||
alarm = mc.alarm_definitions.list(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(alarm))
|
||||
|
@ -957,10 +923,8 @@ def do_alarm_definition_delete(mc, args):
|
|||
fields['alarm_id'] = args.id
|
||||
try:
|
||||
mc.alarm_definitions.delete(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print('Successfully deleted alarm definition')
|
||||
|
||||
|
@ -1014,10 +978,8 @@ def do_alarm_definition_update(mc, args):
|
|||
fields['severity'] = args.severity
|
||||
try:
|
||||
alarm = mc.alarm_definitions.update(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(alarm, indent=2))
|
||||
|
||||
|
@ -1075,10 +1037,8 @@ def do_alarm_definition_patch(mc, args):
|
|||
fields['severity'] = args.severity
|
||||
try:
|
||||
alarm = mc.alarm_definitions.patch(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(alarm, indent=2))
|
||||
|
||||
|
@ -1159,10 +1119,8 @@ def do_alarm_list(mc, args):
|
|||
fields['sort_by'] = args.sort_by
|
||||
try:
|
||||
alarm = mc.alarms.list(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(alarm))
|
||||
|
@ -1202,10 +1160,8 @@ def do_alarm_show(mc, args):
|
|||
fields['alarm_id'] = args.id
|
||||
try:
|
||||
alarm = mc.alarms.get(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(alarm))
|
||||
|
@ -1243,10 +1199,8 @@ def do_alarm_update(mc, args):
|
|||
fields['link'] = args.link
|
||||
try:
|
||||
alarm = mc.alarms.update(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(alarm, indent=2))
|
||||
|
||||
|
@ -1276,10 +1230,8 @@ def do_alarm_patch(mc, args):
|
|||
fields['link'] = args.link
|
||||
try:
|
||||
alarm = mc.alarms.patch(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print(jsonutils.dumps(alarm, indent=2))
|
||||
|
||||
|
@ -1292,10 +1244,8 @@ def do_alarm_delete(mc, args):
|
|||
fields['alarm_id'] = args.id
|
||||
try:
|
||||
mc.alarms.delete(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
print('Successfully deleted alarm')
|
||||
|
||||
|
@ -1397,17 +1347,15 @@ def do_alarm_count(mc, args):
|
|||
fields['offset'] = args.offset
|
||||
try:
|
||||
counts = mc.alarms.count(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(counts))
|
||||
return
|
||||
cols = counts['columns']
|
||||
|
||||
utils.print_list(counts['counts'], [i for i in xrange(len(cols))],
|
||||
utils.print_list(counts['counts'], [i for i in range(len(cols))],
|
||||
field_labels=cols)
|
||||
|
||||
|
||||
|
@ -1427,10 +1375,8 @@ def do_alarm_history(mc, args):
|
|||
fields['offset'] = args.offset
|
||||
try:
|
||||
alarm = mc.alarms.history(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
output_alarm_history(args, alarm)
|
||||
|
||||
|
@ -1466,10 +1412,8 @@ def do_alarm_history_list(mc, args):
|
|||
fields['offset'] = args.offset
|
||||
try:
|
||||
alarm = mc.alarms.history_list(**fields)
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
output_alarm_history(args, alarm)
|
||||
|
||||
|
@ -1479,10 +1423,8 @@ def do_notification_type_list(mc, args):
|
|||
|
||||
try:
|
||||
notification_types = mc.notificationtypes.list()
|
||||
except exc.HTTPException as he:
|
||||
raise exc.CommandError(
|
||||
'HTTPException code=%s message=%s' %
|
||||
(he.code, he.message))
|
||||
except (osc_exc.ClientException, k_exc.HttpError) as he:
|
||||
raise osc_exc.CommandError('%s\n%s' % (he.message, he.details))
|
||||
else:
|
||||
if args.json:
|
||||
print(utils.json_formatter(notification_types))
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# 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 pbr import version
|
||||
|
||||
__all__ = ['version_info', 'version_string']
|
||||
|
||||
version_info = version.VersionInfo('python-monascaclient')
|
||||
version_string = version_info.version_string()
|
|
@ -1,15 +1,16 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
osc-lib>=1.5.1 # Apache-2.0
|
||||
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.20.0 # Apache-2.0
|
||||
|
||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
iso8601>=0.1.11 # MIT
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7.1 # BSD
|
||||
PyYAML>=3.10.0 # MIT
|
||||
requests>=2.14.2 # Apache-2.0
|
||||
|
||||
six>=1.9.0 # MIT
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
bandit>=1.1.0 # Apache-2.0
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
mox3!=0.19.0,>=0.7.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
os-testr>=0.8.0 # Apache-2.0
|
||||
requests-mock>=1.1 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
|
18
tox.ini
18
tox.ini
|
@ -9,22 +9,18 @@ setenv =
|
|||
BRANCH_NAME=master
|
||||
CLIENT_NAME=python-monascaclient
|
||||
OS_TEST_PATH=monascaclient/tests
|
||||
passenv = http_proxy
|
||||
HTTP_PROXY
|
||||
https_proxy
|
||||
HTTPS_PROXY
|
||||
no_proxy
|
||||
NO_PROXY
|
||||
passenv = *_proxy
|
||||
*_PROXY
|
||||
usedevelop = True
|
||||
install_command =
|
||||
{toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
whitelist_externals = bash
|
||||
find
|
||||
rm
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
rm -Rf .testrepository/times.dbm
|
||||
|
||||
[testenv:py27]
|
||||
basepython = python2.7
|
||||
|
@ -58,15 +54,21 @@ commands =
|
|||
oslo_debug_helper -t {env:OS_TEST_PATH} {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
skip_install = True
|
||||
usedevelop = False
|
||||
commands =
|
||||
{[testenv:flake8]commands}
|
||||
{[testenv:bandit]commands}
|
||||
|
||||
[testenv:flake8]
|
||||
skip_install = True
|
||||
usedevelop = False
|
||||
commands =
|
||||
flake8 monascaclient
|
||||
|
||||
[testenv:bandit]
|
||||
skip_install = True
|
||||
usedevelop = False
|
||||
commands =
|
||||
bandit -r monascaclient -n5 -x {env:OS_TEST_PATH}
|
||||
|
||||
|
|
Loading…
Reference in New Issue