Compare commits

...

36 Commits
0.4.0 ... 0.5.0

Author SHA1 Message Date
Stéphane Albert
55f3a3fa75 Updated requirements for mitaka
Preparing mitaka release

Change-Id: Ida7ddf7fcf70f5b9dba91b9c37b4d30581531f32
2016-03-04 15:14:46 +01:00
Jenkins
336f1466e0 Merge "cloudkittyclient with keystone v3 not working" 2016-03-04 14:08:52 +00:00
Jenkins
5b6fd6c529 Merge "Add support for query cost by service" 2016-03-04 11:04:01 +00:00
Jenkins
778782c3c9 Merge "Fix argument order for assertEqual to (expected, observed)" 2016-03-04 10:20:04 +00:00
Jenkins
985e3c2304 Merge "Update requirements" 2016-03-04 10:11:43 +00:00
Pierre-Alexandre Bardina
0cad4b0e99 Update requirements
Update requirements for liberty

Change-Id: I01489c2bc79428174a1005cf45a0803f6dcedfd6
2016-03-04 11:02:42 +01:00
reedip
1703d5538b Fix argument order for assertEqual to (expected, observed)
assertEqual expects that the arguments provided to it should be (expected, observed).
If a particluar order is kept as a convention, then it helps to provide a cleaner
message to the developer if Unit Tests fail.
The following patch fixes this issue

TrivialFix

Change-Id: Id417fb43ecd62563239d492bff3981277565525e
Closes-Bug: #1259292
2016-03-03 22:02:06 +00:00
Aaron-DH
c6e23ab770 Add support for query cost by service
Query cost of each service by using total-get -s servicetype

Change-Id: I7f579c70fe78cbd4031aa6ec20279d7661a2d67c
Closes-Bug: #1549687
2016-02-25 17:24:24 +08:00
Xiangjun Li
450aa61358 cloudkittyclient with keystone v3 not working
cloudkittyclient is failing to pass some domain/project related
information to keystoneclient, which caused "The service catalog
is empty" and "Expecting to find domain in project" error when
executing cloudkittyclient shell.

Change-Id: I386f4ecb38b947a1d8a0c8f1eee72e25ee12771a
Closes-Bug: #1547778
2016-02-20 15:10:44 +08:00
Jenkins
6abecf6348 Merge "Add helpinfo for collector commands." 2016-01-11 11:04:16 +00:00
Jenkins
a63b75c555 Merge "Drop py33 support" 2016-01-11 10:58:51 +00:00
Adam
f8c4caba43 Add helpinfo for collector commands.
Add some helpinfo for subcommand collector*, report*.

Change-Id: Ica1ad18fcaa4368a5d5a953839ab4499db034def
2016-01-10 14:33:21 +08:00
Jenkins
c6f2cb4643 Merge "Fix name not defined error" 2016-01-04 09:31:39 +00:00
Aaron-DH
5c188a2306 Fix name not defined error
Add the missing import packages and format the log messages
Move i18n to package(cloudkittyclient)

Change-Id: I77e7059e8eb91aef131713f0720f58d23ae7c11f
Closes-Bug: #1524680
2016-01-02 20:55:12 +08:00
Jenkins
9dd1a6fbea Merge "Set AuthPlugin in __init__()" 2015-12-31 12:14:02 +00:00
shu-mutou
8b69ecf237 Drop py33 support
"Python 3.3 support is being dropped since OpenStack Liberty."
written in following URL.
https://wiki.openstack.org/wiki/Python3

And already the infra team and the oslo team are dropping py33
support from their projects.

Since we rely on oslo for a lot of our work, and depend on infra
for our CI, we should drop py33 support too.

Change-Id: I3aa4c969425d885873be222c0ea4e32cb1060341
Closes-Bug: #1526170
2015-12-15 18:52:50 +09:00
Jenkins
ea21f9761b Merge "Fixed bug with report total" 2015-12-10 07:50:17 +00:00
Chaozhe.Chen
236bf8b307 Set AuthPlugin in __init__()
self.auth_plugin should be set in __init__()

Change-Id: Ib23fd14a697e4a03acd8c62cf1b09670d169a115
2015-12-03 14:47:39 +08:00
Atsushi SAKAI
6dbfc4502e Fix help message
Fix Required to small case(required)
Add period.

This fix is coming from below patch set 1 comment.
https://review.openstack.org/#/c/251331/

Change-Id: I614a8143ed6cba37dc726f3c85606daaf6a767be
2015-12-01 12:24:30 +09:00
Stéphane Albert
def167f77a Fixed bug with report total
The tenant filter was always sent even if not tenant filtering was used
for total retrieving.

Change-Id: I55565a30389b94f559e16d349d6aa3ef56053ea2
Closes-Bug: #1516484
2015-11-25 15:04:41 +01:00
Jenkins
4fe0255682 Merge "Add common arguments" 2015-11-25 12:24:05 +00:00
Chaozhe.Chen
e4623d6663 Fix a typo in command help
Change-Id: Ib0b00cae66907bffddbbd28f6d77ea952ec08508
2015-11-25 17:08:05 +08:00
chenchaozhe1988
382a2d9565 Add common arguments
Merge same arguments in common arguments to make it concise and convenient.

Change-Id: I75e246d36ed7d38858e9dfdedcc77dd19ea587d5
2015-11-18 16:03:05 +08:00
Gauvain Pocentek
9428ab38aa Do no set the version in setup.cfg
Change-Id: I670e61c94f6f58cd5b31caa220e94f6a30bfb66c
2015-10-30 11:33:41 +09:00
Jenkins
1e095e6da9 Merge "Improve HashMap client" 2015-10-22 12:08:17 +00:00
Jenkins
621c06f8af Merge "Add support for PyScripts rating module" 2015-10-22 04:29:02 +00:00
Stéphane Albert
e4df2e2105 Improve HashMap client
Modified tests to handle new functions.
Refactored tests to ease maintenance.

Change-Id: I24d74e0e9983091d4f81a3f72604fbae22476505
2015-10-21 12:20:51 +02:00
Jeremy Stanley
50f6cb3e54 Update .gitreview for new namespace
Change-Id: I1de00621c99bbaa2f785c50f849b635456167539
2015-10-17 22:36:22 +00:00
Stéphane Albert
d9d61d7727 Add support for PyScripts rating module
Change-Id: I06270893460f76fa73616197783b5e2d48702fe9
2015-10-05 17:43:24 +02:00
Monty Taylor
bf03f8fae6 Change ignore-errors to ignore_errors
Needed for coverage 4.0

Change-Id: I5b11e3f02107759d178c2d292a8eb03827924102
2015-09-23 07:55:26 +00:00
Stéphane Albert
b17283d585 Moving to Liberty cycle (0.5)
Change-Id: Ie18aea965350353624e59bfb769059f7af3c9278
2015-09-22 16:04:28 +02:00
Stéphane Albert
0696510d29 Preparing release 0.4.1
Change-Id: I549c749bcef38d997ec7a49da7478c90655066b4
2015-08-27 16:26:53 +02:00
DeliangFan
4ee7dcb3ee Fix syntax error of shell description
Correct the word mapping.

Change-Id: Ibc8c4afff846f9f88374d99452619522ccc080b0
Closes-Bug: #1481234
2015-08-04 17:07:02 +08:00
Jenkins
5efcd30fdd Merge "Transitioned collector client to new API" 2015-08-01 03:59:40 +00:00
Gauvain Pocentek
52955c5749 setup.cfg: set a version
Change-Id: Ib7b8722424a6e983c42417e00efbe6dba7754557
2015-07-31 15:19:39 +02:00
Stéphane Albert
2ccbed9139 Transitioned collector client to new API
Change-Id: I7f52c288f569c59381a3324714b3b1c6ac8be58a
2015-07-31 09:56:27 +02:00
34 changed files with 1269 additions and 498 deletions

View File

@@ -9,4 +9,4 @@ omit =
cloudkittyclient/common/exceptions.py
[report]
ignore-errors = True
ignore_errors = True

View File

@@ -1,4 +1,4 @@
[gerrit]
host=review.openstack.org
port=29418
project=stackforge/python-cloudkittyclient.git
project=openstack/python-cloudkittyclient.git

View File

@@ -105,6 +105,8 @@ def _get_keystone_session(**kwargs):
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:
@@ -145,7 +147,8 @@ class AuthPlugin(auth.BaseAuthPlugin):
'service_type', 'endpoint_type', 'cacert',
'auth_url', 'insecure', 'cert_file', 'key_file',
'cert', 'key', 'tenant_name', 'project_name',
'project_id', 'user_domain_id', 'user_domain_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):

View File

@@ -23,6 +23,7 @@ import copy
from six.moves.urllib import parse
from cloudkittyclient import exc
from cloudkittyclient.i18n import _
from cloudkittyclient.openstack.common.apiclient import base
@@ -138,7 +139,7 @@ class CrudManager(base.CrudManager):
'name': self.resource_class.__name__,
'args': kwargs
}
raise exc.NotFound(404, msg)
raise exc.HTTPNotFound(msg)
return rl

View File

@@ -27,6 +27,7 @@ import prettytable
import six
from cloudkittyclient import exc
from cloudkittyclient.i18n import _
from cloudkittyclient.openstack.common import cliutils
@@ -45,7 +46,9 @@ def arg(*args, **kwargs):
kwargs['help'] += " Defaults to %s." % kwargs['default']
required = kwargs.get('required', False)
if required:
kwargs['help'] += " 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.
@@ -140,8 +143,10 @@ def find_resource(manager, name_or_id):
try:
return manager.find(name=name_or_id)
except exc.HTTPNotFound:
msg = ("No %s with a name or ID of '%s' exists." %
(manager.resource_class.__name__.lower(), name_or_id))
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)
@@ -152,9 +157,12 @@ def args_array_to_dict(kwargs, key_to_convert):
kwargs[key_to_convert] = dict(v.split("=", 1)
for v in values_to_convert)
except ValueError:
raise exc.CommandError(
'%s must be a list of key=value not "%s"' % (
key_to_convert, values_to_convert))
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
@@ -172,9 +180,12 @@ def args_array_to_list_of_dicts(kwargs, key_to_convert):
dct[kv[0]] = kv[1].strip(" \"'") # strip spaces and quotes
kwargs[key_to_convert].append(dct)
except Exception:
raise exc.CommandError(
'%s must be a list of key1=value1;key2=value2;... not "%s"' % (
key_to_convert, values_to_convert))
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

28
cloudkittyclient/i18n.py Normal file
View File

@@ -0,0 +1,28 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""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
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@@ -1,45 +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
"""
try:
import oslo_i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate
# repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message
# catalog.
_translators = oslo_i18n.TranslatorFactory(domain='cloudkittyclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
except ImportError:
# NOTE(dims): Support for cases where a project wants to use
# code from oslo-incubator, but is not ready to be internationalized
# (like tempest)
_ = _LI = _LW = _LE = _LC = lambda x: x

View File

@@ -44,7 +44,7 @@ from oslo_utils import strutils
import six
from six.moves.urllib import parse
from cloudkittyclient.openstack.common._i18n import _
from cloudkittyclient.i18n import _
from cloudkittyclient.openstack.common.apiclient import exceptions

View File

@@ -38,7 +38,7 @@ from oslo_utils import encodeutils
from oslo_utils import importutils
import requests
from cloudkittyclient.openstack.common._i18n import _
from cloudkittyclient.i18n import _
from cloudkittyclient.openstack.common.apiclient import exceptions
_logger = logging.getLogger(__name__)

View File

@@ -38,7 +38,7 @@ import sys
import six
from cloudkittyclient.openstack.common._i18n import _
from cloudkittyclient.i18n import _
class ClientException(Exception):

View File

@@ -28,7 +28,7 @@ from oslo_utils import encodeutils
from oslo_utils import uuidutils
import six
from cloudkittyclient.openstack.common._i18n import _
from cloudkittyclient.i18n import _
from cloudkittyclient.openstack.common.apiclient import exceptions

View File

@@ -30,7 +30,7 @@ import prettytable
import six
from six import moves
from cloudkittyclient.openstack.common._i18n import _
from cloudkittyclient.i18n import _
class MissingArgs(Exception):

View File

@@ -31,6 +31,7 @@ from cloudkittyclient import client as ckclient
from cloudkittyclient.common import utils
from cloudkittyclient import exc
from cloudkittyclient.openstack.common import cliutils
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
@@ -53,6 +54,9 @@ def _positive_non_zero_int(argument_value):
class CloudkittyShell(object):
def __init__(self):
self.auth_plugin = ckclient.AuthPlugin()
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='cloudkitty',
@@ -119,6 +123,7 @@ class CloudkittyShell(object):
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(
@@ -172,7 +177,6 @@ class CloudkittyShell(object):
def parse_args(self, argv):
# Parse args once to find version
self.auth_plugin = ckclient.AuthPlugin()
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
self.auth_plugin.parse_opts(options)

View File

@@ -94,9 +94,9 @@ class CloudkittyModuleManagerTest(utils.BaseTestCase):
'GET', '/v1/rating/modules'
]
self.http_client.assert_called(*expect)
self.assertEqual(len(resources), 2)
self.assertEqual(resources[0].module_id, 'hashmap')
self.assertEqual(resources[1].module_id, 'noop')
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')
@@ -104,8 +104,8 @@ class CloudkittyModuleManagerTest(utils.BaseTestCase):
'GET', '/v1/rating/modules/hashmap'
]
self.http_client.assert_called(*expect)
self.assertEqual(resource.module_id, 'hashmap')
self.assertEqual(resource.enabled, True)
self.assertEqual('hashmap', resource.module_id)
self.assertTrue(resource.enabled)
class CloudkittyModuleTest(utils.BaseTestCase):

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@ from stevedore import extension
from cloudkittyclient import client as ckclient
from cloudkittyclient.openstack.common.apiclient import client
from cloudkittyclient.v1 import collector
from cloudkittyclient.v1 import core
from cloudkittyclient.v1 import report
from cloudkittyclient.v1 import storage
@@ -55,6 +56,7 @@ class Client(object):
self.http_client = client.BaseClient(self.client)
self.modules = core.CloudkittyModuleManager(self.http_client)
self.collector = collector.CollectorManager(self.http_client)
self.reports = report.ReportManager(self.http_client)
self.quotations = core.QuotationManager(self.http_client)
self.storage = storage.StorageManager(self.http_client)

View File

@@ -12,19 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from cloudkittyclient.common import base
from cloudkittyclient.v1.collector import mapping
from cloudkittyclient.v1.collector import state
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 CollectorManager(object):
def __init__(self, http_client):
self.mappings = mapping.MappingManager(http_client)
self.states = state.StateManager(http_client)

View File

@@ -0,0 +1,30 @@
# 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 Mapping(base.Resource):
key = 'mapping'
def __repr__(self):
return "<Mapping %s>" % self._info
class MappingManager(base.CrudManager):
resource_class = Mapping
base_url = "/v1/collector"
key = "mapping"
collection_key = "mappings"

View File

@@ -0,0 +1,87 @@
# 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('--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('--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('--collector',
help='Map a service to this collector.',
required=True)
@utils.arg('--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('--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('--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('--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('--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())

View File

@@ -0,0 +1,30 @@
# 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"

View File

@@ -15,23 +15,76 @@
from cloudkittyclient.common import base
class Service(base.Resource):
key = 'service'
class BaseAttributeMixin(object):
def _validate_attribute(self, attribute):
attr = getattr(self, attribute)
if attr:
kwargs = {attribute: attr}
return kwargs
def __repr__(self):
return "<hashmap.Service %s>" % self._info
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 FieldManager(client=self.manager.client).findall(
service_id=self.service_id
)
return self._get_resources(FieldManager, self.attribute)
class MappingsMixin(BaseAttributeMixin):
attribute = ''
@property
def mappings(self):
return MappingManager(client=self.manager.client).findall(
service_id=self.service_id
)
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):
@@ -41,18 +94,13 @@ class ServiceManager(base.CrudManager):
collection_key = 'services'
class Field(base.Resource):
class Field(base.Resource, ServiceMixin, MappingsMixin, ThresholdsMixin):
key = 'field'
attribute = 'field_id'
def __repr__(self):
return "<hashmap.Field %s>" % self._info
@property
def service(self):
return ServiceManager(client=self.manager.client).get(
service_id=self.service_id
)
class FieldManager(base.CrudManager):
resource_class = Field
@@ -61,26 +109,12 @@ class FieldManager(base.CrudManager):
collection_key = 'fields'
class Mapping(base.Resource):
class Mapping(base.Resource, ServiceMixin, FieldMixin, GroupMixin):
key = 'mapping'
def __repr__(self):
return "<hashmap.Mapping %s>" % self._info
@property
def service(self):
return ServiceManager(client=self.manager.client).get(
service_id=self.service_id
)
@property
def field(self):
if self.field_id is None:
return None
return FieldManager(client=self.manager.client).get(
service_id=self.service_id
)
class MappingManager(base.CrudManager):
resource_class = Mapping
@@ -89,8 +123,9 @@ class MappingManager(base.CrudManager):
collection_key = 'mappings'
class Group(base.Resource):
class Group(base.Resource, MappingsMixin, ThresholdsMixin):
key = 'group'
attribute = 'group_id'
def __repr__(self):
return "<hashmap.Group %s>" % self._info
@@ -112,7 +147,7 @@ class GroupManager(base.CrudManager):
return self._delete(url)
class Threshold(base.Resource):
class Threshold(base.Resource, ServiceMixin, FieldMixin, GroupMixin):
key = 'threshold'
def __repr__(self):
@@ -124,9 +159,3 @@ class ThresholdManager(base.CrudManager):
base_url = '/v1/rating/module_config/hashmap'
key = 'threshold'
collection_key = 'thresholds'
def group(self, threshold_id):
url = ('%(base_url)s/thresholds/%(threshold_id)s/group' %
{'base_url': self.base_url, 'threshold_id': threshold_id})
out = self._get(url)
return out

View File

@@ -89,7 +89,7 @@ def do_hashmap_field_create(cc, args={}):
help='Service id',
required=True)
def do_hashmap_field_list(cc, args={}):
"""Create a field."""
"""List fields."""
try:
created_field = cc.hashmap.fields.list(service_id=args.service_id)
except exc.HTTPNotFound:
@@ -112,26 +112,36 @@ def do_hashmap_field_delete(cc, args={}):
raise exc.CommandError('Field not found: %s' % args.counter_name)
@utils.arg('-c', '--cost',
help='Mapping cost',
required=True)
@utils.arg('-v', '--value',
help='Mapping value',
required=False)
@utils.arg('-t', '--type',
help='Mapping type (flat, rate)',
required=False)
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)
@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)
@utils.arg('-g', '--group-id',
help='Group id',
required=False)
@common_hashmap_mapping_arguments(create=True)
def do_hashmap_mapping_create(cc, args={}):
"""Create a ampping."""
"""Create a mapping."""
arg_to_field_mapping = {
'cost': 'cost',
'value': 'value',
@@ -152,18 +162,7 @@ def do_hashmap_mapping_create(cc, args={}):
@utils.arg('-m', '--mapping-id',
help='Mapping id',
required=True)
@utils.arg('-c', '--cost',
help='Mapping cost',
required=False)
@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)
@common_hashmap_mapping_arguments()
def do_hashmap_mapping_update(cc, args={}):
"""Update a mapping."""
arg_to_field_mapping = {
@@ -272,26 +271,36 @@ def do_hashmap_group_delete(cc, args={}):
raise exc.CommandError('Group not found: %s' % args.group_id)
@utils.arg('-l', '--level',
help='Threshold level',
required=True)
@utils.arg('-c', '--cost',
help='Threshold cost',
required=True)
@utils.arg('-m', '--map-type',
help='Threshold type (flat, rate)',
required=False)
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('-m', '--map-type',
help='Threshold type (flat, rate)',
required=False)
@utils.arg('-g', '--group-id',
help='Group 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)
@utils.arg('-g', '--group-id',
help='Group id',
required=False)
@common_hashmap_threshold_arguments(create=True)
def do_hashmap_threshold_create(cc, args={}):
"""Create a ampping."""
"""Create a mapping."""
arg_to_field_mapping = {
'level': 'level',
'cost': 'cost',
@@ -312,18 +321,7 @@ def do_hashmap_threshold_create(cc, args={}):
@utils.arg('-t', '--threshold-id',
help='Threshold id',
required=True)
@utils.arg('-l', '--level',
help='Threshold level',
required=False)
@utils.arg('-c', '--cost',
help='Threshold cost',
required=False)
@utils.arg('-m', '--map-type',
help='Threshold type (flat, rate)',
required=False)
@utils.arg('-g', '--group-id',
help='Group id',
required=False)
@common_hashmap_threshold_arguments()
def do_hashmap_threshold_update(cc, args={}):
"""Update a threshold."""
arg_to_field_mapping = {

View File

@@ -0,0 +1,30 @@
# 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'

View File

@@ -0,0 +1,28 @@
# 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)

View File

@@ -0,0 +1,31 @@
# 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

View File

@@ -0,0 +1,116 @@
# 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.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 exc.HTTPNotFound:
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 exc.HTTPNotFound:
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 exc.HTTPNotFound:
raise exc.CommandError('Script not found: %s' % args.counter_name)
@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 exc.HTTPNotFound:
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)

View File

@@ -30,11 +30,17 @@ class ReportManager(base.Manager):
def list_tenants(self):
return self.client.get(self.base_url + "/tenants").json()
def get_total(self, tenant_id, begin=None, end=None):
url = self.base_url + "/total?tenant_id=%s" % tenant_id
filter = [url]
def get_total(self, tenant_id=None, begin=None, end=None, service=None):
url = self.base_url + "/total"
filters = list()
if tenant_id:
filters.append("tenant_id=%s" % tenant_id)
if begin:
filter.append("begin=%s" % begin.isoformat())
filters.append("begin=%s" % begin.isoformat())
if end:
filter.append("end=%s" % end.isoformat())
return self.client.get("&".join(filter)).json()
filters.append("end=%s" % end.isoformat())
if service:
filters.append("service=%s" % service)
if filters:
url += "?%s" % ('&'.join(filters))
return self.client.get(url).json()

View File

@@ -13,11 +13,13 @@
# 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)
@@ -26,17 +28,23 @@ def do_report_tenant_list(cc, args):
@utils.arg('-t', '--tenant-id',
help='Tenant id',
required=False, dest='total_tenant_id')
required=False,
dest='total_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)
def do_total_get(cc, args):
"""Get total reports."""
begin = utils.ts2dt(args.begin) if args.begin else None
end = utils.ts2dt(args.end) if args.end else None
total = cc.reports.get_total(args.total_tenant_id,
total = cc.reports.get_total(tenant_id=args.total_tenant_id,
begin=begin,
end=end)
end=end,
service=args.service)
utils.print_dict({'Total': total or 0.0})

View File

@@ -32,6 +32,7 @@ from cloudkittyclient.common import utils
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)

View File

@@ -2,10 +2,10 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=0.6,!=0.7,<1.0
Babel>=1.3
python-keystoneclient
stevedore
oslo.i18n
oslo.serialization
oslo.utils
pbr>=1.6 # Apache-2.0
Babel>=1.3 # BSD
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
stevedore>=1.5.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils>=3.5.0 # Apache-2.0

View File

@@ -1,6 +1,6 @@
[metadata]
name = python-cloudkittyclient
summary = Cloudkittyclient is the api client for the cloudkitty rating project.
summary = API client of cloudkitty, Rating as a Service project.
description-file =
README.rst
author = OpenStack
@@ -16,7 +16,6 @@ classifier =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
[files]
@@ -29,6 +28,7 @@ console_scripts =
cloudkitty.client.modules =
hashmap = cloudkittyclient.v1.rating.hashmap.extension:Extension
pyscripts = cloudkittyclient.v1.rating.pyscripts.extension:Extension
[build_sphinx]
source-dir = doc/source

11
setup.py Executable file → Normal file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +16,14 @@
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr'],
setup_requires=['pbr>=1.8'],
pbr=True)

View File

@@ -2,14 +2,14 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=0.9.2,<0.10
hacking<0.10,>=0.9.2
coverage>=3.6
discover
python-subunit
sphinx>=1.1.2
oslosphinx
oslotest>=1.1.0.0a1
testrepository>=0.0.18
testscenarios>=0.4
testtools>=0.9.34
coverage>=3.6 # Apache-2.0
discover # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT

View File

@@ -1,6 +1,6 @@
[tox]
minversion = 1.6
envlist = py33,py34,py27,pypy,pep8
envlist = py34,py27,pypy,pep8
skipsdist = True
[testenv]
@@ -32,3 +32,6 @@ show-source = True
ignore = E123,E125,H803
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
[hacking]
import_exceptions = cloudkittyclient.i18n