Rewrite of the client
The client has been completely rewritten in order to use cliff. The code should be easier to maintain: authentication is now entirely handled by keystoneauth, CloudKitty's client and CK's OSC plugin use the exact same classes (no code duplication). New features for users: * Client-side CSV report generation: It is possible for users to generate CSV reports with the new client. There is a default format, but reports may also be configured through a yaml config file. (see documentation) * The documentation has been improved. (A few examples on how to use the python library + complete API bindings and CLI reference). * It is now possible to use the client without Keystone authentication (this requires that CK's API is configured to use the noauth auth strategy). * Various features are brought by cliff: completion, command output formatting (table, shell, yaml, json...). New features for developpers: * Python 2.7/3.5 compatible 'python-cloudkittyclient' module. * Integration tests (for 'openstack rating' and 'cloudkitty') have been added. These allow to create gate jobs running against a CK devstack * Tests are now ran with stestr instead of testr, which allows a better control over execution. * The dependency list has been reduced and upper constraints have been set. Change-Id: I7c6afa46138d499b37b8be3d049b23ab5302a928 Task: 6589 Story: 2001614
This commit is contained in:
parent
f490bd0a84
commit
d070f6a68c
|
@ -7,6 +7,7 @@ build
|
|||
.tox
|
||||
cover
|
||||
.testrepository
|
||||
.stestr
|
||||
.venv
|
||||
dist
|
||||
*.egg
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[DEFAULT]
|
||||
test_path=./cloudkittyclient/tests/unit
|
||||
top_dir=./
|
|
@ -1,4 +0,0 @@
|
|||
[DEFAULT]
|
||||
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkittyclient/tests $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
|
@ -0,0 +1,37 @@
|
|||
- job:
|
||||
name: cloudkittyclient-devstack-functional
|
||||
parent: devstack
|
||||
description: |
|
||||
Job for cloudkittyclient functional tests
|
||||
pre-run: playbooks/cloudkittyclient-devstack-functional/pre.yaml
|
||||
run: playbooks/cloudkittyclient-devstack-functional/run.yaml
|
||||
post-run: playbooks/cloudkittyclient-devstack-functional/post.yaml
|
||||
required-projects:
|
||||
- name: openstack/cloudkitty
|
||||
- name: openstack/python-cloudkittyclient
|
||||
roles:
|
||||
- zuul: openstack-infra/devstack
|
||||
timeout: 5400
|
||||
irrelevant-files:
|
||||
- ^.*\.rst$
|
||||
- ^doc/.*$
|
||||
- ^releasenotes/.*$
|
||||
vars:
|
||||
devstack_plugins:
|
||||
cloudkitty: https://git.openstack.org/openstack/cloudkitty
|
||||
devstack_services:
|
||||
ck-api: true
|
||||
horizon: false
|
||||
tox_install_siblings: false
|
||||
zuul_work_dir: src/git.openstack.org/openstack/python-cloudkittyclient
|
||||
tox_envlist: functional
|
||||
|
||||
- project:
|
||||
check:
|
||||
jobs:
|
||||
- cloudkittyclient-devstack-functional:
|
||||
voting: true
|
||||
gate:
|
||||
jobs:
|
||||
- cloudkittyclient-devstack-functional:
|
||||
voting: true
|
14
README.rst
14
README.rst
|
@ -2,16 +2,8 @@
|
|||
CloudKittyClient
|
||||
================
|
||||
|
||||
.. image:: http://governance.openstack.org/badges/python-cloudkittyclient.svg
|
||||
:target: http://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
:version: 1.1.0
|
||||
:Wiki: `CloudKitty Wiki`_
|
||||
:IRC: #cloudkitty @ freenode
|
||||
|
||||
|
||||
.. _CloudKitty Wiki: https://wiki.openstack.org/wiki/CloudKitty
|
||||
|
||||
.. image:: https://governance.openstack.org/badges/python-cloudkittyclient.svg
|
||||
:target: https://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
This is a client for CloudKitty_. It provides a Python api (the
|
||||
``cloudkittyclient`` module), a command-line script (``cloudkitty``), and an
|
||||
|
@ -21,4 +13,4 @@ The client is available on PyPi_.
|
|||
|
||||
.. _OpenStack Client: https://docs.openstack.org/python-openstackclient/latest/
|
||||
.. _CloudKitty: https://github.com/openstack/cloudkitty
|
||||
.. _PyPi: https://pypi.python.org/pypi/python-cloudkittyclient
|
||||
.. _PyPi: https://pypi.org/project/python-cloudkittyclient/
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# 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.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-cloudkittyclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "cloudkittyclient.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin."""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins."""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication."""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
|
@ -1,535 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2012 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-cloudkittyclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
|
||||
# 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 cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
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=None, 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'. If response_key is None - all response body
|
||||
will be used.
|
||||
: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] if response_key is not None else body
|
||||
# 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=None):
|
||||
"""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'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
body = self.client.get(url).json()
|
||||
data = body[response_key] if response_key is not None else body
|
||||
return self.resource_class(self, data, 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=None, 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., 'server'. If response_key is None - all response body
|
||||
will be used.
|
||||
: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()
|
||||
data = body[response_key] if response_key is not None else body
|
||||
if return_raw:
|
||||
return data
|
||||
return self.resource_class(self, data)
|
||||
|
||||
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'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
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'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
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 %(name)s matching %(args)s.") % {
|
||||
'name': self.resource_class.__name__,
|
||||
'args': 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 %(name)s matching %(args)s.") % {
|
||||
'name': self.resource_class.__name__,
|
||||
'args': kwargs
|
||||
}
|
||||
raise exceptions.NotFound(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.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
return strutils.to_slug(name)
|
||||
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):
|
||||
"""Support for lazy loading details.
|
||||
|
||||
Some clients, such as novaclient have the option to lazy load the
|
||||
details, details which can be loaded with this function.
|
||||
"""
|
||||
# set_loaded() first ... so if we have to bail, we know we tried.
|
||||
self.set_loaded(True)
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
|
||||
new = self.manager.get(self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
if self.manager.client.last_request_id:
|
||||
self._add_details(
|
||||
{'x_request_id': self.manager.client.last_request_id})
|
||||
|
||||
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)
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def set_loaded(self, val):
|
||||
self._loaded = val
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
|
@ -1,392 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "cloudkittyclient.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
self.last_request_id = None
|
||||
|
||||
def _safe_header(self, name, value):
|
||||
if name in SENSITIVE_HEADERS:
|
||||
# because in python3 byte string handling is ... ug
|
||||
v = value.encode('utf-8')
|
||||
h = hashlib.sha1(v)
|
||||
d = h.hexdigest()
|
||||
return encodeutils.safe_decode(name), "{SHA1}%s" % d
|
||||
else:
|
||||
return (encodeutils.safe_decode(name),
|
||||
encodeutils.safe_decode(value))
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -g -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
if not kwargs.get('verify', self.verify):
|
||||
string_parts.insert(1, '--insecure')
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = ("-H '%s: %s'" %
|
||||
self._safe_header(element, kwargs['headers'][element]))
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = json.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", {})
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
self.last_request_id = resp.headers.get('x-openstack-request-id')
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
if self.auth_plugin.opts.get('token'):
|
||||
self.auth_plugin.opts['token'] = None
|
||||
if self.auth_plugin.opts.get('endpoint'):
|
||||
self.auth_plugin.opts['endpoint'] = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from openstack.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
@property
|
||||
def last_request_id(self):
|
||||
return self.http_client.last_request_id
|
||||
|
||||
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.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
|
@ -1,477 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-cloudkittyclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
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 ConnectionError(ClientException):
|
||||
"""Cannot connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionRefused(ConnectionError):
|
||||
"""Connection refused while trying to connect to API service."""
|
||||
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 an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %r") % 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: %r") % 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 HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
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 MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
req_id = response.headers.get("x-openstack-request-id")
|
||||
# NOTE(hdd) true for older versions of nova and cinder
|
||||
if not req_id:
|
||||
req_id = response.headers.get("x-compute-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):
|
||||
error = body.get(list(body)[0])
|
||||
if isinstance(error, dict):
|
||||
kwargs["message"] = (error.get("message") or
|
||||
error.get("faultstring"))
|
||||
kwargs["details"] = (error.get("details") or
|
||||
six.text_type(body))
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = getattr(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,189 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A fake server that "responds" to API methods with pre-canned responses.
|
||||
|
||||
All of these responses come from the spec, so if for some reason the spec's
|
||||
wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-cloudkittyclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from cloudkittyclient.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=None, optional=None):
|
||||
required = required or []
|
||||
optional = optional or []
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization."""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = json.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class FakeHTTPClient(client.HTTPClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.callstack = []
|
||||
self.fixtures = kwargs.pop("fixtures", None) or {}
|
||||
if not args and "auth_plugin" not in kwargs:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called."""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
msg = "Expected %s %s but no calls were made." % expected
|
||||
assert self.callstack, msg
|
||||
|
||||
msg = 'Expected %s %s; got %s %s' % (expected + called)
|
||||
assert expected == called, msg
|
||||
|
||||
if body is not None:
|
||||
if self.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], body))
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test."""
|
||||
expected = (method, url)
|
||||
|
||||
msg = "Expected %s %s but no calls were made." % expected
|
||||
assert self.callstack, msg
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
msg = 'Expected %s %s; got %s' % (method, url, self.callstack)
|
||||
assert found, msg
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
self.last_request_id = headers.get('x-openstack-request-id')
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
|
@ -1,96 +0,0 @@
|
|||
#
|
||||
# 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.
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-cloudkittyclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id, **find_args):
|
||||
"""Look for resource in a given manager.
|
||||
|
||||
Used as a helper for the _find_* methods.
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def _find_hypervisor(cs, hypervisor):
|
||||
#Get a hypervisor by name or ID.
|
||||
return cliutils.find_resource(cs.hypervisors, hypervisor)
|
||||
"""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
return manager.get(int(name_or_id))
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
if six.PY2:
|
||||
tmp_id = encodeutils.safe_encode(name_or_id)
|
||||
else:
|
||||
tmp_id = encodeutils.safe_decode(name_or_id)
|
||||
|
||||
if uuidutils.is_uuid_like(tmp_id):
|
||||
return manager.get(tmp_id)
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# for str id which is not uuid
|
||||
if getattr(manager, 'is_alphanum_id_allowed', False):
|
||||
try:
|
||||
return manager.get(name_or_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
try:
|
||||
try:
|
||||
return manager.find(human_id=name_or_id, **find_args)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
resource = getattr(manager, 'resource_class', None)
|
||||
name_attr = resource.NAME_ATTR if resource else 'name'
|
||||
kwargs = {name_attr: name_or_id}
|
||||
kwargs.update(find_args)
|
||||
return manager.find(**kwargs)
|
||||
except exceptions.NotFound:
|
||||
msg = _("No %(name)s with a name or "
|
||||
"ID of '%(name_or_id)s' exists.") % {
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id}
|
||||
raise exceptions.CommandError(msg)
|
||||
except exceptions.NoUniqueMatch:
|
||||
msg = _("Multiple %(name)s matches found for "
|
||||
"'%(name_or_id)s', use an ID to be more specific.") % {
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id}
|
||||
raise exceptions.CommandError(msg)
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 keystoneauth1 import loading
|
||||
from keystoneauth1 import plugin
|
||||
|
||||
|
||||
class CloudKittyNoAuthPlugin(plugin.BaseAuthPlugin):
|
||||
"""No authentication plugin for CloudKitty
|
||||
|
||||
"""
|
||||
def __init__(self, endpoint='http://localhost:8889', *args, **kwargs):
|
||||
super(CloudKittyNoAuthPlugin, self).__init__()
|
||||
self._endpoint = endpoint
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
return None
|
||||
|
||||
def get_endpoint(self, session, **kwargs):
|
||||
return self._endpoint
|
||||
|
||||
def get_headers(self, session, **kwargs):
|
||||
return {}
|
||||
|
||||
|
||||
class CloudKittyNoAuthLoader(loading.BaseLoader):
|
||||
plugin_class = CloudKittyNoAuthPlugin
|
||||
|
||||
def get_options(self):
|
||||
options = super(CloudKittyNoAuthLoader, self).get_options()
|
||||
options.extend([
|
||||
loading.Opt('endpoint', help='CloudKitty Endpoint',
|
||||
required=True, default='http://localhost:8889'),
|
||||
])
|
||||
return options
|
|
@ -1,3 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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
|
||||
|
@ -9,426 +12,12 @@
|
|||
# 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 contextlib
|
||||
import time
|
||||
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient.auth.identity import v2 as v2_auth
|
||||
from keystoneclient.auth.identity import v3 as v3_auth
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import exceptions as ks_exc
|
||||
from keystoneclient import session
|
||||
from oslo_utils import strutils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from cloudkittyclient.apiclient import auth
|
||||
from cloudkittyclient.apiclient import client
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.common import utils
|
||||
from cloudkittyclient import exc
|
||||
|
||||
|
||||
def _discover_auth_versions(session, auth_url):
|
||||
# discover the API versions the server is supporting based on the
|
||||
# given URL
|
||||
v2_auth_url = None
|
||||
v3_auth_url = None
|
||||
try:
|
||||
ks_discover = discover.Discover(session=session, auth_url=auth_url)
|
||||
v2_auth_url = ks_discover.url_for('2.0')
|
||||
v3_auth_url = ks_discover.url_for('3.0')
|
||||
except ks_exc.DiscoveryFailure:
|
||||
raise
|
||||
except exceptions.ClientException:
|
||||
# Identity service may not support discovery. In that case,
|
||||
# try to determine version from auth_url
|
||||
url_parts = urlparse.urlparse(auth_url)
|
||||
(scheme, netloc, path, params, query, fragment) = url_parts
|
||||
path = path.lower()
|
||||
if path.startswith('/v3'):
|
||||
v3_auth_url = auth_url
|
||||
elif path.startswith('/v2'):
|
||||
v2_auth_url = auth_url
|
||||
else:
|
||||
raise exc.CommandError('Unable to determine the Keystone '
|
||||
'version to authenticate with '
|
||||
'using the given auth_url.')
|
||||
return v2_auth_url, v3_auth_url
|
||||
|
||||
|
||||
def _get_keystone_session(**kwargs):
|
||||
# TODO(fabgia): the heavy lifting here should be really done by Keystone.
|
||||
# Unfortunately Keystone does not support a richer method to perform
|
||||
# discovery and return a single viable URL. A bug against Keystone has
|
||||
# been filed: https://bugs.launchpad.net/python-keystoneclient/+bug/1330677
|
||||
|
||||
# first create a Keystone session
|
||||
cacert = kwargs.pop('cacert', None)
|
||||
cert = kwargs.pop('cert', None)
|
||||
key = kwargs.pop('key', None)
|
||||
insecure = kwargs.pop('insecure', False)
|
||||
auth_url = kwargs.pop('auth_url', None)
|
||||
project_id = kwargs.pop('project_id', None)
|
||||
project_name = kwargs.pop('project_name', None)
|
||||
|
||||
if insecure:
|
||||
verify = False
|
||||
else:
|
||||
verify = cacert or True
|
||||
|
||||
if cert and key:
|
||||
# passing cert and key together is deprecated in favour of the
|
||||
# requests lib form of having the cert and key as a tuple
|
||||
cert = (cert, key)
|
||||
|
||||
# create the keystone client session
|
||||
ks_session = session.Session(verify=verify, cert=cert)
|
||||
v2_auth_url, v3_auth_url = _discover_auth_versions(ks_session, auth_url)
|
||||
|
||||
username = kwargs.pop('username', None)
|
||||
user_id = kwargs.pop('user_id', None)
|
||||
user_domain_name = kwargs.pop('user_domain_name', None)
|
||||
user_domain_id = kwargs.pop('user_domain_id', None)
|
||||
project_domain_name = kwargs.pop('project_domain_name', None)
|
||||
project_domain_id = kwargs.pop('project_domain_id', None)
|
||||
auth = None
|
||||
|
||||
use_domain = (user_domain_id or user_domain_name or
|
||||
project_domain_id or project_domain_name)
|
||||
use_v3 = v3_auth_url and (use_domain or (not v2_auth_url))
|
||||
use_v2 = v2_auth_url and not use_domain
|
||||
|
||||
if use_v3:
|
||||
# the auth_url as v3 specified
|
||||
# e.g. http://no.where:5000/v3
|
||||
# Keystone will return only v3 as viable option
|
||||
auth = v3_auth.Password(
|
||||
v3_auth_url,
|
||||
username=username,
|
||||
password=kwargs.pop('password', None),
|
||||
user_id=user_id,
|
||||
user_domain_name=user_domain_name,
|
||||
user_domain_id=user_domain_id,
|
||||
project_name=project_name,
|
||||
project_id=project_id,
|
||||
project_domain_name=project_domain_name,
|
||||
project_domain_id=project_domain_id)
|
||||
elif use_v2:
|
||||
# the auth_url as v2 specified
|
||||
# e.g. http://no.where:5000/v2.0
|
||||
# Keystone will return only v2 as viable option
|
||||
auth = v2_auth.Password(
|
||||
v2_auth_url,
|
||||
username,
|
||||
kwargs.pop('password', None),
|
||||
tenant_id=project_id,
|
||||
tenant_name=project_name)
|
||||
else:
|
||||
raise exc.CommandError('Unable to determine the Keystone version '
|
||||
'to authenticate with using the given '
|
||||
'auth_url.')
|
||||
|
||||
ks_session.auth = auth
|
||||
return ks_session
|
||||
|
||||
|
||||
def _get_endpoint(ks_session, **kwargs):
|
||||
"""Get an endpoint using the provided keystone session."""
|
||||
|
||||
# set service specific endpoint types
|
||||
endpoint_type = kwargs.get('endpoint_type') or 'publicURL'
|
||||
service_type = kwargs.get('service_type') or 'rating'
|
||||
|
||||
endpoint = ks_session.get_endpoint(service_type=service_type,
|
||||
interface=endpoint_type,
|
||||
region_name=kwargs.get('region_name'))
|
||||
|
||||
return endpoint
|
||||
|
||||
|
||||
class AuthPlugin(auth.BaseAuthPlugin):
|
||||
opt_names = ['tenant_id', 'region_name', 'auth_token',
|
||||
'service_type', 'endpoint_type', 'cacert',
|
||||
'auth_url', 'insecure', 'cert_file', 'key_file',
|
||||
'cert', 'key', 'tenant_name', 'project_name',
|
||||
'project_id', 'project_domain_id', 'project_domain_name',
|
||||
'user_id', 'user_domain_id', 'user_domain_name',
|
||||
'password', 'username', 'endpoint']
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.opt_names.extend(self.common_opt_names)
|
||||
super(AuthPlugin, self).__init__(auth_system, **kwargs)
|
||||
|
||||
def _do_authenticate(self, http_client):
|
||||
token = self.opts.get('token') or self.opts.get('auth_token')
|
||||
endpoint = self.opts.get('endpoint')
|
||||
if not (token and endpoint):
|
||||
project_id = (self.opts.get('project_id') or
|
||||
self.opts.get('tenant_id'))
|
||||
project_name = (self.opts.get('project_name') or
|
||||
self.opts.get('tenant_name'))
|
||||
ks_kwargs = {
|
||||
'username': self.opts.get('username'),
|
||||
'password': self.opts.get('password'),
|
||||
'user_id': self.opts.get('user_id'),
|
||||
'user_domain_id': self.opts.get('user_domain_id'),
|
||||
'user_domain_name': self.opts.get('user_domain_name'),
|
||||
'project_id': project_id,
|
||||
'project_name': project_name,
|
||||
'project_domain_name': self.opts.get('project_domain_name'),
|
||||
'project_domain_id': self.opts.get('project_domain_id'),
|
||||
'auth_url': self.opts.get('auth_url'),
|
||||
'cacert': self.opts.get('cacert'),
|
||||
'cert': self.opts.get('cert'),
|
||||
'key': self.opts.get('key'),
|
||||
'insecure': strutils.bool_from_string(
|
||||
self.opts.get('insecure')),
|
||||
'endpoint_type': self.opts.get('endpoint_type'),
|
||||
}
|
||||
|
||||
# retrieve session
|
||||
ks_session = _get_keystone_session(**ks_kwargs)
|
||||
token = lambda: ks_session.get_token()
|
||||
endpoint = (self.opts.get('endpoint') or
|
||||
_get_endpoint(ks_session, **ks_kwargs))
|
||||
self.opts['token'] = token
|
||||
self.opts['endpoint'] = endpoint
|
||||
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
token = self.opts.get('token')
|
||||
if callable(token):
|
||||
token = token()
|
||||
return token, self.opts.get('endpoint')
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
has_token = self.opts.get('token') or self.opts.get('auth_token')
|
||||
no_auth = has_token and self.opts.get('endpoint')
|
||||
has_project = (self.opts.get('project_id')
|
||||
or (self.opts.get('project_name')
|
||||
and (self.opts.get('user_domain_name')
|
||||
or self.opts.get('user_domain_id'))))
|
||||
has_tenant = self.opts.get('tenant_id') or self.opts.get('tenant_name')
|
||||
has_credential = (self.opts.get('username')
|
||||
and (has_project or has_tenant)
|
||||
and self.opts.get('password')
|
||||
and self.opts.get('auth_url'))
|
||||
missing = not (no_auth or has_credential)
|
||||
if missing:
|
||||
missing_opts = []
|
||||
opts = ['token', 'endpoint', 'username', 'password', 'auth_url',
|
||||
'tenant_id', 'tenant_name']
|
||||
for opt in opts:
|
||||
if not self.opts.get(opt):
|
||||
missing_opts.append(opt)
|
||||
raise exceptions.AuthPluginOptionsMissing(missing_opts)
|
||||
#
|
||||
import sys
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
kwargs['token'] = kwargs.get('token') or kwargs.get('auth_token')
|
||||
module = 'cloudkittyclient.v%s.client' % version
|
||||
__import__(module)
|
||||
client_class = getattr(sys.modules[module], 'Client')
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
|
||||
def _adjust_params(kwargs):
|
||||
timeout = kwargs.get('timeout')
|
||||
if timeout is not None:
|
||||
timeout = int(timeout)
|
||||
if timeout <= 0:
|
||||
timeout = None
|
||||
|
||||
insecure = strutils.bool_from_string(kwargs.get('insecure'))
|
||||
verify = kwargs.get('verify')
|
||||
if verify is None:
|
||||
if insecure:
|
||||
verify = False
|
||||
else:
|
||||
verify = kwargs.get('cacert') or True
|
||||
|
||||
cert = kwargs.get('cert_file')
|
||||
key = kwargs.get('key_file')
|
||||
if cert and key:
|
||||
cert = cert, key
|
||||
return {'verify': verify, 'cert': cert, 'timeout': timeout}
|
||||
|
||||
|
||||
def get_client(version, **kwargs):
|
||||
"""Get an authenticated client, based on the credentials in the kwargs.
|
||||
|
||||
:param api_version: the API version to use ('1')
|
||||
:param kwargs: keyword args containing credentials, either:
|
||||
|
||||
* session: a keystoneauth/keystoneclient session object
|
||||
* service_type: The default service_type for URL discovery
|
||||
* service_name: The default service_name for URL discovery
|
||||
* interface: The default interface for URL discovery
|
||||
(Default: public)
|
||||
* region_name: The default region_name for URL discovery
|
||||
* endpoint_override: Always use this endpoint URL for requests
|
||||
for this cloudkittyclient
|
||||
* auth: An auth plugin to use instead of the session one
|
||||
* user_agent: The User-Agent string to set
|
||||
(Default is python-cloudkittyclient)
|
||||
* connect_retries: the maximum number of retries that should be
|
||||
attempted for connection errors
|
||||
* logger: A logging object
|
||||
|
||||
or (DEPRECATED):
|
||||
|
||||
* os_token: pre-existing token to re-use
|
||||
* os_endpoint: Cloudkitty API endpoint
|
||||
|
||||
or (DEPRECATED):
|
||||
|
||||
* os_username: name of user
|
||||
* os_password: user's password
|
||||
* os_user_id: user's id
|
||||
* os_user_domain_id: the domain id of the user
|
||||
* os_user_domain_name: the domain name of the user
|
||||
* os_project_id: the user project id
|
||||
* os_tenant_id: V2 alternative to os_project_id
|
||||
* os_project_name: the user project name
|
||||
* os_tenant_name: V2 alternative to os_project_name
|
||||
* os_project_domain_name: domain name for the user project
|
||||
* os_project_domain_id: domain id for the user project
|
||||
* os_auth_url: endpoint to authenticate against
|
||||
* os_cert|os_cacert: path of CA TLS certificate
|
||||
* os_key: SSL private key
|
||||
* insecure: allow insecure SSL (no cert verification)
|
||||
"""
|
||||
endpoint = kwargs.get('os_endpoint')
|
||||
|
||||
cli_kwargs = {
|
||||
'username': kwargs.get('os_username'),
|
||||
'password': kwargs.get('os_password'),
|
||||
'tenant_id': (kwargs.get('os_tenant_id')
|
||||
or kwargs.get('os_project_id')),
|
||||
'tenant_name': (kwargs.get('os_tenant_name')
|
||||
or kwargs.get('os_project_name')),
|
||||
'auth_url': kwargs.get('os_auth_url'),
|
||||
'region_name': kwargs.get('os_region_name'),
|
||||
'service_type': kwargs.get('os_service_type'),
|
||||
'endpoint_type': kwargs.get('os_endpoint_type'),
|
||||
'cacert': kwargs.get('os_cacert'),
|
||||
'cert_file': kwargs.get('os_cert'),
|
||||
'key_file': kwargs.get('os_key'),
|
||||
'token': kwargs.get('os_token') or kwargs.get('os_auth_token'),
|
||||
'user_domain_name': kwargs.get('os_user_domain_name'),
|
||||
'user_domain_id': kwargs.get('os_user_domain_id'),
|
||||
'project_domain_name': kwargs.get('os_project_domain_name'),
|
||||
'project_domain_id': kwargs.get('os_project_domain_id'),
|
||||
}
|
||||
|
||||
cli_kwargs.update(kwargs)
|
||||
cli_kwargs.update(_adjust_params(cli_kwargs))
|
||||
|
||||
return Client(version, endpoint, **cli_kwargs)
|
||||
|
||||
|
||||
def get_auth_plugin(endpoint, **kwargs):
|
||||
auth_plugin = AuthPlugin(
|
||||
auth_url=kwargs.get('auth_url'),
|
||||
service_type=kwargs.get('service_type'),
|
||||
token=kwargs.get('token'),
|
||||
endpoint_type=kwargs.get('endpoint_type'),
|
||||
cacert=kwargs.get('cacert'),
|
||||
tenant_id=kwargs.get('project_id') or kwargs.get('tenant_id'),
|
||||
endpoint=endpoint,
|
||||
username=kwargs.get('username'),
|
||||
password=kwargs.get('password'),
|
||||
tenant_name=kwargs.get('tenant_name') or kwargs.get('project_name'),
|
||||
user_domain_name=kwargs.get('user_domain_name'),
|
||||
user_domain_id=kwargs.get('user_domain_id'),
|
||||
project_domain_name=kwargs.get('project_domain_name'),
|
||||
project_domain_id=kwargs.get('project_domain_id')
|
||||
)
|
||||
return auth_plugin
|
||||
|
||||
|
||||
LEGACY_OPTS = ('auth_plugin', 'auth_url', 'token', 'insecure', 'cacert',
|
||||
'tenant_id', 'project_id', 'username', 'password',
|
||||
'project_name', 'tenant_name',
|
||||
'user_domain_name', 'user_domain_id',
|
||||
'project_domain_name', 'project_domain_id',
|
||||
'key_file', 'cert_file', 'verify', 'timeout', 'cert')
|
||||
|
||||
|
||||
def construct_http_client(**kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
if kwargs.get('session') is not None:
|
||||
# Drop legacy options
|
||||
for opt in LEGACY_OPTS:
|
||||
kwargs.pop(opt, None)
|
||||
|
||||
return SessionClient(
|
||||
session=kwargs.pop('session'),
|
||||
service_type=kwargs.pop('service_type', 'rating') or 'rating',
|
||||
interface=kwargs.pop('interface', kwargs.pop('endpoint_type',
|
||||
'publicURL')),
|
||||
region_name=kwargs.pop('region_name', None),
|
||||
user_agent=kwargs.pop('user_agent', 'python-cloudkittyclient'),
|
||||
auth=kwargs.get('auth', None),
|
||||
timings=kwargs.pop('timings', None),
|
||||
**kwargs)
|
||||
else:
|
||||
return client.BaseClient(client.HTTPClient(
|
||||
auth_plugin=kwargs.get('auth_plugin'),
|
||||
region_name=kwargs.get('region_name'),
|
||||
endpoint_type=kwargs.get('endpoint_type'),
|
||||
original_ip=kwargs.get('original_ip'),
|
||||
verify=kwargs.get('verify'),
|
||||
cert=kwargs.get('cert'),
|
||||
timeout=kwargs.get('timeout'),
|
||||
timings=kwargs.get('timings'),
|
||||
keyring_saver=kwargs.get('keyring_saver'),
|
||||
debug=kwargs.get('debug'),
|
||||
user_agent=kwargs.get('user_agent'),
|
||||
http=kwargs.get('http')
|
||||
))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def record_time(times, enabled, *args):
|
||||
"""Record the time of a specific action.
|
||||
|
||||
:param times: A list of tuples holds time data.
|
||||
:type times: list
|
||||
:param enabled: Whether timing is enabled.
|
||||
:type enabled: bool
|
||||
:param args: Other data to be stored besides time data, these args
|
||||
will be joined to a string.
|
||||
"""
|
||||
if not enabled:
|
||||
yield
|
||||
else:
|
||||
start = time.time()
|
||||
yield
|
||||
end = time.time()
|
||||
times.append((' '.join(args), start, end))
|
||||
|
||||
|
||||
class SessionClient(adapter.LegacyJsonAdapter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.times = []
|
||||
self.timings = kwargs.pop('timings', False)
|
||||
super(SessionClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||
# NOTE(sileht): The standard call raises errors from
|
||||
# keystoneauth, where we need to raise the cloudkittyclient errors.
|
||||
raise_exc = kwargs.pop('raise_exc', True)
|
||||
with record_time(self.times, self.timings, method, url):
|
||||
resp, body = super(SessionClient, self).request(url,
|
||||
method,
|
||||
raise_exc=False,
|
||||
**kwargs)
|
||||
|
||||
if raise_exc and resp.status_code >= 400:
|
||||
raise exc.from_response(resp, body)
|
||||
return resp
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# Copyright 2015 Objectif Libre
|
||||
# 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.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from cloudkittyclient.apiclient import base
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""Extracts object ID.
|
||||
|
||||
Abstracts the common pattern of allowing both an object or an
|
||||
object's ID (UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
|
||||
class Manager(object):
|
||||
"""Managers interact with a particular type of API.
|
||||
|
||||
It works with samples, meters, alarms, etc. and provide CRUD operations for
|
||||
them.
|
||||
"""
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
"""Compatible with latest oslo-incubator.apiclient code."""
|
||||
return self.api
|
||||
|
||||
def _create(self, url, body):
|
||||
body = self.api.post(url, json=body).json()
|
||||
if body:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _list(self, url, response_key=None, obj_class=None, body=None,
|
||||
expect_single=False):
|
||||
resp = self.api.get(url)
|
||||
if not resp.content:
|
||||
raise exc.HTTPNotFound
|
||||
body = resp.json()
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
if response_key:
|
||||
try:
|
||||
data = body[response_key]
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
data = body
|
||||
if expect_single:
|
||||
data = [data]
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def _update(self, url, item, response_key=None):
|
||||
if not item.dirty_fields:
|
||||
return item
|
||||
item = self.api.put(url, json=item.dirty_fields).json()
|
||||
# PUT requests may not return a item
|
||||
if item:
|
||||
return self.resource_class(self, item)
|
||||
|
||||
def _delete(self, url):
|
||||
self.api.delete(url)
|
||||
|
||||
|
||||
class CrudManager(base.CrudManager):
|
||||
"""A CrudManager that automatically gets its base URL."""
|
||||
|
||||
base_url = None
|
||||
|
||||
def build_url(self, base_url=None, **kwargs):
|
||||
base_url = base_url or self.base_url
|
||||
return super(CrudManager, self).build_url(base_url, **kwargs)
|
||||
|
||||
def get(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._get(
|
||||
self.build_url(**kwargs))
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._post(
|
||||
self.build_url(**kwargs), kwargs)
|
||||
|
||||
def update(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
params = kwargs.copy()
|
||||
|
||||
return self._put(
|
||||
self.build_url(**kwargs), params)
|
||||
|
||||
def findall(self, base_url=None, **kwargs):
|
||||
"""Find multiple items 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 %(name)s matching %(args)s.") % {
|
||||
'name': self.resource_class.__name__,
|
||||
'args': kwargs
|
||||
}
|
||||
raise exc.HTTPNotFound(msg)
|
||||
return rl
|
||||
|
||||
|
||||
class Resource(base.Resource):
|
||||
"""A resource represents a particular instance of an object.
|
||||
|
||||
Resource might be tenant, user, etc.
|
||||
This is pretty much just a bag for attributes.
|
||||
|
||||
:param manager: Manager object
|
||||
:param info: dictionary representing resource attributes
|
||||
:param loaded: prevent lazy-loading if set to True
|
||||
"""
|
||||
|
||||
key = None
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
||||
|
||||
@property
|
||||
def dirty_fields(self):
|
||||
out = self.to_dict()
|
||||
for k, v in self._info.items():
|
||||
if self.__dict__[k] != v:
|
||||
out[k] = self.__dict__[k]
|
||||
return out
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
return self.manager.update(**self.dirty_fields)
|
||||
except AttributeError:
|
||||
raise exc.NotUpdatableError(self)
|
|
@ -1,271 +0,0 @@
|
|||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# W0603: Using the global statement
|
||||
# W0621: Redefining name %s from outer scope
|
||||
# pylint: disable=W0603,W0621
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import strutils
|
||||
import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
class MissingArgs(Exception):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
def validate_args(fn, *args, **kwargs):
|
||||
"""Check that the supplied args are sufficient for calling a function.
|
||||
|
||||
>>> validate_args(lambda a: None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): a
|
||||
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): b, d
|
||||
|
||||
:param fn: the function to check
|
||||
:param arg: the positional arguments supplied
|
||||
:param kwargs: the keyword arguments supplied
|
||||
"""
|
||||
argspec = inspect.getargspec(fn)
|
||||
|
||||
num_defaults = len(argspec.defaults or [])
|
||||
required_args = argspec.args[:len(argspec.args) - num_defaults]
|
||||
|
||||
def isbound(method):
|
||||
return getattr(method, '__self__', None) is not None
|
||||
|
||||
if isbound(fn):
|
||||
required_args.pop(0)
|
||||
|
||||
missing = [arg for arg in required_args if arg not in kwargs]
|
||||
missing = missing[len(args):]
|
||||
if missing:
|
||||
raise MissingArgs(missing)
|
||||
|
||||
|
||||
def arg(*args, **kwargs):
|
||||
"""Decorator for CLI args.
|
||||
|
||||
Example:
|
||||
|
||||
>>> @arg("name", help="Name of the new entity")
|
||||
... def entity_create(args):
|
||||
... pass
|
||||
"""
|
||||
def _decorator(func):
|
||||
add_arg(func, *args, **kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns the first environment variable set.
|
||||
|
||||
If all are empty, defaults to '' or keyword arg `default`.
|
||||
"""
|
||||
for arg in args:
|
||||
value = os.environ.get(arg)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def add_arg(func, *args, **kwargs):
|
||||
"""Bind CLI arguments to a shell.py `do_foo` function."""
|
||||
|
||||
if not hasattr(func, 'arguments'):
|
||||
func.arguments = []
|
||||
|
||||
# NOTE(sirp): avoid dups that can occur when the module is shared across
|
||||
# tests.
|
||||
if (args, kwargs) not in func.arguments:
|
||||
# Because of the semantics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.arguments.insert(0, (args, kwargs))
|
||||
|
||||
|
||||
def unauthenticated(func):
|
||||
"""Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> @unauthenticated
|
||||
... def mymethod(f):
|
||||
... pass
|
||||
"""
|
||||
func.unauthenticated = True
|
||||
return func
|
||||
|
||||
|
||||
def isunauthenticated(func):
|
||||
"""Checks if the function does not require authentication.
|
||||
|
||||
Mark such functions with the `@unauthenticated` decorator.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return getattr(func, 'unauthenticated', False)
|
||||
|
||||
|
||||
def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
mixed_case_fields=None, field_labels=None):
|
||||
"""Print a list or objects as a table, one row per object.
|
||||
|
||||
:param objs: iterable of :class:`Resource`
|
||||
:param fields: attributes that correspond to columns, in order
|
||||
:param formatters: `dict` of callables for field formatting
|
||||
:param sortby_index: index of the field for sorting table rows
|
||||
:param mixed_case_fields: fields corresponding to object attributes that
|
||||
have mixed case names (e.g., 'serverId')
|
||||
:param field_labels: Labels to use in the heading of the table, default to
|
||||
fields.
|
||||
"""
|
||||
formatters = formatters or {}
|
||||
mixed_case_fields = mixed_case_fields or []
|
||||
field_labels = field_labels or fields
|
||||
if len(field_labels) != len(fields):
|
||||
raise ValueError(_("Field labels list %(labels)s has different number "
|
||||
"of elements than fields list %(fields)s"),
|
||||
{'labels': field_labels, 'fields': fields})
|
||||
|
||||
if sortby_index is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = {'sortby': field_labels[sortby_index]}
|
||||
pt = prettytable.PrettyTable(field_labels)
|
||||
pt.align = 'l'
|
||||
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
if field in formatters:
|
||||
row.append(formatters[field](o))
|
||||
else:
|
||||
if field in mixed_case_fields:
|
||||
field_name = field.replace(' ', '_')
|
||||
else:
|
||||
field_name = field.lower().replace(' ', '_')
|
||||
data = getattr(o, field_name, '')
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
|
||||
|
||||
|
||||
def print_dict(dct, dict_property="Property", wrap=0):
|
||||
"""Print a `dict` as a table of two columns.
|
||||
|
||||
:param dct: `dict` to print
|
||||
:param dict_property: name of the first column
|
||||
:param wrap: wrapping for the second column
|
||||
"""
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'])
|
||||
pt.align = 'l'
|
||||
for k, v in six.iteritems(dct):
|
||||
# convert dict to str to check length
|
||||
if isinstance(v, dict):
|
||||
v = six.text_type(v)
|
||||
if wrap > 0:
|
||||
v = textwrap.fill(six.text_type(v), wrap)
|
||||
# if value has a newline, add in multiple rows
|
||||
# e.g. fault with stacktrace
|
||||
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||
lines = v.strip().split(r'\n')
|
||||
col1 = k
|
||||
for line in lines:
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
pt.add_row([k, v])
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string()).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string()))
|
||||
|
||||
|
||||
def get_password(max_password_prompts=3):
|
||||
"""Read password from TTY."""
|
||||
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
|
||||
pw = None
|
||||
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
|
||||
# Check for Ctrl-D
|
||||
try:
|
||||
for __ in moves.range(max_password_prompts):
|
||||
pw1 = getpass.getpass("OS Password: ")
|
||||
if verify:
|
||||
pw2 = getpass.getpass("Please verify: ")
|
||||
else:
|
||||
pw2 = pw1
|
||||
if pw1 == pw2 and pw1:
|
||||
pw = pw1
|
||||
break
|
||||
except EOFError:
|
||||
pass
|
||||
return pw
|
||||
|
||||
|
||||
def service_type(stype):
|
||||
"""Adds 'service_type' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@service_type('volume')
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
def inner(f):
|
||||
f.service_type = stype
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
def get_service_type(f):
|
||||
"""Retrieves service type from function."""
|
||||
return getattr(f, 'service_type', None)
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
|
@ -1,230 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import textwrap
|
||||
import uuid
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import timeutils
|
||||
import prettytable
|
||||
import six
|
||||
|
||||
from cloudkittyclient.common import cliutils
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient.i18n import _
|
||||
|
||||
|
||||
def iso2dt(iso_date):
|
||||
"""iso8601 format to datetime."""
|
||||
iso_dt = timeutils.parse_isotime(iso_date)
|
||||
trans_dt = timeutils.normalize_time(iso_dt)
|
||||
return trans_dt
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'cloudkittyclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
# Decorator for cli-args
|
||||
def arg(*args, **kwargs):
|
||||
def _decorator(func):
|
||||
if 'help' in kwargs:
|
||||
if 'default' in kwargs:
|
||||
kwargs['help'] += " Defaults to %s." % kwargs['default']
|
||||
required = kwargs.get('required', False)
|
||||
if required:
|
||||
kwargs['help'] += " required."
|
||||
elif 'default' not in kwargs:
|
||||
kwargs['help'] += "."
|
||||
|
||||
# Because of the sematics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def print_list(objs, fields, field_labels, formatters={}, sortby=0):
|
||||
|
||||
def _make_default_formatter(field):
|
||||
return lambda o: getattr(o, field, '')
|
||||
|
||||
new_formatters = {}
|
||||
for field, field_label in six.moves.zip(fields, field_labels):
|
||||
if field in formatters:
|
||||
new_formatters[field_label] = formatters[field]
|
||||
else:
|
||||
new_formatters[field_label] = _make_default_formatter(field)
|
||||
|
||||
cliutils.print_list(objs, field_labels,
|
||||
formatters=new_formatters,
|
||||
sortby_index=sortby)
|
||||
|
||||
|
||||
def nested_list_of_dict_formatter(field, column_names):
|
||||
# (TMaddox) Because the formatting scheme actually drops the whole object
|
||||
# into the formatter, rather than just the specified field, we have to
|
||||
# extract it and then pass the value.
|
||||
return lambda o: format_nested_list_of_dict(getattr(o, field),
|
||||
column_names)
|
||||
|
||||
|
||||
def format_nested_list_of_dict(l, column_names):
|
||||
pt = prettytable.PrettyTable(caching=False, print_empty=False,
|
||||
header=True, hrules=prettytable.FRAME,
|
||||
field_names=column_names)
|
||||
for d in l:
|
||||
pt.add_row(list(map(lambda k: d[k], column_names)))
|
||||
return pt.get_string()
|
||||
|
||||
|
||||
def print_dict(d, dict_property="Property", wrap=0):
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'], print_empty=False)
|
||||
pt.align = 'l'
|
||||
for k, v in sorted(six.iteritems(d)):
|
||||
# convert dict to str to check length
|
||||
if isinstance(v, dict):
|
||||
v = jsonutils.dumps(v)
|
||||
# if value has a newline, add in multiple rows
|
||||
# e.g. fault with stacktrace
|
||||
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||
lines = v.strip().split(r'\n')
|
||||
col1 = k
|
||||
for line in lines:
|
||||
if wrap > 0:
|
||||
line = textwrap.fill(str(line), wrap)
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
if wrap > 0:
|
||||
v = textwrap.fill(str(v), wrap)
|
||||
pt.add_row([k, v])
|
||||
encoded = encodeutils.safe_encode(pt.get_string())
|
||||
# FIXME(gordc): https://bugs.launchpad.net/oslo-incubator/+bug/1370710
|
||||
if six.PY3:
|
||||
encoded = encoded.decode()
|
||||
print(encoded)
|
||||
|
||||
|
||||
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.HTTPNotFound:
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
uuid.UUID(str(name_or_id))
|
||||
return manager.get(name_or_id)
|
||||
except (ValueError, exc.HTTPNotFound):
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
return manager.find(name=name_or_id)
|
||||
except exc.HTTPNotFound:
|
||||
msg = _("No %(name)s with a name or ID of '%(id)s' exists.") % {
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"id": name_or_id
|
||||
}
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
|
||||
def args_array_to_dict(kwargs, key_to_convert):
|
||||
values_to_convert = kwargs.get(key_to_convert)
|
||||
if values_to_convert:
|
||||
try:
|
||||
kwargs[key_to_convert] = dict(v.split("=", 1)
|
||||
for v in values_to_convert)
|
||||
except ValueError:
|
||||
msg = _("%(key)s must be a list of key=value "
|
||||
"not '%(value)s'") % {
|
||||
"key": key_to_convert,
|
||||
"value": values_to_convert
|
||||
}
|
||||
raise exc.CommandError(msg)
|
||||
return kwargs
|
||||
|
||||
|
||||
def args_array_to_list_of_dicts(kwargs, key_to_convert):
|
||||
"""Converts ['a=1;b=2','c=3;d=4'] to [{a:1,b:2},{c:3,d:4}]."""
|
||||
values_to_convert = kwargs.get(key_to_convert)
|
||||
if values_to_convert:
|
||||
try:
|
||||
kwargs[key_to_convert] = []
|
||||
for lst in values_to_convert:
|
||||
pairs = lst.split(";")
|
||||
dct = dict()
|
||||
for pair in pairs:
|
||||
kv = pair.split("=", 1)
|
||||
dct[kv[0]] = kv[1].strip(" \"'") # strip spaces and quotes
|
||||
kwargs[key_to_convert].append(dct)
|
||||
except Exception:
|
||||
msg = _("%(key)s must be a list of "
|
||||
"key1=value1;key2=value2;... not '%(value)s'") % {
|
||||
"key": key_to_convert,
|
||||
"value": values_to_convert
|
||||
}
|
||||
raise exc.CommandError(msg)
|
||||
return kwargs
|
||||
|
||||
|
||||
def key_with_slash_to_nested_dict(kwargs):
|
||||
nested_kwargs = {}
|
||||
for k in list(kwargs):
|
||||
keys = k.split('/', 1)
|
||||
if len(keys) == 2:
|
||||
nested_kwargs.setdefault(keys[0], {})[keys[1]] = kwargs[k]
|
||||
del kwargs[k]
|
||||
kwargs.update(nested_kwargs)
|
||||
return kwargs
|
||||
|
||||
|
||||
def merge_nested_dict(dest, source, depth=0):
|
||||
for (key, value) in six.iteritems(source):
|
||||
if isinstance(value, dict) and depth:
|
||||
merge_nested_dict(dest[key], value,
|
||||
depth=(depth - 1))
|
||||
else:
|
||||
dest[key] = value
|
||||
|
||||
|
||||
def ts2dt(timestamp):
|
||||
"""timestamp to datetime format."""
|
||||
if not isinstance(timestamp, float):
|
||||
timestamp = float(timestamp)
|
||||
return datetime.datetime.utcfromtimestamp(timestamp)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
|
@ -31,10 +31,18 @@ class InvalidEndpoint(BaseException):
|
|||
"""The provided endpoint is invalid."""
|
||||
|
||||
|
||||
class ArgumentRequired(BaseException):
|
||||
"""A required argument was not provided."""
|
||||
|
||||
|
||||
class CommunicationError(BaseException):
|
||||
"""Unable to communicate with server."""
|
||||
|
||||
|
||||
class InvalidArgumentError(BaseException):
|
||||
"""Exception raised when a provided argument is invalid"""
|
||||
|
||||
|
||||
class NotUpdatableError(BaseException):
|
||||
"""This Resource is not updatable."""
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 csv
|
||||
|
||||
from cliff.formatters import base
|
||||
import jsonpath_rw_ext as jp
|
||||
from oslo_log import log
|
||||
import yaml
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DataframeToCsvFormatter(base.ListFormatter):
|
||||
"""Cliff formatter allowing to customize CSV report content."""
|
||||
|
||||
default_config = [
|
||||
('Begin', '$.begin'),
|
||||
('End', '$.end'),
|
||||
('Metric Type', '$.service'),
|
||||
('Qty', '$.volume'),
|
||||
('Cost', '$.rating'),
|
||||
('Project ID', '$.desc.project_id'),
|
||||
('Resource ID', '$.desc.resource_id'),
|
||||
('User ID', '$.desc.user_id'),
|
||||
]
|
||||
|
||||
def _load_config(self, filename):
|
||||
config = self.default_config
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'r') as fd:
|
||||
yml_config = yaml.safe_load(fd.read())
|
||||
if len(yml_config):
|
||||
config = [(list(item.keys())[0], list(item.values())[0])
|
||||
for item in yml_config]
|
||||
else:
|
||||
LOG.warning('Invalid config file {file}. Using default '
|
||||
'configuration'.format(file=filename))
|
||||
except (IOError, yaml.scanner.ScannerError) as err:
|
||||
LOG.warning('Error: {err}. Using default '
|
||||
'configuration'.format(err=err))
|
||||
self.parsers = {}
|
||||
for col, path in config:
|
||||
self.parsers[col] = jp.parse(path)
|
||||
return config
|
||||
|
||||
def add_argument_group(self, parser):
|
||||
group = parser.add_argument_group('dataframe-to-csv formatter')
|
||||
group.add_argument('--format-config-file',
|
||||
type=str, dest='format_config',
|
||||
help='Config file for the dict-to-csv formatter')
|
||||
|
||||
def _get_csv_row(self, config, json_item):
|
||||
row = {}
|
||||
for col, parser in self.parsers.items():
|
||||
items = parser.find(json_item)
|
||||
row[col] = items[0].value if items else ''
|
||||
return row
|
||||
|
||||
def emit_list(self, column_names, data, stdout, parsed_args):
|
||||
config = self._load_config(vars(parsed_args).get('format_config'))
|
||||
self.writer = csv.DictWriter(stdout,
|
||||
fieldnames=[elem[0] for elem in config])
|
||||
self.writer.writeheader()
|
||||
for dataframe in data:
|
||||
rating_data = dataframe[3]
|
||||
for item in rating_data:
|
||||
item['begin'] = dataframe[0]
|
||||
item['end'] = dataframe[1]
|
||||
row = self._get_csv_row(config, item)
|
||||
self.writer.writerow(row)
|
|
@ -1,24 +0,0 @@
|
|||
# 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.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n as i18n
|
||||
|
||||
_translators = i18n.TranslatorFactory(domain='cloudkittyclient')
|
||||
i18n.enable_lazy()
|
||||
|
||||
_ = _translators.primary
|
|
@ -12,7 +12,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient import client as ckclient
|
||||
from osc_lib import utils
|
||||
|
||||
DEFAULT_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_rating_api_version'
|
||||
|
@ -25,9 +25,12 @@ API_VERSIONS = {
|
|||
def make_client(instance):
|
||||
"""Returns a rating service client."""
|
||||
version = instance._api_version[API_NAME]
|
||||
version = int(version)
|
||||
auth_config = instance.get_configuration()['auth']
|
||||
return ckclient.get_client(version, **auth_config)
|
||||
ck_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
version,
|
||||
API_VERSIONS)
|
||||
instance.setup_auth()
|
||||
return ck_client(session=instance.session)
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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
|
||||
|
@ -11,321 +12,135 @@
|
|||
# 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 os
|
||||
from sys import argv
|
||||
|
||||
"""
|
||||
Command-line interface to the OpenStack Cloudkitty API.
|
||||
"""
|
||||
import cliff.app
|
||||
from cliff.commandmanager import CommandManager
|
||||
import os_client_config
|
||||
from oslo_log import log
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
import cloudkittyclient
|
||||
from cloudkittyclient import client as ckclient
|
||||
from cloudkittyclient.common import cliutils
|
||||
from cloudkittyclient.common import utils
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient.v1.collector import shell as collector_shell
|
||||
from cloudkittyclient.v1.report import shell as report_shell
|
||||
from cloudkittyclient.v1.storage import shell as storage_shell
|
||||
|
||||
SUBMODULES_NAMESPACE = 'cloudkitty.client.modules'
|
||||
from cloudkittyclient import client
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
def _positive_non_zero_int(argument_value):
|
||||
if argument_value is None:
|
||||
return None
|
||||
try:
|
||||
value = int(argument_value)
|
||||
except ValueError:
|
||||
msg = "%s must be an integer" % argument_value
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
if value <= 0:
|
||||
msg = "%s must be greater than 0" % argument_value
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
return value
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class CloudkittyShell(object):
|
||||
class CloudKittyShell(cliff.app.App):
|
||||
|
||||
def __init__(self):
|
||||
self.auth_plugin = ckclient.AuthPlugin()
|
||||
legacy_commands = [
|
||||
'module-list',
|
||||
'module-enable',
|
||||
'module-list',
|
||||
'module-enable',
|
||||
'module-disable',
|
||||
'module-set-priority',
|
||||
'info-config-get',
|
||||
'info-service-get',
|
||||
'total-get',
|
||||
'summary-get',
|
||||
'report-tenant-list',
|
||||
'collector-mapping-list',
|
||||
'collector-mapping-get',
|
||||
'collector-mapping-create',
|
||||
'collector-mapping-delete',
|
||||
'collector-state-get',
|
||||
'collector-state-enable',
|
||||
'collector-state-disable',
|
||||
'storage-dataframe-list',
|
||||
'hashmap-service-create',
|
||||
'hashmap-service-list',
|
||||
'hashmap-service-delete',
|
||||
'hashmap-field-create',
|
||||
'hashmap-field-list',
|
||||
'hashmap-field-delete',
|
||||
'hashmap-mapping-create',
|
||||
'hashmap-mapping-update',
|
||||
'hashmap-mapping-list',
|
||||
'hashmap-mapping-delete',
|
||||
'hashmap-group-create',
|
||||
'hashmap-group-list',
|
||||
'hashmap-group-delete',
|
||||
'hashmap-threshold-create'
|
||||
'hashmap-threshold-update'
|
||||
'hashmap-threshold-list',
|
||||
'hashmap-threshold-delete',
|
||||
'hashmap-threshold-get',
|
||||
'hashmap-threshold-group',
|
||||
'pyscripts-script-create',
|
||||
'pyscripts-script-list',
|
||||
'pyscripts-script-get',
|
||||
'pyscripts-script-get-data',
|
||||
'pyscripts-script-delete',
|
||||
'pyscripts-script-update',
|
||||
]
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='cloudkitty',
|
||||
description=__doc__.strip(),
|
||||
epilog='See "cloudkitty help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self.cloud_config = os_client_config.OpenStackConfig()
|
||||
super(CloudKittyShell, self).__init__(
|
||||
description='CloudKitty CLI client',
|
||||
version=utils.get_version(),
|
||||
command_manager=CommandManager('cloudkittyclient'),
|
||||
deferred_help=True,
|
||||
)
|
||||
self._client = None
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=cloudkittyclient.__version__)
|
||||
|
||||
parser.add_argument('-d', '--debug',
|
||||
default=bool(cliutils.env('CLOUDKITTYCLIENT_DEBUG')
|
||||
),
|
||||
action='store_true',
|
||||
help='Defaults to env[CLOUDKITTYCLIENT_DEBUG].')
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help="Print more verbose output.")
|
||||
|
||||
parser.add_argument('--timeout',
|
||||
default=600,
|
||||
type=_positive_non_zero_int,
|
||||
help='Number of seconds to wait for a response.')
|
||||
|
||||
parser.add_argument('--cloudkitty-url', metavar='<CLOUDKITTY_URL>',
|
||||
dest='os_endpoint',
|
||||
default=cliutils.env('CLOUDKITTY_URL'),
|
||||
help=("DEPRECATED, use --os-endpoint instead. "
|
||||
"Defaults to env[CLOUDKITTY_URL]."))
|
||||
|
||||
parser.add_argument('--cloudkitty_url',
|
||||
dest='os_endpoint',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--cloudkitty-api-version',
|
||||
default=cliutils.env(
|
||||
'CLOUDKITTY_API_VERSION', default='1'),
|
||||
help='Defaults to env[CLOUDKITTY_API_VERSION] '
|
||||
'or 1.')
|
||||
|
||||
parser.add_argument('--cloudkitty_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
self.auth_plugin.add_opts(parser)
|
||||
self.auth_plugin.add_common_opts(parser)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
self._find_actions(subparsers, submodule)
|
||||
self._find_actions(subparsers, collector_shell)
|
||||
self._find_actions(subparsers, report_shell)
|
||||
self._find_actions(subparsers, storage_shell)
|
||||
extensions = extension.ExtensionManager(
|
||||
SUBMODULES_NAMESPACE,
|
||||
)
|
||||
for ext in extensions:
|
||||
shell = ext.plugin.get_shell()
|
||||
self._find_actions(subparsers, shell)
|
||||
self._find_actions(subparsers, self)
|
||||
self._add_bash_completion_subparser(subparsers)
|
||||
return parser
|
||||
|
||||
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):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hypen-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', [])
|
||||
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def _setup_logging(debug):
|
||||
format = '%(levelname)s (%(module)s) %(message)s'
|
||||
if debug:
|
||||
logging.basicConfig(format=format, level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(format=format, level=logging.WARN)
|
||||
logging.getLogger('iso8601').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
||||
|
||||
def parse_args(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self.auth_plugin.parse_opts(options)
|
||||
self._setup_logging(options.debug)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.cloudkitty_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 options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Return parsed args
|
||||
return api_version, subcommand_parser.parse_args(argv)
|
||||
|
||||
@staticmethod
|
||||
def no_project_and_domain_set(args):
|
||||
return not (((args.os_project_id or (args.os_project_name and
|
||||
(args.os_project_domain_name or
|
||||
args.os_project_domain_id)))
|
||||
and (args.os_user_domain_name or args.os_user_domain_id))
|
||||
or (args.os_tenant_id or args.os_tenant_name))
|
||||
|
||||
def main(self, argv):
|
||||
parsed = self.parse_args(argv)
|
||||
if parsed == 0:
|
||||
return 0
|
||||
api_version, args = parsed
|
||||
|
||||
# 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 ((self.auth_plugin.opts.get('token')
|
||||
or self.auth_plugin.opts.get('auth_token'))
|
||||
and self.auth_plugin.opts['endpoint']):
|
||||
if not self.auth_plugin.opts['username']:
|
||||
raise exc.CommandError("You must provide a username via "
|
||||
"either --os-username or via "
|
||||
"env[OS_USERNAME]")
|
||||
|
||||
if not self.auth_plugin.opts['password']:
|
||||
raise exc.CommandError("You must provide a password via "
|
||||
"either --os-password or via "
|
||||
"env[OS_PASSWORD]")
|
||||
|
||||
if self.no_project_and_domain_set(args):
|
||||
# steer users towards Keystone V3 API
|
||||
raise exc.CommandError("You must provide a project_id via "
|
||||
"either --os-project-id or via "
|
||||
"env[OS_PROJECT_ID] and "
|
||||
"a domain_name via either "
|
||||
"--os-user-domain-name or via "
|
||||
"env[OS_USER_DOMAIN_NAME] or "
|
||||
"a domain_id via either "
|
||||
"--os-user-domain-id or via "
|
||||
"env[OS_USER_DOMAIN_ID]\n\n"
|
||||
"As an alternative to project_id, "
|
||||
"you can provide a project_name via "
|
||||
"either --os-project-name or via "
|
||||
"env[OS_PROJECT_NAME] and "
|
||||
"a project_domain_name via either "
|
||||
"--os-project-domain-name or via "
|
||||
"env[OS_PROJECT_DOMAIN_NAME] or "
|
||||
"a project_domain_id via either "
|
||||
"--os-project-domain-id or via "
|
||||
"env[OS_PROJECT_DOMAIN_ID]")
|
||||
|
||||
if not self.auth_plugin.opts['auth_url']:
|
||||
raise exc.CommandError("You must provide an auth url via "
|
||||
"either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]")
|
||||
|
||||
client_kwargs = {}
|
||||
client_kwargs.update(self.auth_plugin.opts)
|
||||
client_kwargs['auth_plugin'] = self.auth_plugin
|
||||
client = ckclient.get_client(api_version, **client_kwargs)
|
||||
# call whatever callback was selected
|
||||
# NOTE(peschk_l): Used to warn users about command syntax change in Rocky.
|
||||
# To be deleted in S.
|
||||
def run_subcommand(self, argv):
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exc.HTTPUnauthorized:
|
||||
raise exc.CommandError("Invalid OpenStack Identity credentials.")
|
||||
self.command_manager.find_command(argv)
|
||||
except ValueError:
|
||||
if argv[0] in self.legacy_commands:
|
||||
LOG.warning('WARNING: This command is deprecated, please see'
|
||||
' the reference for the new commands\n')
|
||||
exit(1)
|
||||
return super(CloudKittyShell, self).run_subcommand(argv)
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""Prints all of the commands and options to stdout.
|
||||
def build_option_parser(self, description, version):
|
||||
parser = super(CloudKittyShell, self).build_option_parser(
|
||||
description,
|
||||
version,
|
||||
argparse_kwargs={'allow_abbrev': False})
|
||||
parser.add_argument(
|
||||
'--ck-api-version', type=int, default=1, dest='ck_version',
|
||||
help='Cloudkitty API version (defaults to 1)')
|
||||
if 'OS_AUTH_TYPE' not in os.environ.keys() \
|
||||
and 'OS_PASSWORD' in os.environ.keys():
|
||||
os.environ['OS_AUTH_TYPE'] = 'password'
|
||||
self.cloud_config.register_argparse_arguments(
|
||||
parser, self._args, service_keys=['rating'])
|
||||
return parser
|
||||
|
||||
The cloudkitty.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 __init__(self, prog, indent_increment=2, max_help_position=32,
|
||||
width=None):
|
||||
super(HelpFormatter, self).__init__(prog, indent_increment,
|
||||
max_help_position, width)
|
||||
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
@property
|
||||
def client(self):
|
||||
if self._client is None:
|
||||
self.cloud = self.cloud_config.get_one_cloud(
|
||||
argparse=self.options)
|
||||
session = self.cloud.get_session()
|
||||
adapter_options = dict(
|
||||
service_type=(self.options.os_rating_service_type or
|
||||
self.options.os_service_type),
|
||||
service_name=(self.options.os_rating_service_name or
|
||||
self.options.os_service_name),
|
||||
interface=(self.options.os_rating_interface or
|
||||
self.options.os_interface),
|
||||
region_name=self.options.os_region_name,
|
||||
endpoint_override=(
|
||||
self.options.os_rating_endpoint_override or
|
||||
self.options.os_endpoint_override),
|
||||
)
|
||||
self._client = client.Client(str(self.options.ck_version),
|
||||
session=session,
|
||||
adapter_options=adapter_options)
|
||||
return self._client
|
||||
|
||||
|
||||
def main(args=None):
|
||||
try:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
CloudkittyShell().main(args)
|
||||
|
||||
except Exception as e:
|
||||
if '--debug' in args or '-d' in args:
|
||||
raise
|
||||
else:
|
||||
print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
print("Stopping Cloudkitty Client", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if args is None:
|
||||
args = argv[1:]
|
||||
client_app = CloudKittyShell(args)
|
||||
return client_app.run(args)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 oslotest import base
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
|
||||
"""Test case base class for all unit tests."""
|
|
@ -1,64 +0,0 @@
|
|||
# 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.v2_0 import client as ksclient
|
||||
|
||||
|
||||
def script_keystone_client():
|
||||
ksclient.Client(auth_url='http://no.where',
|
||||
insecure=False,
|
||||
password='password',
|
||||
tenant_id='',
|
||||
tenant_name='tenant_name',
|
||||
username='username').AndReturn(FakeKeystone('abcd1234'))
|
||||
|
||||
|
||||
def fake_headers():
|
||||
return {'X-Auth-Token': 'abcd1234',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-cloudkittyclient'}
|
||||
|
||||
|
||||
class FakeServiceCatalog(object):
|
||||
@staticmethod
|
||||
def url_for(endpoint_type, service_type):
|
||||
return 'http://192.168.1.5:8004/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone(object):
|
||||
service_catalog = FakeServiceCatalog()
|
||||
|
||||
def __init__(self, auth_token):
|
||||
self.auth_token = auth_token
|
||||
|
||||
|
||||
class FakeHTTPResponse(object):
|
||||
|
||||
version = 1.1
|
||||
|
||||
def __init__(self, status, reason, headers, body):
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.status = status
|
||||
self.reason = reason
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
return self.headers.get(name, default)
|
||||
|
||||
def getheaders(self):
|
||||
return self.headers.items()
|
||||
|
||||
def read(self, amt=None):
|
||||
b = self.body
|
||||
self.body = None
|
||||
return b
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from cloudkittyclient.tests import utils
|
||||
|
||||
|
||||
class BaseFunctionalTest(utils.BaseTestCase):
|
||||
|
||||
def _run(self, executable, action,
|
||||
flags='', params='', fmt='-f json', has_output=True):
|
||||
if not has_output:
|
||||
fmt = ''
|
||||
cmd = ' '.join([executable, flags, action, params, fmt])
|
||||
cmd = shlex.split(cmd)
|
||||
p = subprocess.Popen(cmd, env=os.environ.copy(), shell=False,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError('"{cmd}" returned {val}: {msg}'.format(
|
||||
cmd=' '.join(cmd), val=p.returncode, msg=stderr))
|
||||
return json.loads(stdout) if has_output else None
|
||||
|
||||
def openstack(self, action,
|
||||
flags='', params='', fmt='-f json', has_output=True):
|
||||
return self._run('openstack rating', action,
|
||||
flags, params, fmt, has_output)
|
||||
|
||||
def cloudkitty(self, action,
|
||||
flags='', params='', fmt='-f json', has_output=True):
|
||||
return self._run('cloudkitty', action, flags, params, fmt, has_output)
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkCollectorTest(base.BaseFunctionalTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkCollectorTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_create_get_delete_collector_mapping(self):
|
||||
# Create Mapping
|
||||
resp = self.runner(
|
||||
'collector-mapping create', params='compute gnocchi')[0]
|
||||
self.assertEqual(resp['Collector'], 'gnocchi')
|
||||
self.assertEqual(resp['Service'], 'compute')
|
||||
|
||||
# Check that mapping is queryable
|
||||
resp = self.runner('collector-mapping list')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Collector'], 'gnocchi')
|
||||
self.assertEqual(resp['Service'], 'compute')
|
||||
|
||||
# Delete mapping
|
||||
self.runner('collector-mapping delete',
|
||||
params='compute', has_output=False)
|
||||
|
||||
# Check that mapping was deleted
|
||||
resp = self.runner('collector-mapping list')
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_collector_enable_disable(self):
|
||||
# Enable collector
|
||||
resp = self.runner('collector enable gnocchi')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Collector'], 'gnocchi')
|
||||
self.assertEqual(resp['State'], True)
|
||||
|
||||
# Disable collector
|
||||
resp = self.runner('collector disable gnocchi')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Collector'], 'gnocchi')
|
||||
self.assertEqual(resp['State'], False)
|
||||
|
||||
|
||||
class OSCCollectorTest(CkCollectorTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OSCCollectorTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -0,0 +1,319 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkHashmapTest(base.BaseFunctionalTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkHashmapTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def setUp(self):
|
||||
super(CkHashmapTest, self).setUp()
|
||||
self._fields = list()
|
||||
self._services = list()
|
||||
self._mappings = list()
|
||||
self._groups = list()
|
||||
self._thresholds = list()
|
||||
|
||||
def tearDown(self):
|
||||
super(CkHashmapTest, self).tearDown()
|
||||
for field in self._fields:
|
||||
try:
|
||||
self.runner(
|
||||
'hashmap field delete', params=field, has_output=False)
|
||||
except RuntimeError:
|
||||
pass
|
||||
for service in self._services:
|
||||
try:
|
||||
self.runner(
|
||||
'hashmap service delete', params=service, has_output=False)
|
||||
except RuntimeError:
|
||||
pass
|
||||
for group in self._groups:
|
||||
try:
|
||||
self.runner(
|
||||
'hashmap group delete', params=group, has_output=False)
|
||||
except RuntimeError:
|
||||
pass
|
||||
for mapping in self._mappings:
|
||||
try:
|
||||
self.runner(
|
||||
'hashmap mapping delete', params=mapping, has_output=False)
|
||||
except RuntimeError:
|
||||
pass
|
||||
for threshold in self._thresholds:
|
||||
try:
|
||||
self.runner('hashmap threshold delete',
|
||||
params=threshold, has_output=False)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def test_list_mapping_types(self):
|
||||
resp = self.runner('hashmap mapping-types list')
|
||||
found_types = [elem['Mapping types'] for elem in resp]
|
||||
self.assertIn('flat', found_types)
|
||||
self.assertIn('rate', found_types)
|
||||
|
||||
def test_create_get_delete_service(self):
|
||||
# Create service
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
self.assertEqual(resp['Name'], 'testservice')
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
# Check that resp is the same with service get and list
|
||||
resp_with_sid = self.runner(
|
||||
'hashmap service get', params=service_id)
|
||||
resp_without_sid = self.runner('hashmap service list')
|
||||
self.assertEqual(resp_with_sid, resp_without_sid)
|
||||
self.assertEqual(len(resp_with_sid), 1)
|
||||
|
||||
# Check that deletion works
|
||||
self.runner('hashmap service delete',
|
||||
params=resp['Service ID'],
|
||||
has_output=False)
|
||||
resp = self.runner('hashmap service list')
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_group_get_create_delete(self):
|
||||
# Create group
|
||||
resp = self.runner('hashmap group create', params='testgroup')[0]
|
||||
self.assertEqual(resp['Name'], 'testgroup')
|
||||
group_id = resp['Group ID']
|
||||
self._groups.append(group_id)
|
||||
|
||||
resp = self.runner('hashmap group list')
|
||||
self.assertEqual(len(resp), 1)
|
||||
|
||||
# Check that deletion works
|
||||
self.runner('hashmap group delete',
|
||||
params=group_id, has_output=False)
|
||||
resp = self.runner('hashmap group list')
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_create_get_delete_field(self):
|
||||
# Create service
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
# Create field
|
||||
resp = self.runner('hashmap field create',
|
||||
params='{} testfield'.format(service_id))[0]
|
||||
self.assertEqual(resp['Name'], 'testfield')
|
||||
self.assertEqual(resp['Service ID'], service_id)
|
||||
field_id = resp['Field ID']
|
||||
self._fields.append(field_id)
|
||||
|
||||
# Check that resp is the same with field get and list
|
||||
resp_with_fid = self.runner('hashmap field get', params=field_id)
|
||||
resp_with_sid = self.runner('hashmap field list', params=service_id)
|
||||
self.assertEqual(resp_with_fid, resp_with_sid)
|
||||
self.assertEqual(len(resp_with_fid), 1)
|
||||
|
||||
# Check that deletion works
|
||||
self.runner(
|
||||
'hashmap field delete', params=field_id, has_output=False)
|
||||
# resp = self.runner(
|
||||
# 'hashmap field list', params='-s {}'.format(service_id))
|
||||
resp = self.runner(
|
||||
'hashmap field list', params=service_id)
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_create_get_update_delete_mapping_service(self):
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
# Create mapping
|
||||
resp = self.runner('hashmap mapping create',
|
||||
params='-s {} 12'.format(service_id))[0]
|
||||
mapping_id = resp['Mapping ID']
|
||||
self._mappings.append(mapping_id)
|
||||
self.assertEqual(resp['Service ID'], service_id)
|
||||
self.assertEqual(float(resp['Cost']), float(12))
|
||||
|
||||
# Get mapping
|
||||
resp_with_sid = self.runner(
|
||||
'hashmap mapping list', params='-s {}'.format(service_id))[0]
|
||||
resp_with_mid = self.runner(
|
||||
'hashmap mapping get', params=mapping_id)[0]
|
||||
self.assertEqual(resp_with_sid, resp_with_mid)
|
||||
self.assertEqual(resp_with_sid['Mapping ID'], mapping_id)
|
||||
self.assertEqual(resp_with_sid['Service ID'], service_id)
|
||||
self.assertEqual(float(resp_with_sid['Cost']), float(12))
|
||||
|
||||
# Update mapping
|
||||
resp = self.runner('hashmap mapping update',
|
||||
params='--cost 10 {}'.format(mapping_id))[0]
|
||||
self.assertEqual(float(resp['Cost']), float(10))
|
||||
|
||||
# Check that deletion works
|
||||
self.runner(
|
||||
'hashmap mapping delete', params=mapping_id, has_output=False)
|
||||
resp = self.runner(
|
||||
'hashmap mapping list', params='-s {}'.format(service_id))
|
||||
self.assertEqual(len(resp), 0)
|
||||
self.runner(
|
||||
'hashmap service delete', params=service_id, has_output=False)
|
||||
|
||||
def test_create_get_update_delete_mapping_field(self):
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
resp = self.runner('hashmap field create',
|
||||
params='{} testfield'.format(service_id))[0]
|
||||
field_id = resp['Field ID']
|
||||
self._fields.append(field_id)
|
||||
|
||||
# Create mapping
|
||||
resp = self.runner(
|
||||
'hashmap mapping create',
|
||||
params='--field-id {} 12 --value testvalue'.format(field_id))[0]
|
||||
mapping_id = resp['Mapping ID']
|
||||
self._mappings.append(service_id)
|
||||
self.assertEqual(resp['Field ID'], field_id)
|
||||
self.assertEqual(float(resp['Cost']), float(12))
|
||||
self.assertEqual(resp['Value'], 'testvalue')
|
||||
|
||||
# Get mapping
|
||||
resp = self.runner(
|
||||
'hashmap mapping get', params=mapping_id)[0]
|
||||
self.assertEqual(resp['Mapping ID'], mapping_id)
|
||||
self.assertEqual(float(resp['Cost']), float(12))
|
||||
|
||||
# Update mapping
|
||||
resp = self.runner('hashmap mapping update',
|
||||
params='--cost 10 {}'.format(mapping_id))[0]
|
||||
self.assertEqual(float(resp['Cost']), float(10))
|
||||
|
||||
def test_group_mappings_get(self):
|
||||
# Service and group
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
resp = self.runner('hashmap group create', params='testgroup')[0]
|
||||
group_id = resp['Group ID']
|
||||
self._groups.append(group_id)
|
||||
|
||||
# Create service mapping bleonging to testgroup
|
||||
resp = self.runner(
|
||||
'hashmap mapping create',
|
||||
params='-s {} -g {} 12'.format(service_id, group_id))[0]
|
||||
mapping_id = resp['Mapping ID']
|
||||
self._mappings.append(mapping_id)
|
||||
|
||||
resp = self.runner('hashmap group mappings get', params=group_id)[0]
|
||||
self.assertEqual(resp['Group ID'], group_id)
|
||||
self.assertEqual(float(resp['Cost']), float(12))
|
||||
|
||||
def test_create_get_update_delete_threshold_service(self):
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
# Create threshold
|
||||
resp = self.runner('hashmap threshold create',
|
||||
params='-s {} 12 0.9'.format(service_id))[0]
|
||||
threshold_id = resp['Threshold ID']
|
||||
self._thresholds.append(threshold_id)
|
||||
self.assertEqual(resp['Service ID'], service_id)
|
||||
self.assertEqual(float(resp['Level']), float(12))
|
||||
self.assertEqual(float(resp['Cost']), float(0.9))
|
||||
|
||||
# Get threshold
|
||||
resp_with_sid = self.runner(
|
||||
'hashmap threshold list', params='-s {}'.format(service_id))[0]
|
||||
resp_with_tid = self.runner(
|
||||
'hashmap threshold get', params=threshold_id)[0]
|
||||
self.assertEqual(resp_with_sid, resp_with_tid)
|
||||
self.assertEqual(resp_with_sid['Threshold ID'], threshold_id)
|
||||
self.assertEqual(resp_with_sid['Service ID'], service_id)
|
||||
self.assertEqual(float(resp_with_sid['Level']), float(12))
|
||||
self.assertEqual(float(resp_with_sid['Cost']), float(0.9))
|
||||
|
||||
# Update threshold
|
||||
resp = self.runner('hashmap threshold update',
|
||||
params='--cost 10 {}'.format(threshold_id))[0]
|
||||
self.assertEqual(float(resp['Cost']), float(10))
|
||||
|
||||
# Check that deletion works
|
||||
self.runner(
|
||||
'hashmap threshold delete', params=threshold_id, has_output=False)
|
||||
resp = self.runner(
|
||||
'hashmap threshold list', params='-s {}'.format(service_id))
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_create_get_update_delete_threshold_field(self):
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
resp = self.runner('hashmap field create',
|
||||
params='{} testfield'.format(service_id))[0]
|
||||
field_id = resp['Field ID']
|
||||
self._fields.append(field_id)
|
||||
|
||||
# Create threshold
|
||||
resp = self.runner(
|
||||
'hashmap threshold create',
|
||||
params='--field-id {} 12 0.9'.format(field_id))[0]
|
||||
threshold_id = resp['Threshold ID']
|
||||
self._thresholds.append(service_id)
|
||||
self.assertEqual(resp['Field ID'], field_id)
|
||||
self.assertEqual(float(resp['Level']), float(12))
|
||||
self.assertEqual(float(resp['Cost']), float(0.9))
|
||||
|
||||
# Get threshold
|
||||
resp = self.runner('hashmap threshold get', params=threshold_id)[0]
|
||||
self.assertEqual(resp['Threshold ID'], threshold_id)
|
||||
self.assertEqual(float(resp['Level']), float(12))
|
||||
self.assertEqual(float(resp['Cost']), float(0.9))
|
||||
|
||||
# Update threshold
|
||||
resp = self.runner('hashmap threshold update',
|
||||
params='--cost 10 {}'.format(threshold_id))[0]
|
||||
self.assertEqual(float(resp['Cost']), float(10))
|
||||
|
||||
def test_group_thresholds_get(self):
|
||||
# Service and group
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
resp = self.runner('hashmap group create', params='testgroup')[0]
|
||||
group_id = resp['Group ID']
|
||||
self._groups.append(group_id)
|
||||
|
||||
# Create service threshold bleonging to testgroup
|
||||
resp = self.runner(
|
||||
'hashmap threshold create',
|
||||
params='-s {} -g {} 12 0.9'.format(service_id, group_id))[0]
|
||||
threshold_id = resp['Threshold ID']
|
||||
self._thresholds.append(threshold_id)
|
||||
resp = self.runner('hashmap group thresholds get', params=group_id)[0]
|
||||
self.assertEqual(resp['Group ID'], group_id)
|
||||
self.assertEqual(float(resp['Level']), float(12))
|
||||
self.assertEqual(float(resp['Cost']), float(0.9))
|
||||
|
||||
|
||||
class OSCHashmapTest(CkHashmapTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OSCHashmapTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 jsonpath_rw_ext as jp
|
||||
|
||||
from cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkInfoTest(base.BaseFunctionalTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkInfoTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_info_config_get(self):
|
||||
resp = self.runner('info config get')
|
||||
for elem in resp:
|
||||
if elem.get('Section') == 'name':
|
||||
self.assertEqual(elem['Value'], 'OpenStack')
|
||||
|
||||
def test_info_metric_list(self):
|
||||
resp = self.runner('info metric list')
|
||||
res = jp.match1('$.[*].Metric', resp)
|
||||
self.assertIsNotNone(res)
|
||||
|
||||
def test_info_service_get_image_size(self):
|
||||
resp = self.runner('info metric get', params='image.size')[0]
|
||||
self.assertEqual(resp['Metric'], 'image.size')
|
||||
|
||||
|
||||
class OSCInfoTest(CkInfoTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OSCInfoTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -0,0 +1,61 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkPyscriptTest(base.BaseFunctionalTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkPyscriptTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_create_get_update_list_delete(self):
|
||||
# Create
|
||||
resp = self.runner(
|
||||
'pyscript create', params="testscript 'return 0'")[0]
|
||||
script_id = resp['Script ID']
|
||||
self.assertEqual(resp['Name'], 'testscript')
|
||||
|
||||
# Get
|
||||
resp = self.runner('pyscript get', params=script_id)[0]
|
||||
self.assertEqual(resp['Name'], 'testscript')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
|
||||
# Update
|
||||
resp = self.runner(
|
||||
'pyscript update',
|
||||
params="-n newname -d 'return 1' {}".format(script_id))[0]
|
||||
self.assertEqual(resp['Name'], 'newname')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
self.assertEqual(resp['Data'], 'return 1')
|
||||
|
||||
# List
|
||||
resp = self.runner('pyscript list')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Name'], 'newname')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
self.assertEqual(resp['Data'], 'return 1')
|
||||
|
||||
# Delete
|
||||
self.runner('pyscript delete', params=script_id, has_output=False)
|
||||
|
||||
|
||||
class OSCPyscriptTest(CkPyscriptTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkPyscriptTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkRatingTest(base.BaseFunctionalTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkRatingTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_module_enable_get_disable(self):
|
||||
# enable
|
||||
resp = self.runner('module enable', params='hashmap')[0]
|
||||
self.assertTrue(resp['Enabled'])
|
||||
|
||||
# get
|
||||
resp = self.runner('module get', params='hashmap')[0]
|
||||
self.assertTrue(resp['Enabled'])
|
||||
self.assertEqual(resp['Module'], 'hashmap')
|
||||
|
||||
# disable
|
||||
resp = self.runner('module disable', params='hashmap')[0]
|
||||
self.assertFalse(resp['Enabled'])
|
||||
|
||||
def test_module_set_priority(self):
|
||||
resp = self.runner('module set priority', params='hashmap 100')[0]
|
||||
self.assertEqual(resp['Priority'], 100)
|
||||
|
||||
|
||||
class OSCRatingTest(CkRatingTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkRatingTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class CkReportTest(base.BaseFunctionalTest):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkReportTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_get_summary(self):
|
||||
resp = self.runner('summary get')[0]
|
||||
self.assertEqual(resp['Resource Type'], 'ALL')
|
||||
|
||||
def test_get_summary_with_groupby(self):
|
||||
resp = self.runner('summary get', params='-g res_type tenant_id')
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_get_total(self):
|
||||
resp = self.runner('total get')
|
||||
self.assertIn('Total', resp.keys())
|
||||
|
||||
def test_get_tenants(self):
|
||||
self.runner('report tenant list')
|
||||
|
||||
|
||||
class OSCReportTest(CkReportTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OSCReportTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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
|
||||
|
@ -11,20 +12,22 @@
|
|||
# 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 cloudkittyclient.common import base
|
||||
#
|
||||
from cloudkittyclient.tests.functional.v1 import base
|
||||
|
||||
|
||||
class Mapping(base.Resource):
|
||||
class CkStorageTest(base.BaseFunctionalTest):
|
||||
|
||||
key = 'mapping'
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkStorageTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def __repr__(self):
|
||||
return "<Mapping %s>" % self._info
|
||||
def test_dataframes_get(self):
|
||||
self.runner('dataframes get')
|
||||
|
||||
|
||||
class MappingManager(base.CrudManager):
|
||||
resource_class = Mapping
|
||||
base_url = "/v1/collector"
|
||||
key = "mapping"
|
||||
collection_key = "mappings"
|
||||
class OSCStorageTest(CkStorageTest):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CkStorageTest, self).__init__(*args, **kwargs)
|
||||
self.runner = self.openstack
|
|
@ -1,159 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# 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 types
|
||||
|
||||
import mock
|
||||
|
||||
from cloudkittyclient import client
|
||||
from cloudkittyclient.tests import fakes
|
||||
from cloudkittyclient.tests import utils
|
||||
from cloudkittyclient.v1 import client as v1client
|
||||
|
||||
FAKE_ENV = {
|
||||
'username': 'username',
|
||||
'password': 'password',
|
||||
'tenant_name': 'tenant_name',
|
||||
'auth_url': 'http://no.where',
|
||||
'os_endpoint': 'http://no.where',
|
||||
'auth_plugin': 'fake_auth',
|
||||
'token': '1234',
|
||||
'user_domain_name': 'default',
|
||||
'project_domain_name': 'default',
|
||||
}
|
||||
|
||||
|
||||
class ClientTest(utils.BaseTestCase):
|
||||
|
||||
@staticmethod
|
||||
def create_client(env, api_version=1, endpoint=None, exclude=[]):
|
||||
env = dict((k, v) for k, v in env.items()
|
||||
if k not in exclude)
|
||||
|
||||
return client.get_client(api_version, **env)
|
||||
|
||||
def setUp(self):
|
||||
super(ClientTest, self).setUp()
|
||||
|
||||
def test_client_v1_with_session(self):
|
||||
resp = mock.Mock(status_code=200, text=b'')
|
||||
resp.json.return_value = {"modules": []}
|
||||
session = mock.Mock()
|
||||
session.request.return_value = resp
|
||||
c = client.get_client(1, session=session)
|
||||
c.modules.list()
|
||||
self.assertTrue(session.request.called)
|
||||
self.assertTrue(resp.json.called)
|
||||
|
||||
def test_client_version(self):
|
||||
c1 = self.create_client(env=FAKE_ENV, api_version=1)
|
||||
self.assertIsInstance(c1, v1client.Client)
|
||||
|
||||
def test_client_auth_lambda(self):
|
||||
env = FAKE_ENV.copy()
|
||||
env['token'] = lambda: env['token']
|
||||
self.assertIsInstance(env['token'],
|
||||
types.FunctionType)
|
||||
c1 = self.create_client(env)
|
||||
self.assertIsInstance(c1, v1client.Client)
|
||||
|
||||
def test_client_auth_non_lambda(self):
|
||||
env = FAKE_ENV.copy()
|
||||
env['token'] = "1234"
|
||||
self.assertIsInstance(env['token'], str)
|
||||
c1 = self.create_client(env)
|
||||
self.assertIsInstance(c1, v1client.Client)
|
||||
|
||||
@mock.patch('keystoneclient.v2_0.client', fakes.FakeKeystone)
|
||||
def test_client_without_auth_plugin(self):
|
||||
env = FAKE_ENV.copy()
|
||||
del env['auth_plugin']
|
||||
c = self.create_client(env, api_version=1, endpoint='fake_endpoint')
|
||||
self.assertIsInstance(c.auth_plugin, client.AuthPlugin)
|
||||
|
||||
def test_client_without_auth_plugin_keystone_v3(self):
|
||||
env = FAKE_ENV.copy()
|
||||
del env['auth_plugin']
|
||||
expected = {
|
||||
'username': 'username',
|
||||
'endpoint': 'http://no.where',
|
||||
'tenant_name': 'tenant_name',
|
||||
'service_type': None,
|
||||
'token': '1234',
|
||||
'endpoint_type': None,
|
||||
'auth_url': 'http://no.where',
|
||||
'tenant_id': None,
|
||||
'cacert': None,
|
||||
'password': 'password',
|
||||
'user_domain_name': 'default',
|
||||
'user_domain_id': None,
|
||||
'project_domain_name': 'default',
|
||||
'project_domain_id': None,
|
||||
}
|
||||
with mock.patch('cloudkittyclient.client.AuthPlugin') as auth_plugin:
|
||||
self.create_client(env, api_version=1)
|
||||
auth_plugin.assert_called_with(**expected)
|
||||
|
||||
def test_client_with_auth_plugin(self):
|
||||
c = self.create_client(FAKE_ENV, api_version=1)
|
||||
self.assertIsInstance(c.auth_plugin, str)
|
||||
|
||||
def test_v1_client_timeout_invalid_value(self):
|
||||
env = FAKE_ENV.copy()
|
||||
env['timeout'] = 'abc'
|
||||
self.assertRaises(ValueError, self.create_client, env)
|
||||
env['timeout'] = '1.5'
|
||||
self.assertRaises(ValueError, self.create_client, env)
|
||||
|
||||
def _test_v1_client_timeout_integer(self, timeout, expected_value):
|
||||
env = FAKE_ENV.copy()
|
||||
env['timeout'] = timeout
|
||||
expected = {
|
||||
'auth_plugin': 'fake_auth',
|
||||
'timeout': expected_value,
|
||||
'original_ip': None,
|
||||
'http': None,
|
||||
'region_name': None,
|
||||
'verify': True,
|
||||
'timings': None,
|
||||
'keyring_saver': None,
|
||||
'cert': None,
|
||||
'endpoint_type': None,
|
||||
'user_agent': None,
|
||||
'debug': None,
|
||||
}
|
||||
cls = 'cloudkittyclient.apiclient.client.HTTPClient'
|
||||
with mock.patch(cls) as mocked:
|
||||
self.create_client(env)
|
||||
mocked.assert_called_with(**expected)
|
||||
|
||||
def test_v1_client_timeout_zero(self):
|
||||
self._test_v1_client_timeout_integer(0, None)
|
||||
|
||||
def test_v1_client_timeout_valid_value(self):
|
||||
self._test_v1_client_timeout_integer(30, 30)
|
||||
|
||||
def test_v1_client_cacert_in_verify(self):
|
||||
env = FAKE_ENV.copy()
|
||||
env['cacert'] = '/path/to/cacert'
|
||||
client = self.create_client(env)
|
||||
self.assertEqual('/path/to/cacert',
|
||||
client.http_client.http_client.verify)
|
||||
|
||||
def test_v1_client_certfile_and_keyfile(self):
|
||||
env = FAKE_ENV.copy()
|
||||
env['cert_file'] = '/path/to/cert'
|
||||
env['key_file'] = '/path/to/keycert'
|
||||
client = self.create_client(env)
|
||||
self.assertEqual(('/path/to/cert', '/path/to/keycert'),
|
||||
client.http_client.http_client.cert)
|
|
@ -1,26 +0,0 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
test_cloudkittyclient
|
||||
----------------------------------
|
||||
|
||||
Tests for `cloudkittyclient` module.
|
||||
"""
|
||||
|
||||
from cloudkittyclient.tests import base
|
||||
|
||||
|
||||
class TestCloudkittyclient(base.TestCase):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.tests import utils
|
||||
from cloudkittyclient.v1 import collector
|
||||
from cloudkittyclient.v1 import info
|
||||
from cloudkittyclient.v1 import rating
|
||||
from cloudkittyclient.v1.rating import hashmap
|
||||
from cloudkittyclient.v1.rating import pyscripts
|
||||
from cloudkittyclient.v1 import report
|
||||
from cloudkittyclient.v1 import storage
|
||||
|
||||
|
||||
class BaseAPIEndpointTestCase(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseAPIEndpointTestCase, self).setUp()
|
||||
self.api_client = utils.FakeHTTPClient()
|
||||
self.storage = storage.StorageManager(self.api_client)
|
||||
self.rating = rating.RatingManager(self.api_client)
|
||||
self.collector = collector.CollectorManager(self.api_client)
|
||||
self.info = info.InfoManager(self.api_client)
|
||||
self.report = report.ReportManager(self.api_client)
|
||||
self.pyscripts = pyscripts.PyscriptManager(self.api_client)
|
||||
self.hashmap = hashmap.HashmapManager(self.api_client)
|
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient import exc
|
||||
from cloudkittyclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestCollector(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_get_mapping_no_args(self):
|
||||
self.collector.get_mapping()
|
||||
self.api_client.get.assert_called_once_with('/v1/collector/mappings/')
|
||||
|
||||
def test_get_mapping_service_id(self):
|
||||
self.collector.get_mapping(service='testservice')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/collector/mappings/testservice')
|
||||
|
||||
def test_get_mapping_collector(self):
|
||||
self.collector.get_mapping(collector='testcollector')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/collector/mappings/?collector=testcollector')
|
||||
|
||||
def test_get_mapping_collector_service_id(self):
|
||||
self.collector.get_mapping(
|
||||
service='testservice', collector='testcollector')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/collector/mappings/testservice?collector=testcollector')
|
||||
|
||||
def test_create_mapping(self):
|
||||
kwargs = dict(service='testservice', collector='testcollector')
|
||||
self.collector.create_mapping(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/collector/mappings/', json=kwargs)
|
||||
|
||||
def test_create_mapping_no_name(self):
|
||||
self.assertRaises(exc.ArgumentRequired,
|
||||
self.collector.create_mapping,
|
||||
collector='testcollector')
|
||||
|
||||
def test_delete_mapping(self):
|
||||
kwargs = dict(service='testservice')
|
||||
self.collector.delete_mapping(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/collector/mappings/', json=kwargs)
|
||||
|
||||
def test_delete_mapping_no_service(self):
|
||||
self.assertRaises(exc.ArgumentRequired,
|
||||
self.collector.create_mapping)
|
||||
|
||||
def test_get_state(self):
|
||||
self.collector.get_state(name='testcollector')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/collector/states/?name=testcollector')
|
||||
|
||||
def test_set_state(self):
|
||||
kwargs = dict(name='testcollector', enabled=True)
|
||||
self.collector.set_state(**kwargs)
|
||||
self.api_client.put.assert_called_once_with(
|
||||
'/v1/collector/states/', json=kwargs)
|
|
@ -0,0 +1,338 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient import exc
|
||||
from cloudkittyclient.tests.unit.v1 import base
|
||||
from cloudkittyclient.tests import utils
|
||||
|
||||
|
||||
class TestHashmap(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_get_mapping_types(self):
|
||||
self.hashmap.get_mapping_types()
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/types/')
|
||||
|
||||
def test_get_service(self):
|
||||
self.hashmap.get_service()
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/services/')
|
||||
|
||||
def test_get_service_service_id(self):
|
||||
self.hashmap.get_service(service_id='service_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/services/service_id')
|
||||
|
||||
def test_create_service(self):
|
||||
kwargs = dict(name='service')
|
||||
self.hashmap.create_service(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/services/', json=kwargs)
|
||||
|
||||
def test_create_service_no_name(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_service)
|
||||
|
||||
def test_delete_service(self):
|
||||
self.hashmap.delete_service(service_id='service_id')
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/services/',
|
||||
json={'service_id': 'service_id'})
|
||||
|
||||
def test_delete_service_no_id(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.delete_service)
|
||||
|
||||
def test_get_fields_of_service(self):
|
||||
self.hashmap.get_field(service_id='service_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/fields/?service_id=service_id')
|
||||
|
||||
def test_get_field(self):
|
||||
self.hashmap.get_field(field_id='field_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/fields/field_id')
|
||||
|
||||
def test_get_field_no_args(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.get_field)
|
||||
|
||||
def test_get_field_with_service_id_and_field_id(self):
|
||||
self.assertRaises(exc.InvalidArgumentError, self.hashmap.get_field,
|
||||
service_id='service_id', field_id='field_id')
|
||||
|
||||
def test_create_field(self):
|
||||
kwargs = dict(name='name', service_id='service_id')
|
||||
self.hashmap.create_field(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/fields/', json=kwargs)
|
||||
|
||||
def test_create_field_no_name(self):
|
||||
self.assertRaises(exc.ArgumentRequired,
|
||||
self.hashmap.create_field,
|
||||
service_id='service_id')
|
||||
|
||||
def test_create_field_no_service_id(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.hashmap.create_field, name='name')
|
||||
|
||||
def test_delete_field(self):
|
||||
kwargs = dict(field_id='field_id')
|
||||
self.hashmap.delete_field(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/fields/', json=kwargs)
|
||||
|
||||
def test_delete_field_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.delete_field)
|
||||
|
||||
def test_get_mapping_with_id(self):
|
||||
self.hashmap.get_mapping(mapping_id='mapping_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/mapping_id')
|
||||
|
||||
def test_get_mapping_service_id(self):
|
||||
self.hashmap.get_mapping(service_id='service_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/?service_id=service_id')
|
||||
|
||||
def test_get_mapping_no_args(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.get_mapping)
|
||||
|
||||
def test_create_mapping(self):
|
||||
kwargs = dict(cost=2, value='value', field_id='field_id')
|
||||
body = dict(
|
||||
cost=kwargs.get('cost'),
|
||||
value=kwargs.get('value'),
|
||||
service_id=kwargs.get('service_id'),
|
||||
field_id=kwargs.get('field_id'),
|
||||
group_id=kwargs.get('group_id'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
)
|
||||
self.hashmap.create_mapping(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/', json=body)
|
||||
|
||||
def test_create_mapping_no_cost(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_mapping,
|
||||
value='value', field_id='field_id')
|
||||
|
||||
def test_create_mapping_no_id(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_mapping,
|
||||
value='value', cost=12)
|
||||
|
||||
def test_create_mapping_field_and_service_id(self):
|
||||
self.assertRaises(
|
||||
exc.InvalidArgumentError, self.hashmap.create_mapping, cost=12,
|
||||
field_id='field_id', service_id='service_id')
|
||||
|
||||
def test_create_mapping_value_and_service_id(self):
|
||||
self.assertRaises(
|
||||
exc.InvalidArgumentError, self.hashmap.create_mapping,
|
||||
value='value', service_id='service_id', cost=0.8)
|
||||
|
||||
def test_update_mapping(self):
|
||||
kwargs = dict(
|
||||
cost=12,
|
||||
value='value',
|
||||
service_id='service_id',
|
||||
field_id='field_id',
|
||||
tenant_id='tenant_id',
|
||||
type='type',
|
||||
mapping_id='mapping_id',
|
||||
)
|
||||
fake_get = mock.Mock(return_value=utils.FakeRequest(
|
||||
cost='Bad value',
|
||||
value='Bad value',
|
||||
service_id='Bad value',
|
||||
field_id='Bad value',
|
||||
tenant_id='Bad value',
|
||||
type='Bad value',
|
||||
mapping_id='mapping_id',
|
||||
))
|
||||
self.api_client.get = fake_get
|
||||
self.hashmap.update_mapping(**kwargs)
|
||||
self.api_client.get.assert_called_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/mapping_id')
|
||||
self.api_client.put.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/', json=kwargs)
|
||||
|
||||
def test_update_mapping_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.update_mapping)
|
||||
|
||||
def test_get_mapping_group(self):
|
||||
self.hashmap.get_mapping_group(mapping_id='mapping_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/'
|
||||
'hashmap/mappings/group?mapping_id=mapping_id')
|
||||
|
||||
def test_delete_mapping(self):
|
||||
kwargs = dict(mapping_id='mapping_id')
|
||||
self.hashmap.delete_mapping(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/mappings/', json=kwargs)
|
||||
|
||||
def test_delete_mapping_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.delete_mapping)
|
||||
|
||||
def test_get_mapping_group_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.get_mapping_group)
|
||||
|
||||
def test_get_group_no_arg(self):
|
||||
self.hashmap.get_group()
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/')
|
||||
|
||||
def test_get_group(self):
|
||||
self.hashmap.get_group(group_id='group_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/group_id')
|
||||
|
||||
def test_create_group(self):
|
||||
kwargs = dict(name='group')
|
||||
self.hashmap.create_group(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/',
|
||||
json=kwargs)
|
||||
|
||||
def test_create_group_no_name(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_group)
|
||||
|
||||
def test_delete_group(self):
|
||||
kwargs = dict(group_id='group_id')
|
||||
self.hashmap.delete_group(**kwargs)
|
||||
kwargs['recursive'] = False
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/',
|
||||
json=kwargs)
|
||||
|
||||
def test_delete_group_recursive(self):
|
||||
kwargs = dict(group_id='group_id', recursive=True)
|
||||
self.hashmap.delete_group(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/',
|
||||
json=kwargs)
|
||||
|
||||
def test_delete_group_no_id(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_group)
|
||||
|
||||
def test_get_group_mappings(self):
|
||||
self.hashmap.get_group_mappings(group_id='group_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/mappings'
|
||||
'?group_id=group_id')
|
||||
|
||||
def test_get_group_mappings_no_args(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.hashmap.get_group_mappings)
|
||||
|
||||
def test_get_group_thresholds(self):
|
||||
self.hashmap.get_group_thresholds(group_id='group_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/groups/thresholds'
|
||||
'?group_id=group_id')
|
||||
|
||||
def test_get_group_thresholds_no_args(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.hashmap.get_group_thresholds)
|
||||
|
||||
def test_get_threshold_with_id(self):
|
||||
self.hashmap.get_threshold(threshold_id='threshold_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/threshold_id')
|
||||
|
||||
def test_get_threshold_service_id(self):
|
||||
self.hashmap.get_threshold(service_id='service_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/'
|
||||
'?service_id=service_id')
|
||||
|
||||
def test_get_threshold_no_args(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.get_threshold)
|
||||
|
||||
def test_create_threshold(self):
|
||||
kwargs = dict(cost=2, level=123, field_id='field_id')
|
||||
body = dict(
|
||||
cost=kwargs.get('cost'),
|
||||
level=kwargs.get('level'),
|
||||
service_id=kwargs.get('service_id'),
|
||||
field_id=kwargs.get('field_id'),
|
||||
group_id=kwargs.get('group_id'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
)
|
||||
self.hashmap.create_threshold(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/', json=body)
|
||||
|
||||
def test_create_threshold_no_cost(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_threshold,
|
||||
level=123, field_id='field_id')
|
||||
|
||||
def test_create_threshold_no_id(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.create_threshold,
|
||||
level=123, cost=12)
|
||||
|
||||
def test_create_threshold_field_and_service_id(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.hashmap.create_threshold, cost=12,
|
||||
field_id='field_id', service_id='service_id')
|
||||
|
||||
def test_delete_threshold(self):
|
||||
kwargs = dict(threshold_id='threshold_id')
|
||||
self.hashmap.delete_threshold(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/', json=kwargs)
|
||||
|
||||
def test_delete_threshold_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.delete_threshold)
|
||||
|
||||
def test_update_threshold(self):
|
||||
kwargs = dict(
|
||||
cost=12,
|
||||
level=123,
|
||||
service_id='service_id',
|
||||
field_id='field_id',
|
||||
tenant_id='tenant_id',
|
||||
type='type',
|
||||
threshold_id='threshold_id'
|
||||
)
|
||||
fake_get = mock.Mock(return_value=utils.FakeRequest(
|
||||
cost='Bad value',
|
||||
level='Bad value',
|
||||
service_id='Bad value',
|
||||
field_id='Bad value',
|
||||
tenant_id='Bad value',
|
||||
type='Bad value',
|
||||
threshold_id='threshold_id'
|
||||
))
|
||||
self.api_client.get = fake_get
|
||||
self.hashmap.update_threshold(**kwargs)
|
||||
self.api_client.get.assert_called_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/threshold_id')
|
||||
self.api_client.put.assert_called_once_with(
|
||||
'/v1/rating/module_config/hashmap/thresholds/', json=kwargs)
|
||||
|
||||
def test_update_threshold_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.update_threshold)
|
||||
|
||||
def test_get_threshold_group(self):
|
||||
self.hashmap.get_threshold_group(threshold_id='threshold_id')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/'
|
||||
'hashmap/thresholds/group?threshold_id=threshold_id')
|
||||
|
||||
def test_get_threshold_group_no_arg(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.hashmap.get_threshold_group)
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestInfo(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_get_metric(self):
|
||||
self.info.get_metric()
|
||||
self.api_client.get.assert_called_once_with('/v1/info/metrics/')
|
||||
|
||||
def test_get_metric_with_arg(self):
|
||||
self.info.get_metric(metric_name='testmetric')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/info/metrics/testmetric')
|
||||
|
||||
def test_get_config(self):
|
||||
self.info.get_config()
|
||||
self.api_client.get.assert_called_once_with('/v1/info/config/')
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient import exc
|
||||
from cloudkittyclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestPyscripts(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_list_scripts(self):
|
||||
self.pyscripts.list_scripts()
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/')
|
||||
|
||||
def test_list_scripts_no_data(self):
|
||||
self.pyscripts.list_scripts(no_data=True)
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/?no_data=True')
|
||||
|
||||
def test_get_script(self):
|
||||
self.pyscripts.get_script(script_id='testscript')
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/testscript')
|
||||
|
||||
def test_get_script_no_arg(self):
|
||||
self.assertRaises(exc.ArgumentRequired, self.pyscripts.get_script)
|
||||
|
||||
def test_create_script(self):
|
||||
kwargs = dict(name='name', data='data')
|
||||
self.pyscripts.create_script(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/', json=kwargs)
|
||||
|
||||
def test_create_script_no_data(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.pyscripts.create_script, name='name')
|
||||
|
||||
def test_create_script_no_name(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.pyscripts.create_script, data='data')
|
||||
|
||||
def test_update_script(self):
|
||||
args = dict(script_id='script_id', name='name', data='data')
|
||||
self.pyscripts.update_script(**args)
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/script_id')
|
||||
args.pop('script_id', None)
|
||||
self.api_client.put.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/script_id', json=args)
|
||||
|
||||
def test_update_script_no_script_id(self):
|
||||
self.assertRaises(
|
||||
exc.ArgumentRequired, self.pyscripts.update_script, name='name')
|
||||
|
||||
def test_delete_script(self):
|
||||
kwargs = dict(script_id='script_id')
|
||||
self.pyscripts.delete_script(**kwargs)
|
||||
self.api_client.delete.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/script_id')
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestReport(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_get_summary(self):
|
||||
self.report.get_summary()
|
||||
self.api_client.get.assert_called_once_with('/v1/report/summary')
|
||||
|
||||
def test_get_summary_with_groupby(self):
|
||||
self.report.get_summary(groupby=['res_type', 'tenant_id'])
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/summary?groupby=res_type%2Ctenant_id')
|
||||
|
||||
def test_get_summary_with_begin_end(self):
|
||||
self.report.get_summary(begin='begin', end='end')
|
||||
try:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/summary?begin=begin&end=end')
|
||||
# Passing a dict to urlencode can change arg order
|
||||
except AssertionError:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/summary?end=end&begin=begin')
|
||||
|
||||
def test_get_total(self):
|
||||
self.report.get_total()
|
||||
self.api_client.get.assert_called_once_with('/v1/report/total')
|
||||
|
||||
def test_get_total_with_begin_end(self):
|
||||
self.report.get_total(begin='begin', end='end')
|
||||
try:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/total?begin=begin&end=end')
|
||||
# Passing a dict to urlencode can change arg order
|
||||
except AssertionError:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/total?end=end&begin=begin')
|
||||
|
||||
def test_get_tenants(self):
|
||||
self.report.get_tenants()
|
||||
self.api_client.get.assert_called_once_with('/v1/report/tenants')
|
||||
|
||||
def test_get_tenants_with_begin_end(self):
|
||||
self.report.get_tenants(begin='begin', end='end')
|
||||
try:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/tenants?begin=begin&end=end')
|
||||
# Passing a dict to urlencode can change arg order
|
||||
except AssertionError:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/report/tenants?end=end&begin=begin')
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestStorage(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_get_dataframes(self):
|
||||
self.storage.get_dataframes()
|
||||
self.api_client.get.assert_called_once_with('/v1/storage/dataframes')
|
||||
|
||||
def test_get_dataframes_with_begin_end(self):
|
||||
self.storage.get_dataframes(begin='begin', end='end')
|
||||
try:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/storage/dataframes?begin=begin&end=end')
|
||||
# Passing a dict to urlencode can change arg order
|
||||
except AssertionError:
|
||||
self.api_client.get.assert_called_once_with(
|
||||
'/v1/storage/dataframes?end=end&begin=begin')
|
|
@ -12,13 +12,32 @@
|
|||
# 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 fixtures
|
||||
import testtools
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
from keystoneauth1 import session
|
||||
import mock
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self.useFixture(fixtures.FakeLogger())
|
||||
|
||||
|
||||
class FakeRequest(dict):
|
||||
"""Fake requests.Request object."""
|
||||
|
||||
def json(self):
|
||||
return self
|
||||
|
||||
|
||||
class FakeHTTPClient(adapter.Adapter):
|
||||
"""Keystone HTTP adapter with request methods being mocks"""
|
||||
|
||||
def __init__(self):
|
||||
super(FakeHTTPClient, self).__init__(session=session.Session())
|
||||
for attr in ('get', 'put', 'post', 'delete'):
|
||||
setattr(self, attr, mock.Mock(return_value=FakeRequest()))
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from cloudkittyclient.apiclient import client
|
||||
from cloudkittyclient.apiclient import fake_client
|
||||
from cloudkittyclient.tests import utils
|
||||
import cloudkittyclient.v1.core
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/rating/modules': {
|
||||
'GET': (
|
||||
{},
|
||||
{'modules': [
|
||||
{
|
||||
'module_id': 'hashmap',
|
||||
'enabled': True,
|
||||
'priority': 1,
|
||||
},
|
||||
{
|
||||
'module_id': 'noop',
|
||||
'enabled': False,
|
||||
'priority': 1,
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/rating/modules/hashmap': {
|
||||
'GET': (
|
||||
{},
|
||||
{
|
||||
'module_id': 'hashmap',
|
||||
'enabled': True,
|
||||
'priority': 1,
|
||||
}
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{
|
||||
'module_id': 'hashmap',
|
||||
'enabled': False,
|
||||
'priority': 1,
|
||||
}
|
||||
),
|
||||
},
|
||||
'/v1/rating/modules/noop': {
|
||||
'GET': (
|
||||
{},
|
||||
{
|
||||
'module_id': 'noop',
|
||||
'enabled': False,
|
||||
'priority': 1,
|
||||
}
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{
|
||||
'module_id': 'noop',
|
||||
'enabled': True,
|
||||
'priority': 1,
|
||||
}
|
||||
),
|
||||
},
|
||||
'/v1/collectors': {
|
||||
'GET': (
|
||||
{},
|
||||
{'collectors': [
|
||||
{
|
||||
'module_id': 'ceilo',
|
||||
'enabled': True,
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class CloudkittyModuleManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CloudkittyModuleManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = cloudkittyclient.v1.core.CloudkittyModuleManager(self.api)
|
||||
|
||||
def test_list_all(self):
|
||||
resources = list(self.mgr.list())
|
||||
expect = [
|
||||
'GET', '/v1/rating/modules'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(2, len(resources))
|
||||
self.assertEqual('hashmap', resources[0].module_id)
|
||||
self.assertEqual('noop', resources[1].module_id)
|
||||
|
||||
def test_get_module_status(self):
|
||||
resource = self.mgr.get(module_id='hashmap')
|
||||
expect = [
|
||||
'GET', '/v1/rating/modules/hashmap'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual('hashmap', resource.module_id)
|
||||
self.assertTrue(resource.enabled)
|
||||
|
||||
|
||||
class CloudkittyModuleTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CloudkittyModuleTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = cloudkittyclient.v1.core.CloudkittyModuleManager(self.api)
|
||||
|
||||
def test_enable(self):
|
||||
self.ck_module = self.mgr.get(module_id='noop')
|
||||
self.ck_module.enable()
|
||||
# PUT /v1/rating/modules/noop
|
||||
# body : {'enabled': True}
|
||||
expect = [
|
||||
'PUT', '/v1/rating/modules/noop', {'module_id': 'noop',
|
||||
'enabled': True,
|
||||
'priority': 1},
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_disable(self):
|
||||
self.ck_module = self.mgr.get(module_id='hashmap')
|
||||
self.ck_module.disable()
|
||||
# PUT /v1/rating/modules/hashmap
|
||||
# body : {'enabled': False}
|
||||
expect = [
|
||||
'PUT', '/v1/rating/modules/hashmap', {'module_id': 'hashmap',
|
||||
'enabled': False,
|
||||
'priority': 1},
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_set_priority(self):
|
||||
self.ck_module = self.mgr.get(module_id='hashmap')
|
||||
self.ck_module.set_priority(100)
|
||||
# PUT /v1/rating/modules/hashmap
|
||||
# body : {'priority': 100}
|
||||
expect = [
|
||||
'PUT', '/v1/rating/modules/hashmap', {'module_id': 'hashmap',
|
||||
'enabled': True,
|
||||
'priority': 100},
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
|
@ -1,920 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from cloudkittyclient.apiclient import client
|
||||
from cloudkittyclient.apiclient import fake_client
|
||||
from cloudkittyclient.tests import utils
|
||||
from cloudkittyclient.v1.rating import hashmap
|
||||
|
||||
GROUP1 = {
|
||||
'group_id': 'aaa1c2e0-2c6b-4e75-987f-93661eef0fd5',
|
||||
'name': 'object_consumption'}
|
||||
|
||||
GROUP2 = {
|
||||
'group_id': '36171313-9813-4456-bf40-0195b2c98d1e',
|
||||
'name': 'compute_instance'}
|
||||
|
||||
GROUP3 = {
|
||||
'group_id': '1dc7d980-e80a-4449-888f-26686392f4cc',
|
||||
'name': 'networking'}
|
||||
|
||||
SERVICE1 = {
|
||||
'service_id': '2451c2e0-2c6b-4e75-987f-93661eef0fd5',
|
||||
'name': 'compute'}
|
||||
|
||||
SERVICE2 = {
|
||||
'service_id': '338dd381-2c25-4347-b14d-239194c6068c',
|
||||
'name': 'volume'}
|
||||
|
||||
SERVICE3 = {
|
||||
'service_id': '2f5bc5be-3753-450f-9492-37a6dba2fa8a',
|
||||
'name': 'network'}
|
||||
|
||||
SERVICE_MAPPING1 = {
|
||||
'mapping_id': 'ae6145c3-6b00-4954-b698-cbc36a3d6c4b',
|
||||
'service_id': SERVICE3['service_id'],
|
||||
'field_id': None,
|
||||
'group_id': None,
|
||||
'value': None,
|
||||
'cost': 0.50,
|
||||
'type': 'flat'}
|
||||
|
||||
SERVICE_MAPPING1_PUT = {
|
||||
'mapping_id': SERVICE_MAPPING1['mapping_id'],
|
||||
'service_id': SERVICE3['service_id'],
|
||||
'field_id': None,
|
||||
'group_id': None,
|
||||
'value': None,
|
||||
'cost': 0.20,
|
||||
'type': SERVICE_MAPPING1['type']}
|
||||
|
||||
SERVICE_THRESHOLD1 = {
|
||||
'threshold_id': '22e3ae52-a863-47c6-8994-6acdec200346',
|
||||
'service_id': SERVICE3['service_id'],
|
||||
'field_id': None,
|
||||
'group_id': GROUP3['group_id'],
|
||||
'level': 30,
|
||||
'cost': 5.98,
|
||||
'map_type': 'flat'}
|
||||
|
||||
SERVICE_THRESHOLD1_PUT = {
|
||||
'threshold_id': SERVICE_THRESHOLD1['threshold_id'],
|
||||
'service_id': SERVICE3['service_id'],
|
||||
'group_id': SERVICE_THRESHOLD1['group_id'],
|
||||
'level': SERVICE_THRESHOLD1['level'],
|
||||
'cost': 5.99,
|
||||
'map_type': SERVICE_THRESHOLD1['map_type']}
|
||||
|
||||
FIELD1 = {
|
||||
'field_id': 'a53db546-bac0-472c-be4b-5bf9f6117581',
|
||||
'service_id': SERVICE1['service_id'],
|
||||
'name': 'flavor'}
|
||||
|
||||
FIELD2 = {
|
||||
'field_id': 'f818a5a6-da88-474c-bd33-184ed769be63',
|
||||
'service_id': SERVICE1['service_id'],
|
||||
'name': 'image_id'}
|
||||
|
||||
FIELD3 = {
|
||||
'field_id': 'b9861ba3-26d8-4c39-bb66-c607d48ccfce',
|
||||
'service_id': SERVICE1['service_id'],
|
||||
'name': 'vcpus'}
|
||||
|
||||
FIELD_MAPPING1 = {
|
||||
'mapping_id': 'bff0d209-a8e4-46f8-8c1a-f231db375dcb',
|
||||
'service_id': None,
|
||||
'field_id': FIELD1['field_id'],
|
||||
'group_id': GROUP2['group_id'],
|
||||
'value': 'm1.small',
|
||||
'cost': 0.50,
|
||||
'type': 'flat'}
|
||||
|
||||
FIELD_MAPPING1_PUT = {
|
||||
'mapping_id': FIELD_MAPPING1['mapping_id'],
|
||||
'field_id': FIELD_MAPPING1['field_id'],
|
||||
'group_id': FIELD_MAPPING1['group_id'],
|
||||
'value': FIELD_MAPPING1['value'],
|
||||
'cost': 0.20,
|
||||
'type': FIELD_MAPPING1['type']}
|
||||
|
||||
FIELD_MAPPING2 = {
|
||||
'mapping_id': '1f1a05f2-1549-4623-b70a-9ab5c69fcd91',
|
||||
'service_id': None,
|
||||
'field_id': FIELD1['field_id'],
|
||||
'group_id': None,
|
||||
'value': 'm1.tiny',
|
||||
'cost': 1.10,
|
||||
'type': 'flat'}
|
||||
|
||||
FIELD_MAPPING3 = {
|
||||
'mapping_id': 'deb4efe8-77c4-40ca-b8ca-27ec4892fa5f',
|
||||
'service_id': None,
|
||||
'field_id': FIELD1['field_id'],
|
||||
'group_id': None,
|
||||
'value': 'm1.big',
|
||||
'cost': 1.50,
|
||||
'type': 'flat'}
|
||||
|
||||
FIELD_THRESHOLD1 = {
|
||||
'threshold_id': 'a33aca4b-3c12-41c5-a153-134c705fdbe2',
|
||||
'service_id': None,
|
||||
'field_id': FIELD3['field_id'],
|
||||
'group_id': None,
|
||||
'level': 2,
|
||||
'cost': 1.2,
|
||||
'map_type': 'flat'}
|
||||
|
||||
FIELD_THRESHOLD1_PUT = {
|
||||
'threshold_id': FIELD_THRESHOLD1['threshold_id'],
|
||||
'service_id': None,
|
||||
'field_id': FIELD3['field_id'],
|
||||
'group_id': None,
|
||||
'level': FIELD_THRESHOLD1['level'],
|
||||
'cost': 1.5,
|
||||
'map_type': FIELD_THRESHOLD1['map_type']}
|
||||
|
||||
fixtures = {
|
||||
# services
|
||||
'/v1/rating/module_config/hashmap/services': {
|
||||
'GET': (
|
||||
{},
|
||||
{'services':
|
||||
[
|
||||
SERVICE1,
|
||||
SERVICE2,
|
||||
SERVICE3
|
||||
],
|
||||
}
|
||||
),
|
||||
},
|
||||
# a service
|
||||
('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE1['service_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a service
|
||||
('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE3['service_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE3
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a service mapping
|
||||
('/v1/rating/module_config/hashmap/mappings/' +
|
||||
SERVICE_MAPPING1['mapping_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE_MAPPING1
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
SERVICE_MAPPING1_PUT
|
||||
),
|
||||
},
|
||||
# some service mappings
|
||||
('/v1/rating/module_config/hashmap/mappings?service_id=' +
|
||||
SERVICE3['service_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'mappings':
|
||||
[
|
||||
SERVICE_MAPPING1
|
||||
],
|
||||
}
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a service threshold
|
||||
('/v1/rating/module_config/hashmap/thresholds/' +
|
||||
SERVICE_THRESHOLD1['threshold_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
SERVICE_THRESHOLD1
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
SERVICE_THRESHOLD1_PUT
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# service thresholds
|
||||
('/v1/rating/module_config/hashmap/thresholds?service_id=' +
|
||||
SERVICE3['service_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'thresholds':
|
||||
[
|
||||
SERVICE_THRESHOLD1
|
||||
]
|
||||
},
|
||||
),
|
||||
},
|
||||
# service thresholds in a group
|
||||
('/v1/rating/module_config/hashmap/thresholds?group_id=' +
|
||||
GROUP3['group_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'thresholds':
|
||||
[
|
||||
SERVICE_THRESHOLD1
|
||||
]
|
||||
},
|
||||
),
|
||||
},
|
||||
# a field
|
||||
('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD1['field_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
FIELD1
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a field
|
||||
('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD3['field_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
FIELD3
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# some fields
|
||||
('/v1/rating/module_config/hashmap/fields?service_id=' +
|
||||
SERVICE1['service_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'fields': [
|
||||
FIELD1,
|
||||
FIELD2,
|
||||
FIELD3
|
||||
]
|
||||
},
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a field mapping
|
||||
('/v1/rating/module_config/hashmap/mappings/' +
|
||||
FIELD_MAPPING1['mapping_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
FIELD_MAPPING1
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
FIELD_MAPPING1_PUT
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# some mappings
|
||||
('/v1/rating/module_config/hashmap/mappings?field_id=' +
|
||||
FIELD1['field_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'mappings':
|
||||
[
|
||||
FIELD_MAPPING1,
|
||||
FIELD_MAPPING2,
|
||||
FIELD_MAPPING3
|
||||
],
|
||||
}
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# some mappings in a group
|
||||
('/v1/rating/module_config/hashmap/mappings?group_id=' +
|
||||
GROUP2['group_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'mappings':
|
||||
[
|
||||
FIELD_MAPPING1,
|
||||
],
|
||||
}
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# a field threshold
|
||||
('/v1/rating/module_config/hashmap/thresholds/' +
|
||||
FIELD_THRESHOLD1['threshold_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
FIELD_THRESHOLD1
|
||||
),
|
||||
'PUT': (
|
||||
{},
|
||||
FIELD_THRESHOLD1_PUT
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# field thresholds
|
||||
('/v1/rating/module_config/hashmap/thresholds?field_id=' +
|
||||
FIELD3['field_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
{'thresholds':
|
||||
[
|
||||
FIELD_THRESHOLD1
|
||||
]
|
||||
},
|
||||
),
|
||||
},
|
||||
# some groups
|
||||
'/v1/rating/module_config/hashmap/groups': {
|
||||
'GET': (
|
||||
{},
|
||||
{'groups':
|
||||
[
|
||||
GROUP1,
|
||||
GROUP2,
|
||||
GROUP3
|
||||
],
|
||||
}
|
||||
),
|
||||
},
|
||||
# a group
|
||||
('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
GROUP2
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# another group
|
||||
('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP3['group_id']): {
|
||||
'GET': (
|
||||
{},
|
||||
GROUP3
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
# recursive delete group
|
||||
('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'] +
|
||||
'?recursive=True'): {
|
||||
'DELETE': (
|
||||
{},
|
||||
{},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ServiceManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ServiceManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.ServiceManager(self.api)
|
||||
|
||||
def test_list_services(self):
|
||||
resources = list(self.mgr.list())
|
||||
expect = [
|
||||
'GET', '/v1/rating/module_config/hashmap/services']
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertEqual(
|
||||
SERVICE1['service_id'],
|
||||
resources[0].service_id)
|
||||
self.assertEqual(SERVICE1['name'], resources[0].name)
|
||||
self.assertEqual(SERVICE2['name'], resources[1].name)
|
||||
self.assertEqual(SERVICE3['name'], resources[2].name)
|
||||
|
||||
def test_get_a_service(self):
|
||||
resource = self.mgr.get(
|
||||
service_id=SERVICE1['service_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE1['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(SERVICE1['service_id'],
|
||||
resource.service_id)
|
||||
self.assertEqual(SERVICE1['name'],
|
||||
resource.name)
|
||||
|
||||
def test_delete_a_service(self):
|
||||
self.mgr.delete(service_id=SERVICE1['service_id'])
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE1['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
|
||||
class ServiceTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ServiceTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.ServiceManager(self.api)
|
||||
self.resource = self.mgr.get(service_id=SERVICE3['service_id'])
|
||||
|
||||
def test_get_fields(self):
|
||||
self.resource = self.mgr.get(
|
||||
service_id=SERVICE1['service_id'])
|
||||
fields = self.resource.fields[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/fields?service_id=' +
|
||||
SERVICE1['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(3, len(fields))
|
||||
field = fields[0]
|
||||
self.assertEqual(SERVICE1['service_id'],
|
||||
field.service_id)
|
||||
self.assertEqual(FIELD1['field_id'],
|
||||
field.field_id)
|
||||
self.assertEqual(FIELD1['name'],
|
||||
field.name)
|
||||
|
||||
def test_get_mappings(self):
|
||||
mappings = self.resource.mappings[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/mappings?service_id=' +
|
||||
SERVICE3['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(mappings))
|
||||
mapping = mappings[0]
|
||||
self.assertEqual(SERVICE3['service_id'],
|
||||
mapping.service_id)
|
||||
self.assertEqual(SERVICE_MAPPING1['mapping_id'],
|
||||
mapping.mapping_id)
|
||||
self.assertEqual(SERVICE_MAPPING1['value'], mapping.value)
|
||||
self.assertEqual(SERVICE_MAPPING1['cost'], mapping.cost)
|
||||
self.assertEqual(SERVICE_MAPPING1['type'], mapping.type)
|
||||
|
||||
def test_get_thresholds(self):
|
||||
thresholds = self.resource.thresholds[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/thresholds?service_id=' +
|
||||
SERVICE3['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(thresholds))
|
||||
threshold = thresholds[0]
|
||||
self.assertEqual(SERVICE_THRESHOLD1['service_id'],
|
||||
threshold.service_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['threshold_id'],
|
||||
threshold.threshold_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['level'], threshold.level)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['cost'], threshold.cost)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['map_type'], threshold.map_type)
|
||||
|
||||
|
||||
class FieldManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FieldManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.FieldManager(self.api)
|
||||
|
||||
def test_list_fields(self):
|
||||
resources = list(self.mgr.list(service_id=SERVICE1['service_id']))
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/fields?service_id=' +
|
||||
SERVICE1['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertEqual(SERVICE1['service_id'],
|
||||
resources[0].service_id)
|
||||
self.assertEqual(FIELD1['name'], resources[0].name)
|
||||
self.assertEqual(FIELD2['name'], resources[1].name)
|
||||
self.assertEqual(FIELD3['name'], resources[2].name)
|
||||
|
||||
def test_get_a_field(self):
|
||||
resource = self.mgr.get(
|
||||
field_id=FIELD1['field_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD1['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(FIELD1['field_id'], resource.field_id)
|
||||
self.assertEqual(SERVICE1['service_id'], resource.service_id)
|
||||
self.assertEqual(FIELD1['name'], resource.name)
|
||||
|
||||
def test_delete_a_field(self):
|
||||
self.mgr.delete(field_id=FIELD1['field_id'])
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD1['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
|
||||
class FieldTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FieldTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.FieldManager(self.api)
|
||||
self.resource = self.mgr.get(field_id=FIELD1['field_id'])
|
||||
|
||||
def test_get_service(self):
|
||||
service = self.resource.service
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE1['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(SERVICE1['service_id'], service.service_id)
|
||||
self.assertEqual(SERVICE1['name'], service.name)
|
||||
|
||||
def test_get_mappings(self):
|
||||
mappings = self.resource.mappings[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/mappings?field_id=' +
|
||||
FIELD1['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(3, len(mappings))
|
||||
mapping = mappings[0]
|
||||
self.assertEqual(FIELD1['field_id'], mapping.field_id)
|
||||
self.assertEqual(FIELD_MAPPING1['mapping_id'], mapping.mapping_id)
|
||||
self.assertEqual(FIELD_MAPPING1['value'], mapping.value)
|
||||
self.assertEqual(FIELD_MAPPING1['cost'], mapping.cost)
|
||||
self.assertEqual(FIELD_MAPPING1['type'], mapping.type)
|
||||
|
||||
def test_get_thresholds(self):
|
||||
resource = self.mgr.get(field_id=FIELD3['field_id'])
|
||||
thresholds = resource.thresholds[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/thresholds?field_id=' +
|
||||
FIELD3['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(thresholds))
|
||||
threshold = thresholds[0]
|
||||
self.assertEqual(FIELD3['field_id'], threshold.field_id)
|
||||
self.assertEqual(FIELD_THRESHOLD1['threshold_id'],
|
||||
threshold.threshold_id)
|
||||
self.assertEqual(FIELD_THRESHOLD1['level'],
|
||||
threshold.level)
|
||||
self.assertEqual(FIELD_THRESHOLD1['cost'],
|
||||
threshold.cost)
|
||||
self.assertEqual(FIELD_THRESHOLD1['map_type'],
|
||||
threshold.map_type)
|
||||
|
||||
|
||||
class MappingManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MappingManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.MappingManager(self.api)
|
||||
|
||||
def test_get_mappings_by_group(self):
|
||||
mappings = self.mgr.findall(group_id=GROUP2['group_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/mappings?group_id=' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(mappings))
|
||||
mapping = mappings[0]
|
||||
self.assertEqual(FIELD1['field_id'], mapping.field_id)
|
||||
self.assertEqual(FIELD_MAPPING1['group_id'], mapping.group_id)
|
||||
self.assertEqual(FIELD_MAPPING1['mapping_id'], mapping.mapping_id)
|
||||
self.assertEqual(FIELD_MAPPING1['value'], mapping.value)
|
||||
self.assertEqual(FIELD_MAPPING1['cost'], mapping.cost)
|
||||
self.assertEqual(FIELD_MAPPING1['type'], mapping.type)
|
||||
|
||||
def test_get_a_mapping(self):
|
||||
resource = self.mgr.get(mapping_id=FIELD_MAPPING1['mapping_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/mappings/' +
|
||||
FIELD_MAPPING1['mapping_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(FIELD_MAPPING1['mapping_id'], resource.mapping_id)
|
||||
self.assertEqual(FIELD1['field_id'], resource.field_id)
|
||||
self.assertEqual(FIELD_MAPPING1['value'], resource.value)
|
||||
self.assertEqual(FIELD_MAPPING1['cost'], resource.cost)
|
||||
self.assertEqual(FIELD_MAPPING1['type'], resource.type)
|
||||
|
||||
def test_update_a_mapping(self):
|
||||
resource = self.mgr.get(mapping_id=FIELD_MAPPING1['mapping_id'])
|
||||
resource.cost = 0.2
|
||||
self.mgr.update(**resource.dirty_fields)
|
||||
expect = [
|
||||
'PUT', ('/v1/rating/module_config/hashmap/mappings/' +
|
||||
FIELD_MAPPING1['mapping_id']),
|
||||
FIELD_MAPPING1_PUT]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_delete_a_mapping(self):
|
||||
self.mgr.delete(mapping_id=FIELD_MAPPING1['mapping_id'])
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/mappings/' +
|
||||
FIELD_MAPPING1['mapping_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
|
||||
class MappingTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MappingTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.MappingManager(self.api)
|
||||
self.resource = self.mgr.get(mapping_id=FIELD_MAPPING1['mapping_id'])
|
||||
|
||||
def test_get_service_mapping_parent(self):
|
||||
resource = self.mgr.get(mapping_id=SERVICE_MAPPING1['mapping_id'])
|
||||
service = resource.service
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE3['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(SERVICE3['service_id'], service.service_id)
|
||||
field = resource.field
|
||||
self.assertIsNone(field)
|
||||
|
||||
def test_get_field_mapping_parent(self):
|
||||
service = self.resource.service
|
||||
self.assertIsNone(service)
|
||||
field = self.resource.field
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD1['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(FIELD1['field_id'], field.field_id)
|
||||
|
||||
def test_get_group(self):
|
||||
group = self.resource.group
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(GROUP2['group_id'], group.group_id)
|
||||
self.assertEqual(GROUP2['name'], group.name)
|
||||
|
||||
|
||||
class ThresholdManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ThresholdManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.ThresholdManager(self.api)
|
||||
|
||||
def test_get_thresholds_by_group(self):
|
||||
mappings = self.mgr.findall(group_id=GROUP3['group_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/thresholds?group_id=' +
|
||||
GROUP3['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(mappings))
|
||||
mapping = mappings[0]
|
||||
self.assertEqual(SERVICE_THRESHOLD1['threshold_id'],
|
||||
mapping.threshold_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['service_id'],
|
||||
mapping.service_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['group_id'],
|
||||
mapping.group_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['level'],
|
||||
mapping.level)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['cost'],
|
||||
mapping.cost)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['map_type'],
|
||||
mapping.map_type)
|
||||
|
||||
def test_get_a_threshold(self):
|
||||
resource = self.mgr.get(
|
||||
threshold_id=SERVICE_THRESHOLD1['threshold_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/thresholds/' +
|
||||
SERVICE_THRESHOLD1['threshold_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['threshold_id'],
|
||||
resource.threshold_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['service_id'],
|
||||
resource.service_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['level'],
|
||||
resource.level)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['cost'],
|
||||
resource.cost)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['map_type'],
|
||||
resource.map_type)
|
||||
|
||||
def test_update_a_threshold(self):
|
||||
resource = self.mgr.get(
|
||||
threshold_id=SERVICE_THRESHOLD1['threshold_id'])
|
||||
resource.cost = 5.99
|
||||
self.mgr.update(**resource.dirty_fields)
|
||||
expect = [
|
||||
'PUT', ('/v1/rating/module_config/hashmap/thresholds/' +
|
||||
SERVICE_THRESHOLD1['threshold_id']),
|
||||
SERVICE_THRESHOLD1_PUT]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_delete_a_threshold(self):
|
||||
self.mgr.delete(threshold_id=SERVICE_THRESHOLD1['threshold_id'])
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/thresholds/' +
|
||||
SERVICE_THRESHOLD1['threshold_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
|
||||
class ThresholdTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ThresholdTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.ThresholdManager(self.api)
|
||||
self.resource = self.mgr.get(
|
||||
threshold_id=SERVICE_THRESHOLD1['threshold_id'])
|
||||
|
||||
def test_get_service_threshold_parent(self):
|
||||
service = self.resource.service
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/services/' +
|
||||
SERVICE3['service_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(SERVICE3['service_id'], service.service_id)
|
||||
field = self.resource.field
|
||||
self.assertIsNone(field)
|
||||
|
||||
def test_get_field_mapping_parent(self):
|
||||
resource = self.mgr.get(
|
||||
threshold_id=FIELD_THRESHOLD1['threshold_id'])
|
||||
service = resource.service
|
||||
self.assertIsNone(service)
|
||||
field = resource.field
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/fields/' +
|
||||
FIELD3['field_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(FIELD3['field_id'], field.field_id)
|
||||
|
||||
def test_get_group(self):
|
||||
group = self.resource.group
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP3['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(GROUP3['group_id'], group.group_id)
|
||||
self.assertEqual(GROUP3['name'], group.name)
|
||||
|
||||
|
||||
class GroupManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(GroupManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.GroupManager(self.api)
|
||||
|
||||
def test_get_a_group(self):
|
||||
resource = self.mgr.get(group_id=GROUP2['group_id'])
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(GROUP2['group_id'], resource.group_id)
|
||||
self.assertEqual(GROUP2['name'], resource.name)
|
||||
|
||||
def test_list_groups(self):
|
||||
resources = list(self.mgr.list())
|
||||
expect = [
|
||||
'GET', '/v1/rating/module_config/hashmap/groups']
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(3, len(resources))
|
||||
self.assertEqual(
|
||||
resources[0].group_id,
|
||||
GROUP1['group_id'])
|
||||
self.assertEqual(GROUP1['name'], resources[0].name)
|
||||
self.assertEqual(GROUP2['name'], resources[1].name)
|
||||
self.assertEqual(GROUP3['name'], resources[2].name)
|
||||
|
||||
def test_delete_a_group(self):
|
||||
self.mgr.delete(group_id=GROUP2['group_id'])
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_delete_a_group_recursively(self):
|
||||
self.mgr.delete(group_id=GROUP2['group_id'],
|
||||
recursive=True)
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'] +
|
||||
'?recursive=True')]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
|
||||
class GroupTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(GroupTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = hashmap.GroupManager(self.api)
|
||||
self.resource = self.mgr.get(group_id=GROUP2['group_id'])
|
||||
|
||||
def test_delete(self):
|
||||
self.resource.delete()
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_delete_recursive(self):
|
||||
self.resource.delete(recursive=True)
|
||||
expect = [
|
||||
'DELETE', ('/v1/rating/module_config/hashmap/groups/' +
|
||||
GROUP2['group_id'] +
|
||||
'?recursive=True')]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_get_mappings(self):
|
||||
mappings = self.resource.mappings[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/mappings?group_id=' +
|
||||
GROUP2['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(mappings))
|
||||
mapping = mappings[0]
|
||||
self.assertEqual(FIELD1['field_id'], mapping.field_id)
|
||||
self.assertEqual(FIELD_MAPPING1['mapping_id'], mapping.mapping_id)
|
||||
self.assertEqual(FIELD_MAPPING1['value'], mapping.value)
|
||||
self.assertEqual(FIELD_MAPPING1['cost'], mapping.cost)
|
||||
self.assertEqual(FIELD_MAPPING1['type'], mapping.type)
|
||||
|
||||
def test_get_thresholds(self):
|
||||
resource = self.mgr.get(group_id=GROUP3['group_id'])
|
||||
thresholds = resource.thresholds[:]
|
||||
expect = [
|
||||
'GET', ('/v1/rating/module_config/hashmap/thresholds?group_id=' +
|
||||
GROUP3['group_id'])]
|
||||
self.http_client.assert_called(*expect)
|
||||
self.assertEqual(1, len(thresholds))
|
||||
threshold = thresholds[0]
|
||||
self.assertEqual(SERVICE3['service_id'], threshold.service_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['threshold_id'],
|
||||
threshold.threshold_id)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['level'],
|
||||
threshold.level)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['cost'],
|
||||
threshold.cost)
|
||||
self.assertEqual(SERVICE_THRESHOLD1['map_type'],
|
||||
threshold.map_type)
|
|
@ -1,139 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from cloudkittyclient.apiclient import client
|
||||
from cloudkittyclient.apiclient import fake_client
|
||||
from cloudkittyclient.tests import utils
|
||||
import cloudkittyclient.v1.report
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/report/summary': {
|
||||
'GET': (
|
||||
{},
|
||||
{'summary': [
|
||||
{
|
||||
'tenant_id': 'ALL',
|
||||
'res_type': 'ALL',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '2325.29992'
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/report/summary?tenant_id=649de47ad78a44bd8562b0aa84389b2b': {
|
||||
'GET': (
|
||||
{},
|
||||
{'summary': [
|
||||
{
|
||||
'tenant_id': '649de47ad78a44bd8562b0aa84389b2b',
|
||||
'res_type': 'ALL',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '990.14996'
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/report/summary?service=compute': {
|
||||
'GET': (
|
||||
{},
|
||||
{'summary': [
|
||||
{
|
||||
'tenant_id': 'ALL',
|
||||
'res_type': 'compute',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '690.0'
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/report/summary?groupby=res_type%2Ctenant_id': {
|
||||
'GET': (
|
||||
{},
|
||||
{'summary': [
|
||||
{
|
||||
'tenant_id': '3747afc360b64702a53bdd64dc1b8976',
|
||||
'res_type': 'compute',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '517.5'
|
||||
},
|
||||
{
|
||||
'tenant_id': '3747afc360b64702a53bdd64dc1b8976',
|
||||
'res_type': 'volume',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '817.64996'
|
||||
},
|
||||
{
|
||||
'tenant_id': '649de47ad78a44bd8562b0aa84389b2b',
|
||||
'res_type': 'compute',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '172.5'
|
||||
},
|
||||
{
|
||||
'tenant_id': '649de47ad78a44bd8562b0aa84389b2b',
|
||||
'res_type': 'volume',
|
||||
'begin': '2017-01-01T00:00:00',
|
||||
'end': '2017-02-01T00:00:00',
|
||||
'rate': '817.64996'
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ReportSummaryManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ReportSummaryManagerTest, self).setUp()
|
||||
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||
self.api = client.BaseClient(self.http_client)
|
||||
self.mgr = cloudkittyclient.v1.report.ReportSummaryManager(self.api)
|
||||
|
||||
def test_get_summary(self):
|
||||
self.mgr.get_summary()
|
||||
expect = [
|
||||
'GET', '/v1/report/summary'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_get_summary_with_tenant(self):
|
||||
self.mgr.get_summary(tenant_id='649de47ad78a44bd8562b0aa84389b2b')
|
||||
expect = [
|
||||
'GET',
|
||||
'/v1/report/summary?tenant_id=649de47ad78a44bd8562b0aa84389b2b'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_get_summary_with_service(self):
|
||||
self.mgr.get_summary(service='compute')
|
||||
expect = [
|
||||
'GET',
|
||||
'/v1/report/summary?service=compute'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
||||
|
||||
def test_get_summary_with_groupby(self):
|
||||
self.mgr.get_summary(groupby='res_type,tenant_id')
|
||||
expect = [
|
||||
'GET',
|
||||
'/v1/report/summary?groupby=res_type%2Ctenant_id'
|
||||
]
|
||||
self.http_client.assert_called(*expect)
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 pbr.version
|
||||
|
||||
from oslo_utils import timeutils
|
||||
|
||||
|
||||
def get_version():
|
||||
"""Returns cloudkittyclient's version."""
|
||||
return pbr.version.VersionInfo('python-cloudkittyclient').version_string()
|
||||
|
||||
|
||||
def iso2dt(iso_date):
|
||||
"""iso8601 format to datetime."""
|
||||
iso_dt = timeutils.parse_isotime(iso_date)
|
||||
trans_dt = timeutils.normalize_time(iso_dt)
|
||||
return trans_dt
|
||||
|
||||
|
||||
def get_client_from_osc(obj):
|
||||
if hasattr(obj.app, 'client_manager'):
|
||||
return obj.app.client_manager.rating
|
||||
return obj.app.client
|
||||
|
||||
|
||||
def dict_to_cols(dict_obj, cols):
|
||||
"""Converts a dict to a cliff-compatible value list.
|
||||
|
||||
For cliff lister.Lister objects, you should use list_to_cols() instead
|
||||
of this function.
|
||||
'cols' shouls be a list of (key, Name) tuples.
|
||||
"""
|
||||
values = []
|
||||
for col in cols:
|
||||
values.append(dict_obj.get(col[0]))
|
||||
return values
|
||||
|
||||
|
||||
def list_to_cols(list_obj, cols):
|
||||
if not isinstance(list_obj, list):
|
||||
list_obj = [list_obj]
|
||||
values = []
|
||||
for item in list_obj:
|
||||
values.append(dict_to_cols(item, cols))
|
||||
return values
|
|
@ -1,16 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.v1.client import Client # noqa
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 string import Formatter as StringFormatter
|
||||
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
|
||||
class BaseManager(object):
|
||||
"""Base class for Endpoint Manager objects."""
|
||||
|
||||
url = ''
|
||||
|
||||
def __init__(self, api_client):
|
||||
self.api_client = api_client
|
||||
self._formatter = StringFormatter()
|
||||
|
||||
def _get_format_kwargs(self, **kwargs):
|
||||
it = self._formatter.parse(self.url)
|
||||
output = {i[1]: '' for i in it}
|
||||
for key in output.keys():
|
||||
if kwargs.get(key):
|
||||
output[key] = kwargs[key]
|
||||
if 'endpoint' in output.keys():
|
||||
output.pop('endpoint')
|
||||
return output
|
||||
|
||||
def get_url(self,
|
||||
endpoint,
|
||||
kwargs,
|
||||
authorized_args=[]):
|
||||
"""Returns the required url for a request against CloudKitty's API.
|
||||
|
||||
:param endpoint: The endpoint on which the request should be done
|
||||
:type endpoint: str
|
||||
:param kwargs: kwargs that will be used to build the query (part after
|
||||
'?' in the url) and to format the url.
|
||||
:type kwargs: dict
|
||||
:param authorized_args: The arguments that are authorized in url
|
||||
parameters
|
||||
:type authorized_args: list
|
||||
"""
|
||||
query_kwargs = {
|
||||
key: kwargs[key] for key in authorized_args
|
||||
if kwargs.get(key, None)
|
||||
}
|
||||
kwargs = self._get_format_kwargs(**kwargs)
|
||||
url = self.url.format(endpoint=endpoint, **kwargs)
|
||||
query = urlencode(query_kwargs)
|
||||
if query:
|
||||
url += '?' + query
|
||||
return url
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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
|
||||
|
@ -12,62 +12,30 @@
|
|||
# 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 keystoneauth1 import adapter
|
||||
from keystoneauth1 import session as ks_session
|
||||
|
||||
from stevedore import extension
|
||||
|
||||
from cloudkittyclient import client as ckclient
|
||||
from cloudkittyclient.v1 import collector
|
||||
from cloudkittyclient.v1 import core
|
||||
from cloudkittyclient.v1 import info
|
||||
from cloudkittyclient.v1 import rating
|
||||
from cloudkittyclient.v1 import report
|
||||
from cloudkittyclient.v1 import storage
|
||||
|
||||
SUBMODULES_NAMESPACE = 'cloudkitty.client.modules'
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the Cloudkitty v1 API.
|
||||
|
||||
:param session: a keystoneauth/keystoneclient session object
|
||||
:type session: keystoneclient.session.Session
|
||||
:param str service_type: The default service_type for URL discovery
|
||||
:param str service_name: The default service_name for URL discovery
|
||||
:param str interface: The default interface for URL discovery
|
||||
(Default: public)
|
||||
:param str region_name: The default region_name for URL discovery
|
||||
:param str endpoint_override: Always use this endpoint URL for requests
|
||||
for this cloudkittyclient
|
||||
:param auth: An auth plugin to use instead of the session one
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
:param str user_agent: The User-Agent string to set
|
||||
(Default is python-cloudkittyclient)
|
||||
:param int connect_retries: the maximum number of retries that should be
|
||||
attempted for connection errors
|
||||
:param logger: A logging object
|
||||
:type logger: logging.Logger
|
||||
"""
|
||||
def __init__(self, session=None, adapter_options={}, **kwargs):
|
||||
adapter_options.setdefault('service_type', 'rating')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Cloudkitty v1 API."""
|
||||
self.session = session
|
||||
if self.session is None:
|
||||
self.session = ks_session.Session(**kwargs)
|
||||
|
||||
if not kwargs.get('auth_plugin'):
|
||||
kwargs['auth_plugin'] = ckclient.get_auth_plugin(*args, **kwargs)
|
||||
self.auth_plugin = kwargs.get('auth_plugin')
|
||||
|
||||
self.http_client = ckclient.construct_http_client(**kwargs)
|
||||
self.modules = core.CloudkittyModuleManager(self.http_client)
|
||||
self.collector = collector.CollectorManager(self.http_client)
|
||||
self.reports = report.ReportManager(self.http_client)
|
||||
self.reportsummary = report.ReportSummaryManager(self.http_client)
|
||||
self.quotations = core.QuotationManager(self.http_client)
|
||||
self.storage = storage.StorageManager(self.http_client)
|
||||
self.config = core.ConfigInfoManager(self.http_client)
|
||||
self.service_info = core.ServiceInfoManager(self.http_client)
|
||||
self._expose_submodules()
|
||||
|
||||
def _expose_submodules(self):
|
||||
extensions = extension.ExtensionManager(
|
||||
SUBMODULES_NAMESPACE,
|
||||
)
|
||||
for ext in extensions:
|
||||
client = ext.plugin.get_client(self.http_client)
|
||||
setattr(self, ext.name, client)
|
||||
self.api_client = adapter.Adapter(
|
||||
session=self.session, **adapter_options)
|
||||
self.info = info.InfoManager(self.api_client)
|
||||
self.collector = collector.CollectorManager(self.api_client)
|
||||
self.rating = rating.RatingManager(self.api_client)
|
||||
self.report = report.ReportManager(self.api_client)
|
||||
self.storage = storage.StorageManager(self.api_client)
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class CollectorManager(base.BaseManager):
|
||||
"""Class used to handle /v1/collector/mappings endpoint"""
|
||||
url = '/v1/collector/{endpoint}/{service_id}'
|
||||
|
||||
def get_mapping(self, **kwargs):
|
||||
"""Returns a service to collector mapping.
|
||||
|
||||
If the service is not specified, returns a list of mappings for the
|
||||
given collector.
|
||||
|
||||
:param service: Name of the service to filter on.
|
||||
:type service: str
|
||||
:param collector: Name of the collector to filter on.
|
||||
:type collector: str
|
||||
"""
|
||||
LOG.warning('WARNING: Collector mappings are deprecated and will '
|
||||
'be removed in a future release')
|
||||
kwargs['service_id'] = kwargs.get('service') or ''
|
||||
authorized_args = ['collector']
|
||||
url = self.get_url('mappings', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_mapping(self, **kwargs):
|
||||
"""Creates a service to collector mapping.
|
||||
|
||||
:param service: Name of the service to filter on.
|
||||
:type service: str
|
||||
:param collector: Name of the collector to filter on.
|
||||
:type collector: str
|
||||
"""
|
||||
LOG.warning('WARNING: Collector mappings are deprecated and will '
|
||||
'be removed in a future release')
|
||||
for arg in ('collector', 'service'):
|
||||
if not kwargs.get(arg):
|
||||
raise exc.ArgumentRequired(
|
||||
"'{arg}' argument is required.".format(arg=arg))
|
||||
url = self.get_url('mappings', kwargs)
|
||||
body = dict(
|
||||
collector=kwargs['collector'],
|
||||
service=kwargs['service'])
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_mapping(self, **kwargs):
|
||||
"""Deletes a service to collector mapping.
|
||||
|
||||
:param service: Name of the service of which the mapping
|
||||
should be deleted.
|
||||
:type service: str
|
||||
"""
|
||||
LOG.warning('WARNING: Collector mappings are deprecated and will '
|
||||
'be removed in a future release')
|
||||
if not kwargs.get('service'):
|
||||
raise exc.ArgumentRequired("'service' argument is required.")
|
||||
body = dict(service=kwargs['service'])
|
||||
url = self.get_url('mappings', kwargs)
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def get_state(self, **kwargs):
|
||||
"""Returns the state of a collector.
|
||||
|
||||
:param name: Name of the collector.
|
||||
:type name: str
|
||||
"""
|
||||
LOG.warning('WARNING: Collector mappings are deprecated and will '
|
||||
'be removed in a future release')
|
||||
if not kwargs.get('name'):
|
||||
raise exc.ArgumentRequired("'name' argument is required.")
|
||||
authorized_args = ['name']
|
||||
url = self.get_url('states', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def set_state(self, **kwargs):
|
||||
"""Sets the state of the collector.
|
||||
|
||||
:param name: Name of the collector
|
||||
:type name: str
|
||||
:param enabled: State of the collector
|
||||
:type name: bool
|
||||
"""
|
||||
LOG.warning('WARNING: Collector mappings are deprecated and will '
|
||||
'be removed in a future release')
|
||||
if not kwargs.get('name'):
|
||||
raise exc.ArgumentRequired("'name' argument is required.")
|
||||
kwargs['enabled'] = kwargs.get('enabled') or False
|
||||
url = self.get_url('states', kwargs)
|
||||
body = dict(
|
||||
name=kwargs['name'],
|
||||
enabled=kwargs['enabled'],
|
||||
)
|
||||
self.api_client.put(url, json=body)
|
||||
return self.get_state(**kwargs)
|
|
@ -1,22 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.v1.collector import mapping
|
||||
from cloudkittyclient.v1.collector import state
|
||||
|
||||
|
||||
class CollectorManager(object):
|
||||
def __init__(self, http_client):
|
||||
self.mappings = mapping.MappingManager(http_client)
|
||||
self.states = state.StateManager(http_client)
|
|
@ -1,87 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.common import utils
|
||||
|
||||
|
||||
@utils.arg('-c', '--collector',
|
||||
help='Collector name to filter on.',
|
||||
required=False,
|
||||
default=None)
|
||||
def do_collector_mapping_list(cc, args):
|
||||
"""List collector mapping."""
|
||||
data = cc.collector.mappings.list(collector=args.collector)
|
||||
fields = ['service', 'collector']
|
||||
fields_labels = ['Service', 'Collector']
|
||||
utils.print_list(data, fields, fields_labels, sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-s', '--service',
|
||||
help='Which service to get the mapping for.',
|
||||
required=True)
|
||||
def do_collector_mapping_get(cc, args):
|
||||
"""Show collector mapping detail."""
|
||||
data = cc.collector.mappings.get(mapping_id=args.service)
|
||||
utils.print_dict(data.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-c', '--collector',
|
||||
help='Map a service to this collector.',
|
||||
required=True)
|
||||
@utils.arg('-s', '--service',
|
||||
help='Map a collector to this service.',
|
||||
required=True)
|
||||
def do_collector_mapping_create(cc, args):
|
||||
"""Create collector mapping."""
|
||||
out = cc.collector.mappings.create(service=args.service,
|
||||
collector=args.collector)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-s', '--service',
|
||||
help='Filter on this service.',
|
||||
required=True)
|
||||
def do_collector_mapping_delete(cc, args):
|
||||
"""Delete collector mapping."""
|
||||
# TODO(sheeprine): Use a less hacky way to do this
|
||||
cc.collector.mappings.delete(mapping_id=args.service)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Name of the collector.',
|
||||
required=True)
|
||||
def do_collector_state_get(cc, args):
|
||||
"""Show collector state."""
|
||||
data = cc.collector.states.get(state_id=args.name)
|
||||
utils.print_dict(data.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Name of the collector.',
|
||||
required=True)
|
||||
def do_collector_state_enable(cc, args):
|
||||
"""Enable collector state."""
|
||||
new_state = cc.collector.states.update(name=args.name, enabled=True)
|
||||
utils.print_dict(new_state.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Name of the collector.',
|
||||
required=True)
|
||||
def do_collector_state_disable(cc, args):
|
||||
"""Disable collector state."""
|
||||
new_state = cc.collector.states.update(name=args.name, enabled=False)
|
||||
utils.print_dict(new_state.to_dict())
|
|
@ -1,109 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
from cloudkittyclient.v1.collector import shell
|
||||
|
||||
|
||||
class CliCollectorMappingList(command.Command):
|
||||
"""List collector mappings."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingList, self).get_parser(prog_name)
|
||||
parser.add_argument('-c', '--collector',
|
||||
help='Collector name to filter on.',
|
||||
required=False,
|
||||
default=None)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_mapping_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliCollectorMappingGet(command.Command):
|
||||
"""Show collector mapping detail."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Which service to get the mapping for.',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_mapping_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliCollectorMappingCreate(command.Command):
|
||||
"""Create collector mappings."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('-c', '--collector',
|
||||
help='Map a service to this collector.',
|
||||
required=True)
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Map a collector to this service.',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_mapping_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliCollectorMappingDelete(command.Command):
|
||||
"""Delete collector mappings."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Filter on this service',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_mapping_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class BaseCliCollectorState(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BaseCliCollectorState, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Name of the collector',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorStateGet(BaseCliCollectorState):
|
||||
"""Show collector state."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_state_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliCollectorStateEnable(BaseCliCollectorState):
|
||||
"""Enable collector state."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_state_enable(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliCollectorStateDisable(BaseCliCollectorState):
|
||||
"""Disable collector state."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_collector_state_disable(ckclient, parsed_args)
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.common import base
|
||||
|
||||
|
||||
class State(base.Resource):
|
||||
|
||||
key = 'state'
|
||||
|
||||
def __repr__(self):
|
||||
return "<State %s>" % self._info
|
||||
|
||||
|
||||
class StateManager(base.CrudManager):
|
||||
resource_class = State
|
||||
base_url = "/v1/collector"
|
||||
key = "state"
|
||||
collection_key = "states"
|
|
@ -0,0 +1,151 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import command
|
||||
from cliff import lister
|
||||
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class CliCollectorMappingGet(lister.Lister):
|
||||
"""(DEPRECATED) Get a service to collector mapping."""
|
||||
|
||||
columns = [
|
||||
('service', 'Service'),
|
||||
('collector', 'Collector'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).collector.get_mapping(
|
||||
service=parsed_args.service,
|
||||
)
|
||||
resp = [resp] if resp.get('mappings') is None else resp['mappings']
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingGet, self).get_parser(prog_name)
|
||||
parser.add_argument('service', type=str,
|
||||
help='Name of the service to filter on')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorMappingList(lister.Lister):
|
||||
"""(DEPRECATED) List service to collector mappings."""
|
||||
|
||||
columns = [
|
||||
('service', 'Service'),
|
||||
('collector', 'Collector'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).collector.get_mapping(
|
||||
collector=parsed_args.collector)
|
||||
resp = [resp] if resp.get('mappings') is None else resp['mappings']
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingList, self).get_parser(prog_name)
|
||||
parser.add_argument('--collector', type=str,
|
||||
help='Name of the collector to filter on')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorMappingCreate(lister.Lister):
|
||||
"""(DEPRECATED) Create a service to collector mapping."""
|
||||
|
||||
columns = [
|
||||
('service', 'Service'),
|
||||
('collector', 'Collector'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).collector.create_mapping(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('service', type=str, help='Name of the service')
|
||||
parser.add_argument('collector', type=str,
|
||||
help='Name of the collector')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorMappingDelete(command.Command):
|
||||
"""(DEPRECATED) Delete a service to collector mapping."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).collector.delete_mapping(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorMappingDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('service', type=str, help='Name of the service')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorGetState(lister.Lister):
|
||||
"""(DEPRECATED) Get the state of a collector."""
|
||||
|
||||
columns = [
|
||||
('name', 'Collector'),
|
||||
('enabled', 'State'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).collector.get_state(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorGetState, self).get_parser(prog_name)
|
||||
parser.add_argument('name', type=str, help='Name of the collector')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorEnable(lister.Lister):
|
||||
"""(DEPRECATED) Enable a collector."""
|
||||
|
||||
columns = [
|
||||
('name', 'Collector'),
|
||||
('enabled', 'State'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
parsed_args.enabled = True
|
||||
resp = utils.get_client_from_osc(self).collector.set_state(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCollectorEnable, self).get_parser(prog_name)
|
||||
parser.add_argument('name', type=str, help='Name of the collector')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCollectorDisable(CliCollectorEnable):
|
||||
"""(DEPRECATED) Disable a collector."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
parsed_args.disabled = True
|
||||
resp = utils.get_client_from_osc(self).collector.set_state(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
|
@ -1,89 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.common import base
|
||||
|
||||
|
||||
class CloudkittyModule(base.Resource):
|
||||
|
||||
key = 'module'
|
||||
|
||||
def __repr__(self):
|
||||
return "<CloudkittyModule %s>" % self._info
|
||||
|
||||
def enable(self):
|
||||
self.enabled = True
|
||||
self.update()
|
||||
|
||||
def disable(self):
|
||||
self.enabled = False
|
||||
self.update()
|
||||
|
||||
def set_priority(self, value):
|
||||
self.priority = value
|
||||
self.update()
|
||||
|
||||
|
||||
class CloudkittyModuleManager(base.CrudManager):
|
||||
resource_class = CloudkittyModule
|
||||
base_url = "/v1/rating"
|
||||
key = 'module'
|
||||
collection_key = "modules"
|
||||
|
||||
|
||||
class Collector(base.Resource):
|
||||
|
||||
key = 'collector'
|
||||
|
||||
def __repr__(self):
|
||||
return "<Collector %s>" % self._info
|
||||
|
||||
|
||||
class CollectorManager(base.Manager):
|
||||
resource_class = Collector
|
||||
base_url = "/v1/rating"
|
||||
key = "collector"
|
||||
collection_key = "collectors"
|
||||
|
||||
|
||||
class QuotationManager(base.Manager):
|
||||
base_url = "/v1/rating/quote"
|
||||
|
||||
def quote(self, resources):
|
||||
out = self.api.post(self.base_url,
|
||||
json={'resources': resources}).json()
|
||||
return out
|
||||
|
||||
|
||||
class ServiceInfo(base.Resource):
|
||||
|
||||
key = "service"
|
||||
|
||||
def __repr__(self):
|
||||
return "<Service %s>" % self._info
|
||||
|
||||
|
||||
class ServiceInfoManager(base.CrudManager):
|
||||
resource_class = ServiceInfo
|
||||
base_url = "/v1/info"
|
||||
key = "service"
|
||||
collection_key = "services"
|
||||
|
||||
|
||||
class ConfigInfoManager(base.Manager):
|
||||
base_url = "/v1/info/config"
|
||||
|
||||
def get_config(self):
|
||||
out = self.api.get(self.base_url).json()
|
||||
return out
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
class InfoManager(base.BaseManager):
|
||||
"""Class used to handle /v1/info endpoint"""
|
||||
url = '/v1/info/{endpoint}/{metric_name}'
|
||||
|
||||
def get_metric(self, **kwargs):
|
||||
"""Returns info for the given service.
|
||||
|
||||
If metric_name is not specified, returns info for all services.
|
||||
|
||||
:param metric_name: Name of the service on which you want information
|
||||
:type metric_name: str
|
||||
"""
|
||||
url = self.get_url('metrics', kwargs)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_config(self, **kwargs):
|
||||
"""Returns the current configuration."""
|
||||
url = self.get_url('config', kwargs)
|
||||
return self.api_client.get(url).json()
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import lister
|
||||
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class CliInfoMetricGet(lister.Lister):
|
||||
"""Get information about current metrics."""
|
||||
info_columns = [
|
||||
('metric_id', 'Metric'),
|
||||
('unit', 'Unit'),
|
||||
('metadata', 'Metadata'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).info.get_metric(
|
||||
metric_name=parsed_args.metric_name,
|
||||
)
|
||||
values = utils.list_to_cols([resp], self.info_columns)
|
||||
return [col[1] for col in self.info_columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliInfoMetricGet, self).get_parser(prog_name)
|
||||
parser.add_argument('metric_name',
|
||||
type=str, default='', help='Metric name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliInfoMetricList(lister.Lister):
|
||||
"""Get information about a single metric."""
|
||||
info_columns = [
|
||||
('metric_id', 'Metric'),
|
||||
('unit', 'Unit'),
|
||||
('metadata', 'Metadata'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).info.get_metric()
|
||||
values = utils.list_to_cols(resp['metrics'], self.info_columns)
|
||||
return [col[1] for col in self.info_columns], values
|
||||
|
||||
|
||||
class CliInfoConfigGet(lister.Lister):
|
||||
"""Get information about the current configuration."""
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).info.get_config()
|
||||
values = [(key, value) for key, value in resp.items()]
|
||||
return ('Section', 'Value'), values
|
|
@ -0,0 +1,170 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import lister
|
||||
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient import utils
|
||||
from cloudkittyclient.v1 import base
|
||||
from cloudkittyclient.v1.rating import hashmap
|
||||
from cloudkittyclient.v1.rating import pyscripts
|
||||
|
||||
|
||||
class RatingManager(base.BaseManager):
|
||||
"""Class used to handle /v1/rating endpoint"""
|
||||
|
||||
url = '/v1/rating/{endpoint}/{module_id}'
|
||||
|
||||
def __init__(self, api_client):
|
||||
super(RatingManager, self).__init__(api_client)
|
||||
self.hashmap = hashmap.HashmapManager(api_client)
|
||||
self.pyscripts = pyscripts.PyscriptManager(api_client)
|
||||
|
||||
def get_module(self, **kwargs):
|
||||
"""Returns the given module.
|
||||
|
||||
If module_id is not specified, returns the list of loaded modules.
|
||||
|
||||
:param module_id: ID of the module on which you want information.
|
||||
:type module_id: str
|
||||
"""
|
||||
authorized_args = ['module_id']
|
||||
url = self.get_url('modules', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def update_module(self, **kwargs):
|
||||
"""Update the given module.
|
||||
|
||||
:param module_id: Id of the module to update.
|
||||
:type module_id: str
|
||||
:param enabled: Set to True to enable the module, False to disable it.
|
||||
:type enabled: bool
|
||||
:param priority: New priority of the module.
|
||||
:type priority: int
|
||||
"""
|
||||
if not kwargs.get('module_id', None):
|
||||
raise exc.ArgumentRequired("'module_id' argument is required.")
|
||||
url = self.get_url('modules', kwargs)
|
||||
module = self.get_module(**kwargs)
|
||||
for key in module.keys():
|
||||
value = kwargs.get(key, None)
|
||||
if value is not None and module[key] != value:
|
||||
module[key] = value
|
||||
self.api_client.put(url, json=module)
|
||||
return self.get_module(**kwargs)
|
||||
|
||||
def reload_modules(self, **kwargs):
|
||||
"""Triggers a reload of all rating modules."""
|
||||
url = self.get_url('reload_modules', kwargs)
|
||||
self.api_client.get(url)
|
||||
|
||||
def get_quotation(self, **kwargs):
|
||||
"""Returns a quote base on multiple resource descriptions.
|
||||
|
||||
:param res_data: A list of resource descriptions.
|
||||
:type res_data: list
|
||||
"""
|
||||
if not kwargs.get('res_data', None):
|
||||
raise exc.ArgumentRequired("'res_data' argument is required.")
|
||||
url = self.get_url('quote')
|
||||
return self.api_client.post(url, kwargs['res_data'])
|
||||
|
||||
|
||||
class CliModuleGet(lister.Lister):
|
||||
"""Get a rating module or list loaded rating modules.
|
||||
|
||||
If module_id is not specified, returns a list of all loaded
|
||||
rating modules.
|
||||
"""
|
||||
columns = [
|
||||
('module_id', 'Module'),
|
||||
('enabled', 'Enabled'),
|
||||
('priority', 'Priority'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.get_module(
|
||||
module_id=parsed_args.module_id,
|
||||
)
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleGet, self).get_parser(prog_name)
|
||||
parser.add_argument('module_id', type=str, help='Module name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliModuleList(lister.Lister):
|
||||
"""List loaded rating modules."""
|
||||
|
||||
columns = [
|
||||
('module_id', 'Module'),
|
||||
('enabled', 'Enabled'),
|
||||
('priority', 'Priority'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.get_module()
|
||||
values = utils.list_to_cols(resp['modules'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
|
||||
class CliModuleSet(lister.Lister):
|
||||
columns = [
|
||||
('module_id', 'Module'),
|
||||
('enabled', 'Enabled'),
|
||||
('priority', 'Priority'),
|
||||
]
|
||||
|
||||
def _take_action(self, **kwargs):
|
||||
resp = utils.get_client_from_osc(self).rating.update_module(**kwargs)
|
||||
values = [resp.get(col[0]) for col in self.columns]
|
||||
return [col[1] for col in self.columns], [values]
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleSet, self).get_parser(prog_name)
|
||||
parser.add_argument('module_id', type=str, help='Module name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliModuleEnable(CliModuleSet):
|
||||
"""Enable a rating module."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
kwargs = vars(parsed_args)
|
||||
kwargs['enabled'] = True
|
||||
return self._take_action(**kwargs)
|
||||
|
||||
|
||||
class CliModuleDisable(CliModuleEnable):
|
||||
"""Disable a rating module."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
kwargs = vars(parsed_args)
|
||||
kwargs['enabled'] = False
|
||||
return self._take_action(**kwargs)
|
||||
|
||||
|
||||
class CliModuleSetPriority(CliModuleSet):
|
||||
"""Set the priority of a rating module."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleSetPriority, self).get_parser(prog_name)
|
||||
parser.add_argument('priority', type=int, help='Priority (int)')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
return self._take_action(**vars(parsed_args))
|
|
@ -0,0 +1,446 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient import exc
|
||||
from cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
class HashmapManager(base.BaseManager):
|
||||
"""Class used to manage the Hashmap rating module"""
|
||||
|
||||
url = '/v1/rating/module_config/hashmap/{endpoint}/{resource_id}'
|
||||
|
||||
def get_mapping_types(self, **kwargs):
|
||||
"""Returns a list of all available mapping types."""
|
||||
url = self.get_url('types', kwargs)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_service(self, **kwargs):
|
||||
"""Returns the service corresponding to the provided ID.
|
||||
|
||||
If no ID is provided, returns a list of all hashmap services.
|
||||
|
||||
:param service_id: ID of the service
|
||||
:type service_id: str
|
||||
"""
|
||||
if kwargs.get('service_id'):
|
||||
kwargs['resource_id'] = kwargs['service_id']
|
||||
url = self.get_url('services', kwargs)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_service(self, **kwargs):
|
||||
"""Creates a hashmap service.
|
||||
|
||||
:param name: Name of the service
|
||||
:type name: str
|
||||
"""
|
||||
if not kwargs.get('name'):
|
||||
raise exc.ArgumentRequired("Argument 'service_name' is mandatory.")
|
||||
url = self.get_url('services', kwargs)
|
||||
body = dict(name=kwargs['name'])
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_service(self, **kwargs):
|
||||
"""Deletes a hashmap service
|
||||
|
||||
:param service_id: ID of the service to delete
|
||||
:type service_id: uuid
|
||||
"""
|
||||
if not kwargs.get('service_id'):
|
||||
raise exc.ArgumentRequired("Argument 'service_id' is mandatory.")
|
||||
url = self.get_url('services', kwargs)
|
||||
body = dict(service_id=kwargs['service_id'])
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def get_field(self, **kwargs):
|
||||
"""Returns a hashmap field.
|
||||
|
||||
Either service_id or field_id must be specified. If service_id is
|
||||
provided, all fields of the given service are returned. If field_id
|
||||
is specified, only this field is returned.
|
||||
|
||||
:param service_id: ID of the service of which you want fields
|
||||
:type service_id: str
|
||||
:param field_id: ID of the field you want
|
||||
:type field_id: str
|
||||
"""
|
||||
if not kwargs.get('service_id') and not kwargs.get('field_id'):
|
||||
raise exc.ArgumentRequired("Either 'service_id' or 'field_id' "
|
||||
"must be specified.")
|
||||
elif kwargs.get('service_id') and kwargs.get('field_id'):
|
||||
raise exc.InvalidArgumentError(
|
||||
"You can't specify both 'service_id' and 'field_id'")
|
||||
elif kwargs.get('field_id'):
|
||||
kwargs['resource_id'] = kwargs['field_id']
|
||||
kwargs.pop('service_id', None)
|
||||
else:
|
||||
kwargs.pop('resource_id', None)
|
||||
authorized_args = ['service_id']
|
||||
url = self.get_url('fields', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_field(self, **kwargs):
|
||||
"""Creates a hashmap field.
|
||||
|
||||
:param name: Field name
|
||||
:type name: str
|
||||
:param service_id: ID of the service the field belongs to
|
||||
:type service_id: uuid
|
||||
"""
|
||||
if not kwargs.get('name'):
|
||||
raise exc.ArgumentRequired("'name' argument is required")
|
||||
if not kwargs.get('service_id'):
|
||||
raise exc.ArgumentRequired("'service_id' argument is required")
|
||||
body = dict(name=kwargs['name'], service_id=kwargs['service_id'])
|
||||
url = self.get_url('fields', kwargs)
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_field(self, **kwargs):
|
||||
"""Deletes the given field.
|
||||
|
||||
:param field_id: ID of the field to delete.
|
||||
:type field_id: uuid
|
||||
"""
|
||||
if not kwargs.get('field_id'):
|
||||
raise exc.ArgumentRequired("'field_id' argument is required")
|
||||
url = self.get_url('fields', kwargs)
|
||||
body = dict(field_id=kwargs['field_id'])
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def get_mapping(self, **kwargs):
|
||||
"""Get hashmap mappings.
|
||||
|
||||
If mapping_id is not provided, you need to specify either service_id,
|
||||
field_id or group_id.
|
||||
|
||||
:param mapping_id: ID of the mapping
|
||||
:type mapping_id: uuid
|
||||
:param service_id: ID of the service to filter on
|
||||
:type service_id: uuid
|
||||
:param group_id: ID of the group to filter on
|
||||
:type group_id: uuid
|
||||
:param field_id: ID of the field to filter on
|
||||
:type field_id: uuid
|
||||
:param tenant_id: ID of the tenant to filter on
|
||||
:type tenant_id: uuid
|
||||
:param filter_tenant: Explicitly filter on given tenant (allows to
|
||||
filter on tenant being None). Defaults to false.
|
||||
:type filter_tenant: bool
|
||||
:param no_group: Filter on orphaned mappings.
|
||||
:type no_group: bool
|
||||
"""
|
||||
if not kwargs.get('mapping_id'):
|
||||
if not kwargs.get('service_id') and not kwargs.get('field_id') \
|
||||
and not kwargs.get('group_id'):
|
||||
raise exc.ArgumentRequired("You must provide either 'field_id'"
|
||||
", 'service_id' or 'group_id'.")
|
||||
allowed_args = ['service_id', 'group_id', 'field_id', 'tenant_id',
|
||||
'filter_tenant', 'no_group']
|
||||
else:
|
||||
allowed_args = []
|
||||
kwargs['resource_id'] = kwargs['mapping_id']
|
||||
url = self.get_url('mappings', kwargs, allowed_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_mapping(self, **kwargs):
|
||||
"""Create a hashmap mapping.
|
||||
|
||||
:param cost: Cost of the mapping
|
||||
:type cost: decimal.Decimal
|
||||
:param field_id: ID of the field the mapping belongs to
|
||||
:type field_id: uuid
|
||||
:param service_id: ID of the service the mapping belongs to
|
||||
:type service_id: uuid
|
||||
:param tenant_id: ID of the tenant the mapping belongs to
|
||||
:type tenant_id: uuid
|
||||
:param group_id: ID of the group the mapping belongs to
|
||||
:type group_id: uuid
|
||||
:param type: Type of the mapping (flat or rate)
|
||||
:type type: str
|
||||
:param value: Value of the mapping
|
||||
:type value: str
|
||||
"""
|
||||
if not kwargs.get('cost'):
|
||||
raise exc.ArgumentRequired("'cost' argument is required")
|
||||
if not kwargs.get('value'):
|
||||
if not kwargs.get('service_id'):
|
||||
raise exc.ArgumentRequired(
|
||||
"'service_id' must be specified if no value is provided")
|
||||
if kwargs.get('value') and kwargs.get('service_id'):
|
||||
raise exc.InvalidArgumentError(
|
||||
"You can't specify a value when 'service_id' is specified.")
|
||||
if not kwargs.get('service_id') and not kwargs.get('field_id'):
|
||||
raise exc.ArgumentRequired("You must specify either 'service_id'"
|
||||
" or 'field_id'")
|
||||
elif kwargs.get('service_id') and kwargs.get('field_id'):
|
||||
raise exc.InvalidArgumentError(
|
||||
"You can't specify both 'service_id'and 'field_id'")
|
||||
body = dict(
|
||||
cost=kwargs.get('cost'),
|
||||
value=kwargs.get('value'),
|
||||
service_id=kwargs.get('service_id'),
|
||||
group_id=kwargs.get('group_id'),
|
||||
field_id=kwargs.get('field_id'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
)
|
||||
url = self.get_url('mappings', kwargs)
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_mapping(self, **kwargs):
|
||||
"""Delete a hashmap mapping.
|
||||
|
||||
:param mapping_id: ID of the mapping to delete.
|
||||
:type mapping_id: uuid
|
||||
"""
|
||||
if not kwargs.get('mapping_id'):
|
||||
raise exc.ArgumentRequired("'mapping_id' argument is required")
|
||||
url = self.get_url('mappings', kwargs)
|
||||
body = dict(mapping_id=kwargs['mapping_id'])
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def update_mapping(self, **kwargs):
|
||||
"""Update a hashmap mapping.
|
||||
|
||||
:param mapping_id: ID of the mapping to update
|
||||
:type mapping_id: uuid
|
||||
:param cost: Cost of the mapping
|
||||
:type cost: decimal.Decimal
|
||||
:param field_id: ID of the field the mapping belongs to
|
||||
:type field_id: uuid
|
||||
:param service_id: ID of the field the mapping belongs to
|
||||
:type service_id: uuid
|
||||
:param tenant_id: ID of the field the mapping belongs to
|
||||
:type tenant_id: uuid
|
||||
:param type: Type of the mapping (flat or rate)
|
||||
:type type: str
|
||||
:param value: Value of the mapping
|
||||
:type value: str
|
||||
"""
|
||||
if not kwargs.get('mapping_id'):
|
||||
raise exc.ArgumentRequired("'mapping_id' argument is required")
|
||||
mapping = self.get_mapping(**kwargs)
|
||||
for key in mapping.keys():
|
||||
value = kwargs.get(key, None)
|
||||
if value is not None and mapping[key] != value:
|
||||
mapping[key] = value
|
||||
url = self.get_url('mappings', kwargs)
|
||||
self.api_client.put(url, json=mapping)
|
||||
return self.get_mapping(**kwargs)
|
||||
|
||||
def get_mapping_group(self, **kwargs):
|
||||
"""Get the group attached to a mapping.
|
||||
|
||||
:param mapping_id: ID of the mapping to update
|
||||
:type mapping_id: uuid
|
||||
"""
|
||||
if not kwargs.get('mapping_id'):
|
||||
raise exc.ArgumentRequired("'mapping_id' argument is required")
|
||||
kwargs['resource_id'] = 'group'
|
||||
allowed_args = ['mapping_id']
|
||||
url = self.get_url('mappings', kwargs, allowed_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_group(self, **kwargs):
|
||||
"""Get the hashmap group corresponding to the given ID.
|
||||
|
||||
If group_id is not specified, returns a list of all hashmap groups.
|
||||
|
||||
:param group_id: Group ID
|
||||
:type group_id: uuid
|
||||
"""
|
||||
kwargs['resource_id'] = kwargs.get('group_id') or ''
|
||||
url = self.get_url('groups', kwargs)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_group(self, **kwargs):
|
||||
"""Create a hashmap group.
|
||||
|
||||
:param name: Name of the group
|
||||
:type name: str
|
||||
"""
|
||||
if not kwargs.get('name'):
|
||||
raise exc.ArgumentRequired("'name' argument is required")
|
||||
body = dict(name=kwargs['name'])
|
||||
url = self.get_url('groups', kwargs)
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_group(self, **kwargs):
|
||||
"""Delete a hashmap group.
|
||||
|
||||
:param group_id: ID of the group to delete
|
||||
:type group_id: uuid
|
||||
:param recursive: Delete mappings recursively
|
||||
:type recursive: bool
|
||||
"""
|
||||
if not kwargs.get('group_id'):
|
||||
raise exc.ArgumentRequired("'group_id' argument is required")
|
||||
body = dict(
|
||||
group_id=kwargs['group_id'],
|
||||
recursive=kwargs.get('recursive', False))
|
||||
url = self.get_url('groups', kwargs)
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def get_group_mappings(self, **kwargs):
|
||||
"""Get the mappings attached to the given group.
|
||||
|
||||
:param group_id: ID of the group
|
||||
:type group_id: uuid
|
||||
"""
|
||||
if not kwargs.get('group_id'):
|
||||
raise exc.ArgumentRequired("'group_id' argument is required")
|
||||
authorized_args = ['group_id']
|
||||
kwargs['resource_id'] = 'mappings'
|
||||
url = self.get_url('groups', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_group_thresholds(self, **kwargs):
|
||||
"""Get the thresholds attached to the given group.
|
||||
|
||||
:param group_id: ID of the group
|
||||
:type group_id: uuid
|
||||
"""
|
||||
if not kwargs.get('group_id'):
|
||||
raise exc.ArgumentRequired("'group_id' argument is required")
|
||||
authorized_args = ['group_id']
|
||||
kwargs['resource_id'] = 'thresholds'
|
||||
url = self.get_url('groups', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_threshold(self, **kwargs):
|
||||
"""Get hashmap thresholds.
|
||||
|
||||
If threshold_id is not provided, you need to specify either service_id,
|
||||
field_id or group_id.
|
||||
|
||||
:param threshold_id: ID of the threshold
|
||||
:type threshold_id: uuid
|
||||
:param service_id: ID of the service to filter on
|
||||
:type service_id: uuid
|
||||
:param group_id: ID of the group to filter on
|
||||
:type group_id: uuid
|
||||
:param field_id: ID of the field to filter on
|
||||
:type field_id: uuid
|
||||
:param tenant_id: ID of the tenant to filter on
|
||||
:type tenant_id: uuid
|
||||
:param filter_tenant: Explicitly filter on given tenant (allows to
|
||||
filter on tenant being None). Defaults to false.
|
||||
:type filter_tenant: bool
|
||||
:param no_group: Filter on orphaned thresholds.
|
||||
:type no_group: bool
|
||||
"""
|
||||
if not kwargs.get('threshold_id'):
|
||||
if not kwargs.get('service_id') and not kwargs.get('field_id') \
|
||||
and not kwargs.get('group_id'):
|
||||
raise exc.ArgumentRequired("You must provide either 'field_id'"
|
||||
", 'service_id' or 'group_id'.")
|
||||
allowed_args = ['service_id', 'group_id', 'field_id', 'tenant_id',
|
||||
'filter_tenant', 'no_group']
|
||||
else:
|
||||
allowed_args = []
|
||||
kwargs['resource_id'] = kwargs['threshold_id']
|
||||
url = self.get_url('thresholds', kwargs, allowed_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_threshold(self, **kwargs):
|
||||
"""Create a hashmap threshold.
|
||||
|
||||
:param cost: Cost of the threshold
|
||||
:type cost: decimal.Decimal
|
||||
:param field_id: ID of the field the threshold belongs to
|
||||
:type field_id: uuid
|
||||
:param service_id: ID of the service the threshold belongs to
|
||||
:type service_id: uuid
|
||||
:param tenant_id: ID of the tenant the threshold belongs to
|
||||
:type tenant_id: uuid
|
||||
:param group_id: ID of the group the threshold belongs to
|
||||
:type group_id: uuid
|
||||
:param type: Type of the threshold (flat or rate)
|
||||
:type type: str
|
||||
:param level: Level of the threshold
|
||||
:type level: str
|
||||
"""
|
||||
for arg in ['cost', 'level']:
|
||||
if not kwargs.get(arg):
|
||||
raise exc.ArgumentRequired(
|
||||
"'{}' argument is required".format(arg))
|
||||
if not kwargs.get('service_id') and not kwargs.get('field_id'):
|
||||
raise exc.ArgumentRequired("You must specify either 'service_id'"
|
||||
" or 'field_id'")
|
||||
body = dict(
|
||||
cost=kwargs.get('cost'),
|
||||
level=kwargs.get('level'),
|
||||
service_id=kwargs.get('service_id'),
|
||||
field_id=kwargs.get('field_id'),
|
||||
group_id=kwargs.get('group_id'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
)
|
||||
url = self.get_url('thresholds', kwargs)
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def delete_threshold(self, **kwargs):
|
||||
"""Delete a hashmap threshold.
|
||||
|
||||
:param threshold_id: ID of the threshold to delete.
|
||||
:type threshold_id: uuid
|
||||
"""
|
||||
if not kwargs.get('threshold_id'):
|
||||
raise exc.ArgumentRequired("'threshold_id' argument is required")
|
||||
url = self.get_url('thresholds', kwargs)
|
||||
body = dict(threshold_id=kwargs['threshold_id'])
|
||||
self.api_client.delete(url, json=body)
|
||||
|
||||
def update_threshold(self, **kwargs):
|
||||
"""Update a hashmap threshold.
|
||||
|
||||
:param threshold_id: ID of the threshold to update
|
||||
:type threshold_id: uuid
|
||||
:param cost: Cost of the threshold
|
||||
:type cost: decimal.Decimal
|
||||
:param field_id: ID of the field the threshold belongs to
|
||||
:type field_id: uuid
|
||||
:param service_id: ID of the field the threshold belongs to
|
||||
:type service_id: uuid
|
||||
:param tenant_id: ID of the field the threshold belongs to
|
||||
:type tenant_id: uuid
|
||||
:param type: Type of the threshold (flat or rate)
|
||||
:type type: str
|
||||
:param level: Level of the threshold
|
||||
:type level: str
|
||||
"""
|
||||
if not kwargs.get('threshold_id'):
|
||||
raise exc.ArgumentRequired("'threshold_id' argument is required")
|
||||
threshold = self.get_threshold(**kwargs)
|
||||
for key in threshold.keys():
|
||||
value = kwargs.get(key, None)
|
||||
if value is not None and threshold[key] != value:
|
||||
threshold[key] = value
|
||||
url = self.get_url('thresholds', kwargs)
|
||||
self.api_client.put(url, json=threshold)
|
||||
return self.get_threshold(**kwargs)
|
||||
|
||||
def get_threshold_group(self, **kwargs):
|
||||
"""Get the group attached to a threshold.
|
||||
|
||||
:param threshold_id: ID of the threshold to update
|
||||
:type threshold_id: uuid
|
||||
"""
|
||||
if not kwargs.get('threshold_id'):
|
||||
raise exc.ArgumentRequired("'threshold_id' argument is required")
|
||||
kwargs['resource_id'] = 'group'
|
||||
allowed_args = ['threshold_id']
|
||||
url = self.get_url('thresholds', kwargs, allowed_args)
|
||||
return self.api_client.get(url).json()
|
|
@ -1,161 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.common import base
|
||||
|
||||
|
||||
class BaseAttributeMixin(object):
|
||||
def _validate_attribute(self, attribute):
|
||||
attr = getattr(self, attribute)
|
||||
if attr:
|
||||
kwargs = {attribute: attr}
|
||||
return kwargs
|
||||
|
||||
def _get_resource(self, mgr, attribute):
|
||||
kwargs = self._validate_attribute(attribute)
|
||||
if kwargs:
|
||||
return mgr(client=self.manager.client).get(**kwargs)
|
||||
|
||||
def _get_resources(self, mgr, attribute):
|
||||
kwargs = self._validate_attribute(attribute)
|
||||
if kwargs:
|
||||
try:
|
||||
return mgr(client=self.manager.client).findall(**kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
class ServiceMixin(BaseAttributeMixin):
|
||||
@property
|
||||
def service(self):
|
||||
return self._get_resource(ServiceManager, 'service_id')
|
||||
|
||||
|
||||
class FieldMixin(BaseAttributeMixin):
|
||||
@property
|
||||
def field(self):
|
||||
return self._get_resource(FieldManager, 'field_id')
|
||||
|
||||
|
||||
class GroupMixin(BaseAttributeMixin):
|
||||
@property
|
||||
def group(self):
|
||||
return self._get_resource(GroupManager, 'group_id')
|
||||
|
||||
|
||||
class FieldsMixin(BaseAttributeMixin):
|
||||
attribute = ''
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
return self._get_resources(FieldManager, self.attribute)
|
||||
|
||||
|
||||
class MappingsMixin(BaseAttributeMixin):
|
||||
attribute = ''
|
||||
|
||||
@property
|
||||
def mappings(self):
|
||||
return self._get_resources(MappingManager, self.attribute)
|
||||
|
||||
|
||||
class ThresholdsMixin(BaseAttributeMixin):
|
||||
attribute = ''
|
||||
|
||||
@property
|
||||
def thresholds(self):
|
||||
return self._get_resources(ThresholdManager, self.attribute)
|
||||
|
||||
|
||||
class Service(base.Resource, FieldsMixin, MappingsMixin, ThresholdsMixin):
|
||||
key = 'service'
|
||||
attribute = 'service_id'
|
||||
|
||||
def __repr__(self):
|
||||
return "<hashmap.Service %s>" % self._info
|
||||
|
||||
|
||||
class ServiceManager(base.CrudManager):
|
||||
resource_class = Service
|
||||
base_url = '/v1/rating/module_config/hashmap'
|
||||
key = 'service'
|
||||
collection_key = 'services'
|
||||
|
||||
|
||||
class Field(base.Resource, ServiceMixin, MappingsMixin, ThresholdsMixin):
|
||||
key = 'field'
|
||||
attribute = 'field_id'
|
||||
|
||||
def __repr__(self):
|
||||
return "<hashmap.Field %s>" % self._info
|
||||
|
||||
|
||||
class FieldManager(base.CrudManager):
|
||||
resource_class = Field
|
||||
base_url = '/v1/rating/module_config/hashmap'
|
||||
key = 'field'
|
||||
collection_key = 'fields'
|
||||
|
||||
|
||||
class Mapping(base.Resource, ServiceMixin, FieldMixin, GroupMixin):
|
||||
key = 'mapping'
|
||||
|
||||
def __repr__(self):
|
||||
return "<hashmap.Mapping %s>" % self._info
|
||||
|
||||
|
||||
class MappingManager(base.CrudManager):
|
||||
resource_class = Mapping
|
||||
base_url = '/v1/rating/module_config/hashmap'
|
||||
key = 'mapping'
|
||||
collection_key = 'mappings'
|
||||
|
||||
|
||||
class Group(base.Resource, MappingsMixin, ThresholdsMixin):
|
||||
key = 'group'
|
||||
attribute = 'group_id'
|
||||
|
||||
def __repr__(self):
|
||||
return "<hashmap.Group %s>" % self._info
|
||||
|
||||
def delete(self, recursive=False):
|
||||
return self.manager.delete(group_id=self.group_id, recursive=recursive)
|
||||
|
||||
|
||||
class GroupManager(base.CrudManager):
|
||||
resource_class = Group
|
||||
base_url = '/v1/rating/module_config/hashmap'
|
||||
key = 'group'
|
||||
collection_key = 'groups'
|
||||
|
||||
def delete(self, group_id, recursive=False):
|
||||
url = self.build_url(group_id=group_id)
|
||||
if recursive:
|
||||
url += "?recursive=True"
|
||||
return self._delete(url)
|
||||
|
||||
|
||||
class Threshold(base.Resource, ServiceMixin, FieldMixin, GroupMixin):
|
||||
key = 'threshold'
|
||||
|
||||
def __repr__(self):
|
||||
return "<hashmap.Threshold %s>" % self._info
|
||||
|
||||
|
||||
class ThresholdManager(base.CrudManager):
|
||||
resource_class = Threshold
|
||||
base_url = '/v1/rating/module_config/hashmap'
|
||||
key = 'threshold'
|
||||
collection_key = 'thresholds'
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.v1.rating import hashmap
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the Hashmap v1 API.
|
||||
|
||||
:param http_client: A http client.
|
||||
"""
|
||||
|
||||
def __init__(self, http_client):
|
||||
"""Initialize a new client for the Hashmap v1 API."""
|
||||
self.http_client = http_client
|
||||
self.services = hashmap.ServiceManager(self.http_client)
|
||||
self.fields = hashmap.FieldManager(self.http_client)
|
||||
self.mappings = hashmap.MappingManager(self.http_client)
|
||||
self.groups = hashmap.GroupManager(self.http_client)
|
||||
self.thresholds = hashmap.ThresholdManager(self.http_client)
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.v1.rating.hashmap import client
|
||||
from cloudkittyclient.v1.rating.hashmap import shell
|
||||
|
||||
|
||||
class Extension(object):
|
||||
"""Hashmap extension.
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_client(http_client):
|
||||
return client.Client(http_client)
|
||||
|
||||
@staticmethod
|
||||
def get_shell():
|
||||
return shell
|
|
@ -1,434 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from oslo_utils import strutils
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.common import utils
|
||||
from cloudkittyclient import exc
|
||||
|
||||
_bool_strict = functools.partial(strutils.bool_from_string, strict=True)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Service name',
|
||||
required=True)
|
||||
def do_hashmap_service_create(cc, args={}):
|
||||
"""Create a service."""
|
||||
arg_to_field_mapping = {
|
||||
'name': 'name'
|
||||
}
|
||||
fields = {}
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
fields[arg_to_field_mapping.get(k, k)] = v
|
||||
out = cc.hashmap.services.create(**fields)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
def do_hashmap_service_list(cc, args={}):
|
||||
"""List services."""
|
||||
try:
|
||||
services = cc.hashmap.services.list()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Services not found.')
|
||||
else:
|
||||
field_labels = ['Name', 'Service id']
|
||||
fields = ['name', 'service_id']
|
||||
utils.print_list(services, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service uuid',
|
||||
required=True)
|
||||
def do_hashmap_service_delete(cc, args={}):
|
||||
"""Delete a service."""
|
||||
try:
|
||||
cc.hashmap.services.delete(service_id=args.service_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Service not found: %s' % args.service_id)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Field name',
|
||||
required=True)
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=True)
|
||||
def do_hashmap_field_create(cc, args={}):
|
||||
"""Create a field."""
|
||||
arg_to_field_mapping = {
|
||||
'name': 'name',
|
||||
'service_id': 'service_id'
|
||||
}
|
||||
fields = {}
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
fields[arg_to_field_mapping.get(k, k)] = v
|
||||
out = cc.hashmap.fields.create(**fields)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=True)
|
||||
def do_hashmap_field_list(cc, args={}):
|
||||
"""List fields."""
|
||||
try:
|
||||
created_field = cc.hashmap.fields.list(service_id=args.service_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Fields not found in service: %s'
|
||||
% args.service_id)
|
||||
else:
|
||||
field_labels = ['Name', 'Field id']
|
||||
fields = ['name', 'field_id']
|
||||
utils.print_list(created_field, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-f', '--field-id',
|
||||
help='Field uuid',
|
||||
required=True)
|
||||
def do_hashmap_field_delete(cc, args={}):
|
||||
"""Delete a field."""
|
||||
try:
|
||||
cc.hashmap.fields.delete(field_id=args.field_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Field not found: %s' % args.field_id)
|
||||
|
||||
|
||||
def common_hashmap_mapping_arguments(create=False):
|
||||
def _wrapper(func):
|
||||
@utils.arg('-c', '--cost',
|
||||
help='Mapping cost',
|
||||
required=create)
|
||||
@utils.arg('-v', '--value',
|
||||
help='Mapping value',
|
||||
required=False)
|
||||
@utils.arg('-t', '--type',
|
||||
help='Mapping type (flat, rate)',
|
||||
required=False)
|
||||
@utils.arg('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
@utils.arg('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
@functools.wraps(func)
|
||||
def _wrapped(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return _wrapped
|
||||
return _wrapper
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
@utils.arg('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
@common_hashmap_mapping_arguments(create=True)
|
||||
def do_hashmap_mapping_create(cc, args={}):
|
||||
"""Create a mapping."""
|
||||
arg_to_field_mapping = {
|
||||
'cost': 'cost',
|
||||
'value': 'value',
|
||||
'type': 'type',
|
||||
'service_id': 'service_id',
|
||||
'field_id': 'field_id',
|
||||
'group_id': 'group_id',
|
||||
'project_id': 'tenant_id',
|
||||
}
|
||||
fields = {}
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
fields[arg_to_field_mapping.get(k, k)] = v
|
||||
out = cc.hashmap.mappings.create(**fields)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-m', '--mapping-id',
|
||||
help='Mapping id',
|
||||
required=True)
|
||||
@common_hashmap_mapping_arguments()
|
||||
def do_hashmap_mapping_update(cc, args={}):
|
||||
"""Update a mapping."""
|
||||
arg_to_field_mapping = {
|
||||
'mapping_id': 'mapping_id',
|
||||
'cost': 'cost',
|
||||
'value': 'value',
|
||||
'type': 'type',
|
||||
'group_id': 'group_id',
|
||||
'project_id': 'tenant_id',
|
||||
}
|
||||
try:
|
||||
mapping = cc.hashmap.mappings.get(mapping_id=args.mapping_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Mapping not found: %s' % args.mapping_id)
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
setattr(mapping, k, v)
|
||||
cc.hashmap.mappings.update(**mapping.dirty_fields)
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
@utils.arg('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
@utils.arg('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
@utils.arg('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
def do_hashmap_mapping_list(cc, args={}):
|
||||
"""List mappings."""
|
||||
if (args.group_id is None and
|
||||
args.service_id is None and args.field_id is None):
|
||||
raise exc.CommandError("Provide either group-id, service-id or "
|
||||
"field-id")
|
||||
try:
|
||||
mappings = cc.hashmap.mappings.list(service_id=args.service_id,
|
||||
field_id=args.field_id,
|
||||
group_id=args.group_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Mappings not found for field: %s'
|
||||
% args.field_id)
|
||||
else:
|
||||
field_labels = ['Mapping id', 'Value', 'Cost',
|
||||
'Type', 'Field id',
|
||||
'Service id', 'Group id', 'Tenant id']
|
||||
fields = ['mapping_id', 'value', 'cost',
|
||||
'type', 'field_id',
|
||||
'service_id', 'group_id', 'tenant_id']
|
||||
utils.print_list(mappings, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-m', '--mapping-id',
|
||||
help='Mapping uuid',
|
||||
required=True)
|
||||
def do_hashmap_mapping_delete(cc, args={}):
|
||||
"""Delete a mapping."""
|
||||
try:
|
||||
cc.hashmap.mappings.delete(mapping_id=args.mapping_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Mapping not found: %s' % args.mapping_id)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Group name',
|
||||
required=True)
|
||||
def do_hashmap_group_create(cc, args={}):
|
||||
"""Create a group."""
|
||||
arg_to_field_mapping = {
|
||||
'name': 'name',
|
||||
}
|
||||
fields = {}
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
fields[arg_to_field_mapping.get(k, k)] = v
|
||||
group = cc.hashmap.groups.create(**fields)
|
||||
utils.print_dict(group.to_dict())
|
||||
|
||||
|
||||
def do_hashmap_group_list(cc, args={}):
|
||||
"""List groups."""
|
||||
try:
|
||||
groups = cc.hashmap.groups.list()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Groups not found.')
|
||||
else:
|
||||
field_labels = ['Name',
|
||||
'Group id']
|
||||
fields = ['name', 'group_id']
|
||||
utils.print_list(groups, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-g', '--group-id',
|
||||
help='Group uuid',
|
||||
required=True)
|
||||
@utils.arg('-r', '--recursive',
|
||||
help="""Delete the group's mappings""",
|
||||
required=False,
|
||||
default=False)
|
||||
def do_hashmap_group_delete(cc, args={}):
|
||||
"""Delete a group."""
|
||||
try:
|
||||
cc.hashmap.groups.delete(group_id=args.group_id,
|
||||
recursive=args.recursive)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Group not found: %s' % args.group_id)
|
||||
|
||||
|
||||
def common_hashmap_threshold_arguments(create=False):
|
||||
def _wrapper(func):
|
||||
@utils.arg('-l', '--level',
|
||||
help='Threshold level',
|
||||
required=create)
|
||||
@utils.arg('-c', '--cost',
|
||||
help='Threshold cost',
|
||||
required=create)
|
||||
@utils.arg('-t', '--type',
|
||||
help='Threshold type (flat, rate)',
|
||||
required=False)
|
||||
@utils.arg('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
@utils.arg('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
@functools.wraps(func)
|
||||
def _wrapped(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return _wrapped
|
||||
return _wrapper
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
@utils.arg('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
@common_hashmap_threshold_arguments(create=True)
|
||||
def do_hashmap_threshold_create(cc, args={}):
|
||||
"""Create a mapping."""
|
||||
arg_to_field_mapping = {
|
||||
'level': 'level',
|
||||
'cost': 'cost',
|
||||
'type': 'type',
|
||||
'service_id': 'service_id',
|
||||
'field_id': 'field_id',
|
||||
'group_id': 'group_id',
|
||||
'project_id': 'tenant_id',
|
||||
}
|
||||
fields = {}
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
fields[arg_to_field_mapping.get(k, k)] = v
|
||||
out = cc.hashmap.thresholds.create(**fields)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-i', '--threshold-id',
|
||||
help='Threshold id',
|
||||
required=True)
|
||||
@common_hashmap_threshold_arguments()
|
||||
def do_hashmap_threshold_update(cc, args={}):
|
||||
"""Update a threshold."""
|
||||
arg_to_field_mapping = {
|
||||
'threshold_id': 'threshold_id',
|
||||
'cost': 'cost',
|
||||
'level': 'level',
|
||||
'type': 'type',
|
||||
'group_id': 'group_id',
|
||||
'project_id': 'tenant_id',
|
||||
}
|
||||
try:
|
||||
threshold = cc.hashmap.thresholds.get(threshold_id=args.threshold_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Threshold not found: %s' % args.threshold_id)
|
||||
for k, v in vars(args).items():
|
||||
if k in arg_to_field_mapping:
|
||||
if v is not None:
|
||||
setattr(threshold, k, v)
|
||||
cc.hashmap.thresholds.update(**threshold.dirty_fields)
|
||||
|
||||
|
||||
@utils.arg('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
@utils.arg('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
@utils.arg('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
@utils.arg('--no-group',
|
||||
type=_bool_strict, metavar='{True,False}',
|
||||
help='If True, list only orhpaned thresholds',
|
||||
required=False)
|
||||
@utils.arg('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
def do_hashmap_threshold_list(cc, args={}):
|
||||
"""List thresholds."""
|
||||
if (args.group_id is None and
|
||||
args.service_id is None and args.field_id is None):
|
||||
raise exc.CommandError("Provide either group-id, service-id or "
|
||||
"field-id")
|
||||
try:
|
||||
thresholds = cc.hashmap.thresholds.list(service_id=args.service_id,
|
||||
field_id=args.field_id,
|
||||
group_id=args.group_id,
|
||||
no_group=args.no_group)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Thresholds not found')
|
||||
else:
|
||||
field_labels = ['Threshold id', 'Level', 'Cost',
|
||||
'Type', 'Field id',
|
||||
'Service id', 'Group id', 'Tenant id']
|
||||
fields = ['threshold_id', 'level', 'cost',
|
||||
'type', 'field_id',
|
||||
'service_id', 'group_id', 'tenant_id']
|
||||
utils.print_list(thresholds, fields, field_labels, sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
def do_hashmap_threshold_delete(cc, args={}):
|
||||
"""Delete a threshold."""
|
||||
try:
|
||||
cc.hashmap.thresholds.delete(threshold_id=args.threshold_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Threshold not found: %s' % args.threshold_id)
|
||||
|
||||
|
||||
@utils.arg('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
def do_hashmap_threshold_get(cc, args={}):
|
||||
"""Get a threshold."""
|
||||
try:
|
||||
threshold = cc.hashmap.thresholds.get(threshold_id=args.threshold_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Threshold not found: %s' % args.threshold_id)
|
||||
utils.print_dict(threshold.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
def do_hashmap_threshold_group(cc, args={}):
|
||||
"""Get a threshold group."""
|
||||
try:
|
||||
threshold = cc.hashmap.thresholds.group(threshold_id=args.threshold_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Threshold not found: %s' % args.threshold_id)
|
||||
utils.print_dict(threshold.to_dict())
|
|
@ -1,355 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from osc_lib.command import command
|
||||
from oslo_utils import strutils
|
||||
|
||||
from cloudkittyclient.v1.rating.hashmap import shell
|
||||
|
||||
|
||||
_bool_strict = functools.partial(strutils.bool_from_string, strict=True)
|
||||
|
||||
|
||||
class CliHashmapServiceCreate(command.Command):
|
||||
"""Create a service."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapServiceCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Service name',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_service_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapServiceList(command.Command):
|
||||
"""List services."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_service_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapServiceDelete(command.Command):
|
||||
"""Delete a service."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapServiceDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_service_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapFieldCreate(command.Command):
|
||||
"""Create a field."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapFieldCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=True)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Field name',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_field_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapFieldList(command.Command):
|
||||
"""List fields."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapFieldList, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_field_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapFieldDelete(command.Command):
|
||||
"""Delete a field."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapFieldDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_field_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapMappingCommon(command.Command):
|
||||
def get_parser(self, prog_name, cost=False):
|
||||
parser = super(CliHashmapMappingCommon, self).get_parser(prog_name)
|
||||
parser.add_argument('-c', '--cost',
|
||||
help='Mapping Cost',
|
||||
required=cost)
|
||||
parser.add_argument('-v', '--value',
|
||||
help='Mapping Value',
|
||||
required=False)
|
||||
parser.add_argument('-t', '--type',
|
||||
help='Mapping type (flat, rate)',
|
||||
required=False)
|
||||
parser.add_argument('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
parser.add_argument('-p', '--project-id',
|
||||
help='Project/Tenant id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
|
||||
class CliHashmapMappingCreate(CliHashmapMappingCommon):
|
||||
"""Create a mapping."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapMappingCreate, self).get_parser(prog_name,
|
||||
cost=True)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
parser.add_argument('-f', '--field-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_mapping_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapMappingUpdate(CliHashmapMappingCommon):
|
||||
"""Update a mapping."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapMappingUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument('-m', '--mapping-id',
|
||||
help='Mapping id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_mapping_update(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapMappingList(command.Command):
|
||||
"""List mappings."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapMappingList, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
parser.add_argument('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
parser.add_argument('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
parser.add_argument('-p', '--project-id',
|
||||
help='Project id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_mapping_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapMappingDelete(command.Command):
|
||||
"""Delete a mapping."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapMappingDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-m', '--mapping-id',
|
||||
help='Mapping id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_mapping_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapGroupCreate(command.Command):
|
||||
"""Create a group."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapGroupCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Group name.',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_group_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapGroupList(command.Command):
|
||||
"""List groups."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_group_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapGroupDelete(command.Command):
|
||||
"""Delete a group."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapGroupDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-g', '--group-id',
|
||||
help='Group uuid',
|
||||
required=True)
|
||||
parser.add_argument('-r', '--recursive',
|
||||
help="""Delete the group's mappings.""",
|
||||
required=False,
|
||||
default=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_group_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdCommon(command.Command):
|
||||
def get_parser(self, prog_name, create=False):
|
||||
parser = super(CliHashmapThresholdCommon, self).get_parser(prog_name)
|
||||
parser.add_argument('-l', '--level',
|
||||
help='Threshold level',
|
||||
required=create)
|
||||
parser.add_argument('-c', '--cost',
|
||||
help='Threshold cost',
|
||||
required=create)
|
||||
parser.add_argument('-t', '--type',
|
||||
help='Threshold type',
|
||||
required=False)
|
||||
parser.add_argument('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
parser.add_argument('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
|
||||
class CliHashmapThresholdCreate(CliHashmapThresholdCommon):
|
||||
"""Create a threshold."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdCreate, self).get_parser(prog_name,
|
||||
create=True)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
parser.add_argument('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdUpdate(CliHashmapThresholdCommon):
|
||||
"""Update a threshold."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument('-i', '--threshold-id',
|
||||
help='Threshold id',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_update(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdList(command.Command):
|
||||
"""List thresholds."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdList, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id',
|
||||
help='Service id',
|
||||
required=False)
|
||||
parser.add_argument('-f', '--field-id',
|
||||
help='Field id',
|
||||
required=False)
|
||||
parser.add_argument('-g', '--group-id',
|
||||
help='Group id',
|
||||
required=False)
|
||||
parser.add_argument('--no-group',
|
||||
type=_bool_strict, metavar='{True,False}',
|
||||
help='If True, list only orphaned thresholds',
|
||||
required=False)
|
||||
parser.add_argument('-p', '--project-id',
|
||||
help='Project/tenant id',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdDelete(command.Command):
|
||||
"""Delete a threshold."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdGet(command.Command):
|
||||
"""Get a threshold."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliHashmapThresholdGroup(command.Command):
|
||||
"""Get a threshold group."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliHashmapThresholdGroup, self).get_parser(prog_name)
|
||||
parser.add_argument('-i', '--threshold-id',
|
||||
help='Threshold uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_hashmap_threshold_group(ckclient, parsed_args)
|
|
@ -0,0 +1,567 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import command
|
||||
from cliff import lister
|
||||
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class CliGetMappingTypes(lister.Lister):
|
||||
"""Get hashmap mapping types/"""
|
||||
def take_action(self, parsed_args):
|
||||
client = utils.get_client_from_osc(self)
|
||||
resp = client.rating.hashmap.get_mapping_types()
|
||||
return ['Mapping types'], [[item] for item in resp]
|
||||
|
||||
|
||||
class CliGetService(lister.Lister):
|
||||
"""Get a hashmap service"""
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_service(
|
||||
service_id=parsed_args.service_id,
|
||||
)
|
||||
# NOTE(lukapeschke): This can't be done with 'or', because it would
|
||||
# lead to resp being [[]] if resp['services'] is an empty list. Having
|
||||
# a list in a list causes cliff to display a row of 'None' instead of
|
||||
# nothing
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetService, self).get_parser(prog_name)
|
||||
parser.add_argument('service_id', type=str, help='Service ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListService(lister.Lister):
|
||||
"""List hashmap services."""
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_service()
|
||||
# NOTE(lukapeschke): This can't be done with 'or', because it would
|
||||
# lead to resp being [[]] if resp['services'] is an empty list. Having
|
||||
# a list in a list causes cliff to display a row of 'None' instead of
|
||||
# nothing
|
||||
values = utils.list_to_cols(resp['services'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
|
||||
class CliCreateService(lister.Lister):
|
||||
"""Create a hashmap service."""
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.create_service(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateService, self).get_parser(prog_name)
|
||||
parser.add_argument('name', type=str, help='Service Name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteService(command.Command):
|
||||
"""Delete a hashmap service"""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.hashmap.delete_service(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteService, self).get_parser(prog_name)
|
||||
parser.add_argument('service_id', type=str, help='Service ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliGetField(lister.Lister):
|
||||
"""Get a Hashmap field."""
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_field(
|
||||
field_id=parsed_args.field_id,
|
||||
)
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetField, self).get_parser(prog_name)
|
||||
parser.add_argument('field_id', type=str, help='Field ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListField(lister.Lister):
|
||||
"""List hashmap fields for the given service."""
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_field(
|
||||
service_id=parsed_args.service_id,
|
||||
)
|
||||
values = utils.list_to_cols(resp['fields'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliListField, self).get_parser(prog_name)
|
||||
parser.add_argument('service_id', type=str, help='Service ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCreateField(lister.Lister):
|
||||
"""Create a hashmap field."""
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.create_field(
|
||||
**vars(parsed_args))
|
||||
resp = [resp] if resp.get('fields') is None else resp['fields']
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateField, self).get_parser(prog_name)
|
||||
parser.add_argument('service_id', type=str, help='Service ID')
|
||||
parser.add_argument('name', type=str, help='Field name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteField(command.Command):
|
||||
"""Delete a hashmap field."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.hashmap.delete_field(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteField, self).get_parser(prog_name)
|
||||
parser.add_argument('field_id', type=str, help='Field ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliGetMapping(lister.Lister):
|
||||
"""Get a hashmap mapping."""
|
||||
|
||||
columns = [
|
||||
('mapping_id', 'Mapping ID'),
|
||||
('value', 'Value'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_mapping(
|
||||
mapping_id=parsed_args.mapping_id)
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetMapping, self).get_parser(prog_name)
|
||||
parser.add_argument('mapping_id', type=str,
|
||||
help='Mapping ID to filter on')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListMapping(lister.Lister):
|
||||
"""List hashmap mappings."""
|
||||
|
||||
columns = [
|
||||
('mapping_id', 'Mapping ID'),
|
||||
('value', 'Value'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_mapping(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols(resp['mappings'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliListMapping, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str,
|
||||
help='Service ID to filter on')
|
||||
parser.add_argument('-g', '--group-id', type=str,
|
||||
help='Group ID to filter on')
|
||||
parser.add_argument('--field-id', type=str,
|
||||
help='Field ID to filter on')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID to filter on')
|
||||
parser.add_argument('--filter-tenant', action='store_true',
|
||||
help='Explicitly filter on given tenant (allows '
|
||||
'to filter on tenant being None)')
|
||||
parser.add_argument('--no-group', action='store_true',
|
||||
help='Filter on orphaned mappings')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCreateMapping(lister.Lister):
|
||||
"""Create a Hashmap mapping."""
|
||||
columns = [
|
||||
('mapping_id', 'Mapping ID'),
|
||||
('value', 'Value'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.create_mapping(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateMapping, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str, help='Service ID')
|
||||
parser.add_argument('-g', '--group-id', type=str, help='Group ID')
|
||||
parser.add_argument('--field-id', type=str, help='Field ID')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID')
|
||||
parser.add_argument('-t', '--type', type=str, help='Mapping type')
|
||||
parser.add_argument('--value', type=str, help='Value')
|
||||
parser.add_argument('cost', type=float, help='Cost')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteMapping(command.Command):
|
||||
"""Delete a Hashmap mapping."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.hashmap.delete_mapping(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteMapping, self).get_parser(prog_name)
|
||||
parser.add_argument('mapping_id', type=str, help='Mapping ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliUpdateMapping(lister.Lister):
|
||||
"""Update a Hashmap mapping."""
|
||||
|
||||
columns = [
|
||||
('mapping_id', 'Mapping ID'),
|
||||
('value', 'Value'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.update_mapping(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliUpdateMapping, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str, help='Service ID')
|
||||
parser.add_argument('-g', '--group-id', type=str, help='Group ID')
|
||||
parser.add_argument('--field-id', type=str, help='Field ID')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID')
|
||||
parser.add_argument('--value', type=str, help='Value')
|
||||
parser.add_argument('--cost', type=str, help='Cost')
|
||||
parser.add_argument('mapping_id', type=str, help='Mapping ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListGroup(lister.Lister):
|
||||
"""List existing hashmap groups."""
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('group_id', 'Group ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_group()
|
||||
values = utils.list_to_cols(resp['groups'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
|
||||
class CliCreateGroup(lister.Lister):
|
||||
"""Create a Hashmap group."""
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('group_id', 'Group ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.create_group(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateGroup, self).get_parser(prog_name)
|
||||
parser.add_argument('name', type=str, help='Group Name')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteGroup(command.Command):
|
||||
"""Create a Hashmap group."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.hashmap.delete_group(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteGroup, self).get_parser(prog_name)
|
||||
parser.add_argument('--recursive', action='store_true',
|
||||
help='Delete mappings recursively')
|
||||
parser.add_argument('group_id', type=str, help='Group ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliGetGroupMappings(lister.Lister):
|
||||
"""Get all Hashmap mappings for the given group."""
|
||||
|
||||
columns = [
|
||||
('mapping_id', 'Mapping ID'),
|
||||
('value', 'Value'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = utils.get_client_from_osc(self)
|
||||
resp = client.rating.hashmap.get_group_mappings(**vars(parsed_args))
|
||||
return ([col[1] for col in self.columns],
|
||||
utils.list_to_cols(resp.get('mappings', []), self.columns))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetGroupMappings, self).get_parser(prog_name)
|
||||
parser.add_argument('group_id', type=str, help='Group ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliGetGroupThresholds(lister.Lister):
|
||||
"""Get all thresholds for the given group."""
|
||||
|
||||
columns = [
|
||||
('threshold_id', 'Threshold ID'),
|
||||
('level', 'Level'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = utils.get_client_from_osc(self)
|
||||
resp = client.rating.hashmap.get_group_thresholds(**vars(parsed_args))
|
||||
return ([col[1] for col in self.columns],
|
||||
utils.list_to_cols(resp.get('thresholds', []), self.columns))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetGroupThresholds, self).get_parser(prog_name)
|
||||
parser.add_argument('group_id', type=str, help='Group ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliGetThreshold(lister.Lister):
|
||||
"""Get a Hashmap threshold."""
|
||||
|
||||
columns = [
|
||||
('threshold_id', 'Threshold ID'),
|
||||
('level', 'Level'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_threshold(
|
||||
threshold_id=parsed_args.threshold_id,
|
||||
)
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetThreshold, self).get_parser(prog_name)
|
||||
parser.add_argument('threshold_id', type=str,
|
||||
help='Threshold ID to filter on')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListThreshold(lister.Lister):
|
||||
"""List Hashmap thresholds"""
|
||||
columns = [
|
||||
('threshold_id', 'Threshold ID'),
|
||||
('level', 'Level'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.get_threshold(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols(resp['thresholds'], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliListThreshold, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str,
|
||||
help='Service ID to filter on')
|
||||
parser.add_argument('-g', '--group-id', type=str,
|
||||
help='Group ID to filter on')
|
||||
parser.add_argument('--field-id', type=str,
|
||||
help='Field ID to filter on')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID to filter on')
|
||||
parser.add_argument('--filter-tenant', action='store_true',
|
||||
help='Explicitly filter on given tenant (allows '
|
||||
'to filter on tenant being None)')
|
||||
parser.add_argument('--no-group', action='store_true',
|
||||
help='Filter on orphaned thresholds')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCreateThreshold(lister.Lister):
|
||||
"""Create a Hashmap threshold."""
|
||||
columns = [
|
||||
('threshold_id', 'Threshold ID'),
|
||||
('level', 'Level'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.create_threshold(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateThreshold, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str, help='Service ID')
|
||||
parser.add_argument('-g', '--group-id', type=str, help='Group ID')
|
||||
parser.add_argument('--field-id', type=str, help='Field ID')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID')
|
||||
parser.add_argument('-t', '--type', type=str, help='Threshold type')
|
||||
parser.add_argument('level', type=str, help='Threshold level')
|
||||
parser.add_argument('cost', type=float, help='Cost')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteThreshold(command.Command):
|
||||
"""Delete a Hashmap threshold."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.hashmap.delete_threshold(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteThreshold, self).get_parser(prog_name)
|
||||
parser.add_argument('threshold_id', type=str, help='Threshold ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliUpdateThreshold(lister.Lister):
|
||||
"""Update a Hashmap threshold."""
|
||||
|
||||
columns = [
|
||||
('threshold_id', 'Threshold ID'),
|
||||
('level', 'Level'),
|
||||
('cost', 'Cost'),
|
||||
('type', 'Type'),
|
||||
('field_id', 'Field ID'),
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.hashmap.update_threshold(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols([resp], self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliUpdateThreshold, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--service-id', type=str, help='Service ID')
|
||||
parser.add_argument('-g', '--group-id', type=str, help='Group ID')
|
||||
parser.add_argument('--field-id', type=str, help='Field ID')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Project ID')
|
||||
parser.add_argument('-l', '--level', type=str, help='Threshold level')
|
||||
parser.add_argument('--cost', type=str, help='Cost')
|
||||
parser.add_argument('threshold_id', type=str, help='Threshold ID')
|
||||
return parser
|
|
@ -0,0 +1,91 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient import exc
|
||||
from cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
class PyscriptManager(base.BaseManager):
|
||||
"""Class used to manage the Pyscript rating module"""
|
||||
|
||||
url = '/v1/rating/module_config/pyscripts/{endpoint}/{script_id}'
|
||||
|
||||
def list_scripts(self, **kwargs):
|
||||
"""Get a list of all pyscripts.
|
||||
|
||||
:param no_data: Set to True to remove script data from output.
|
||||
:type no_data: bool
|
||||
"""
|
||||
authorized_args = ['no_data']
|
||||
url = self.get_url('scripts', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_script(self, **kwargs):
|
||||
"""Get the script corresponding to the given ID.
|
||||
|
||||
:param script_id: ID of the script.
|
||||
:type script_id: str
|
||||
"""
|
||||
if not kwargs.get('script_id'):
|
||||
raise exc.ArgumentRequired("Argument 'script_id' is required.")
|
||||
url = self.get_url('scripts', kwargs)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def create_script(self, **kwargs):
|
||||
"""Create a new script.
|
||||
|
||||
:param name: Name of the script to create
|
||||
:type name: str
|
||||
:param data: Content of the script
|
||||
:type data: str
|
||||
"""
|
||||
for arg in ('name', 'data'):
|
||||
if not kwargs.get(arg):
|
||||
raise exc.ArgumentRequired(
|
||||
"'Argument {} is required.'".format(arg))
|
||||
url = self.get_url('scripts', kwargs)
|
||||
body = dict(name=kwargs['name'], data=kwargs['data'])
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def update_script(self, **kwargs):
|
||||
"""Update an existing script.
|
||||
|
||||
:param script_id: ID of the script to update
|
||||
:type script_id: str
|
||||
:param name: Name of the script to create
|
||||
:type name: str
|
||||
:param data: Content of the script
|
||||
:type data: str
|
||||
"""
|
||||
if not kwargs.get('script_id'):
|
||||
raise exc.ArgumentRequired("Argument 'script_id' is required.")
|
||||
script = self.get_script(script_id=kwargs['script_id'])
|
||||
for key in ('name', 'data'):
|
||||
if kwargs.get(key):
|
||||
script[key] = kwargs[key]
|
||||
script.pop('checksum', None)
|
||||
url = self.get_url('scripts', kwargs)
|
||||
return self.api_client.put(url, json=script).json()
|
||||
|
||||
def delete_script(self, **kwargs):
|
||||
"""Delete a script.
|
||||
|
||||
:param script_id: ID of the script to update
|
||||
:type script_id: str
|
||||
"""
|
||||
if not kwargs.get('script_id'):
|
||||
raise exc.ArgumentRequired("Argument 'script_id' is required.")
|
||||
url = self.get_url('scripts', kwargs)
|
||||
self.api_client.delete(url)
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.common import base
|
||||
|
||||
|
||||
class Script(base.Resource):
|
||||
key = 'script'
|
||||
|
||||
def __repr__(self):
|
||||
return "<pyscripts.Script %s>" % self._info
|
||||
|
||||
|
||||
class ScriptManager(base.CrudManager):
|
||||
resource_class = Script
|
||||
base_url = '/v1/rating/module_config/pyscripts'
|
||||
key = 'script'
|
||||
collection_key = 'scripts'
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.v1.rating import pyscripts
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the PyScripts v1 API.
|
||||
|
||||
:param http_client: A http client.
|
||||
"""
|
||||
|
||||
def __init__(self, http_client):
|
||||
"""Initialize a new client for the PyScripts v1 API."""
|
||||
self.http_client = http_client
|
||||
self.scripts = pyscripts.ScriptManager(self.http_client)
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.v1.rating.pyscripts import client
|
||||
from cloudkittyclient.v1.rating.pyscripts import shell
|
||||
|
||||
|
||||
class Extension(object):
|
||||
"""PyScripts extension.
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_client(http_client):
|
||||
return client.Client(http_client)
|
||||
|
||||
@staticmethod
|
||||
def get_shell():
|
||||
return shell
|
|
@ -1,117 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import functools
|
||||
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.common import utils
|
||||
from cloudkittyclient import exc
|
||||
|
||||
_bool_strict = functools.partial(strutils.bool_from_string, strict=True)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Script name',
|
||||
required=True)
|
||||
@utils.arg('-f', '--file',
|
||||
help='Script file',
|
||||
required=False)
|
||||
def do_pyscripts_script_create(cc, args={}):
|
||||
"""Create a script."""
|
||||
script_args = {'name': args.name}
|
||||
if args.file:
|
||||
with open(args.file) as fp:
|
||||
script_args['data'] = fp.read()
|
||||
out = cc.pyscripts.scripts.create(**script_args)
|
||||
utils.print_dict(out.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-d', '--show-data',
|
||||
help='Show data in the listing',
|
||||
required=False,
|
||||
default=False)
|
||||
def do_pyscripts_script_list(cc, args={}):
|
||||
"""List scripts."""
|
||||
request_args = {}
|
||||
if not args.show_data:
|
||||
request_args['no_data'] = True
|
||||
scripts = cc.pyscripts.scripts.list(**request_args)
|
||||
field_labels = ['Name', 'Script id', 'Data', 'Checksum']
|
||||
fields = ['name', 'script_id', 'data', 'checksum']
|
||||
utils.print_list(scripts,
|
||||
fields,
|
||||
field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
def do_pyscripts_script_get(cc, args={}):
|
||||
"""Get script."""
|
||||
try:
|
||||
script = cc.pyscripts.scripts.get(script_id=args.script_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Script not found: %s' % args.script_id)
|
||||
utils.print_dict(script.to_dict())
|
||||
|
||||
|
||||
@utils.arg('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
def do_pyscripts_script_get_data(cc, args={}):
|
||||
"""Get script data."""
|
||||
try:
|
||||
script = cc.pyscripts.scripts.get(script_id=args.script_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Script not found: %s' % args.script_id)
|
||||
six.print_(script.data)
|
||||
|
||||
|
||||
@utils.arg('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
def do_pyscripts_script_delete(cc, args={}):
|
||||
"""Delete a script."""
|
||||
try:
|
||||
cc.pyscripts.scripts.delete(script_id=args.script_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Script not found: %s' % args.script_id)
|
||||
|
||||
|
||||
@utils.arg('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
@utils.arg('-f', '--file',
|
||||
help='Script file',
|
||||
required=True)
|
||||
def do_pyscripts_script_update(cc, args={}):
|
||||
"""Update a mapping."""
|
||||
excluded_fields = [
|
||||
'checksum',
|
||||
]
|
||||
with open(args.file) as fp:
|
||||
content = fp.read()
|
||||
try:
|
||||
script = cc.pyscripts.scripts.get(script_id=args.script_id)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Script not found: %s' % args.script_id)
|
||||
script_dict = script.to_dict()
|
||||
for field in excluded_fields:
|
||||
del script_dict[field]
|
||||
script_dict['data'] = content
|
||||
cc.pyscripts.scripts.update(**script_dict)
|
|
@ -1,115 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from osc_lib.command import command
|
||||
from oslo_utils import strutils
|
||||
|
||||
from cloudkittyclient.v1.rating.pyscripts import shell
|
||||
|
||||
|
||||
_bool_strict = functools.partial(strutils.bool_from_string, strict=True)
|
||||
|
||||
|
||||
class CliPyScriptCreate(command.Command):
|
||||
"""Create a script."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Script name',
|
||||
required=True)
|
||||
parser.add_argument('-f', '--file',
|
||||
help='Script file',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_create(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliPyScriptList(command.Command):
|
||||
"""List scripts."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptList, self).get_parser(prog_name)
|
||||
parser.add_argument('-d', '--show-data',
|
||||
help='Show data in the listing',
|
||||
required=False,
|
||||
default=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliPyScriptGet(command.Command):
|
||||
"""Get script."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliPyScriptGetData(command.Command):
|
||||
"""Get script data."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptGetData, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_get_data(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliPyScriptDelete(command.Command):
|
||||
"""Get script data."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptDelete, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_delete(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliPyScriptUpdate(command.Command):
|
||||
"""Update a script."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliPyScriptUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument('-s', '--script-id',
|
||||
help='Script uuid',
|
||||
required=True)
|
||||
parser.add_argument('-f', '--file',
|
||||
help='Script file',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_pyscripts_script_update(ckclient, parsed_args)
|
|
@ -0,0 +1,123 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import command
|
||||
from cliff import lister
|
||||
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class BaseScriptCli(lister.Lister):
|
||||
|
||||
columns = [
|
||||
('name', 'Name'),
|
||||
('script_id', 'Script ID'),
|
||||
('checksum', 'Checksum'),
|
||||
('data', 'Data'),
|
||||
]
|
||||
|
||||
|
||||
class CliGetScript(BaseScriptCli):
|
||||
"""Get a PyScript."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.pyscripts.get_script(
|
||||
**vars(parsed_args))
|
||||
resp = [resp]
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetScript, self).get_parser(prog_name)
|
||||
parser.add_argument('script_id', type=str, help='Script ID')
|
||||
return parser
|
||||
|
||||
|
||||
class CliListScripts(BaseScriptCli):
|
||||
"""List existing PyScripts."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
resp = utils.get_client_from_osc(self).rating.pyscripts.list_scripts(
|
||||
**vars(parsed_args))
|
||||
resp = resp.get('scripts') or []
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliListScripts, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'-n', '--no-data', action='store_true',
|
||||
help='Set to true to remove script data from output')
|
||||
return parser
|
||||
|
||||
|
||||
class CliCreateScript(BaseScriptCli):
|
||||
"""Create a PyScript."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
try:
|
||||
with open(parsed_args.data, 'r') as fd:
|
||||
parsed_args.data = fd.read()
|
||||
except IOError:
|
||||
pass
|
||||
resp = utils.get_client_from_osc(self).rating.pyscripts.create_script(
|
||||
**vars(parsed_args))
|
||||
resp = [resp]
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliCreateScript, self).get_parser(prog_name)
|
||||
parser.add_argument('name', type=str, help='Script Name')
|
||||
parser.add_argument('data', type=str, help='Script Data or data file')
|
||||
return parser
|
||||
|
||||
|
||||
class CliUpdateScript(BaseScriptCli):
|
||||
"""Update a PyScript."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
if parsed_args.data:
|
||||
try:
|
||||
with open(parsed_args.data, 'r') as fd:
|
||||
parsed_args.data = fd.read()
|
||||
except IOError:
|
||||
pass
|
||||
resp = utils.get_client_from_osc(self).rating.pyscripts.update_script(
|
||||
**vars(parsed_args))
|
||||
resp = [resp]
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliUpdateScript, self).get_parser(prog_name)
|
||||
parser.add_argument('script_id', type=str, help='Script ID')
|
||||
parser.add_argument('-n', '--name', type=str, help='Script Name')
|
||||
parser.add_argument('-d', '--data', type=str,
|
||||
help='Script Data or data file')
|
||||
return parser
|
||||
|
||||
|
||||
class CliDeleteScript(command.Command):
|
||||
"""Delete a PyScript."""
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.get_client_from_osc(self).rating.pyscripts.delete_script(
|
||||
**vars(parsed_args))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliDeleteScript, self).get_parser(prog_name)
|
||||
parser.add_argument('script_id', type=str, help='Script ID')
|
||||
return parser
|
|
@ -0,0 +1,79 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class ReportManager(base.BaseManager):
|
||||
"""Class used to handle /v1/report endpoint."""
|
||||
url = '/v1/report/{endpoint}'
|
||||
|
||||
def get_summary(self, **kwargs):
|
||||
"""Returns a list of summaries.
|
||||
|
||||
:param begin: Begin timestamp
|
||||
:type begin: datetime.datetime
|
||||
:param end: End timestamp
|
||||
:type end: datetime.datetime
|
||||
:param tenant_id: Tenant ID
|
||||
:type tenant_id: str
|
||||
:param groupby: Fields to group by.
|
||||
:type groupby: list
|
||||
:param all_tenants: Get summary from all tenants (admin only). Defaults
|
||||
to False.
|
||||
:type all_tenants: bool
|
||||
"""
|
||||
authorized_args = [
|
||||
'begin', 'end', 'tenant_id', 'service', 'groupby', 'all_tenants']
|
||||
if kwargs.get('groupby', None):
|
||||
kwargs['groupby'] = ','.join(kwargs['groupby'])
|
||||
url = self.get_url('summary', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_total(self, **kwargs):
|
||||
"""Returns the total for the given tenant.
|
||||
|
||||
:param begin: Begin timestamp
|
||||
:type begin: datetime.datetime
|
||||
:param end: End timestamp
|
||||
:type end: datetime.datetime
|
||||
:param tenant_id: Tenant ID
|
||||
:type tenant_id: str
|
||||
:param all_tenants: Get total from all tenants (admin only). Defaults
|
||||
to False.
|
||||
:type all_tenants: bool
|
||||
"""
|
||||
LOG.warning('WARNING: /v1/report/total/ endpoint is deprecated, '
|
||||
'please use /v1/report/summary instead.')
|
||||
authorized_args = [
|
||||
'begin', 'end', 'tenant_id', 'service', 'all_tenants']
|
||||
url = self.get_url('total', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
||||
|
||||
def get_tenants(self, **kwargs):
|
||||
"""Returns a list of tenants.
|
||||
|
||||
:param begin: Begin timestamp
|
||||
:type begin: datetime.datetime
|
||||
:param end: End timestamp
|
||||
:type end: datetime.datetime
|
||||
"""
|
||||
url = self.get_url('tenants', kwargs, ['begin', 'end'])
|
||||
return self.api_client.get(url).json()
|
|
@ -1,81 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.common import base
|
||||
|
||||
|
||||
class ReportSummary(base.Resource):
|
||||
|
||||
key = 'summary'
|
||||
|
||||
def __init(self, tenant_id=None, res_type=None, begin=None,
|
||||
end=None, rate=None):
|
||||
self.tenant_id = tenant_id
|
||||
self.res_type = res_type
|
||||
self.begin = begin
|
||||
self.end = end
|
||||
self.rate = rate
|
||||
|
||||
def __repr__(self):
|
||||
return "<Summary %s" % self._info
|
||||
|
||||
|
||||
class ReportManager(base.CrudManager):
|
||||
|
||||
base_url = "/v1/report"
|
||||
|
||||
def list_tenants(self):
|
||||
return self.client.get(self.base_url + "/tenants").json()
|
||||
|
||||
def get_total(self, tenant_id=None, begin=None, end=None,
|
||||
service=None, all_tenants=False):
|
||||
url = self.base_url + "/total"
|
||||
filters = list()
|
||||
if tenant_id:
|
||||
filters.append("tenant_id=%s" % tenant_id)
|
||||
if begin:
|
||||
filters.append("begin=%s" % begin.isoformat())
|
||||
if end:
|
||||
filters.append("end=%s" % end.isoformat())
|
||||
if service:
|
||||
filters.append("service=%s" % service)
|
||||
if all_tenants:
|
||||
filters.append("all_tenants=%s" % all_tenants)
|
||||
if filters:
|
||||
url += "?%s" % ('&'.join(filters))
|
||||
return self.client.get(url).json()
|
||||
|
||||
|
||||
class ReportSummaryManager(ReportManager):
|
||||
|
||||
resource_class = ReportSummary
|
||||
key = 'summary'
|
||||
collection_key = "summary"
|
||||
|
||||
def get_summary(self, tenant_id=None, begin=None, end=None,
|
||||
service=None, groupby=None, all_tenants=False):
|
||||
kwargs = {}
|
||||
if tenant_id:
|
||||
kwargs['tenant_id'] = tenant_id
|
||||
if begin:
|
||||
kwargs['begin'] = begin.isoformat()
|
||||
if end:
|
||||
kwargs['end'] = end.isoformat()
|
||||
if service:
|
||||
kwargs['service'] = service
|
||||
if groupby:
|
||||
kwargs['groupby'] = groupby
|
||||
if all_tenants:
|
||||
kwargs['all_tenants'] = all_tenants
|
||||
return super(ReportManager, self).list(**kwargs)
|
|
@ -1,96 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from __future__ import print_function
|
||||
|
||||
from cloudkittyclient.common import utils
|
||||
|
||||
|
||||
def do_report_tenant_list(cc, args):
|
||||
"""List tenant report."""
|
||||
tenants = cc.reports.list_tenants()
|
||||
out_table = utils.prettytable.PrettyTable()
|
||||
out_table.add_column("Tenant UUID", tenants)
|
||||
print(out_table)
|
||||
|
||||
|
||||
@utils.arg('-t', '--tenant-id',
|
||||
help='Tenant id',
|
||||
required=False,
|
||||
dest='total_tenant_id')
|
||||
@utils.arg('-b', '--begin',
|
||||
help='Starting date/time (YYYY-MM-DDTHH:MM:SS)',
|
||||
required=False)
|
||||
@utils.arg('-e', '--end',
|
||||
help='Ending date/time (YYYY-MM-DDTHH:MM:SS)',
|
||||
required=False)
|
||||
@utils.arg('-s', '--service',
|
||||
help='Service Type',
|
||||
required=False)
|
||||
@utils.arg('-a', '--all-tenants',
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest='all_tenants',
|
||||
help='Allows to get total from all tenants'
|
||||
' (admin only).')
|
||||
def do_total_get(cc, args):
|
||||
"""Get total reports."""
|
||||
begin = utils.iso2dt(args.begin) if args.begin else None
|
||||
end = utils.iso2dt(args.end) if args.end else None
|
||||
total = cc.reports.get_total(tenant_id=args.total_tenant_id,
|
||||
begin=begin,
|
||||
end=end,
|
||||
service=args.service,
|
||||
all_tenants=args.all_tenants)
|
||||
utils.print_dict({'Total': total or 0.0})
|
||||
|
||||
|
||||
@utils.arg('-t', '--tenant-id',
|
||||
help='Tenant id',
|
||||
required=False,
|
||||
dest='summary_tenant_id')
|
||||
@utils.arg('-b', '--begin',
|
||||
help='Begin timestamp',
|
||||
required=False)
|
||||
@utils.arg('-e', '--end',
|
||||
help='End timestamp',
|
||||
required=False)
|
||||
@utils.arg('-s', '--service',
|
||||
help='Service Type',
|
||||
required=False)
|
||||
@utils.arg('-g', '--groupby',
|
||||
help=('Fields to groupby, separated by commas '
|
||||
'if multiple, now support res_type,tenant_id'),
|
||||
required=False)
|
||||
@utils.arg('-a', '--all-tenants',
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest='all_tenants',
|
||||
help='Allows to get summary from all tenants'
|
||||
' (admin only).')
|
||||
def do_summary_get(cc, args):
|
||||
"""Get summary report."""
|
||||
begin = utils.ts2dt(args.begin) if args.begin else None
|
||||
end = utils.ts2dt(args.end) if args.end else None
|
||||
summarys = cc.reportsummary.get_summary(tenant_id=args.summary_tenant_id,
|
||||
begin=begin,
|
||||
end=end,
|
||||
service=args.service,
|
||||
groupby=args.groupby,
|
||||
all_tenants=args.all_tenants)
|
||||
field_labels = ['Tenant ID', 'Resource Type', 'Rate',
|
||||
'Begin Time', 'End Time']
|
||||
fields = ['tenant_id', 'res_type', 'rate', 'begin', 'end']
|
||||
utils.print_list(summarys, fields, field_labels, sortby=0)
|
|
@ -1,88 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
from cloudkittyclient.v1.report import shell
|
||||
|
||||
|
||||
class CliTotalGet(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliTotalGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-t', '--tenant-id',
|
||||
help='Tenant id',
|
||||
required=False,
|
||||
dest='total_tenant_id')
|
||||
parser.add_argument('-b', '--begin',
|
||||
help='Begin timestamp',
|
||||
required=False)
|
||||
parser.add_argument('-e', '--end',
|
||||
help='End timestamp',
|
||||
required=False)
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Service Type',
|
||||
required=False)
|
||||
parser.add_argument('-a', '--all-tenants',
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest='all_tenants',
|
||||
help='Allows to get total from all tenants'
|
||||
' (admin only).')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_total_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliReportTenantList(command.Command):
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_report_tenant_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliSummaryGet(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliSummaryGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-t', '--tenant-id',
|
||||
help='Tenant id',
|
||||
required=False,
|
||||
dest='summary_tenant_id')
|
||||
parser.add_argument('-b', '--begin',
|
||||
help='Begin timestamp',
|
||||
required=False)
|
||||
parser.add_argument('-e', '--end',
|
||||
help='End timestamp',
|
||||
required=False)
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Service Type',
|
||||
required=False)
|
||||
parser.add_argument('-g', '--groupby',
|
||||
help=('Fields to groupby, separated by '
|
||||
'commas if multiple, now support '
|
||||
'res_type,tenant_id'),
|
||||
required=False)
|
||||
parser.add_argument('-a', '--all-tenants',
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest='all_tenants',
|
||||
help='Allows to get summary from all tenants'
|
||||
' (admin only).')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_summary_get(ckclient, parsed_args)
|
|
@ -0,0 +1,126 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class CliSummaryGet(lister.Lister):
|
||||
"""Get a summary report."""
|
||||
summary_columns = [
|
||||
('tenant_id', 'Tenant ID'),
|
||||
('res_type', 'Resource Type'),
|
||||
('rate', 'Rate'),
|
||||
('begin', 'Begin Time'),
|
||||
('end', 'End Time'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
for arg in ['begin', 'end']:
|
||||
value = getattr(parsed_args, arg)
|
||||
if value is not None:
|
||||
try:
|
||||
setattr(parsed_args, arg, utils.iso2dt(value))
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentError(
|
||||
'Invalid timestamp "{}"'.format(value))
|
||||
resp = utils.get_client_from_osc(self).report.get_summary(
|
||||
**vars(parsed_args))
|
||||
values = utils.list_to_cols(
|
||||
resp.get('summary', []), self.summary_columns)
|
||||
return [col[1] for col in self.summary_columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliSummaryGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-t', '--tenant-id', type=str,
|
||||
help='Tenant id.')
|
||||
parser.add_argument('-b', '--begin', type=str,
|
||||
help='Begin timestamp.')
|
||||
parser.add_argument('-e', '--end', type=str,
|
||||
help='End timestamp.')
|
||||
parser.add_argument('-s', '--service', type=str,
|
||||
help='Service Type.')
|
||||
parser.add_argument('-g', '--groupby', nargs='+',
|
||||
help='Fields to group by, space-separated. '
|
||||
'(res_type and tenant_id are supported for now)')
|
||||
parser.add_argument('-a', '--all-tenants', action='store_true',
|
||||
help='Allows to get summary from all tenants '
|
||||
'(admin only). Defaults to False.')
|
||||
return parser
|
||||
|
||||
|
||||
class CliTotalGet(show.ShowOne):
|
||||
"""(DEPRECATED) Get total reports."""
|
||||
def take_action(self, parsed_args):
|
||||
for arg in ['begin', 'end']:
|
||||
value = getattr(parsed_args, arg)
|
||||
if value is not None:
|
||||
try:
|
||||
setattr(parsed_args, arg, utils.iso2dt(value))
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentError(
|
||||
'Invalid timestamp "{}"'.format(value))
|
||||
resp = utils.get_client_from_osc(self).report.get_total(
|
||||
**vars(parsed_args))
|
||||
return ('Total', ), (float(resp), )
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliTotalGet, self).get_parser(prog_name)
|
||||
parser.add_argument('-t', '--tenant-id',
|
||||
help='Tenant id.')
|
||||
parser.add_argument('-b', '--begin', type=str,
|
||||
help='Begin timestamp.')
|
||||
parser.add_argument('-e', '--end', type=str,
|
||||
help='End timestamp.')
|
||||
parser.add_argument('-s', '--service',
|
||||
help='Service Type.')
|
||||
parser.add_argument('-g', '--groupby', nargs='+',
|
||||
help='Fields to group by, space-separated. '
|
||||
'(res_type and tenant_id are supported for now)')
|
||||
parser.add_argument('-a', '--all-tenants', action='store_true',
|
||||
help='Allows to get summary from all tenants '
|
||||
'(admin only). Defaults to False.')
|
||||
return parser
|
||||
|
||||
|
||||
class CliTenantList(lister.Lister):
|
||||
"""Get rated tenants for the given period.
|
||||
|
||||
Begin defaults to the beginning of the current month and end defaults to
|
||||
the beginning of the next month.
|
||||
"""
|
||||
def take_action(self, parsed_args):
|
||||
for arg in ['begin', 'end']:
|
||||
value = getattr(parsed_args, arg)
|
||||
if value is not None:
|
||||
try:
|
||||
setattr(parsed_args, arg, utils.iso2dt(value))
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentError(
|
||||
'Invalid timestamp "{}"'.format(value))
|
||||
client = utils.get_client_from_osc(self)
|
||||
return (('Tenant ID', ),
|
||||
(client.report.get_tenants(**vars(parsed_args)), ))
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliTenantList, self).get_parser(prog_name)
|
||||
parser.add_argument('-b', '--begin', type=str,
|
||||
help='Begin timestamp.')
|
||||
parser.add_argument('-e', '--end', type=str,
|
||||
help='End timestamp.')
|
||||
return parser
|
|
@ -1,114 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.apiclient import exceptions
|
||||
from cloudkittyclient.common import utils
|
||||
from cloudkittyclient import exc
|
||||
|
||||
|
||||
def do_module_list(cc, args):
|
||||
'''List the samples for this meters.'''
|
||||
try:
|
||||
modules = cc.modules.list()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Modules not found')
|
||||
else:
|
||||
field_labels = ['Module', 'Enabled', 'Priority']
|
||||
fields = ['module_id', 'enabled', 'priority']
|
||||
utils.print_list(modules, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
def do_module_enable(cc, args):
|
||||
'''Enable a module.'''
|
||||
try:
|
||||
module = cc.modules.get(module_id=args.name)
|
||||
module.enable()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Module not found: %s' % args.name)
|
||||
else:
|
||||
field_labels = ['Module', 'Enabled', 'Priority']
|
||||
fields = ['module_id', 'enabled', 'priority']
|
||||
modules = [cc.modules.get(module_id=args.name)]
|
||||
utils.print_list(modules, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
def do_module_disable(cc, args):
|
||||
'''Disable a module.'''
|
||||
try:
|
||||
module = cc.modules.get(module_id=args.name)
|
||||
module.disable()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Module not found: %s' % args.name)
|
||||
else:
|
||||
field_labels = ['Module', 'Enabled', 'Priority']
|
||||
fields = ['module_id', 'enabled', 'priority']
|
||||
modules = [cc.modules.get(module_id=args.name)]
|
||||
utils.print_list(modules, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
@utils.arg('-p', '--priority',
|
||||
help='Module priority',
|
||||
required=True)
|
||||
def do_module_set_priority(cc, args):
|
||||
'''Set module priority.'''
|
||||
try:
|
||||
module = cc.modules.get(module_id=args.name)
|
||||
module.set_priority(args.priority)
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Module not found: %s' % args.name)
|
||||
else:
|
||||
field_labels = ['Module', 'Enabled', 'Priority']
|
||||
fields = ['module_id', 'enabled', 'priority']
|
||||
modules = [cc.modules.get(module_id=args.name)]
|
||||
utils.print_list(modules, fields, field_labels,
|
||||
sortby=0)
|
||||
|
||||
|
||||
def do_info_config_get(cc, args):
|
||||
'''Get cloudkitty configuration.'''
|
||||
utils.print_dict(cc.config.get_config(), dict_property="Section")
|
||||
|
||||
|
||||
@utils.arg('-n', '--name',
|
||||
help='Service name',
|
||||
required=False)
|
||||
def do_info_service_get(cc, args):
|
||||
'''Get service info.'''
|
||||
if args.name:
|
||||
try:
|
||||
services_info = [cc.service_info.get(service_id=args.name)]
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('Service not found: %s' % args.name)
|
||||
else:
|
||||
try:
|
||||
services_info = cc.service_info.list()
|
||||
except exceptions.NotFound:
|
||||
raise exc.CommandError('ServiceInfo not found')
|
||||
|
||||
field_labels = ['Service', 'Metadata', 'Unit']
|
||||
fields = ['service_id', 'metadata', 'unit']
|
||||
utils.print_list(services_info, fields, field_labels, sortby=0)
|
|
@ -1,91 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
|
||||
# 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 osc_lib.command import command
|
||||
|
||||
from cloudkittyclient.v1 import shell
|
||||
|
||||
|
||||
class CliModuleList(command.Command):
|
||||
"""List modules."""
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_module_list(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliModuleEnable(command.Command):
|
||||
"""Enable a module."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleEnable, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_module_enable(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliModuleDisable(command.Command):
|
||||
"""Disable a module."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleDisable, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_module_disable(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliModuleSetPriority(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliModuleSetPriority, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Module name',
|
||||
required=True)
|
||||
parser.add_argument('-p', '--priority',
|
||||
help='Module priority',
|
||||
required=True)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_module_set_priority(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliInfoGetConfig(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliInfoGetConfig, self).get_parser(prog_name)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_info_config_get(ckclient, parsed_args)
|
||||
|
||||
|
||||
class CliInfoGetService(command.Command):
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliInfoGetService, self).get_parser(prog_name)
|
||||
parser.add_argument('-n', '--name',
|
||||
help='Service name',
|
||||
required=False)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_info_service_get(ckclient, parsed_args)
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.v1 import base
|
||||
|
||||
|
||||
class StorageManager(base.BaseManager):
|
||||
"""Class used to handle /v1/storage endpoint"""
|
||||
|
||||
url = '/v1/storage/dataframes'
|
||||
|
||||
def get_dataframes(self, **kwargs):
|
||||
"""Returns a list of rated dataframes.
|
||||
|
||||
:param begin: Begin timestamp
|
||||
:type begin: datetime
|
||||
:param end: End timestamp
|
||||
:type end: datetime
|
||||
:param tenant_id: ID of the tenant to filter on
|
||||
:type tenant_id: str
|
||||
:param resource_type: Resource type to filter on
|
||||
:type resource_type: str
|
||||
"""
|
||||
authorized_args = ['begin', 'end', 'tenant_id', 'resource_type']
|
||||
url = self.get_url('', kwargs, authorized_args)
|
||||
return self.api_client.get(url).json()
|
|
@ -1,20 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.v1.storage import dataframe
|
||||
|
||||
|
||||
class StorageManager(object):
|
||||
def __init__(self, http_client):
|
||||
self.dataframes = dataframe.DataFrameManager(http_client)
|
|
@ -1,29 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# 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 cloudkittyclient.common import base
|
||||
|
||||
|
||||
class DataFrameResource(base.Resource):
|
||||
key = 'dataframe'
|
||||
|
||||
def __repr__(self):
|
||||
return "<DataFrameResource %s>" % self._info
|
||||
|
||||
|
||||
class DataFrameManager(base.CrudManager):
|
||||
resource_class = DataFrameResource
|
||||
base_url = '/v1/storage'
|
||||
key = 'dataframe'
|
||||
collection_key = 'dataframes'
|
|
@ -1,41 +0,0 @@
|
|||
# Copyright 2015 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cloudkittyclient.common import utils
|
||||
|
||||
|
||||
@utils.arg('-b', '--begin',
|
||||
help='Starting date/time (YYYY-MM-DDTHH:MM:SS)',
|
||||
required=False)
|
||||
@utils.arg('-e', '--end',
|
||||
help='Ending date/time (YYYY-MM-DDTHH:MM:SS)',
|
||||
required=False)
|
||||
@utils.arg('-t', '--tenant',
|
||||
help='Tenant ID',
|
||||
required=False,
|
||||
default=None)
|
||||
@utils.arg('-r', '--resource-type',
|
||||
help='Resource type (compute, image, ...)',
|
||||
required=False,
|
||||
default=None)
|
||||
def do_storage_dataframe_list(cc, args):
|
||||
"""List dataframes."""
|
||||
data = cc.storage.dataframes.list(begin=args.begin, end=args.end,
|
||||
tenant_id=args.tenant,
|
||||
resource_type=args.resource_type)
|
||||
fields = ['begin', 'end', 'tenant_id', 'resources']
|
||||
fields_labels = ['Begin', 'End', 'Tenant ID', 'Resources']
|
||||
utils.print_list(data, fields, fields_labels, sortby=0)
|
|
@ -1,44 +0,0 @@
|
|||
# Copyright 2016 Objectif Libre
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
from cloudkittyclient.v1.storage import shell
|
||||
|
||||
|
||||
class CliStorageDataframeList(command.Command):
|
||||
"""List dataframes."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliStorageDataframeList, self).get_parser(prog_name)
|
||||
parser.add_argument('-b', '--begin',
|
||||
help='Starting date/time (YYYY-MM-ddThh:mm:ss)',
|
||||
required=False)
|
||||
parser.add_argument('-e', '--end',
|
||||
help='Ending date/time (YYYY-MM-ddThh:mm:ss)',
|
||||
required=False)
|
||||
parser.add_argument('-t', '--tenant',
|
||||
help='Tenant ID',
|
||||
required=False,
|
||||
default=None)
|
||||
parser.add_argument('-r', '--resource-type',
|
||||
help='Resource type (compute, image...)',
|
||||
required=False,
|
||||
default=None)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
ckclient = self.app.client_manager.rating
|
||||
shell.do_storage_dataframe_list(ckclient, parsed_args)
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Objectif Libre
|
||||
#
|
||||
# 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 cliff import lister
|
||||
|
||||
from cloudkittyclient import exc
|
||||
from cloudkittyclient import utils
|
||||
|
||||
|
||||
class CliGetDataframes(lister.Lister):
|
||||
"""List stored dataframes or generate CSV reports.
|
||||
|
||||
Dataframes can be filtered on resource_type and project_id.
|
||||
CSV reports can be generated with the 'df-to-csv' formatter.
|
||||
A config file may be provided to configure the output of that formatter.
|
||||
See documentation for more details.
|
||||
"""
|
||||
columns = [
|
||||
('begin', 'Begin'),
|
||||
('end', 'End'),
|
||||
('tenant_id', 'Project ID'),
|
||||
('resources', 'Resources'),
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
for arg in ['begin', 'end']:
|
||||
value = getattr(parsed_args, arg)
|
||||
if value is not None:
|
||||
try:
|
||||
setattr(parsed_args, arg, utils.iso2dt(value))
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentError(
|
||||
'Invalid timestamp "{}"'.format(value))
|
||||
resp = utils.get_client_from_osc(self).storage.get_dataframes(
|
||||
**vars(parsed_args)).get('dataframes', [])
|
||||
values = utils.list_to_cols(resp, self.columns)
|
||||
for value in values:
|
||||
for resource in value[3]:
|
||||
rating = float(resource['rating'])
|
||||
volume = float(resource['volume'])
|
||||
if volume > 0:
|
||||
resource['rate_value'] = '{:.4f}'.format(rating / volume)
|
||||
else:
|
||||
resource['rate_value'] = ''
|
||||
return [col[1] for col in self.columns], values
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CliGetDataframes, self).get_parser(prog_name)
|
||||
parser.add_argument('-b', '--begin', type=str, help='Begin timestamp')
|
||||
parser.add_argument('-e', '--end', type=str, help='End timestamp')
|
||||
parser.add_argument('-p', '--project-id', type=str, dest='tenant_id',
|
||||
help='Id of the tenant to filter on')
|
||||
parser.add_argument('-r', '--resource_type', type=str,
|
||||
help='Resource type to filter on')
|
||||
return parser
|
|
@ -0,0 +1,6 @@
|
|||
=========================
|
||||
collector (/v1/collector)
|
||||
=========================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.collector
|
||||
:members:
|
|
@ -0,0 +1,15 @@
|
|||
=============
|
||||
Api Reference
|
||||
=============
|
||||
|
||||
A ``client.Client`` instance has the following submodules (each one
|
||||
corresponding to an API endpoint):
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
report
|
||||
info
|
||||
rating
|
||||
collector
|
||||
storage
|
|
@ -0,0 +1,6 @@
|
|||
===============
|
||||
info (/v1/info)
|
||||
===============
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.info
|
||||
:members:
|
|
@ -0,0 +1,18 @@
|
|||
===================
|
||||
rating (/v1/rating)
|
||||
===================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.rating
|
||||
:members:
|
||||
|
||||
rating.hashmap (/v1/rating/module_config/hashmap)
|
||||
=================================================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.rating.hashmap
|
||||
:members:
|
||||
|
||||
rating.pyscripts (/v1/rating/module_config/pyscripts)
|
||||
=====================================================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.rating.pyscripts
|
||||
:members:
|
|
@ -0,0 +1,6 @@
|
|||
===================
|
||||
report (/v1/report)
|
||||
===================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.report
|
||||
:members:
|
|
@ -0,0 +1,6 @@
|
|||
=====================
|
||||
storage (/v1/storage)
|
||||
=====================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.storage
|
||||
:members:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
=============
|
||||
CLI Reference
|
||||
=============
|
||||
|
||||
.. autoprogram-cliff:: cloudkittyclient
|
||||
:application: cloudkitty
|
||||
:ignored: --format, --column, --max-width, --fit-width, --print-empty, --format-config-file, --noindent, --quote, --sort-column
|
|
@ -20,9 +20,9 @@ sys.path.insert(0, os.path.abspath('../..'))
|
|||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'cliff.sphinxext',
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'openstackdocstheme'
|
||||
'openstackdocstheme',
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
.. include:: ../../../CONTRIBUTING.rst
|
||||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -10,10 +10,11 @@ Contents:
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install/index
|
||||
contributor/index
|
||||
cli/index
|
||||
user/index
|
||||
install
|
||||
usage
|
||||
contributor
|
||||
cli_reference
|
||||
api_reference/index
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue