Skip to content

Commit c8d7a9e

Browse files
committed
Add support for POST /v2/dataframes API endpoint to the client
Support for the ``/v2/dataframes`` endpoint has been added to the client. A new ``dataframes add`` CLI command is also available. Change-Id: I7fe9072d7280f251edc865a653a0b9ed2ab26c90 Story: 2005890 Task: 35970
1 parent dd4112a commit c8d7a9e

12 files changed

Lines changed: 452 additions & 8 deletions

File tree

cloudkittyclient/common/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def __init__(self,
2626
insecure=False,
2727
**kwargs):
2828
adapter_options.setdefault('service_type', 'rating')
29+
adapter_options.setdefault('additional_headers', {
30+
'Content-Type': 'application/json',
31+
})
2932

3033
if insecure:
3134
verify_cert = False

cloudkittyclient/tests/functional/base.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,30 @@
2424
class BaseFunctionalTest(utils.BaseTestCase):
2525

2626
def _run(self, executable, action,
27-
flags='', params='', fmt='-f json', has_output=True):
27+
flags='', params='', fmt='-f json', stdin=None, has_output=True):
2828
if not has_output:
2929
fmt = ''
3030
cmd = ' '.join([executable, flags, action, params, fmt])
3131
cmd = shlex.split(cmd)
32-
p = subprocess.Popen(cmd, env=os.environ.copy(), shell=False,
33-
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
34-
stdout, stderr = p.communicate()
32+
p = subprocess.Popen(
33+
cmd, env=os.environ.copy(), shell=False,
34+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
35+
stdin=subprocess.PIPE if stdin else None,
36+
)
37+
stdout, stderr = p.communicate(input=stdin)
3538
if p.returncode != 0:
3639
raise RuntimeError('"{cmd}" returned {val}: {msg}'.format(
3740
cmd=' '.join(cmd), val=p.returncode, msg=stderr))
3841
return json.loads(stdout) if has_output else None
3942

4043
def openstack(self, action,
41-
flags='', params='', fmt='-f json', has_output=True):
44+
flags='', params='', fmt='-f json',
45+
stdin=None, has_output=True):
4246
return self._run('openstack rating', action,
43-
flags, params, fmt, has_output)
47+
flags, params, fmt, stdin, has_output)
4448

4549
def cloudkitty(self, action,
46-
flags='', params='', fmt='-f json', has_output=True):
47-
return self._run('cloudkitty', action, flags, params, fmt, has_output)
50+
flags='', params='', fmt='-f json',
51+
stdin=None, has_output=True):
52+
return self._run('cloudkitty', action, flags, params, fmt,
53+
stdin, has_output)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Copyright 2019 Objectif Libre
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
#
15+
import os
16+
import uuid
17+
18+
from cloudkittyclient.tests.functional import base
19+
20+
21+
class CkDataframesTest(base.BaseFunctionalTest):
22+
dataframes_data = """
23+
{
24+
"dataframes": [
25+
{
26+
"period": {
27+
"begin": "20190723T122810Z",
28+
"end": "20190723T132810Z"
29+
},
30+
"usage": {
31+
"metric_one": [
32+
{
33+
"vol": {
34+
"unit": "GiB",
35+
"qty": 1.2
36+
},
37+
"rating": {
38+
"price": 0.04
39+
},
40+
"groupby": {
41+
"group_one": "one",
42+
"group_two": "two"
43+
},
44+
"metadata": {
45+
"attr_one": "one",
46+
"attr_two": "two"
47+
}
48+
}
49+
],
50+
"metric_two": [
51+
{
52+
"vol": {
53+
"unit": "MB",
54+
"qty": 200.4
55+
},
56+
"rating": {
57+
"price": 0.06
58+
},
59+
"groupby": {
60+
"group_one": "one",
61+
"group_two": "two"
62+
},
63+
"metadata": {
64+
"attr_one": "one",
65+
"attr_two": "two"
66+
}
67+
}
68+
]
69+
}
70+
},
71+
{
72+
"period": {
73+
"begin": "20190823T122810Z",
74+
"end": "20190823T132810Z"
75+
},
76+
"usage": {
77+
"metric_one": [
78+
{
79+
"vol": {
80+
"unit": "GiB",
81+
"qty": 2.4
82+
},
83+
"rating": {
84+
"price": 0.08
85+
},
86+
"groupby": {
87+
"group_one": "one",
88+
"group_two": "two"
89+
},
90+
"metadata": {
91+
"attr_one": "one",
92+
"attr_two": "two"
93+
}
94+
}
95+
],
96+
"metric_two": [
97+
{
98+
"vol": {
99+
"unit": "MB",
100+
"qty": 400.8
101+
},
102+
"rating": {
103+
"price": 0.12
104+
},
105+
"groupby": {
106+
"group_one": "one",
107+
"group_two": "two"
108+
},
109+
"metadata": {
110+
"attr_one": "one",
111+
"attr_two": "two"
112+
}
113+
}
114+
]
115+
}
116+
}
117+
]
118+
}
119+
"""
120+
121+
def __init__(self, *args, **kwargs):
122+
super(CkDataframesTest, self).__init__(*args, **kwargs)
123+
self.runner = self.cloudkitty
124+
125+
def setUp(self):
126+
super(CkDataframesTest, self).setUp()
127+
128+
self.fixture_file_name = '{}.json'.format(uuid.uuid4())
129+
with open(self.fixture_file_name, 'w') as f:
130+
f.write(self.dataframes_data)
131+
132+
def tearDown(self):
133+
files = os.listdir('.')
134+
if self.fixture_file_name in files:
135+
os.remove(self.fixture_file_name)
136+
137+
super(CkDataframesTest, self).tearDown()
138+
139+
def test_dataframes_add_with_no_args(self):
140+
self.assertRaisesRegexp(
141+
RuntimeError,
142+
'error: too few arguments',
143+
self.runner,
144+
'dataframes add',
145+
fmt='',
146+
has_output=False,
147+
)
148+
149+
def test_dataframes_add(self):
150+
self.runner(
151+
'dataframes add {}'.format(self.fixture_file_name),
152+
fmt='',
153+
has_output=False,
154+
)
155+
156+
def test_dataframes_add_with_hyphen_stdin(self):
157+
with open(self.fixture_file_name, 'r') as f:
158+
self.runner(
159+
'dataframes add -',
160+
fmt='',
161+
stdin=f.read().encode(),
162+
has_output=False,
163+
)
164+
165+
166+
class OSCDataframesTest(CkDataframesTest):
167+
def __init__(self, *args, **kwargs):
168+
super(OSCDataframesTest, self).__init__(*args, **kwargs)
169+
self.runner = self.openstack

cloudkittyclient/tests/unit/v2/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# under the License.
1414

1515
from cloudkittyclient.tests import utils
16+
from cloudkittyclient.v2 import dataframes
1617
from cloudkittyclient.v2 import scope
1718
from cloudkittyclient.v2 import summary
1819

@@ -22,5 +23,6 @@ class BaseAPIEndpointTestCase(utils.BaseTestCase):
2223
def setUp(self):
2324
super(BaseAPIEndpointTestCase, self).setUp()
2425
self.api_client = utils.FakeHTTPClient()
26+
self.dataframes = dataframes.DataframesManager(self.api_client)
2527
self.scope = scope.ScopeManager(self.api_client)
2628
self.summary = summary.SummaryManager(self.api_client)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Copyright 2019 Objectif Libre
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
#
15+
import json
16+
17+
from cloudkittyclient import exc
18+
from cloudkittyclient.tests.unit.v2 import base
19+
20+
21+
class TestDataframes(base.BaseAPIEndpointTestCase):
22+
dataframes_data = """
23+
{
24+
"dataframes": [
25+
{
26+
"period": {
27+
"begin": "20190723T122810Z",
28+
"end": "20190723T132810Z"
29+
},
30+
"usage": {
31+
"metric_one": [
32+
{
33+
"vol": {
34+
"unit": "GiB",
35+
"qty": 1.2
36+
},
37+
"rating": {
38+
"price": 0.04
39+
},
40+
"groupby": {
41+
"group_one": "one",
42+
"group_two": "two"
43+
},
44+
"metadata": {
45+
"attr_one": "one",
46+
"attr_two": "two"
47+
}
48+
}
49+
],
50+
"metric_two": [
51+
{
52+
"vol": {
53+
"unit": "MB",
54+
"qty": 200.4
55+
},
56+
"rating": {
57+
"price": 0.06
58+
},
59+
"groupby": {
60+
"group_one": "one",
61+
"group_two": "two"
62+
},
63+
"metadata": {
64+
"attr_one": "one",
65+
"attr_two": "two"
66+
}
67+
}
68+
]
69+
}
70+
},
71+
{
72+
"period": {
73+
"begin": "20190823T122810Z",
74+
"end": "20190823T132810Z"
75+
},
76+
"usage": {
77+
"metric_one": [
78+
{
79+
"vol": {
80+
"unit": "GiB",
81+
"qty": 2.4
82+
},
83+
"rating": {
84+
"price": 0.08
85+
},
86+
"groupby": {
87+
"group_one": "one",
88+
"group_two": "two"
89+
},
90+
"metadata": {
91+
"attr_one": "one",
92+
"attr_two": "two"
93+
}
94+
}
95+
],
96+
"metric_two": [
97+
{
98+
"vol": {
99+
"unit": "MB",
100+
"qty": 400.8
101+
},
102+
"rating": {
103+
"price": 0.12
104+
},
105+
"groupby": {
106+
"group_one": "one",
107+
"group_two": "two"
108+
},
109+
"metadata": {
110+
"attr_one": "one",
111+
"attr_two": "two"
112+
}
113+
}
114+
]
115+
}
116+
}
117+
]
118+
}
119+
"""
120+
121+
def test_add_dataframes_with_string(self):
122+
self.dataframes.add_dataframes(
123+
dataframes=self.dataframes_data,
124+
)
125+
self.api_client.post.assert_called_once_with(
126+
'/v2/dataframes',
127+
data=self.dataframes_data,
128+
)
129+
130+
def test_add_dataframes_with_json_object(self):
131+
json_data = json.loads(self.dataframes_data)
132+
133+
self.dataframes.add_dataframes(
134+
dataframes=json_data,
135+
)
136+
self.api_client.post.assert_called_once_with(
137+
'/v2/dataframes',
138+
data=json.dumps(json_data),
139+
)
140+
141+
def test_add_dataframes_with_neither_string_nor_object_raises_exc(self):
142+
self.assertRaises(
143+
exc.InvalidArgumentError,
144+
self.dataframes.add_dataframes,
145+
dataframes=[open],
146+
)
147+
148+
def test_add_dataframes_with_no_args_raises_exc(self):
149+
self.assertRaises(
150+
exc.ArgumentRequired,
151+
self.dataframes.add_dataframes)

0 commit comments

Comments
 (0)