summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuka Peschke <mail@lukapeschke.com>2017-11-08 15:38:47 +0100
committerLuka Peschke <luka.peschke@objectif-libre.com>2017-11-08 15:40:34 +0100
commitb58453fa85c973d86efe988a44142cc57a4e5a37 (patch)
tree373c91f59ad89af9a1bcc9629aafd75f903365d6
parent568276b9272a4136d2c981b800921e0fd7291c16 (diff)
Committing initial work
This is the initial work done on CloudKitty's tempest plugin. Change-Id: I3251a51271c2ce3ff4bb667d1f6e09e77896b8d7
Notes
Notes (review): Code-Review+2: Maxime Cottret <maxime.cottret@objectif-libre.com> Workflow+1: Gauvain Pocentek <gauvain.pocentek@objectif-libre.com> Code-Review+1: Martin CAMEY <martin.camey@objectif-libre.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 14 Nov 2017 13:13:59 +0000 Reviewed-on: https://review.openstack.org/518549 Project: openstack/cloudkitty-tempest-plugin Branch: refs/heads/master
-rw-r--r--.coveragerc6
-rw-r--r--.gitignore59
-rw-r--r--.mailmap3
-rw-r--r--.stestr.conf3
-rw-r--r--.testr.conf5
-rw-r--r--CONTRIBUTING.rst17
-rw-r--r--HACKING.rst4
-rw-r--r--LICENSE176
-rw-r--r--README.rst25
-rw-r--r--babel.cfg2
-rw-r--r--cloudkitty_tempest_plugin/README.rst6
-rw-r--r--cloudkitty_tempest_plugin/__init__.py0
-rw-r--r--cloudkitty_tempest_plugin/config.py33
-rw-r--r--cloudkitty_tempest_plugin/plugin.py42
-rw-r--r--cloudkitty_tempest_plugin/services/__init__.py0
-rw-r--r--cloudkitty_tempest_plugin/services/client.py455
-rw-r--r--cloudkitty_tempest_plugin/tests/__init__.py0
-rw-r--r--cloudkitty_tempest_plugin/tests/api/__init__.py0
-rw-r--r--cloudkitty_tempest_plugin/tests/api/base.py84
-rw-r--r--cloudkitty_tempest_plugin/tests/api/test_cloudkitty_api.py155
-rw-r--r--cloudkitty_tempest_plugin/tests/api/test_hashmap_api.py328
-rw-r--r--cloudkitty_tempest_plugin/tests/api/test_pyscript_api.py86
-rw-r--r--cloudkitty_tempest_plugin/tests/scenario/__init__.py0
-rw-r--r--requirements.txt11
-rw-r--r--setup.cfg28
-rw-r--r--setup.py29
-rw-r--r--test-requirements.txt15
-rw-r--r--tox.ini43
28 files changed, 1615 insertions, 0 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..9d0a594
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,6 @@
1[run]
2branch = True
3source = cloudkitty_tempest_plugin
4
5[report]
6ignore_errors = True
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59b35f5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
1*.py[cod]
2
3# C extensions
4*.so
5
6# Packages
7*.egg*
8*.egg-info
9dist
10build
11eggs
12parts
13bin
14var
15sdist
16develop-eggs
17.installed.cfg
18lib
19lib64
20
21# Installer logs
22pip-log.txt
23
24# Unit test / coverage reports
25cover/
26.coverage*
27!.coveragerc
28.tox
29nosetests.xml
30.testrepository
31.stestr
32.venv
33
34# Translations
35*.mo
36
37# Mr Developer
38.mr.developer.cfg
39.project
40.pydevproject
41
42# Complexity
43output/*.html
44output/*/index.html
45
46# Sphinx
47doc/build
48
49# pbr generates these
50AUTHORS
51ChangeLog
52
53# Editors
54*~
55.*.swp
56.*sw?
57
58# Files created by releasenotes build
59releasenotes/build
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..516ae6f
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,3 @@
1# Format is:
2# <preferred e-mail> <other e-mail 1>
3# <preferred e-mail> <other e-mail 2>
diff --git a/.stestr.conf b/.stestr.conf
new file mode 100644
index 0000000..90265d7
--- /dev/null
+++ b/.stestr.conf
@@ -0,0 +1,3 @@
1[DEFAULT]
2test_path=./cloudkitty_tempest_plugin/tests
3top_dir=./
diff --git a/.testr.conf b/.testr.conf
new file mode 100644
index 0000000..c551119
--- /dev/null
+++ b/.testr.conf
@@ -0,0 +1,5 @@
1[DEFAULT]
2test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkitty_tempest_plugin/tests $LISTOPT $IDOPTION
3test_id_option=--load-list $IDFILE
4test_list_option=--list
5group_regex=gabbi\.(suitemaker|driver)\.(test_[^_]+_[^_]+)
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..fa75a68
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,17 @@
1If you would like to contribute to the development of OpenStack, you must
2follow the steps in this page:
3
4 http://docs.openstack.org/infra/manual/developers.html
5
6If you already have a good understanding of how the system works and your
7OpenStack accounts are set up, you can skip to the development workflow
8section of this documentation to learn how changes to OpenStack should be
9submitted for review via the Gerrit tool:
10
11 http://docs.openstack.org/infra/manual/developers.html#development-workflow
12
13Pull requests submitted through GitHub will be ignored.
14
15Bugs should be filed on Storyboard, not GitHub:
16
17 https://storyboard.openstack.org/#!/project/890
diff --git a/HACKING.rst b/HACKING.rst
new file mode 100644
index 0000000..a9b88f0
--- /dev/null
+++ b/HACKING.rst
@@ -0,0 +1,4 @@
1cloudkitty-tempest-plugin Style Commandments
2===============================================
3
4Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..68c771a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
1
2 Apache License
3 Version 2.0, January 2004
4 http://www.apache.org/licenses/
5
6 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8 1. Definitions.
9
10 "License" shall mean the terms and conditions for use, reproduction,
11 and distribution as defined by Sections 1 through 9 of this document.
12
13 "Licensor" shall mean the copyright owner or entity authorized by
14 the copyright owner that is granting the License.
15
16 "Legal Entity" shall mean the union of the acting entity and all
17 other entities that control, are controlled by, or are under common
18 control with that entity. For the purposes of this definition,
19 "control" means (i) the power, direct or indirect, to cause the
20 direction or management of such entity, whether by contract or
21 otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 outstanding shares, or (iii) beneficial ownership of such entity.
23
24 "You" (or "Your") shall mean an individual or Legal Entity
25 exercising permissions granted by this License.
26
27 "Source" form shall mean the preferred form for making modifications,
28 including but not limited to software source code, documentation
29 source, and configuration files.
30
31 "Object" form shall mean any form resulting from mechanical
32 transformation or translation of a Source form, including but
33 not limited to compiled object code, generated documentation,
34 and conversions to other media types.
35
36 "Work" shall mean the work of authorship, whether in Source or
37 Object form, made available under the License, as indicated by a
38 copyright notice that is included in or attached to the work
39 (an example is provided in the Appendix below).
40
41 "Derivative Works" shall mean any work, whether in Source or Object
42 form, that is based on (or derived from) the Work and for which the
43 editorial revisions, annotations, elaborations, or other modifications
44 represent, as a whole, an original work of authorship. For the purposes
45 of this License, Derivative Works shall not include works that remain
46 separable from, or merely link (or bind by name) to the interfaces of,
47 the Work and Derivative Works thereof.
48
49 "Contribution" shall mean any work of authorship, including
50 the original version of the Work and any modifications or additions
51 to that Work or Derivative Works thereof, that is intentionally
52 submitted to Licensor for inclusion in the Work by the copyright owner
53 or by an individual or Legal Entity authorized to submit on behalf of
54 the copyright owner. For the purposes of this definition, "submitted"
55 means any form of electronic, verbal, or written communication sent
56 to the Licensor or its representatives, including but not limited to
57 communication on electronic mailing lists, source code control systems,
58 and issue tracking systems that are managed by, or on behalf of, the
59 Licensor for the purpose of discussing and improving the Work, but
60 excluding communication that is conspicuously marked or otherwise
61 designated in writing by the copyright owner as "Not a Contribution."
62
63 "Contributor" shall mean Licensor and any individual or Legal Entity
64 on behalf of whom a Contribution has been received by Licensor and
65 subsequently incorporated within the Work.
66
67 2. Grant of Copyright License. Subject to the terms and conditions of
68 this License, each Contributor hereby grants to You a perpetual,
69 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 copyright license to reproduce, prepare Derivative Works of,
71 publicly display, publicly perform, sublicense, and distribute the
72 Work and such Derivative Works in Source or Object form.
73
74 3. Grant of Patent License. Subject to the terms and conditions of
75 this License, each Contributor hereby grants to You a perpetual,
76 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 (except as stated in this section) patent license to make, have made,
78 use, offer to sell, sell, import, and otherwise transfer the Work,
79 where such license applies only to those patent claims licensable
80 by such Contributor that are necessarily infringed by their
81 Contribution(s) alone or by combination of their Contribution(s)
82 with the Work to which such Contribution(s) was submitted. If You
83 institute patent litigation against any entity (including a
84 cross-claim or counterclaim in a lawsuit) alleging that the Work
85 or a Contribution incorporated within the Work constitutes direct
86 or contributory patent infringement, then any patent licenses
87 granted to You under this License for that Work shall terminate
88 as of the date such litigation is filed.
89
90 4. Redistribution. You may reproduce and distribute copies of the
91 Work or Derivative Works thereof in any medium, with or without
92 modifications, and in Source or Object form, provided that You
93 meet the following conditions:
94
95 (a) You must give any other recipients of the Work or
96 Derivative Works a copy of this License; and
97
98 (b) You must cause any modified files to carry prominent notices
99 stating that You changed the files; and
100
101 (c) You must retain, in the Source form of any Derivative Works
102 that You distribute, all copyright, patent, trademark, and
103 attribution notices from the Source form of the Work,
104 excluding those notices that do not pertain to any part of
105 the Derivative Works; and
106
107 (d) If the Work includes a "NOTICE" text file as part of its
108 distribution, then any Derivative Works that You distribute must
109 include a readable copy of the attribution notices contained
110 within such NOTICE file, excluding those notices that do not
111 pertain to any part of the Derivative Works, in at least one
112 of the following places: within a NOTICE text file distributed
113 as part of the Derivative Works; within the Source form or
114 documentation, if provided along with the Derivative Works; or,
115 within a display generated by the Derivative Works, if and
116 wherever such third-party notices normally appear. The contents
117 of the NOTICE file are for informational purposes only and
118 do not modify the License. You may add Your own attribution
119 notices within Derivative Works that You distribute, alongside
120 or as an addendum to the NOTICE text from the Work, provided
121 that such additional attribution notices cannot be construed
122 as modifying the License.
123
124 You may add Your own copyright statement to Your modifications and
125 may provide additional or different license terms and conditions
126 for use, reproduction, or distribution of Your modifications, or
127 for any such Derivative Works as a whole, provided Your use,
128 reproduction, and distribution of the Work otherwise complies with
129 the conditions stated in this License.
130
131 5. Submission of Contributions. Unless You explicitly state otherwise,
132 any Contribution intentionally submitted for inclusion in the Work
133 by You to the Licensor shall be under the terms and conditions of
134 this License, without any additional terms or conditions.
135 Notwithstanding the above, nothing herein shall supersede or modify
136 the terms of any separate license agreement you may have executed
137 with Licensor regarding such Contributions.
138
139 6. Trademarks. This License does not grant permission to use the trade
140 names, trademarks, service marks, or product names of the Licensor,
141 except as required for reasonable and customary use in describing the
142 origin of the Work and reproducing the content of the NOTICE file.
143
144 7. Disclaimer of Warranty. Unless required by applicable law or
145 agreed to in writing, Licensor provides the Work (and each
146 Contributor provides its Contributions) on an "AS IS" BASIS,
147 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 implied, including, without limitation, any warranties or conditions
149 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 PARTICULAR PURPOSE. You are solely responsible for determining the
151 appropriateness of using or redistributing the Work and assume any
152 risks associated with Your exercise of permissions under this License.
153
154 8. Limitation of Liability. In no event and under no legal theory,
155 whether in tort (including negligence), contract, or otherwise,
156 unless required by applicable law (such as deliberate and grossly
157 negligent acts) or agreed to in writing, shall any Contributor be
158 liable to You for damages, including any direct, indirect, special,
159 incidental, or consequential damages of any character arising as a
160 result of this License or out of the use or inability to use the
161 Work (including but not limited to damages for loss of goodwill,
162 work stoppage, computer failure or malfunction, or any and all
163 other commercial damages or losses), even if such Contributor
164 has been advised of the possibility of such damages.
165
166 9. Accepting Warranty or Additional Liability. While redistributing
167 the Work or Derivative Works thereof, You may choose to offer,
168 and charge a fee for, acceptance of support, warranty, indemnity,
169 or other liability obligations and/or rights consistent with this
170 License. However, in accepting such obligations, You may act only
171 on Your own behalf and on Your sole responsibility, not on behalf
172 of any other Contributor, and only if You agree to indemnify,
173 defend, and hold each Contributor harmless for any liability
174 incurred by, or claims asserted against, such Contributor by reason
175 of your accepting any such warranty or additional liability.
176
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..c1eb0c5
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,25 @@
1=================================
2Tempest integration of CloudKitty
3=================================
4
5This project defines a tempest plugin containing tests used to verify the
6functionality of a cloudkitty installation. The plugin will automatically load
7these tests into tempest.
8
9Dependencies
10------------
11
12This plugin tests the CloudKitty API. This supposes that the 'rating' role
13exists in your OpenStack installation.
14
15Developers
16----------
17For more information on cloudkitty, refer to:
18http://docs.openstack.org/developer/cloudkitty/
19
20For more information on tempest plugins, refer to:
21http://docs.openstack.org/developer/tempest/plugin.html#using-plugins
22
23Bugs
24----
25Please report bugs to: https://storyboard.openstack.org/#!/project/890
diff --git a/babel.cfg b/babel.cfg
new file mode 100644
index 0000000..15cd6cb
--- /dev/null
+++ b/babel.cfg
@@ -0,0 +1,2 @@
1[python: **.py]
2
diff --git a/cloudkitty_tempest_plugin/README.rst b/cloudkitty_tempest_plugin/README.rst
new file mode 100644
index 0000000..9e19517
--- /dev/null
+++ b/cloudkitty_tempest_plugin/README.rst
@@ -0,0 +1,6 @@
1===============================================
2Tempest Integration of CloudKitty
3===============================================
4
5This directory contains Tempest tests to cover the CloudKitty project.
6
diff --git a/cloudkitty_tempest_plugin/__init__.py b/cloudkitty_tempest_plugin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudkitty_tempest_plugin/__init__.py
diff --git a/cloudkitty_tempest_plugin/config.py b/cloudkitty_tempest_plugin/config.py
new file mode 100644
index 0000000..fab2b57
--- /dev/null
+++ b/cloudkitty_tempest_plugin/config.py
@@ -0,0 +1,33 @@
1# Copyright 2017 Objectif Libre
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from oslo_config import cfg
17
18rating_group = cfg.OptGroup(name='rating_plugin',
19 title='Rating Service Options')
20
21RatingGroup = [
22 cfg.StrOpt('service_name',
23 default='rating',
24 help="Service name of the Rating service."),
25 cfg.StrOpt('user_name',
26 default='cloudkitty',
27 help="User name for the Rating service."),
28 cfg.StrOpt('endpoint_type',
29 default='publicURL',
30 choices=['public', 'admin', 'internal',
31 'publicURL', 'adminURL', 'internalURL'],
32 help="The endpoint type to use for the rating service."),
33]
diff --git a/cloudkitty_tempest_plugin/plugin.py b/cloudkitty_tempest_plugin/plugin.py
new file mode 100644
index 0000000..8472876
--- /dev/null
+++ b/cloudkitty_tempest_plugin/plugin.py
@@ -0,0 +1,42 @@
1# Copyright 2017 Objectif Libre
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16
17import os
18
19from tempest import config
20from tempest.test_discover import plugins
21
22from cloudkitty_tempest_plugin import config as project_config
23
24
25class CloudkittyTempestPlugin(plugins.TempestPlugin):
26 def load_tests(self):
27 base_path = os.path.split(os.path.dirname(
28 os.path.abspath(__file__)))[0]
29 test_dir = "cloudkitty_tempest_plugin/tests"
30 full_test_dir = os.path.join(base_path, test_dir)
31 return full_test_dir, base_path
32
33 def register_opts(self, conf):
34 config.register_opt_group(conf,
35 project_config.rating_group,
36 project_config.RatingGroup)
37
38 def get_opt_lists(self):
39 return [
40 (project_config.rating_group.name,
41 project_config.RatingGroup),
42 ]
diff --git a/cloudkitty_tempest_plugin/services/__init__.py b/cloudkitty_tempest_plugin/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudkitty_tempest_plugin/services/__init__.py
diff --git a/cloudkitty_tempest_plugin/services/client.py b/cloudkitty_tempest_plugin/services/client.py
new file mode 100644
index 0000000..911d21d
--- /dev/null
+++ b/cloudkitty_tempest_plugin/services/client.py
@@ -0,0 +1,455 @@
1# -*- coding: utf-8 -*-
2# Copyright 2017 Objectif Libre
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16import six
17
18from keystoneauth1.identity import v3
19from keystoneauth1 import session
20from keystoneclient import client
21from oslo_serialization import jsonutils as json
22from tempest import config
23from tempest.lib.common import rest_client
24from tempest import manager
25
26CONF = config.CONF
27
28CLOUDKITTY_API_VERSION = 'v1'
29
30
31class RatingClient(rest_client.RestClient):
32 """Implementation of cloudkittyclient for testing purposes"""
33 api_version = 'v1'
34
35 @staticmethod
36 def deserialize(body):
37 return json.loads(body.replace("\n", ""))
38
39 @staticmethod
40 def serialize(body):
41 return json.dumps(body)
42
43 def _do_request(self, method, uri, body=None, expected_code=200):
44 resp, body = self.request(method, uri, body=body)
45 self.expected_success(expected_code, resp.status)
46 body = self.deserialize(body) if body else dict()
47 if not isinstance(body, dict) and not isinstance(body, list):
48 body = dict()
49 # ResponseBody inherits from dict, so lists must be converted
50 body = dict(body=body) if isinstance(body, list) else body
51 return rest_client.ResponseBody(resp, body)
52
53 def get_collector_mappings(self, service=None):
54 uri = '/collector/mappings/'
55 if service:
56 uri += service + '/'
57 return self._do_request('GET', uri)
58
59 def create_collector_mapping(self, collector='gnocchi', service='compute'):
60 uri = '/collector/mappings/'
61 request_body = {
62 'collector': collector,
63 'service': service,
64 }
65 return self._do_request('POST', uri, body=self.serialize(request_body))
66
67 def delete_collector_mapping(self, service='compute'):
68 uri = '/collector/mappings'
69 request_body = {
70 'service': service,
71 }
72 return self._do_request('DELETE', uri,
73 body=self.serialize(request_body),
74 expected_code=204)
75
76 def get_collector_state(self, collector="gnocchi"):
77 uri = '/collector/states/'
78 request_body = {
79 'name': collector,
80 }
81 return self._do_request('GET', uri, body=self.serialize(request_body))
82
83 def set_collector_state(self, collector="gnocchi", enabled=True):
84 uri = '/collector/states/'
85 request_body = {
86 'name': collector,
87 'enabled': enabled,
88 }
89 return self._do_request('PUT', uri, body=self.serialize(request_body))
90
91 def get_config(self):
92 uri = '/info/config/'
93 return self._do_request('GET', uri)
94
95 def get_service(self, service_name=None):
96 uri = '/info/services/'
97 if service_name:
98 uri += service_name + '/'
99 return self._do_request('GET', uri)
100
101 def get_rating_module(self, module_name=None):
102 uri = '/rating/modules/'
103 if module_name:
104 uri += module_name + '/'
105 return self._do_request('GET', uri)
106
107 def update_rating_module(self, module_name, description='',
108 enabled=False, hot_config=True, priority=1):
109 uri = '/rating/modules/'
110 request_body = {
111 'module_id': module_name,
112 'description': description,
113 'enabled': enabled,
114 'hot-config': hot_config,
115 'priority': priority,
116 }
117 return self._do_request('PUT', uri, body=self.serialize(request_body),
118 expected_code=302)
119
120 def reload_rating_modules(self):
121 uri = '/rating/reload_modules'
122 return self._do_request('GET', uri, expected_code=204)
123
124 def get_report_summary(self):
125 uri = '/report/summary/'
126 return self._do_request('GET', uri)
127
128 def get_rated_tenants(self):
129 uri = '/report/tenants/'
130 return self._do_request('GET', uri)
131
132 def get_report_total(self):
133 uri = '/report/total/'
134 return self._do_request('GET', uri)
135
136 def get_storage_dataframes(self):
137 uri = '/storage/dataframes/'
138 return self._do_request('GET', uri)
139
140 def get_hashmap_mapping_types(self):
141 uri = '/rating/module_config/hashmap/types/'
142 return self._do_request('GET', uri)
143
144 def get_hashmap_service(self, service_id=None):
145 uri = '/rating/module_config/hashmap/services/'
146 if service_id:
147 uri += service_id + '/'
148 return self._do_request('GET', uri)
149
150 def create_hashmap_service(self, name, service_id=None):
151 uri = '/rating/module_config/hashmap/services/'
152 request_body = {
153 'name': name,
154 }
155 if service_id:
156 request_body['service_id'] = service_id
157 return self._do_request('POST', uri,
158 body=self.serialize(request_body),
159 expected_code=201)
160
161 def delete_hashmap_service(self, service_id):
162 uri = '/rating/module_config/hashmap/services/'
163 request_body = {
164 'service_id': service_id,
165 }
166 return self._do_request('DELETE', uri,
167 body=self.serialize(request_body),
168 expected_code=204)
169
170 def get_hashmap_fields(self, service_id):
171 uri = '/rating/module_config/hashmap/fields/'
172 request_body = {
173 'service_id': service_id,
174 }
175 return self._do_request('GET', uri,
176 body=self.serialize(request_body))
177
178 def get_hashmap_field(self, field_id):
179 uri = '/rating/module_config/hashmap/fields/' + field_id + '/'
180 return self._do_request('GET', uri)
181
182 def create_hashmap_field(self, field_name, service_id, field_id=None):
183 uri = '/rating/module_config/hashmap/fields/'
184 request_body = {
185 'name': field_name,
186 'service_id': service_id,
187 }
188 if field_id:
189 request_body['field_id'] = field_id
190 return self._do_request('POST', uri,
191 body=self.serialize(request_body),
192 expected_code=201)
193
194 def delete_hashmap_field(self, field_id):
195 uri = '/rating/module_config/hashmap/fields/'
196 request_body = {
197 'field_id': field_id,
198 }
199 return self._do_request('DELETE', uri,
200 body=self.serialize(request_body),
201 expected_code=204)
202
203 def get_hashmap_mappings(self, service_id=None, field_id=None,
204 group_id=None, no_group=False, tenant_id=None,
205 filter_tenant=False):
206 args = locals()
207 args.pop('self')
208 uri = '/rating/module_config/hashmap/mappings/'
209 request_body = dict((k, v)
210 for k, v in six.iteritems(args) if v is not None)
211 return self._do_request('GET', uri,
212 body=self.serialize(request_body))
213
214 def get_hashmap_mapping(self, mapping_id):
215 uri = '/rating/module_config/hashmap/mappings/' + mapping_id + '/'
216 return self._do_request('GET', uri)
217
218 def create_hashmap_mapping(self, cost=0, field_id=None, group_id=None,
219 map_type=None, mapping_id=None, service_id=None,
220 tenant_id=None, value=None):
221 args = locals()
222 args.pop('self')
223 uri = '/rating/module_config/hashmap/mappings/'
224 request_body = dict((k, v)
225 for k, v in six.iteritems(args) if v is not None)
226 return self._do_request('POST', uri,
227 body=self.serialize(request_body),
228 expected_code=201)
229
230 def delete_hashmap_mapping(self, mapping_id):
231 uri = '/rating/module_config/hashmap/mappings/'
232 request_body = {
233 'mapping_id': mapping_id,
234 }
235 return self._do_request('DELETE', uri,
236 body=self.serialize(request_body),
237 expected_code=204)
238
239 def update_hashmap_mapping(self, mapping_id, cost=0,
240 field_id=None, group_id=None, map_type=None,
241 service_id=None, tenant_id=None, value=None):
242 args = locals()
243 args.pop('self')
244 uri = '/rating/module_config/hashmap/mappings/'
245 request_body = dict((k, v)
246 for k, v in six.iteritems(args) if v is not None)
247 return self._do_request('PUT', uri,
248 body=self.serialize(request_body),
249 expected_code=302)
250
251 def get_hashmap_mapping_group(self, mapping_id):
252 uri = '/rating/module_config/hashmap/mappings/group/'
253 request_body = {
254 'mapping_id': mapping_id,
255 }
256 return self._do_request('GET', uri,
257 body=self.serialize(request_body))
258
259 def get_hashmap_group(self, group_id=None):
260 uri = '/rating/module_config/hashmap/groups/'
261 if group_id:
262 uri += group_id + '/'
263 return self._do_request('GET', uri)
264
265 def create_hashmap_group(self, group_name, group_id=None):
266 uri = '/rating/module_config/hashmap/groups'
267 request_body = {
268 'name': group_name,
269 }
270 if group_id:
271 request_body['group_id'] = group_id
272 return self._do_request('POST', uri,
273 body=self.serialize(request_body),
274 expected_code=201)
275
276 def delete_hashmap_group(self, group_id):
277 uri = '/rating/module_config/hashmap/groups'
278 request_body = {
279 'group_id': group_id,
280 }
281 return self._do_request('DELETE', uri,
282 body=self.serialize(request_body),
283 expected_code=204)
284
285 def get_hashmap_group_mappings(self, group_id):
286 uri = '/rating/module_config/hashmap/groups/mappings/'
287 request_body = {
288 'group_id': group_id,
289 }
290 return self._do_request('GET', uri,
291 body=self.serialize(request_body))
292
293 def get_hashmap_group_threshold(self, group_id):
294 uri = '/rating/module_config/hashmap/groups/thresholds/'
295 request_body = {
296 'group_id': group_id,
297 }
298 return self._do_request('GET', uri,
299 body=self.serialize(request_body))
300
301 def get_hashmap_threshold(self, threshold_id):
302 uri = '/rating/module_config/hashmap/thresholds/' + threshold_id + '/'
303 return self._do_request('GET', uri)
304
305 def get_hashmap_thresholds(self, service_id=None,
306 field_id=None, group_id=None):
307 args = locals()
308 args.pop('self')
309 uri = '/rating/module_config/hashmap/thresholds/'
310 request_body = dict((k, v)
311 for k, v in six.iteritems(args) if v is not None)
312 return self._do_request('GET', uri,
313 body=self.serialize(request_body))
314
315 def create_hashmap_threshold(self, field_id=None, group_id=None,
316 threshold_id=None, map_type=None, cost=None,
317 service_id=None, tenant_id=None, level=None):
318 args = locals()
319 args.pop('self')
320 uri = '/rating/module_config/hashmap/thresholds/'
321 request_body = dict((k, v)
322 for k, v in six.iteritems(args) if v is not None)
323 return self._do_request('POST', uri,
324 body=self.serialize(request_body),
325 expected_code=201)
326
327 def update_hashmap_threshold(self, threshold_id, field_id=None,
328 group_id=None, map_type=None, cost=None,
329 service_id=None, tenant_id=None, level=None):
330 args = locals()
331 args.pop('self')
332 uri = '/rating/module_config/hashmap/thresholds/'
333 request_body = dict((k, v)
334 for k, v in six.iteritems(args) if v is not None)
335 return self._do_request('PUT', uri,
336 body=self.serialize(request_body),
337 expected_code=302)
338
339 def delete_hashmap_threshold(self, threshold_id):
340 uri = '/rating/module_config/hashmap/thresholds/'
341 request_body = {
342 'threshold_id': threshold_id,
343 }
344 return self._do_request('DELETE', uri,
345 body=self.serialize(request_body),
346 expected_code=204)
347
348 def get_pyscripts(self, no_data=False):
349 uri = '/rating/module_config/pyscripts/scripts/'
350 request_body = {
351 'no_data': no_data,
352 }
353 return self._do_request('GET', uri, body=self.serialize(request_body))
354
355 def get_pyscript(self, script_id):
356 uri = '/rating/module_config/pyscripts/scripts/' + script_id + '/'
357 return self._do_request('GET', uri)
358
359 @staticmethod
360 def _get_pyscript_request_body(name, data, checksum, script_id):
361 args = locals()
362 request_body = dict((k, v)
363 for k, v in six.iteritems(args) if v is not None)
364 return request_body
365
366 def create_pyscript(self, name, data, checksum=None, script_id=None):
367 uri = '/rating/module_config/pyscripts/scripts/'
368 request_body = self._get_pyscript_request_body(name, data,
369 checksum, script_id)
370 return self._do_request('POST', uri,
371 body=self.serialize(request_body),
372 expected_code=201)
373
374 def update_pyscript(self, script_id, name=None, data=None, checksum=None):
375 uri = '/rating/module_config/pyscripts/scripts/'
376 request_body = self._get_pyscript_request_body(name, data,
377 checksum, script_id)
378 return self._do_request('PUT', uri,
379 body=self.serialize(request_body),
380 expected_code=201)
381
382 def delete_pyscript(self, script_id):
383 uri = '/rating/module_config/pyscripts/scripts/'
384 request_body = {
385 'script_id': script_id,
386 }
387 return self._do_request('DELETE', uri,
388 body=self.serialize(request_body),
389 expected_code=204)
390
391
392class Manager(manager.Manager):
393
394 rating_params = {
395 'service': CONF.rating_plugin.service_name,
396 'region': CONF.identity.region,
397 'endpoint_type': CONF.rating_plugin.endpoint_type,
398 }
399
400 def __init__(self, credentials=None, service=None):
401 super(Manager, self).__init__(credentials)
402 self.set_rating_client()
403
404 def set_rating_client(self):
405 self.rating_client = RatingClient(self.auth_provider,
406 **self.rating_params)
407
408
409class CustomIdentityClient(object):
410 """Custom Keystone client
411
412 Class used by the CK tempest plugin to add the 'rating' role to
413 the dynamically allocated test tenant
414 """
415
416 def __init__(self):
417 self.admin_auth = v3.Password(
418 auth_url=CONF.identity.uri_v3,
419 username=CONF.auth.admin_username,
420 password=CONF.auth.admin_password,
421 project_name=CONF.auth.admin_project_name,
422 project_domain_name=CONF.auth.admin_domain_name,
423 user_domain_name=CONF.auth.admin_domain_name,
424 )
425 self.admin_session = session.Session(auth=self.admin_auth)
426 self.admin_client = client.Client(session=self.admin_session)
427 self.ck_user_id = self._get_ck_user_id()
428 self.rating_role_id = self._get_rating_role_id()
429
430 def enable_rating(self, project_id):
431 """Assigns the 'rating' role to ck user on the given project"""
432 self.admin_client.roles.grant(self.rating_role_id,
433 user=self.ck_user_id,
434 project=project_id)
435
436 @staticmethod
437 def _find_item(iterable, key, value):
438 item = None
439 for elem in iterable:
440 if getattr(elem, key, None) == value:
441 item = elem
442 return item
443
444 def _get_ck_user_id(self):
445 users = self.admin_client.users.list()
446 return getattr(
447 self._find_item(users, 'name', CONF.rating_plugin.user_name),
448 'id', None,
449 )
450
451 def _get_rating_role_id(self):
452 roles = self.admin_client.roles.list()
453 return getattr(
454 self._find_item(roles, 'name', 'rating'), 'id', None,
455 )
diff --git a/cloudkitty_tempest_plugin/tests/__init__.py b/cloudkitty_tempest_plugin/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/__init__.py
diff --git a/cloudkitty_tempest_plugin/tests/api/__init__.py b/cloudkitty_tempest_plugin/tests/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/api/__init__.py
diff --git a/cloudkitty_tempest_plugin/tests/api/base.py b/cloudkitty_tempest_plugin/tests/api/base.py
new file mode 100644
index 0000000..1836124
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/api/base.py
@@ -0,0 +1,84 @@
1# -*- coding: utf-8 -*-
2# Copyright 2017 Objectif Libre
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16import six
17
18from tempest import config
19from tempest.lib import exceptions
20import tempest.test
21
22from cloudkitty_tempest_plugin.services import client
23
24CONF = config.CONF
25
26
27def skipIf(flag, reason):
28 def decorator(f):
29 def wrapper(self, *args, **kwargs):
30 if getattr(self, flag):
31 self.skipTest(reason)
32 else:
33 f(self, *args, **kwargs)
34 return wrapper
35 return decorator
36
37
38class BaseRatingTest(tempest.test.BaseTestCase):
39 """Base test class for all Rating API tests."""
40
41 client_manager = client.Manager
42
43 @classmethod
44 def setup_clients(cls):
45 super(BaseRatingTest, cls).setup_clients()
46 os_var = 'os_{}'.format(cls.credentials[0])
47 cls.rating_client = getattr(cls, os_var).rating_client
48
49 @classmethod
50 def setup_credentials(cls):
51 super(BaseRatingTest, cls).setup_credentials()
52 os_var = 'os_{}'.format(cls.credentials[0])
53 project_id = getattr(cls, os_var).credentials.project_id
54 cls.skip_rating_tests = False
55 try:
56 cls.custom_identity_client = client.CustomIdentityClient()
57 cls.custom_identity_client.enable_rating(project_id)
58 except Exception:
59 cls.skip_rating_tests = True
60
61 @classmethod
62 def resource_setup(cls):
63 super(BaseRatingTest, cls).resource_setup()
64 cls._created_resources = {
65 'collector_mapping': list(),
66 'hashmap_service': list(),
67 'hashmap_field': list(),
68 'hashmap_mapping': list(),
69 'hashmap_group': list(),
70 'hashmap_threshold': list(),
71 'pyscript': list(),
72 }
73
74 @classmethod
75 def resource_cleanup(cls):
76 super(BaseRatingTest, cls).resource_cleanup()
77 for method, item_ids in six.iteritems(cls._created_resources):
78 delete_method = 'delete_' + method
79 delete_method = getattr(cls.rating_client, delete_method)
80 for item_id in item_ids:
81 try:
82 delete_method(item_id)
83 except exceptions.NotFound:
84 pass
diff --git a/cloudkitty_tempest_plugin/tests/api/test_cloudkitty_api.py b/cloudkitty_tempest_plugin/tests/api/test_cloudkitty_api.py
new file mode 100644
index 0000000..6cd6760
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/api/test_cloudkitty_api.py
@@ -0,0 +1,155 @@
1# -*- coding: utf-8 -*-
2# Copyright 2017 Objectif Libre
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16from tempest.lib.common.utils import data_utils
17from tempest.lib import decorators
18
19from cloudkitty_tempest_plugin.tests.api import base
20
21
22class CloudkittyAdminAPITest(base.BaseRatingTest):
23
24 credentials = ['admin']
25
26 @decorators.idempotent_id('9c1d4c27-6e7c-42d7-b663-d88f097b7131')
27 def test_get_collector_mappings(self):
28 self.rating_client.get_collector_mappings()
29
30 def _find_item(self, haystack, needle, key, assert_method):
31 found = False
32 for item in haystack:
33 try:
34 if item[key] == needle:
35 found = True
36 except KeyError:
37 continue
38 assert_method(found)
39
40 @decorators.idempotent_id('af902c86-6022-4b94-a716-ec7932d5ae78')
41 def test_create_get_delete_collector_mapping(self):
42 mapping = self.rating_client.create_collector_mapping(
43 collector=data_utils.rand_name('gnocchi'),
44 service=data_utils.rand_name('compute'),
45 )
46 self._created_resources['collector_mapping'].append(
47 mapping['service'],
48 )
49 mappings = self.rating_client.get_collector_mappings()
50 self._find_item(mappings['mappings'],
51 mapping['service'],
52 'service',
53 self.assertTrue)
54 self.rating_client.delete_collector_mapping(mapping['service'])
55 mappings = self.rating_client.get_collector_mappings()
56 self._find_item(mappings['mappings'],
57 mapping['service'],
58 'service',
59 self.assertFalse)
60
61 @decorators.idempotent_id('3fd83647-3058-4450-9588-a528557585c5')
62 def test_get_collector_state(self):
63 collector = self.rating_client.get_collector_state(
64 collector=data_utils.rand_name('gnocchi'),
65 )
66 self.assertFalse(collector['enabled'])
67
68 @decorators.idempotent_id('71131104-fdae-43ec-9bed-c8d1d5ba7eb0')
69 def test_set_collector_state(self):
70 collector_name = data_utils.rand_name('gnocchi')
71 self.rating_client.set_collector_state(
72 collector=collector_name,
73 enabled=True,
74 )
75 collector = self.rating_client.get_collector_state(collector_name)
76 self.assertTrue(collector['enabled'])
77 self.rating_client.set_collector_state(
78 collector=collector_name,
79 enabled=False,
80 )
81 collector = self.rating_client.get_collector_state(collector_name)
82 self.assertFalse(collector['enabled'])
83
84 @decorators.idempotent_id('fba44b6a-6ca4-4155-b5c6-c4eb2465e4fb')
85 def test_get_rating_modules(self):
86 modules = self.rating_client.get_rating_module()
87 self._find_item(modules['modules'],
88 'hashmap',
89 'module_id',
90 self.assertTrue)
91 self._find_item(modules['modules'],
92 'pyscripts',
93 'module_id',
94 self.assertTrue)
95 self._find_item(modules['modules'],
96 'noop',
97 'module_id',
98 self.assertTrue)
99 self.assertEqual(
100 'hashmap',
101 self.rating_client.get_rating_module('hashmap')['module_id'],
102 )
103 self.assertEqual(
104 'pyscripts',
105 self.rating_client.get_rating_module('pyscripts')['module_id'],
106 )
107
108 @decorators.idempotent_id('7fc9e020-9547-4a66-a691-94cab7181358')
109 def test_update_rating_module(self):
110 self.rating_client.update_rating_module('hashmap', enabled=True)
111 module = self.rating_client.get_rating_module('hashmap')
112 self.assertTrue(module['enabled'])
113 self.rating_client.update_rating_module('hashmap', enabled=False)
114 module = self.rating_client.get_rating_module('hashmap')
115 self.assertFalse(module['enabled'])
116
117 @decorators.idempotent_id('daeef22b-d52d-4e89-abb0-ae492e4648d4')
118 def test_reload_rating_modules(self):
119 self.rating_client.reload_rating_modules()
120
121 @base.skipIf('skip_rating_tests',
122 'Rating role was not given to CloudKitty')
123 @decorators.idempotent_id('e439019e-9e8a-4bcd-aa83-95bdba6e6115')
124 def test_get_rated_tenants(self):
125 rated_tenants = self.rating_client.get_rated_tenants()['body']
126 self.assertGreater(len(rated_tenants), 0)
127
128
129class CloudkittyPrimaryAPITest(base.BaseRatingTest):
130
131 credentials = ['primary']
132
133 @decorators.idempotent_id('3285bccf-d043-4ad1-b64f-af4db8317cf9')
134 def test_get_config(self):
135 self.rating_client.get_config()
136
137 @decorators.idempotent_id('43b03099-0493-4291-9749-85cd8d512811')
138 def test_get_services(self):
139 self.rating_client.get_service()
140
141 @decorators.idempotent_id('64ecae87-0138-41bd-829f-91302dae7802')
142 def test_get_service(self):
143 self.rating_client.get_service('compute')
144
145 @decorators.idempotent_id('cccbff8a-24b2-4251-8f7b-ea941d048b9d')
146 def test_report_summary(self):
147 self.rating_client.get_report_summary()
148
149 @decorators.idempotent_id('2492dfd7-0688-4957-93ac-8c91933c28f5')
150 def test_report_total(self):
151 self.rating_client.get_report_total()
152
153 @decorators.idempotent_id('e233139b-3c75-4b70-b1f5-0776ef32c916')
154 def test_get_storage_dataframes(self):
155 self.rating_client.get_storage_dataframes()
diff --git a/cloudkitty_tempest_plugin/tests/api/test_hashmap_api.py b/cloudkitty_tempest_plugin/tests/api/test_hashmap_api.py
new file mode 100644
index 0000000..1f360ab
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/api/test_hashmap_api.py
@@ -0,0 +1,328 @@
1# -*- coding: utf-8 -*-
2# Copyright 2017 Objectif Libre
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16from tempest.lib.common.utils import data_utils
17from tempest.lib import decorators
18
19from cloudkitty_tempest_plugin.tests.api import base
20
21
22class CloudkittyHashmapAPITest(base.BaseRatingTest):
23
24 credentials = ['admin']
25
26 @decorators.idempotent_id('7037a3f8-b462-4243-a0bc-ffa3b4700397')
27 def test_get_hashmap_rating_types(self):
28 self.rating_client.get_hashmap_mapping_types()
29
30 def _setup_dummy_service(self):
31 service = self.rating_client.create_hashmap_service(
32 data_utils.rand_name('service'),
33 )
34 self._created_resources['hashmap_service'].append(
35 service['service_id'])
36 return service['service_id']
37
38 @decorators.idempotent_id('9e968284-7209-46e1-9742-4882b6e2cf2f')
39 def test_create_delete_hashmap_service(self):
40 service_id = self._setup_dummy_service()
41 self.rating_client.delete_hashmap_service(service_id)
42
43 @decorators.idempotent_id('9e9a67d1-e53d-46cf-8e13-8a332c40c32f')
44 def test_get_hashmap_services(self):
45 self.rating_client.get_hashmap_service()
46
47 @decorators.idempotent_id('6c4260c0-8701-4959-b00e-4789d31715a7')
48 def test_get_hashmap_service(self):
49 service_id = self._setup_dummy_service()
50 self.rating_client.get_hashmap_service(service_id=service_id)
51
52 def _setup_dummy_fields(self, service_id):
53 field_ids = list()
54 for i in range(3):
55 field = self.rating_client.create_hashmap_field(
56 data_utils.rand_name('hashmap_field'),
57 service_id,
58 )
59 field_ids.append(field['field_id'])
60 self._created_resources['hashmap_field'].append(field['field_id'])
61 return field_ids
62
63 @decorators.idempotent_id('974669f9-392e-4aec-8e15-d24db23f08d4')
64 def test_create_delete_hashmap_field(self):
65 service_id = self._setup_dummy_service()
66 field_ids = self._setup_dummy_fields(service_id)
67 for field_id in field_ids:
68 self.rating_client.delete_hashmap_field(field_id)
69 self.rating_client.delete_hashmap_service(service_id)
70
71 @decorators.idempotent_id('9ae98590-ff55-47f9-885e-baa4da1957d1')
72 def test_get_hashmap_field(self):
73 service_id = self._setup_dummy_service()
74 self._setup_dummy_fields(service_id)
75 fields = self.rating_client.get_hashmap_fields(service_id)
76 for field in fields['fields']:
77 field_info = self.rating_client.get_hashmap_field(
78 field['field_id'],
79 )
80 self.assertEqual(field_info['field_id'], field['field_id'])
81 self.assertEqual(field_info['service_id'], field['service_id'])
82 self.assertEqual(field_info['name'], field['name'])
83
84 def _find_item(self, haystack, needle, key):
85 found = False
86 for item in haystack:
87 try:
88 if item[key] == needle:
89 found = True
90 except KeyError:
91 continue
92 self.assertTrue(found)
93
94 @decorators.idempotent_id('4deb6914-7ba0-4219-a119-61b39bd58807')
95 def test_get_hashmap_mappings(self):
96 service_id = self._setup_dummy_service()
97 field_ids = self._setup_dummy_fields(service_id)
98 field_mapping = self.rating_client.create_hashmap_mapping(
99 field_id=field_ids[0],
100 value='dummy mapping',
101 )
102 service_mapping = self.rating_client.create_hashmap_mapping(
103 service_id=service_id,
104 )
105 self._created_resources['hashmap_mapping'].append(
106 field_mapping['mapping_id'],
107 )
108 self._created_resources['hashmap_mapping'].append(
109 service_mapping['mapping_id'],
110 )
111
112 service_filtered_mappings = self.rating_client.get_hashmap_mappings(
113 service_id=service_id,
114 )
115 field_filtered_mappings = self.rating_client.get_hashmap_mappings(
116 field_id=field_ids[0],
117 )
118 self._find_item(service_filtered_mappings['mappings'],
119 service_mapping['mapping_id'],
120 'mapping_id')
121 self._find_item(field_filtered_mappings['mappings'],
122 field_mapping['mapping_id'],
123 'mapping_id')
124
125 @decorators.idempotent_id('b7ad24e5-c72c-469a-886b-db43aab8f328')
126 def test_create_delete_hashmap_mapping(self):
127 service_id = self._setup_dummy_service()
128 field_ids = self._setup_dummy_fields(service_id)
129 mapping_ids = list()
130 for field_id in field_ids:
131 mapping = self.rating_client.create_hashmap_mapping(
132 field_id=field_id,
133 value='dummy mapping',
134 )
135 self.assertEqual('dummy mapping', mapping['value'])
136 mapping_ids.append(mapping['mapping_id'])
137 mapping = self.rating_client.create_hashmap_mapping(
138 service_id=service_id,
139 )
140 mapping_ids.append(mapping['mapping_id'])
141 self._created_resources['hashmap_mapping'] += mapping_ids
142 for mapping_id in mapping_ids:
143 self.rating_client.delete_hashmap_mapping(mapping_id)
144
145 @decorators.idempotent_id('6a04634d-2d3a-406e-980a-e7c7c9cc081b')
146 def test_update_hashmap_mapping(self):
147 service_id = self._setup_dummy_service()
148 field_ids = self._setup_dummy_fields(service_id)
149 mapping = self.rating_client.create_hashmap_mapping(
150 field_id=field_ids[0],
151 value='dummy field',
152 )
153 self._created_resources['hashmap_mapping'].append(
154 mapping['mapping_id'],
155 )
156 self.assertEqual('dummy field', mapping['value'])
157 self.rating_client.update_hashmap_mapping(
158 mapping['mapping_id'],
159 value='new value',
160 )
161 mapping = self.rating_client.get_hashmap_mapping(
162 mapping['mapping_id']
163 )
164 self.assertEqual('new value', mapping['value'])
165
166 @decorators.idempotent_id('0f9200ab-146b-4349-a579-ce12062f465b')
167 def test_create_delete_hashmap_group(self):
168 group = self.rating_client.create_hashmap_group(
169 data_utils.rand_name('dummy_group'),
170 )
171 self._created_resources['hashmap_group'].append(group['group_id'])
172 self.rating_client.delete_hashmap_group(group['group_id'])
173
174 @decorators.idempotent_id('858a019a-fb64-4656-b7a6-c92917f641ab')
175 def test_get_hashmap_group(self):
176 group = self.rating_client.create_hashmap_group(
177 data_utils.rand_name('dummy_group'),
178 )
179 self._created_resources['hashmap_group'].append(group['group_id'])
180 group_name = group['name']
181 groups = self.rating_client.get_hashmap_group()
182 self._find_item(groups['groups'], group_name, 'name')
183 group = self.rating_client.get_hashmap_group(group['group_id'])
184 self.assertEqual(group['name'], group_name)
185
186 @decorators.idempotent_id('98ca42dd-a9e2-477a-8e42-16389aed1f44')
187 def test_get_hashmap_mapping_group(self):
188 service_id = self._setup_dummy_service()
189 field_ids = self._setup_dummy_fields(service_id)
190 group = self.rating_client.create_hashmap_group(
191 data_utils.rand_name('dummy_group'),
192 )
193 group_name = group['name']
194 self._created_resources['hashmap_group'].append(group['group_id'])
195 mapping = self.rating_client.create_hashmap_mapping(
196 field_id=field_ids[0],
197 group_id=group['group_id'],
198 value='dummy mapping',
199 )
200 self._created_resources['hashmap_mapping'].append(
201 mapping['mapping_id'],
202 )
203 group = self.rating_client.get_hashmap_mapping_group(
204 mapping['mapping_id'],
205 )
206 self.assertEqual(group['name'], group_name)
207
208 @decorators.idempotent_id('92860fc8-596a-42fd-b0d5-97e0f5a7bd2c')
209 def test_get_hashmap_group_mappings(self):
210 service_id = self._setup_dummy_service()
211 field_ids = self._setup_dummy_fields(service_id)
212 group = self.rating_client.create_hashmap_group(
213 data_utils.rand_name('dummy_group'),
214 )
215 self._created_resources['hashmap_group'].append(group['group_id'])
216 for i in range(3):
217 mapping = self.rating_client.create_hashmap_mapping(
218 field_id=field_ids[i],
219 group_id=group['group_id'],
220 value='dummy mapping {}'.format(i),
221 )
222 self._created_resources['hashmap_mapping'].append(
223 mapping['mapping_id']
224 )
225 mappings = self.rating_client.get_hashmap_group_mappings(
226 group['group_id'],
227 )
228 for i in range(3):
229 self._find_item(mappings['mappings'],
230 'dummy mapping {}'.format(i),
231 'value')
232
233 @decorators.idempotent_id('d2b3dba3-91df-4aa7-9ae2-96d971df2dbf')
234 def test_create_delete_update_hashmap_threshold(self):
235 service_id = self._setup_dummy_service()
236 field_ids = self._setup_dummy_fields(service_id)
237 thresholds = list()
238 thresholds.append(self.rating_client.create_hashmap_threshold(
239 service_id=service_id,
240 level=0.95,
241 cost=12,
242 ))
243 self._created_resources['hashmap_threshold'].append(
244 thresholds[-1]['threshold_id']
245 )
246 for idx, field_id in enumerate(field_ids):
247 thresholds.append(self.rating_client.create_hashmap_threshold(
248 field_id=field_id,
249 level=0.95 * (idx + 1),
250 cost=12 * (idx + 1),
251 ))
252 self._created_resources['hashmap_threshold'].append(
253 thresholds[-1]['threshold_id']
254 )
255 for threshold in thresholds:
256 self.rating_client.update_hashmap_threshold(
257 threshold['threshold_id'],
258 cost=42,
259 level=1.23,
260 )
261 for threshold in thresholds:
262 self.rating_client.delete_hashmap_threshold(
263 threshold['threshold_id'],
264 )
265
266 @decorators.idempotent_id('dc463432-3b92-44ac-8caf-b789857f9db7')
267 def test_get_hashmap_threshold(self):
268 service_id = self._setup_dummy_service()
269 self._setup_dummy_fields(service_id)
270 created_threshold = self.rating_client.create_hashmap_threshold(
271 service_id=service_id,
272 level=1.95,
273 cost=42,
274 )
275 self._created_resources['hashmap_threshold'].append(
276 created_threshold['threshold_id'],
277 )
278 threshold = self.rating_client.get_hashmap_threshold(
279 created_threshold['threshold_id'],
280 )
281 self.assertEqual(threshold['level'], created_threshold['level'])
282 self.rating_client.delete_hashmap_threshold(
283 created_threshold['threshold_id'],
284 )
285
286 @decorators.idempotent_id('d04caad5-eb18-40ce-817e-13c257633cca')
287 def test_get_hashmap_thresholds(self):
288 service_id = self._setup_dummy_service()
289 field_ids = self._setup_dummy_fields(service_id)
290 group = self.rating_client.create_hashmap_group(
291 data_utils.rand_name('dummy_group'),
292 )
293 self._created_resources['hashmap_group'].append(group['group_id'])
294 field_threshold = self.rating_client.create_hashmap_threshold(
295 group_id=group['group_id'],
296 field_id=field_ids[0],
297 level=1.95,
298 cost=42,
299 )
300 self._created_resources['hashmap_threshold'].append(
301 field_threshold['threshold_id'],
302 )
303 service_threshold = self.rating_client.create_hashmap_threshold(
304 service_id=service_id,
305 group_id=group['group_id'],
306 level=1.95,
307 cost=42,
308 )
309 self._created_resources['hashmap_threshold'].append(
310 service_threshold['threshold_id'],
311 )
312 thresholds = self.rating_client.get_hashmap_thresholds(
313 service_id=service_id,
314 )
315 self._find_item(thresholds['thresholds'],
316 group['group_id'],
317 'group_id')
318 thresholds = self.rating_client.get_hashmap_thresholds(
319 field_id=field_ids[0],
320 )
321 self._find_item(thresholds['thresholds'],
322 group['group_id'],
323 'group_id')
324 thresholds = self.rating_client.get_hashmap_thresholds(
325 group_id=group['group_id']
326 )
327 self._find_item(thresholds['thresholds'], service_id, 'service_id')
328 self._find_item(thresholds['thresholds'], field_ids[0], 'field_id')
diff --git a/cloudkitty_tempest_plugin/tests/api/test_pyscript_api.py b/cloudkitty_tempest_plugin/tests/api/test_pyscript_api.py
new file mode 100644
index 0000000..deffe96
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/api/test_pyscript_api.py
@@ -0,0 +1,86 @@
1# -*- coding: utf-8 -*-
2# Copyright 2017 Objectif Libre
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16from tempest.lib.common.utils import data_utils
17from tempest.lib import decorators
18from tempest.lib import exceptions as lib_exc
19
20from cloudkitty_tempest_plugin.tests.api import base
21
22SCRIPT_DATA_ONE = """
23def dumbfunc():
24 return 0
25
26data = dumbfunc()
27"""
28
29SCRIPT_DATA_TWO = """
30def dumbfunc():
31 return 0
32
33data = dumbfunc() + 2
34"""
35
36
37class CloudkittyPyscriptAPITest(base.BaseRatingTest):
38
39 credentials = ['admin']
40
41 @decorators.idempotent_id('2015c966-b707-40f7-b84d-9aa6550b9e41')
42 def test_get_pyscripts(self):
43 self.rating_client.get_pyscripts()
44
45 @decorators.idempotent_id('9e78cccc-ca85-42ce-8648-4cd9682375df')
46 def test_create_update_delete_pyscript(self):
47 pyscript = self.rating_client.create_pyscript(
48 data_utils.rand_name('dummy_script'),
49 SCRIPT_DATA_ONE,
50 )
51 self._created_resources['pyscript'].append(pyscript['script_id'])
52 self.assertEqual(pyscript['data'], SCRIPT_DATA_ONE)
53 self.rating_client.update_pyscript(pyscript['script_id'],
54 data=SCRIPT_DATA_TWO,
55 name=pyscript['name'])
56 pyscript = self.rating_client.get_pyscript(pyscript['script_id'])
57 self.assertEqual(pyscript['data'], SCRIPT_DATA_TWO)
58 self.rating_client.delete_pyscript(pyscript['script_id'])
59
60 @decorators.idempotent_id('3fbaf8b4-c472-4509-8d73-55dc4a87a442')
61 def test_get_pyscript(self):
62 pyscript = self.rating_client.create_pyscript(
63 data_utils.rand_name('dummy_script'),
64 SCRIPT_DATA_ONE,
65 )
66 self.assertEqual(pyscript['data'], SCRIPT_DATA_ONE)
67 self._created_resources['pyscript'].append(pyscript['script_id'])
68 pyscript = self.rating_client.get_pyscript(pyscript['script_id'])
69
70
71class CloudkittyPyscriptAPITestNegative(base.BaseRatingTest):
72
73 credentials = ['admin']
74
75 @decorators.idempotent_id('999c97cc-1d71-43b8-988f-d89b8fac4040')
76 @decorators.attr(type=['negative'])
77 def test_update_script_negative(self):
78 pyscript = self.rating_client.create_pyscript(
79 data_utils.rand_name('dummy_script'),
80 SCRIPT_DATA_ONE,
81 )
82 self._created_resources['pyscript'].append(pyscript['script_id'])
83 fake_id = data_utils.rand_uuid()
84 self.assertRaises(lib_exc.NotFound,
85 self.rating_client.get_pyscript,
86 fake_id)
diff --git a/cloudkitty_tempest_plugin/tests/scenario/__init__.py b/cloudkitty_tempest_plugin/tests/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudkitty_tempest_plugin/tests/scenario/__init__.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..72497c1
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,11 @@
1# The order of packages is significant, because pip processes them in the order
2# of appearance. Changing the order has an impact on the overall integration
3# process, which may cause wedges in the gate later.
4
5keystoneauth1>=2.18.0 # Apache-2.0
6oslo.config>=3.18.0 # Apache-2.0
7oslo.serialization>=2.14.0 # Apache-2.0
8pbr>=2.0 # Apache-2.0
9python-keystoneclient>=3.6.0 # Apache-2.0
10six>=1.9.0 # MIT
11tempest>=15.0.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..212fe63
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,28 @@
1[metadata]
2name = cloudkitty_tempest_plugin
3summary = Tempest plugin for CloudKitty
4description-file =
5 README.rst
6author = OpenStack
7author-email = openstack-dev@lists.openstack.org
8home-page = http://www.openstack.org/
9classifier =
10 Environment :: OpenStack
11 Intended Audience :: Information Technology
12 Intended Audience :: System Administrators
13 License :: OSI Approved :: Apache Software License
14 Operating System :: POSIX :: Linux
15 Programming Language :: Python
16 Programming Language :: Python :: 2
17 Programming Language :: Python :: 2.7
18 Programming Language :: Python :: 3
19 Programming Language :: Python :: 3.3
20 Programming Language :: Python :: 3.4
21
22[files]
23packages =
24 cloudkitty_tempest_plugin
25
26[entry_points]
27tempest.test_plugins =
28 cloudkitty_tests = cloudkitty_tempest_plugin.plugin:CloudkittyTempestPlugin
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..056c16c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
1# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain 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,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
17import setuptools
18
19# In python < 2.7.4, a lazy loading of package `pbr` will break
20# setuptools if some other modules registered functions in `atexit`.
21# solution from: http://bugs.python.org/issue15881#msg170215
22try:
23 import multiprocessing # noqa
24except ImportError:
25 pass
26
27setuptools.setup(
28 setup_requires=['pbr'],
29 pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..3887e0d
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,15 @@
1# The order of packages is significant, because pip processes them in the order
2# of appearance. Changing the order has an impact on the overall integration
3# process, which may cause wedges in the gate later.
4
5hacking>=0.12.0,<0.13 # Apache-2.0
6
7coverage>=4.0,!=4.4 # Apache-2.0
8python-subunit>=0.0.18 # Apache-2.0/BSD
9sphinx>=1.6.2 # BSD
10oslotest>=1.10.0 # Apache-2.0
11stestr>=1.0.0 # Apache-2.0
12testtools>=1.4.0 # MIT
13openstackdocstheme>=1.11.0 # Apache-2.0
14# releasenotes
15reno>=1.8.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..2d0b04a
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,43 @@
1[tox]
2minversion = 2.0
3envlist = py34,py27,pypy,pep8
4skipsdist = True
5
6[testenv]
7usedevelop = True
8install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
9setenv =
10 VIRTUAL_ENV={envdir}
11 PYTHONWARNINGS=default::DeprecationWarning
12 OS_STDOUT_CAPTURE=1
13 OS_STDERR_CAPTURE=1
14 OS_TEST_TIMEOUT=60
15deps = -r{toxinidir}/test-requirements.txt
16commands = stestr run {posargs}
17
18[testenv:pep8]
19commands = flake8 {posargs}
20
21[testenv:venv]
22commands = {posargs}
23
24[testenv:cover]
25setenv =
26 VIRTUAL_ENV={envdir}
27 PYTHON=coverage run --source cloudkitty_tempest_plugin --parallel-mode
28commands =
29 stestr run {posargs}
30 coverage combine
31 coverage html -d cover
32 coverage xml -o cover/coverage.xml
33
34[testenv:debug]
35commands = oslo_debug_helper {posargs}
36
37[flake8]
38# E123, E125 skipped as they are invalid PEP-8.
39
40show-source = True
41ignore = E123,E125
42builtins = _
43exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build