Compare commits
43 Commits
2023.1-eom
...
6.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dea9c3043a | ||
|
|
2d965f9459 | ||
|
|
5bd8a2f8fd | ||
|
|
a41e96b0e3 | ||
|
|
e1408eba2b | ||
|
|
59dc73588f | ||
|
|
ab5b02d84c | ||
|
|
ec1bf47a47 | ||
| c26fc952ed | |||
|
|
f8da9dab78 | ||
|
|
2240208aa5 | ||
|
|
c4711dfd16 | ||
|
|
4fec2c3a0b | ||
|
|
4058c7f379 | ||
| 5573390afa | |||
|
|
4f375f8d23 | ||
|
|
826297eb04 | ||
| 9db6f56b9a | |||
|
|
8e6f9aee06 | ||
|
|
9fda7927a1 | ||
|
|
ca7a0b1be2 | ||
|
|
33905a1913 | ||
|
|
e2d507900a | ||
| acbd7ae72e | |||
|
|
247b3ee51d | ||
|
|
79a4606ce2 | ||
|
|
e912feb85d | ||
|
|
9bb62e0338 | ||
| 8d992f59dd | |||
|
|
6ee36ef0e3 | ||
|
|
98c13304bd | ||
|
|
024399e10a | ||
|
|
0698fdde3a | ||
| bb26dbc381 | |||
| d876b1b77f | |||
| 2a3733e869 | |||
| 00043cc23f | |||
|
|
d30991cefa | ||
| 36566c32fc | |||
|
|
3e9fd5b540 | ||
| 255a871785 | |||
|
|
244f229af7 | ||
| 0835706738 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ dist
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
releasenotes/build
|
||||
.idea/
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
run: playbooks/cloudkittyclient-devstack-functional/run.yaml
|
||||
post-run: playbooks/cloudkittyclient-devstack-functional/post.yaml
|
||||
required-projects:
|
||||
- name: openstack/ceilometer
|
||||
- name: openstack/cloudkitty
|
||||
- name: openstack/python-cloudkittyclient
|
||||
roles:
|
||||
@@ -18,11 +19,10 @@
|
||||
- ^releasenotes/.*$
|
||||
vars:
|
||||
devstack_plugins:
|
||||
ceilometer: https://opendev.org/openstack/ceilometer
|
||||
cloudkitty: https://opendev.org/openstack/cloudkitty
|
||||
devstack_localrc:
|
||||
CLOUDKITTY_FETCHER: keystone
|
||||
DEVSTACK_GATE_USE_PYTHON3: "True"
|
||||
USE_PYTHON3: True
|
||||
devstack_services:
|
||||
ck-api: true
|
||||
horizon: false
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from osc_lib import utils
|
||||
|
||||
DEFAULT_API_VERSION = '1'
|
||||
DEFAULT_API_VERSION = '2'
|
||||
API_VERSION_OPTION = 'os_rating_api_version'
|
||||
API_NAME = "rating"
|
||||
API_VERSIONS = {
|
||||
|
||||
@@ -18,7 +18,7 @@ from sys import argv
|
||||
|
||||
import cliff.app
|
||||
from cliff.commandmanager import CommandManager
|
||||
import os_client_config
|
||||
from openstack import config as occ
|
||||
from oslo_log import log
|
||||
|
||||
from cloudkittyclient import client
|
||||
@@ -90,7 +90,7 @@ class CloudKittyShell(cliff.app.App):
|
||||
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self.cloud_config = os_client_config.OpenStackConfig()
|
||||
self.cloud_config = occ.OpenStackConfig()
|
||||
super(CloudKittyShell, self).__init__(
|
||||
description='CloudKitty CLI client',
|
||||
version=utils.get_version(),
|
||||
@@ -128,7 +128,7 @@ class CloudKittyShell(cliff.app.App):
|
||||
@property
|
||||
def client(self):
|
||||
if self._client is None:
|
||||
self.cloud = self.cloud_config.get_one_cloud(
|
||||
self.cloud = self.cloud_config.get_one(
|
||||
argparse=self.options)
|
||||
session = self.cloud.get_session()
|
||||
adapter_options = dict(
|
||||
|
||||
@@ -15,26 +15,57 @@
|
||||
#
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from cloudkittyclient.tests import utils
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseFunctionalTest(utils.BaseTestCase):
|
||||
|
||||
# DevStack is using VENV by default. Therefore, to execute the commands,
|
||||
# we need to activate the VENV. And, to do that, we need the VENV path.
|
||||
# This path is hardcoded here because we could not find a variable in this
|
||||
# Python code to retrieve the VENV variable from the test machine.
|
||||
# It seems that because of the stack TOX -> stestr -> this python code, and
|
||||
# so on, we are not able to access the DevStack variables here.
|
||||
#
|
||||
# If somebody finds a solution, we can remove the hardcoded path here.
|
||||
DEV_STACK_VENV_BASE_PATH = "/opt/stack/data/venv"
|
||||
|
||||
BASE_COMMAND_WITH_VENV = "source %s/bin/activate && %s "
|
||||
|
||||
def _run(self, executable, action,
|
||||
flags='', params='', fmt='-f json', stdin=None, has_output=True):
|
||||
if not has_output:
|
||||
fmt = ''
|
||||
|
||||
does_venv_exist = not os.system("ls -lah /opt/stack/data/venv")
|
||||
LOG.info("Test to check if the VENV file exist returned: [%s].",
|
||||
does_venv_exist)
|
||||
|
||||
system_variables = os.environ.copy()
|
||||
LOG.info("System variables [%s] found when executing the tests.",
|
||||
system_variables)
|
||||
|
||||
cmd = ' '.join([executable, flags, action, params, fmt])
|
||||
cmd = shlex.split(cmd)
|
||||
|
||||
actual_command_with_venv = self.BASE_COMMAND_WITH_VENV % (
|
||||
self.DEV_STACK_VENV_BASE_PATH, cmd)
|
||||
|
||||
LOG.info("Command being executed: [%s].", actual_command_with_venv)
|
||||
|
||||
p = subprocess.Popen(
|
||||
cmd, env=os.environ.copy(), shell=False,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE if stdin else None,
|
||||
["bash", "-c", actual_command_with_venv],
|
||||
env=os.environ.copy(), shell=False, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, stdin=subprocess.PIPE if stdin else None
|
||||
)
|
||||
stdout, stderr = p.communicate(input=stdin)
|
||||
LOG.info("Standard output [%s] and error output [%s] for command "
|
||||
"[%s]. ", stdout, stderr, actual_command_with_venv)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError('"{cmd}" returned {val}: {msg}'.format(
|
||||
cmd=' '.join(cmd), val=p.returncode, msg=stderr))
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from cloudkittyclient.tests.functional import base
|
||||
|
||||
|
||||
@@ -136,13 +139,15 @@ class CkHashmapTest(base.BaseFunctionalTest):
|
||||
self.assertEqual(len(resp), 0)
|
||||
|
||||
def test_create_get_update_delete_mapping_service(self):
|
||||
future_date = datetime.now() + timedelta(days=1)
|
||||
date_iso = future_date.isoformat()
|
||||
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]
|
||||
params=f'-s {service_id} 12 --start {date_iso}')[0]
|
||||
mapping_id = resp['Mapping ID']
|
||||
self._mappings.append(mapping_id)
|
||||
self.assertEqual(resp['Service ID'], service_id)
|
||||
@@ -173,6 +178,8 @@ class CkHashmapTest(base.BaseFunctionalTest):
|
||||
'hashmap service delete', params=service_id, has_output=False)
|
||||
|
||||
def test_create_get_update_delete_mapping_field(self):
|
||||
future_date = datetime.now() + timedelta(days=1)
|
||||
date_iso = future_date.isoformat()
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
@@ -185,7 +192,8 @@ class CkHashmapTest(base.BaseFunctionalTest):
|
||||
# Create mapping
|
||||
resp = self.runner(
|
||||
'hashmap mapping create',
|
||||
params='--field-id {} 12 --value testvalue'.format(field_id))[0]
|
||||
params=f'--field-id {field_id} 12 --value '
|
||||
f'testvalue --start {date_iso}')[0]
|
||||
mapping_id = resp['Mapping ID']
|
||||
self._mappings.append(service_id)
|
||||
self.assertEqual(resp['Field ID'], field_id)
|
||||
@@ -203,6 +211,45 @@ class CkHashmapTest(base.BaseFunctionalTest):
|
||||
params='--cost 10 {}'.format(mapping_id))[0]
|
||||
self.assertEqual(float(resp['Cost']), float(10))
|
||||
|
||||
def test_create_get_update_delete_mapping_field_started(self):
|
||||
resp = self.runner('hashmap service create',
|
||||
params='testservice_date_started')[0]
|
||||
service_id = resp['Service ID']
|
||||
self._services.append(service_id)
|
||||
|
||||
resp = self.runner(
|
||||
'hashmap field create',
|
||||
params='{} testfield_date_started'.format(service_id))[0]
|
||||
field_id = resp['Field ID']
|
||||
self._fields.append(field_id)
|
||||
|
||||
# Create mapping
|
||||
resp = self.runner(
|
||||
'hashmap mapping create',
|
||||
params=f'--field-id {field_id} 12 --value '
|
||||
f'testvalue')[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))
|
||||
|
||||
# Should not be able to update a rule that is running (start < now)
|
||||
try:
|
||||
self.runner('hashmap mapping update',
|
||||
params='--cost 10 {}'.format(mapping_id))[0]
|
||||
except RuntimeError as e:
|
||||
expected_error = ("You are allowed to update only the attribute "
|
||||
"[end] as this rule is already running as it "
|
||||
"started on ")
|
||||
self.assertIn(expected_error, str(e))
|
||||
|
||||
def test_group_mappings_get(self):
|
||||
# Service and group
|
||||
resp = self.runner('hashmap service create', params='testservice')[0]
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from cloudkittyclient.tests.functional import base
|
||||
|
||||
|
||||
@@ -23,9 +26,12 @@ class CkPyscriptTest(base.BaseFunctionalTest):
|
||||
self.runner = self.cloudkitty
|
||||
|
||||
def test_create_get_update_list_delete(self):
|
||||
future_date = datetime.now() + timedelta(days=1)
|
||||
date_iso = future_date.isoformat()
|
||||
# Create
|
||||
resp = self.runner(
|
||||
'pyscript create', params="testscript 'return 0'")[0]
|
||||
'pyscript create', params=f"testscript "
|
||||
f"'return 0' --start {date_iso}")[0]
|
||||
script_id = resp['Script ID']
|
||||
self.assertEqual(resp['Name'], 'testscript')
|
||||
|
||||
@@ -37,8 +43,9 @@ class CkPyscriptTest(base.BaseFunctionalTest):
|
||||
# Update
|
||||
resp = self.runner(
|
||||
'pyscript update',
|
||||
params="-n newname -d 'return 1' {}".format(script_id))[0]
|
||||
self.assertEqual(resp['Name'], 'newname')
|
||||
params="-d 'return 1' {} --description "
|
||||
"desc".format(script_id))[0]
|
||||
self.assertEqual(resp['Script Description'], 'desc')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
self.assertEqual(resp['Data'], 'return 1')
|
||||
|
||||
@@ -46,13 +53,49 @@ class CkPyscriptTest(base.BaseFunctionalTest):
|
||||
resp = self.runner('pyscript list')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Name'], 'newname')
|
||||
self.assertEqual(resp['Script Description'], 'desc')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
self.assertEqual(resp['Data'], 'return 1')
|
||||
|
||||
# Delete
|
||||
self.runner('pyscript delete', params=script_id, has_output=False)
|
||||
|
||||
def test_create_get_update_list_delete_started(self):
|
||||
# Create
|
||||
resp = self.runner(
|
||||
'pyscript create', params="testscript_started "
|
||||
"'return 0'")[0]
|
||||
script_id = resp['Script ID']
|
||||
self.assertEqual(resp['Name'], 'testscript_started')
|
||||
|
||||
# Get
|
||||
resp = self.runner('pyscript get', params=script_id)[0]
|
||||
self.assertEqual(resp['Name'], 'testscript_started')
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
|
||||
# Should not be able to update a rule that is running (start < now)
|
||||
try:
|
||||
self.runner(
|
||||
'pyscript update',
|
||||
params="-d 'return 1' {} --description "
|
||||
"desc".format(script_id))[0]
|
||||
except RuntimeError as e:
|
||||
expected_error = ("You are allowed to update only the attribute "
|
||||
"[end] as this rule is already running as it "
|
||||
"started on ")
|
||||
self.assertIn(expected_error, str(e))
|
||||
|
||||
# List
|
||||
resp = self.runner('pyscript list')
|
||||
self.assertEqual(len(resp), 1)
|
||||
resp = resp[0]
|
||||
self.assertEqual(resp['Script Description'], None)
|
||||
self.assertEqual(resp['Script ID'], script_id)
|
||||
self.assertEqual(resp['Data'], 'return 0')
|
||||
|
||||
# Delete
|
||||
self.runner('pyscript delete', params=script_id, has_output=False)
|
||||
|
||||
|
||||
class OSCPyscriptTest(CkPyscriptTest):
|
||||
|
||||
|
||||
@@ -110,7 +110,10 @@ class TestHashmap(base.BaseAPIEndpointTestCase):
|
||||
self.assertRaises(exc.ArgumentRequired, self.hashmap.get_mapping)
|
||||
|
||||
def test_create_mapping(self):
|
||||
kwargs = dict(cost=2, value='value', field_id='field_id')
|
||||
kwargs = dict(cost=2, value='value', field_id='field_id',
|
||||
name='name', start="2024-01-01",
|
||||
end="2024-01-01",
|
||||
description="description")
|
||||
body = dict(
|
||||
cost=kwargs.get('cost'),
|
||||
value=kwargs.get('value'),
|
||||
@@ -119,6 +122,10 @@ class TestHashmap(base.BaseAPIEndpointTestCase):
|
||||
group_id=kwargs.get('group_id'),
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
start="2024-01-01",
|
||||
end="2024-01-01",
|
||||
description="description",
|
||||
name='name'
|
||||
)
|
||||
self.hashmap.create_mapping(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
|
||||
@@ -38,7 +38,8 @@ class TestPyscripts(base.BaseAPIEndpointTestCase):
|
||||
self.assertRaises(exc.ArgumentRequired, self.pyscripts.get_script)
|
||||
|
||||
def test_create_script(self):
|
||||
kwargs = dict(name='name', data='data')
|
||||
kwargs = dict(name='name', data='data', start=None,
|
||||
end=None, description=None)
|
||||
self.pyscripts.create_script(**kwargs)
|
||||
self.api_client.post.assert_called_once_with(
|
||||
'/v1/rating/module_config/pyscripts/scripts/', json=kwargs)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
# under the License.
|
||||
#
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import pbr.version
|
||||
|
||||
@@ -82,11 +81,6 @@ def format_http_errors(ignore):
|
||||
"""
|
||||
|
||||
def wrap(cls):
|
||||
# If you want pretty errors, use python3.
|
||||
# __qualname__ does not exist in python 2
|
||||
if sys.version_info.major < 3:
|
||||
return cls
|
||||
|
||||
def predicate(item):
|
||||
# This avoids decorating functions of parent classes
|
||||
return (inspect.isfunction(item)
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
import uuid
|
||||
|
||||
from cloudkittyclient.common import base
|
||||
from cloudkittyclient import exc
|
||||
|
||||
@@ -171,6 +173,14 @@ class HashmapManager(base.BaseManager):
|
||||
:type type: str
|
||||
:param value: Value of the mapping
|
||||
:type value: str
|
||||
:param name: Name of the mapping
|
||||
:type name: str
|
||||
:param start: Date the mapping starts being valid
|
||||
:type start: str
|
||||
:param end: Date the mapping stops being valid
|
||||
:type end: str
|
||||
:param description: Description of the mapping
|
||||
:type description: str
|
||||
"""
|
||||
if kwargs.get('cost') is None:
|
||||
raise exc.ArgumentRequired("'cost' argument is required")
|
||||
@@ -196,6 +206,16 @@ class HashmapManager(base.BaseManager):
|
||||
tenant_id=kwargs.get('tenant_id'),
|
||||
type=kwargs.get('type') or 'flat',
|
||||
)
|
||||
if kwargs.get('description'):
|
||||
body['description'] = kwargs.get('description')
|
||||
if kwargs.get('start'):
|
||||
body['start'] = kwargs.get('start')
|
||||
if kwargs.get('end'):
|
||||
body['end'] = kwargs.get('end')
|
||||
if kwargs.get('name'):
|
||||
body['name'] = kwargs.get('name')
|
||||
else:
|
||||
body['name'] = uuid.uuid4().hex[:24]
|
||||
url = self.get_url('mappings', kwargs)
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
|
||||
@@ -257,6 +257,10 @@ class CliCreateMapping(lister.Lister):
|
||||
('service_id', 'Service ID'),
|
||||
('group_id', 'Group ID'),
|
||||
('tenant_id', 'Project ID'),
|
||||
('name', 'Mapping Name'),
|
||||
('start', 'Mapping Start Date'),
|
||||
('end', 'Mapping End Date'),
|
||||
('Description', 'Mapping Description')
|
||||
]
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
@@ -275,6 +279,11 @@ class CliCreateMapping(lister.Lister):
|
||||
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')
|
||||
parser.add_argument('--name', type=str, help='Mapping Name')
|
||||
parser.add_argument('--start', type=str, help='Mapping Start')
|
||||
parser.add_argument('--end', type=str, help='Mapping End')
|
||||
parser.add_argument('--description', type=str,
|
||||
help='Mapping Description')
|
||||
return parser
|
||||
|
||||
|
||||
@@ -321,6 +330,11 @@ class CliUpdateMapping(lister.Lister):
|
||||
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')
|
||||
parser.add_argument('--name', type=str, help='Mapping Name')
|
||||
parser.add_argument('--start', type=str, help='Mapping Start')
|
||||
parser.add_argument('--end', type=str, help='Mapping End')
|
||||
parser.add_argument('--description', type=str,
|
||||
help='Mapping Description')
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -50,13 +50,22 @@ class PyscriptManager(base.BaseManager):
|
||||
:type name: str
|
||||
:param data: Content of the script
|
||||
:type data: str
|
||||
:param start: Date the script starts being valid
|
||||
:type start: str
|
||||
:param end: Date the script stops being valid
|
||||
:type end: str
|
||||
:param description: Description of the script
|
||||
:type description: 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'])
|
||||
body = dict(name=kwargs['name'], data=kwargs['data'],
|
||||
start=kwargs.get('start'),
|
||||
end=kwargs.get('end'),
|
||||
description=kwargs.get('description'))
|
||||
return self.api_client.post(url, json=body).json()
|
||||
|
||||
def update_script(self, **kwargs):
|
||||
@@ -68,11 +77,17 @@ class PyscriptManager(base.BaseManager):
|
||||
:type name: str
|
||||
:param data: Content of the script
|
||||
:type data: str
|
||||
:param start: Date the script starts being valid
|
||||
:type start: str
|
||||
:param end: Date the script stops being valid
|
||||
:type end: str
|
||||
:param description: Description of the script
|
||||
:type description: 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'):
|
||||
for key in ('name', 'data', 'start', 'end', 'description'):
|
||||
if kwargs.get(key):
|
||||
script[key] = kwargs[key]
|
||||
script.pop('checksum', None)
|
||||
|
||||
@@ -26,6 +26,9 @@ class BaseScriptCli(lister.Lister):
|
||||
('script_id', 'Script ID'),
|
||||
('checksum', 'Checksum'),
|
||||
('data', 'Data'),
|
||||
('start', 'Script Start Date'),
|
||||
('end', 'Script End Date'),
|
||||
('description', 'Script Description')
|
||||
]
|
||||
|
||||
|
||||
@@ -82,6 +85,10 @@ class CliCreateScript(BaseScriptCli):
|
||||
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')
|
||||
parser.add_argument('--start', type=str, help='Script Start')
|
||||
parser.add_argument('--end', type=str, help='Script End')
|
||||
parser.add_argument('--description', type=str,
|
||||
help='Script Description')
|
||||
return parser
|
||||
|
||||
|
||||
@@ -107,6 +114,10 @@ class CliUpdateScript(BaseScriptCli):
|
||||
parser.add_argument('-n', '--name', type=str, help='Script Name')
|
||||
parser.add_argument('-d', '--data', type=str,
|
||||
help='Script Data or data file')
|
||||
parser.add_argument('--start', type=str, help='Script Start')
|
||||
parser.add_argument('--end', type=str, help='Script End')
|
||||
parser.add_argument('--description', type=str,
|
||||
help='Script Description')
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ class ReprocessingManager(base.BaseManager):
|
||||
|
||||
url = '/v2/task/reprocesses'
|
||||
|
||||
url_to_post = '/v2/task/reprocess'
|
||||
|
||||
def get_reprocessing_tasks(self, offset=0, limit=100, scope_ids=[],
|
||||
order="DESC", **kwargs):
|
||||
"""Returns a paginated list of reprocessing tasks.
|
||||
@@ -75,4 +73,4 @@ class ReprocessingManager(base.BaseManager):
|
||||
|
||||
body = dict(filter(lambda elem: bool(elem[1]), body.items()))
|
||||
|
||||
return self.api_client.post(self.url_to_post, json=body).json()
|
||||
return self.api_client.post(self.url, json=body).json()
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
from oslo_utils import strutils
|
||||
|
||||
from cloudkittyclient.common import base
|
||||
from cloudkittyclient import exc
|
||||
|
||||
from distutils.util import strtobool
|
||||
|
||||
|
||||
class ScopeManager(base.BaseManager):
|
||||
"""Class used to handle /v2/scope endpoint"""
|
||||
@@ -133,7 +133,8 @@ class ScopeManager(base.BaseManager):
|
||||
)
|
||||
|
||||
if kwargs.get('active'):
|
||||
body['active'] = strtobool(kwargs.get('active'))
|
||||
body['active'] = strutils.bool_from_string(
|
||||
kwargs.get('active'), strict=True)
|
||||
|
||||
# Stripping None
|
||||
body = dict(filter(lambda elem: elem[1] is not None, body.items()))
|
||||
|
||||
@@ -35,10 +35,20 @@ class CliSummaryGet(lister.Lister):
|
||||
help='Maximal number of elements')
|
||||
parser.add_argument('-g', '--groupby', type=str, action='append',
|
||||
help='Attribute to group the summary by. Can be '
|
||||
'specified several times')
|
||||
'specified several times. One can also group '
|
||||
'by different time options such as: "time-d" '
|
||||
'to group by day of the year, "time-w" to '
|
||||
'group by week of the year, "time-m" to '
|
||||
'group by month, and "time-y" to group data '
|
||||
'by year.')
|
||||
parser.add_argument('--filter', type=filter_, action='append',
|
||||
help="Optional filter, in 'key:value' format. Can "
|
||||
"be specified several times.")
|
||||
"be specified several times. It is also "
|
||||
"possible to filter data using the group by "
|
||||
"values. However, one needs to group by as "
|
||||
"well; for instance, if one wants to filter "
|
||||
"by resource id (id), then we need to group "
|
||||
"by id via the option '-g id'.")
|
||||
parser.add_argument('-b', '--begin', type=timeutils.parse_isotime,
|
||||
help="Start of the period to query, in iso8601 "
|
||||
"format. Example: 2019-05-01T00:00:00Z.")
|
||||
|
||||
@@ -48,9 +48,8 @@ project = 'python-cloudkittyclient'
|
||||
copyright = '2017, OpenStack Foundation'
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/python-cloudkittyclient'
|
||||
bug_project = 'cloudkitty'
|
||||
bug_tag = 'python-cloudkittyclient'
|
||||
openstackdocs_repo_name = 'openstack/python-cloudkittyclient'
|
||||
openstackdocs_use_storyboard = True
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
@@ -42,17 +42,17 @@ Version
|
||||
-------
|
||||
|
||||
Two versions of the client exist: v1 and v2. The v2 version adds support for
|
||||
v2 API endpoints. The default API version is 1. You can specify which API
|
||||
v2 API endpoints. The default API version is 2. You can specify which API
|
||||
version you want to use via a CLI option:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# EITHER
|
||||
cloudkitty --os-rating-api-version 2 summary get
|
||||
cloudkitty --os-rating-api-version 1 module list
|
||||
|
||||
# OR
|
||||
export OS_RATING_API_VERSION=2
|
||||
cloudkitty summary get
|
||||
export OS_RATING_API_VERSION=1
|
||||
cloudkitty module list
|
||||
|
||||
Again, the option can also be provided to the OSC plugin, both via the CLI
|
||||
flag or the environment variable.
|
||||
@@ -68,7 +68,7 @@ to use it without keystone authentication, cloudkittyclient provides the
|
||||
>>> from cloudkittyclient import auth as ck_auth
|
||||
|
||||
>>> auth = ck_auth.CloudKittyNoAuthPlugin(endpoint='http://127.0.0.1:8889')
|
||||
>>> client = ck_client.Client('1', auth=auth)
|
||||
>>> client = ck_client.Client('2', auth=auth)
|
||||
>>> client.report.get_summary()
|
||||
{u'summary': [{u'begin': u'2018-03-01T00:00:00',
|
||||
u'end': u'2018-04-01T00:00:00',
|
||||
@@ -95,7 +95,7 @@ Else, use it the same way as any other OpenStack client::
|
||||
|
||||
>>> ck_session = session.Session(auth=auth)
|
||||
|
||||
>>> c = ck_client.Client('1', session=ck_session)
|
||||
>>> c = ck_client.Client('2', session=ck_session)
|
||||
|
||||
>>> c.report.get_summary()
|
||||
{u'summary': [{u'begin': u'2018-03-01T00:00:00',
|
||||
@@ -112,25 +112,25 @@ Else, use it the same way as any other OpenStack client::
|
||||
and ``cacert``::
|
||||
|
||||
>>> client = ck_client.Client(
|
||||
'1', auth=auth, insecure=False, cacert='/path/to/ca')
|
||||
'2', auth=auth, insecure=False, cacert='/path/to/ca')
|
||||
|
||||
|
||||
If you want to use the v2 API, you have to specify it at client instanciation
|
||||
If you want to use the v1 API, you have to specify it at client instanciation
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
c = ck_client.Client('2', session=session)
|
||||
c = ck_client.Client('1', session=session)
|
||||
|
||||
When using the ``cloudkitty`` CLI client with keystone authentication, the
|
||||
auth plugin to use should automagically be detected. If not, you can specify
|
||||
the auth plugin to use with ``--os-auth-type/--os-auth-plugin``::
|
||||
|
||||
$ cloudkitty --debug --os-auth-type cloudkitty-noauth summary get
|
||||
+------------+---------------+------------+---------------------+---------------------+
|
||||
| Project ID | Resource Type | Rate | Begin Time | End Time |
|
||||
+------------+---------------+------------+---------------------+---------------------+
|
||||
| ALL | ALL | 1676.95499 | 2018-03-01T00:00:00 | 2018-04-01T00:00:00 |
|
||||
+------------+---------------+------------+---------------------+---------------------+
|
||||
+---------------------------+---------------------------+------------+-------------------+
|
||||
| Begin | End | Qty | Rate |
|
||||
+---------------------------+---------------------------+------------+-------------------+
|
||||
| 2025-12-01T00:00:00+01:00 | 2026-01-01T00:00:00+01:00 | 21662194.0 | 3618130.211340219 |
|
||||
+---------------------------+---------------------------+------------+-------------------+
|
||||
|
||||
|
||||
CSV report generation
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The default API version has been changed from v1 to v2. Users who want
|
||||
to continue using the v1 API must now explicitly specify the API version
|
||||
using the ``--os-rating-api-version 1`` CLI option or by setting the
|
||||
``OS_RATING_API_VERSION=1`` environment variable. If no version is
|
||||
specified, the client will now use the v2 API by default.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixed a bug where creating a reprocessing task would fail due to sending a
|
||||
POST request to the wrong endpoint.
|
||||
5
releasenotes/notes/remove-py39-8c71acfb47b84070.yaml
Normal file
5
releasenotes/notes/remove-py39-8c71acfb47b84070.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Support for Python 3.9 has been removed. Now Python 3.10 is the minimum
|
||||
version supported.
|
||||
6
releasenotes/source/2023.1.rst
Normal file
6
releasenotes/source/2023.1.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2023.1 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: unmaintained/2023.1
|
||||
6
releasenotes/source/2023.2.rst
Normal file
6
releasenotes/source/2023.2.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2023.2 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/2023.2
|
||||
6
releasenotes/source/2024.1.rst
Normal file
6
releasenotes/source/2024.1.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2024.1 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/2024.1
|
||||
6
releasenotes/source/2024.2.rst
Normal file
6
releasenotes/source/2024.2.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2024.2 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/2024.2
|
||||
6
releasenotes/source/2025.1.rst
Normal file
6
releasenotes/source/2025.1.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2025.1 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/2025.1
|
||||
6
releasenotes/source/2025.2.rst
Normal file
6
releasenotes/source/2025.2.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
===========================
|
||||
2025.2 Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/2025.2
|
||||
@@ -8,6 +8,12 @@ Contents
|
||||
:maxdepth: 2
|
||||
|
||||
unreleased
|
||||
2025.2
|
||||
2025.1
|
||||
2024.2
|
||||
2024.1
|
||||
2023.2
|
||||
2023.1
|
||||
zed
|
||||
yoga
|
||||
xena
|
||||
|
||||
@@ -3,4 +3,4 @@ Victoria Series Release Notes
|
||||
=============================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/victoria
|
||||
:branch: unmaintained/victoria
|
||||
|
||||
@@ -3,4 +3,4 @@ Wallaby Series Release Notes
|
||||
============================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/wallaby
|
||||
:branch: unmaintained/wallaby
|
||||
|
||||
@@ -3,4 +3,4 @@ Xena Series Release Notes
|
||||
=========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/xena
|
||||
:branch: unmaintained/xena
|
||||
|
||||
@@ -3,4 +3,4 @@ Yoga Series Release Notes
|
||||
=========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/yoga
|
||||
:branch: unmaintained/yoga
|
||||
|
||||
@@ -3,4 +3,4 @@ Zed Series Release Notes
|
||||
========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/zed
|
||||
:branch: unmaintained/zed
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
# date but we do not test them so no guarantee of having them all correct. If
|
||||
# you find any incorrect lower bounds, let us know or propose a fix.
|
||||
|
||||
# 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.
|
||||
|
||||
pbr>=5.5.1 # Apache-2.0
|
||||
cliff>=3.5.0 # Apache-2.0
|
||||
keystoneauth1>=4.3.0 # Apache-2.0
|
||||
@@ -13,5 +9,5 @@ oslo.utils>=4.7.0 # Apache-2.0
|
||||
oslo.log>=4.4.0 # Apache-2.0
|
||||
PyYAML>=5.3.1 # MIT
|
||||
jsonpath-rw-ext>=1.2.0 # Apache-2.0
|
||||
os-client-config>=2.1.0 # Apache-2.0
|
||||
openstacksdk>=0.10.0 # Apache-2.0
|
||||
osc-lib>=2.3.0 # Apache-2.0
|
||||
|
||||
@@ -6,7 +6,7 @@ description_file =
|
||||
author = OpenStack
|
||||
author_email = openstack-discuss@lists.openstack.org
|
||||
home_page = https://docs.openstack.org/python-cloudkittyclient/latest/
|
||||
python_requires = >=3.6
|
||||
python_requires = >=3.10
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
@@ -17,10 +17,9 @@ classifier =
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
|
||||
[files]
|
||||
packages =
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking>=3.0.1,<3.1.0 # Apache-2.0
|
||||
|
||||
# remove this pyflakes from here once you bump the
|
||||
# hacking to 3.2.0 or above. hacking 3.2.0 takes
|
||||
# care of pyflakes version compatibilty.
|
||||
pyflakes>=2.1.1
|
||||
|
||||
hacking>=7.0.0,<7.1.0 # Apache-2.0
|
||||
coverage>=4.0,!=4.4 # Apache-2.0
|
||||
python-subunit>=1.4.0 # Apache-2.0/BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
|
||||
39
tox.ini
39
tox.ini
@@ -1,7 +1,6 @@
|
||||
[tox]
|
||||
minversion = 3.18.0
|
||||
envlist = py3,pep8
|
||||
skipsdist = True
|
||||
ignore_basepython_conflict = True
|
||||
|
||||
[testenv]
|
||||
@@ -9,14 +8,13 @@ basepython = python3
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
DEVSTACK_VENV={env:DEVSTACK_VENV}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = stestr run {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
PYTHON=coverage run --source cloudkittyclient --parallel-mode
|
||||
commands =
|
||||
stestr run {posargs}
|
||||
@@ -29,12 +27,40 @@ commands =
|
||||
commands = oslo_debug_helper -t cloudkittyclient/tests {posargs}
|
||||
|
||||
[testenv:functional-v1]
|
||||
passenv = OS_CLOUD OS_PROJECT_DOMAIN_ID OS_USER_DOMAIN_ID OS_PROJECT_DOMAIN_NAME OS_USER_DOMAIN_NAME OS_PROJECT_NAME OS_IDENTITY_API_VERSION OS_PASSWORD OS_AUTH_TYPE OS_AUTH_URL OS_USERNAME OS_ENDPOINT
|
||||
passenv =
|
||||
OS_CLOUD
|
||||
OS_PROJECT_DOMAIN_ID
|
||||
OS_USER_DOMAIN_ID
|
||||
OS_PROJECT_DOMAIN_NAME
|
||||
OS_USER_DOMAIN_NAME
|
||||
OS_PROJECT_NAME
|
||||
OS_IDENTITY_API_VERSION
|
||||
OS_PASSWORD
|
||||
OS_AUTH_TYPE
|
||||
OS_AUTH_URL
|
||||
OS_USERNAME
|
||||
OS_ENDPOINT
|
||||
DEVSTACK_VENV
|
||||
VIRTUAL_ENV
|
||||
setenv = OS_RATING_API_VERSION=1
|
||||
commands = stestr run --concurrency=1 --test-path ./cloudkittyclient/tests/functional/v1
|
||||
|
||||
[testenv:functional-v2]
|
||||
passenv = OS_CLOUD OS_PROJECT_DOMAIN_ID OS_USER_DOMAIN_ID OS_PROJECT_DOMAIN_NAME OS_USER_DOMAIN_NAME OS_PROJECT_NAME OS_IDENTITY_API_VERSION OS_PASSWORD OS_AUTH_TYPE OS_AUTH_URL OS_USERNAME OS_ENDPOINT
|
||||
passenv =
|
||||
OS_CLOUD
|
||||
OS_PROJECT_DOMAIN_ID
|
||||
OS_USER_DOMAIN_ID
|
||||
OS_PROJECT_DOMAIN_NAME
|
||||
OS_USER_DOMAIN_NAME
|
||||
OS_PROJECT_NAME
|
||||
OS_IDENTITY_API_VERSION
|
||||
OS_PASSWORD
|
||||
OS_AUTH_TYPE
|
||||
OS_AUTH_URL
|
||||
OS_USERNAME
|
||||
OS_ENDPOINT
|
||||
DEVSTACK_VENV
|
||||
VIRTUAL_ENV
|
||||
setenv = OS_RATING_API_VERSION=2
|
||||
commands = stestr run --concurrency=1 --test-path ./cloudkittyclient/tests/functional/v2
|
||||
|
||||
@@ -51,7 +77,6 @@ deps =
|
||||
commands = sphinx-build --keep-going -b html doc/source doc/build/html
|
||||
|
||||
[testenv:pdf-docs]
|
||||
envdir = {toxworkdir}/docs
|
||||
deps = {[testenv:docs]deps}
|
||||
allowlist_externals =
|
||||
make
|
||||
@@ -72,7 +97,7 @@ import_exceptions = cloudkittyclient.i18n
|
||||
|
||||
[testenv:releasenotes]
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
commands =
|
||||
sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
Reference in New Issue
Block a user