Compare commits
20 Commits
queens-eol
...
2.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b49fbd4d0 | ||
|
|
e9a92a2941 | ||
|
|
1ed287c92a | ||
|
|
63ac84b165 | ||
|
|
1cf5b3aca2 | ||
|
|
503dd3247a | ||
|
|
7b9d447eb8 | ||
|
|
a9eb87c436 | ||
|
|
fff37a84fa | ||
|
|
81cdcba4f3 | ||
|
|
214083c695 | ||
|
|
c78ae05005 | ||
| 1348fd67e1 | |||
|
|
7bf6f139cd | ||
|
|
9dfca5f854 | ||
|
|
b79833f67b | ||
|
|
f8d87cdb28 | ||
|
|
d070f6a68c | ||
|
|
1b56089682 | ||
| f490bd0a84 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ build
|
||||
.tox
|
||||
cover
|
||||
.testrepository
|
||||
.stestr
|
||||
.venv
|
||||
dist
|
||||
*.egg
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[gerrit]
|
||||
host=review.opendev.org
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-cloudkittyclient.git
|
||||
defaultbranch=stable/queens
|
||||
|
||||
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -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
|
||||
41
.zuul.yaml
41
.zuul.yaml
@@ -1,8 +1,45 @@
|
||||
- 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:
|
||||
templates:
|
||||
- check-requirements
|
||||
- openstack-cover-jobs
|
||||
- openstack-python-jobs
|
||||
- openstack-python35-jobs
|
||||
- openstack-python36-jobs
|
||||
- openstackclient-plugin-jobs
|
||||
post:
|
||||
- publish-openstack-docs-pti
|
||||
check:
|
||||
jobs:
|
||||
- openstack-tox-cover
|
||||
- cloudkittyclient-devstack-functional:
|
||||
voting: true
|
||||
gate:
|
||||
jobs:
|
||||
- cloudkittyclient-devstack-functional:
|
||||
voting: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
python-cloudkittyclient Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
||||
|
||||
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)
|
||||
47
cloudkittyclient/auth.py
Normal file
47
cloudkittyclient/auth.py
Normal file
@@ -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."""
|
||||
|
||||
|
||||
85
cloudkittyclient/format.py
Normal file
85
cloudkittyclient/format.py
Normal file
@@ -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,17 @@ 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()
|
||||
adapter_options = dict(
|
||||
interface=instance.interface,
|
||||
region_name=instance.region_name,
|
||||
)
|
||||
return ck_client(session=instance.session,
|
||||
adapter_options=adapter_options)
|
||||
|
||||
|
||||
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
|
||||
47
cloudkittyclient/tests/functional/v1/base.py
Normal file
47
cloudkittyclient/tests/functional/v1/base.py
Normal file
@@ -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)
|
||||
67
cloudkittyclient/tests/functional/v1/test_collector.py
Normal file
67
cloudkittyclient/tests/functional/v1/test_collector.py
Normal file
@@ -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
|
||||
319
cloudkittyclient/tests/functional/v1/test_hashmap.py
Normal file
319
cloudkittyclient/tests/functional/v1/test_hashmap.py
Normal file
@@ -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
|
||||
47
cloudkittyclient/tests/functional/v1/test_info.py
Normal file
47
cloudkittyclient/tests/functional/v1/test_info.py
Normal file
@@ -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
|
||||
61
cloudkittyclient/tests/functional/v1/test_pyscripts.py
Normal file
61
cloudkittyclient/tests/functional/v1/test_pyscripts.py
Normal file
@@ -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
|
||||
48
cloudkittyclient/tests/functional/v1/test_rating.py
Normal file
48
cloudkittyclient/tests/functional/v1/test_rating.py
Normal file
@@ -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
|
||||
44
cloudkittyclient/tests/functional/v1/test_report.py
Normal file
44
cloudkittyclient/tests/functional/v1/test_report.py
Normal file
@@ -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
cloudkittyclient/tests/unit/v1/__init__.py
Normal file
0
cloudkittyclient/tests/unit/v1/__init__.py
Normal file
37
cloudkittyclient/tests/unit/v1/base.py
Normal file
37
cloudkittyclient/tests/unit/v1/base.py
Normal file
@@ -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)
|
||||
72
cloudkittyclient/tests/unit/v1/test_collector.py
Normal file
72
cloudkittyclient/tests/unit/v1/test_collector.py
Normal file
@@ -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)
|
||||
338
cloudkittyclient/tests/unit/v1/test_hashmap.py
Normal file
338
cloudkittyclient/tests/unit/v1/test_hashmap.py
Normal file
@@ -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)
|
||||
32
cloudkittyclient/tests/unit/v1/test_info.py
Normal file
32
cloudkittyclient/tests/unit/v1/test_info.py
Normal file
@@ -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/')
|
||||
71
cloudkittyclient/tests/unit/v1/test_pyscripts.py
Normal file
71
cloudkittyclient/tests/unit/v1/test_pyscripts.py
Normal file
@@ -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')
|
||||
66
cloudkittyclient/tests/unit/v1/test_report.py
Normal file
66
cloudkittyclient/tests/unit/v1/test_report.py
Normal file
@@ -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')
|
||||
63
cloudkittyclient/tests/unit/v1/test_report_cli.py
Normal file
63
cloudkittyclient/tests/unit/v1/test_report_cli.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# 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 collections
|
||||
import mock
|
||||
|
||||
from cloudkittyclient.tests.unit.v1 import base
|
||||
from cloudkittyclient.v1 import report_cli
|
||||
|
||||
|
||||
class TestReportCli(base.BaseAPIEndpointTestCase):
|
||||
|
||||
def test_report_tenant_list(self):
|
||||
|
||||
class DummyAPIClient(object):
|
||||
def get_tenants(*args, **kwargs):
|
||||
return ['ee530dfc-319a-438f-9d43-346cfef501d6',
|
||||
'91743a9a-688b-4526-b568-7b501531176c',
|
||||
'4468704c-972e-4cfd-a342-9b71c493b79b']
|
||||
|
||||
class ClientWrap(object):
|
||||
report = DummyAPIClient()
|
||||
|
||||
class DummyParsedArgs(object):
|
||||
def __init__(self):
|
||||
self.begin = '2042-01-01T00:00:00'
|
||||
self.end = '2042-12-01T00:00:00'
|
||||
|
||||
class DummyCliTenantList(report_cli.CliTenantList):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __get_client_from_osc(*args):
|
||||
return ClientWrap()
|
||||
|
||||
parsed_args = DummyParsedArgs()
|
||||
cli_class_instance = DummyCliTenantList()
|
||||
|
||||
with mock.patch('cloudkittyclient.utils.get_client_from_osc',
|
||||
new=__get_client_from_osc):
|
||||
# NOTE(peschk_l): self is only used used to get a client so just we
|
||||
# just override __init__ in order to skip class instanciation. In
|
||||
# python3 we could just have passed None
|
||||
result = report_cli.CliTenantList.take_action(
|
||||
cli_class_instance, parsed_args)
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0] == ('Tenant ID', )
|
||||
assert isinstance(result[1], collections.Iterable)
|
||||
|
||||
for res in result[1]:
|
||||
assert isinstance(res, collections.Iterable)
|
||||
33
cloudkittyclient/tests/unit/v1/test_storage.py
Normal file
33
cloudkittyclient/tests/unit/v1/test_storage.py
Normal file
@@ -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)
|
||||
58
cloudkittyclient/utils.py
Normal file
58
cloudkittyclient/utils.py
Normal file
@@ -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
|
||||
|
||||
64
cloudkittyclient/v1/base.py
Normal file
64
cloudkittyclient/v1/base.py
Normal file
@@ -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,44 @@
|
||||
# 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={},
|
||||
cacert=None,
|
||||
insecure=False,
|
||||
**kwargs):
|
||||
adapter_options.setdefault('service_type', 'rating')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Cloudkitty v1 API."""
|
||||
if insecure:
|
||||
verify_cert = False
|
||||
else:
|
||||
if cacert:
|
||||
verify_cert = cacert
|
||||
else:
|
||||
verify_cert = True
|
||||
|
||||
if not kwargs.get('auth_plugin'):
|
||||
kwargs['auth_plugin'] = ckclient.get_auth_plugin(*args, **kwargs)
|
||||
self.auth_plugin = kwargs.get('auth_plugin')
|
||||
self.session = session
|
||||
if self.session is None:
|
||||
self.session = ks_session.Session(
|
||||
verify=verify_cert, **kwargs)
|
||||
|
||||
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)
|
||||
|
||||
115
cloudkittyclient/v1/collector.py
Normal file
115
cloudkittyclient/v1/collector.py
Normal file
@@ -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"
|
||||
151
cloudkittyclient/v1/collector_cli.py
Normal file
151
cloudkittyclient/v1/collector_cli.py
Normal file
@@ -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
|
||||
37
cloudkittyclient/v1/info.py
Normal file
37
cloudkittyclient/v1/info.py
Normal file
@@ -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()
|
||||
62
cloudkittyclient/v1/info_cli.py
Normal file
62
cloudkittyclient/v1/info_cli.py
Normal file
@@ -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))
|
||||
|
||||
446
cloudkittyclient/v1/rating/hashmap.py
Normal file
446
cloudkittyclient/v1/rating/hashmap.py
Normal file
@@ -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)
|
||||
567
cloudkittyclient/v1/rating/hashmap_cli.py
Normal file
567
cloudkittyclient/v1/rating/hashmap_cli.py
Normal file
@@ -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
|
||||
91
cloudkittyclient/v1/rating/pyscripts.py
Normal file
91
cloudkittyclient/v1/rating/pyscripts.py
Normal file
@@ -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)
|
||||
123
cloudkittyclient/v1/rating/pyscripts_cli.py
Normal file
123
cloudkittyclient/v1/rating/pyscripts_cli.py
Normal file
@@ -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
|
||||
79
cloudkittyclient/v1/report.py
Normal file
79
cloudkittyclient/v1/report.py
Normal file
@@ -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)
|
||||
129
cloudkittyclient/v1/report_cli.py
Normal file
129
cloudkittyclient/v1/report_cli.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# -*- 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)
|
||||
tenants = client.report.get_tenants(**vars(parsed_args))
|
||||
output = []
|
||||
for tenant in tenants:
|
||||
output.append((tenant, ))
|
||||
return (('Tenant ID', ), output)
|
||||
|
||||
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)
|
||||
38
cloudkittyclient/v1/storage.py
Normal file
38
cloudkittyclient/v1/storage.py
Normal file
@@ -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)
|
||||
67
cloudkittyclient/v1/storage_cli.py
Normal file
67
cloudkittyclient/v1/storage_cli.py
Normal file
@@ -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
|
||||
7
doc/requirements.txt
Normal file
7
doc/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||
sphinx>=1.6.2,!=1.6.6,!=1.6.7 # BSD
|
||||
reno>=2.5.0 # Apache-2.0
|
||||
6
doc/source/api_reference/collector.rst
Normal file
6
doc/source/api_reference/collector.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
=========================
|
||||
collector (/v1/collector)
|
||||
=========================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.collector
|
||||
:members:
|
||||
15
doc/source/api_reference/index.rst
Normal file
15
doc/source/api_reference/index.rst
Normal file
@@ -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
|
||||
6
doc/source/api_reference/info.rst
Normal file
6
doc/source/api_reference/info.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===============
|
||||
info (/v1/info)
|
||||
===============
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.info
|
||||
:members:
|
||||
18
doc/source/api_reference/rating.rst
Normal file
18
doc/source/api_reference/rating.rst
Normal file
@@ -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:
|
||||
6
doc/source/api_reference/report.rst
Normal file
6
doc/source/api_reference/report.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===================
|
||||
report (/v1/report)
|
||||
===================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.report
|
||||
:members:
|
||||
6
doc/source/api_reference/storage.rst
Normal file
6
doc/source/api_reference/storage.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
=====================
|
||||
storage (/v1/storage)
|
||||
=====================
|
||||
|
||||
.. automodule:: cloudkittyclient.v1.storage
|
||||
:members:
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user