Committing initial work

This is the initial work done on CloudKitty's tempest plugin.

Change-Id: I3251a51271c2ce3ff4bb667d1f6e09e77896b8d7
This commit is contained in:
Luka Peschke 2017-11-08 15:38:47 +01:00 committed by Luka Peschke
parent 568276b927
commit b58453fa85
28 changed files with 1615 additions and 0 deletions

6
.coveragerc Normal file
View File

@ -0,0 +1,6 @@
[run]
branch = True
source = cloudkitty_tempest_plugin
[report]
ignore_errors = True

59
.gitignore vendored Normal file
View File

@ -0,0 +1,59 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg*
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
cover/
.coverage*
!.coveragerc
.tox
nosetests.xml
.testrepository
.stestr
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?
# Files created by releasenotes build
releasenotes/build

3
.mailmap Normal file
View File

@ -0,0 +1,3 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

3
.stestr.conf Normal file
View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=./cloudkitty_tempest_plugin/tests
top_dir=./

5
.testr.conf Normal file
View File

@ -0,0 +1,5 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkitty_tempest_plugin/tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=gabbi\.(suitemaker|driver)\.(test_[^_]+_[^_]+)

17
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,17 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Storyboard, not GitHub:
https://storyboard.openstack.org/#!/project/890

4
HACKING.rst Normal file
View File

@ -0,0 +1,4 @@
cloudkitty-tempest-plugin Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE Normal file
View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

25
README.rst Normal file
View File

@ -0,0 +1,25 @@
=================================
Tempest integration of CloudKitty
=================================
This project defines a tempest plugin containing tests used to verify the
functionality of a cloudkitty installation. The plugin will automatically load
these tests into tempest.
Dependencies
------------
This plugin tests the CloudKitty API. This supposes that the 'rating' role
exists in your OpenStack installation.
Developers
----------
For more information on cloudkitty, refer to:
http://docs.openstack.org/developer/cloudkitty/
For more information on tempest plugins, refer to:
http://docs.openstack.org/developer/tempest/plugin.html#using-plugins
Bugs
----
Please report bugs to: https://storyboard.openstack.org/#!/project/890

2
babel.cfg Normal file
View File

@ -0,0 +1,2 @@
[python: **.py]

View File

@ -0,0 +1,6 @@
===============================================
Tempest Integration of CloudKitty
===============================================
This directory contains Tempest tests to cover the CloudKitty project.

View File

View File

@ -0,0 +1,33 @@
# Copyright 2017 Objectif Libre
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
rating_group = cfg.OptGroup(name='rating_plugin',
title='Rating Service Options')
RatingGroup = [
cfg.StrOpt('service_name',
default='rating',
help="Service name of the Rating service."),
cfg.StrOpt('user_name',
default='cloudkitty',
help="User name for the Rating service."),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the rating service."),
]

View File

@ -0,0 +1,42 @@
# Copyright 2017 Objectif Libre
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from tempest import config
from tempest.test_discover import plugins
from cloudkitty_tempest_plugin import config as project_config
class CloudkittyTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "cloudkitty_tempest_plugin/tests"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
config.register_opt_group(conf,
project_config.rating_group,
project_config.RatingGroup)
def get_opt_lists(self):
return [
(project_config.rating_group.name,
project_config.RatingGroup),
]

View File

@ -0,0 +1,455 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import six
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient import client
from oslo_serialization import jsonutils as json
from tempest import config
from tempest.lib.common import rest_client
from tempest import manager
CONF = config.CONF
CLOUDKITTY_API_VERSION = 'v1'
class RatingClient(rest_client.RestClient):
"""Implementation of cloudkittyclient for testing purposes"""
api_version = 'v1'
@staticmethod
def deserialize(body):
return json.loads(body.replace("\n", ""))
@staticmethod
def serialize(body):
return json.dumps(body)
def _do_request(self, method, uri, body=None, expected_code=200):
resp, body = self.request(method, uri, body=body)
self.expected_success(expected_code, resp.status)
body = self.deserialize(body) if body else dict()
if not isinstance(body, dict) and not isinstance(body, list):
body = dict()
# ResponseBody inherits from dict, so lists must be converted
body = dict(body=body) if isinstance(body, list) else body
return rest_client.ResponseBody(resp, body)
def get_collector_mappings(self, service=None):
uri = '/collector/mappings/'
if service:
uri += service + '/'
return self._do_request('GET', uri)
def create_collector_mapping(self, collector='gnocchi', service='compute'):
uri = '/collector/mappings/'
request_body = {
'collector': collector,
'service': service,
}
return self._do_request('POST', uri, body=self.serialize(request_body))
def delete_collector_mapping(self, service='compute'):
uri = '/collector/mappings'
request_body = {
'service': service,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def get_collector_state(self, collector="gnocchi"):
uri = '/collector/states/'
request_body = {
'name': collector,
}
return self._do_request('GET', uri, body=self.serialize(request_body))
def set_collector_state(self, collector="gnocchi", enabled=True):
uri = '/collector/states/'
request_body = {
'name': collector,
'enabled': enabled,
}
return self._do_request('PUT', uri, body=self.serialize(request_body))
def get_config(self):
uri = '/info/config/'
return self._do_request('GET', uri)
def get_service(self, service_name=None):
uri = '/info/services/'
if service_name:
uri += service_name + '/'
return self._do_request('GET', uri)
def get_rating_module(self, module_name=None):
uri = '/rating/modules/'
if module_name:
uri += module_name + '/'
return self._do_request('GET', uri)
def update_rating_module(self, module_name, description='',
enabled=False, hot_config=True, priority=1):
uri = '/rating/modules/'
request_body = {
'module_id': module_name,
'description': description,
'enabled': enabled,
'hot-config': hot_config,
'priority': priority,
}
return self._do_request('PUT', uri, body=self.serialize(request_body),
expected_code=302)
def reload_rating_modules(self):
uri = '/rating/reload_modules'
return self._do_request('GET', uri, expected_code=204)
def get_report_summary(self):
uri = '/report/summary/'
return self._do_request('GET', uri)
def get_rated_tenants(self):
uri = '/report/tenants/'
return self._do_request('GET', uri)
def get_report_total(self):
uri = '/report/total/'
return self._do_request('GET', uri)
def get_storage_dataframes(self):
uri = '/storage/dataframes/'
return self._do_request('GET', uri)
def get_hashmap_mapping_types(self):
uri = '/rating/module_config/hashmap/types/'
return self._do_request('GET', uri)
def get_hashmap_service(self, service_id=None):
uri = '/rating/module_config/hashmap/services/'
if service_id:
uri += service_id + '/'
return self._do_request('GET', uri)
def create_hashmap_service(self, name, service_id=None):
uri = '/rating/module_config/hashmap/services/'
request_body = {
'name': name,
}
if service_id:
request_body['service_id'] = service_id
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def delete_hashmap_service(self, service_id):
uri = '/rating/module_config/hashmap/services/'
request_body = {
'service_id': service_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def get_hashmap_fields(self, service_id):
uri = '/rating/module_config/hashmap/fields/'
request_body = {
'service_id': service_id,
}
return self._do_request('GET', uri,
body=self.serialize(request_body))
def get_hashmap_field(self, field_id):
uri = '/rating/module_config/hashmap/fields/' + field_id + '/'
return self._do_request('GET', uri)
def create_hashmap_field(self, field_name, service_id, field_id=None):
uri = '/rating/module_config/hashmap/fields/'
request_body = {
'name': field_name,
'service_id': service_id,
}
if field_id:
request_body['field_id'] = field_id
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def delete_hashmap_field(self, field_id):
uri = '/rating/module_config/hashmap/fields/'
request_body = {
'field_id': field_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def get_hashmap_mappings(self, service_id=None, field_id=None,
group_id=None, no_group=False, tenant_id=None,
filter_tenant=False):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/mappings/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('GET', uri,
body=self.serialize(request_body))
def get_hashmap_mapping(self, mapping_id):
uri = '/rating/module_config/hashmap/mappings/' + mapping_id + '/'
return self._do_request('GET', uri)
def create_hashmap_mapping(self, cost=0, field_id=None, group_id=None,
map_type=None, mapping_id=None, service_id=None,
tenant_id=None, value=None):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/mappings/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def delete_hashmap_mapping(self, mapping_id):
uri = '/rating/module_config/hashmap/mappings/'
request_body = {
'mapping_id': mapping_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def update_hashmap_mapping(self, mapping_id, cost=0,
field_id=None, group_id=None, map_type=None,
service_id=None, tenant_id=None, value=None):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/mappings/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('PUT', uri,
body=self.serialize(request_body),
expected_code=302)
def get_hashmap_mapping_group(self, mapping_id):
uri = '/rating/module_config/hashmap/mappings/group/'
request_body = {
'mapping_id': mapping_id,
}
return self._do_request('GET', uri,
body=self.serialize(request_body))
def get_hashmap_group(self, group_id=None):
uri = '/rating/module_config/hashmap/groups/'
if group_id:
uri += group_id + '/'
return self._do_request('GET', uri)
def create_hashmap_group(self, group_name, group_id=None):
uri = '/rating/module_config/hashmap/groups'
request_body = {
'name': group_name,
}
if group_id:
request_body['group_id'] = group_id
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def delete_hashmap_group(self, group_id):
uri = '/rating/module_config/hashmap/groups'
request_body = {
'group_id': group_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def get_hashmap_group_mappings(self, group_id):
uri = '/rating/module_config/hashmap/groups/mappings/'
request_body = {
'group_id': group_id,
}
return self._do_request('GET', uri,
body=self.serialize(request_body))
def get_hashmap_group_threshold(self, group_id):
uri = '/rating/module_config/hashmap/groups/thresholds/'
request_body = {
'group_id': group_id,
}
return self._do_request('GET', uri,
body=self.serialize(request_body))
def get_hashmap_threshold(self, threshold_id):
uri = '/rating/module_config/hashmap/thresholds/' + threshold_id + '/'
return self._do_request('GET', uri)
def get_hashmap_thresholds(self, service_id=None,
field_id=None, group_id=None):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/thresholds/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('GET', uri,
body=self.serialize(request_body))
def create_hashmap_threshold(self, field_id=None, group_id=None,
threshold_id=None, map_type=None, cost=None,
service_id=None, tenant_id=None, level=None):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/thresholds/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def update_hashmap_threshold(self, threshold_id, field_id=None,
group_id=None, map_type=None, cost=None,
service_id=None, tenant_id=None, level=None):
args = locals()
args.pop('self')
uri = '/rating/module_config/hashmap/thresholds/'
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return self._do_request('PUT', uri,
body=self.serialize(request_body),
expected_code=302)
def delete_hashmap_threshold(self, threshold_id):
uri = '/rating/module_config/hashmap/thresholds/'
request_body = {
'threshold_id': threshold_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
def get_pyscripts(self, no_data=False):
uri = '/rating/module_config/pyscripts/scripts/'
request_body = {
'no_data': no_data,
}
return self._do_request('GET', uri, body=self.serialize(request_body))
def get_pyscript(self, script_id):
uri = '/rating/module_config/pyscripts/scripts/' + script_id + '/'
return self._do_request('GET', uri)
@staticmethod
def _get_pyscript_request_body(name, data, checksum, script_id):
args = locals()
request_body = dict((k, v)
for k, v in six.iteritems(args) if v is not None)
return request_body
def create_pyscript(self, name, data, checksum=None, script_id=None):
uri = '/rating/module_config/pyscripts/scripts/'
request_body = self._get_pyscript_request_body(name, data,
checksum, script_id)
return self._do_request('POST', uri,
body=self.serialize(request_body),
expected_code=201)
def update_pyscript(self, script_id, name=None, data=None, checksum=None):
uri = '/rating/module_config/pyscripts/scripts/'
request_body = self._get_pyscript_request_body(name, data,
checksum, script_id)
return self._do_request('PUT', uri,
body=self.serialize(request_body),
expected_code=201)
def delete_pyscript(self, script_id):
uri = '/rating/module_config/pyscripts/scripts/'
request_body = {
'script_id': script_id,
}
return self._do_request('DELETE', uri,
body=self.serialize(request_body),
expected_code=204)
class Manager(manager.Manager):
rating_params = {
'service': CONF.rating_plugin.service_name,
'region': CONF.identity.region,
'endpoint_type': CONF.rating_plugin.endpoint_type,
}
def __init__(self, credentials=None, service=None):
super(Manager, self).__init__(credentials)
self.set_rating_client()
def set_rating_client(self):
self.rating_client = RatingClient(self.auth_provider,
**self.rating_params)
class CustomIdentityClient(object):
"""Custom Keystone client
Class used by the CK tempest plugin to add the 'rating' role to
the dynamically allocated test tenant
"""
def __init__(self):
self.admin_auth = v3.Password(
auth_url=CONF.identity.uri_v3,
username=CONF.auth.admin_username,
password=CONF.auth.admin_password,
project_name=CONF.auth.admin_project_name,
project_domain_name=CONF.auth.admin_domain_name,
user_domain_name=CONF.auth.admin_domain_name,
)
self.admin_session = session.Session(auth=self.admin_auth)
self.admin_client = client.Client(session=self.admin_session)
self.ck_user_id = self._get_ck_user_id()
self.rating_role_id = self._get_rating_role_id()
def enable_rating(self, project_id):
"""Assigns the 'rating' role to ck user on the given project"""
self.admin_client.roles.grant(self.rating_role_id,
user=self.ck_user_id,
project=project_id)
@staticmethod
def _find_item(iterable, key, value):
item = None
for elem in iterable:
if getattr(elem, key, None) == value:
item = elem
return item
def _get_ck_user_id(self):
users = self.admin_client.users.list()
return getattr(
self._find_item(users, 'name', CONF.rating_plugin.user_name),
'id', None,
)
def _get_rating_role_id(self):
roles = self.admin_client.roles.list()
return getattr(
self._find_item(roles, 'name', 'rating'), 'id', None,
)

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import six
from tempest import config
from tempest.lib import exceptions
import tempest.test
from cloudkitty_tempest_plugin.services import client
CONF = config.CONF
def skipIf(flag, reason):
def decorator(f):
def wrapper(self, *args, **kwargs):
if getattr(self, flag):
self.skipTest(reason)
else:
f(self, *args, **kwargs)
return wrapper
return decorator
class BaseRatingTest(tempest.test.BaseTestCase):
"""Base test class for all Rating API tests."""
client_manager = client.Manager
@classmethod
def setup_clients(cls):
super(BaseRatingTest, cls).setup_clients()
os_var = 'os_{}'.format(cls.credentials[0])
cls.rating_client = getattr(cls, os_var).rating_client
@classmethod
def setup_credentials(cls):
super(BaseRatingTest, cls).setup_credentials()
os_var = 'os_{}'.format(cls.credentials[0])
project_id = getattr(cls, os_var).credentials.project_id
cls.skip_rating_tests = False
try:
cls.custom_identity_client = client.CustomIdentityClient()
cls.custom_identity_client.enable_rating(project_id)
except Exception:
cls.skip_rating_tests = True
@classmethod
def resource_setup(cls):
super(BaseRatingTest, cls).resource_setup()
cls._created_resources = {
'collector_mapping': list(),
'hashmap_service': list(),
'hashmap_field': list(),
'hashmap_mapping': list(),
'hashmap_group': list(),
'hashmap_threshold': list(),
'pyscript': list(),
}
@classmethod
def resource_cleanup(cls):
super(BaseRatingTest, cls).resource_cleanup()
for method, item_ids in six.iteritems(cls._created_resources):
delete_method = 'delete_' + method
delete_method = getattr(cls.rating_client, delete_method)
for item_id in item_ids:
try:
delete_method(item_id)
except exceptions.NotFound:
pass

View File

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from cloudkitty_tempest_plugin.tests.api import base
class CloudkittyAdminAPITest(base.BaseRatingTest):
credentials = ['admin']
@decorators.idempotent_id('9c1d4c27-6e7c-42d7-b663-d88f097b7131')
def test_get_collector_mappings(self):
self.rating_client.get_collector_mappings()
def _find_item(self, haystack, needle, key, assert_method):
found = False
for item in haystack:
try:
if item[key] == needle:
found = True
except KeyError:
continue
assert_method(found)
@decorators.idempotent_id('af902c86-6022-4b94-a716-ec7932d5ae78')
def test_create_get_delete_collector_mapping(self):
mapping = self.rating_client.create_collector_mapping(
collector=data_utils.rand_name('gnocchi'),
service=data_utils.rand_name('compute'),
)
self._created_resources['collector_mapping'].append(
mapping['service'],
)
mappings = self.rating_client.get_collector_mappings()
self._find_item(mappings['mappings'],
mapping['service'],
'service',
self.assertTrue)
self.rating_client.delete_collector_mapping(mapping['service'])
mappings = self.rating_client.get_collector_mappings()
self._find_item(mappings['mappings'],
mapping['service'],
'service',
self.assertFalse)
@decorators.idempotent_id('3fd83647-3058-4450-9588-a528557585c5')
def test_get_collector_state(self):
collector = self.rating_client.get_collector_state(
collector=data_utils.rand_name('gnocchi'),
)
self.assertFalse(collector['enabled'])
@decorators.idempotent_id('71131104-fdae-43ec-9bed-c8d1d5ba7eb0')
def test_set_collector_state(self):
collector_name = data_utils.rand_name('gnocchi')
self.rating_client.set_collector_state(
collector=collector_name,
enabled=True,
)
collector = self.rating_client.get_collector_state(collector_name)
self.assertTrue(collector['enabled'])
self.rating_client.set_collector_state(
collector=collector_name,
enabled=False,
)
collector = self.rating_client.get_collector_state(collector_name)
self.assertFalse(collector['enabled'])
@decorators.idempotent_id('fba44b6a-6ca4-4155-b5c6-c4eb2465e4fb')
def test_get_rating_modules(self):
modules = self.rating_client.get_rating_module()
self._find_item(modules['modules'],
'hashmap',
'module_id',
self.assertTrue)
self._find_item(modules['modules'],
'pyscripts',
'module_id',
self.assertTrue)
self._find_item(modules['modules'],
'noop',
'module_id',
self.assertTrue)
self.assertEqual(
'hashmap',
self.rating_client.get_rating_module('hashmap')['module_id'],
)
self.assertEqual(
'pyscripts',
self.rating_client.get_rating_module('pyscripts')['module_id'],
)
@decorators.idempotent_id('7fc9e020-9547-4a66-a691-94cab7181358')
def test_update_rating_module(self):
self.rating_client.update_rating_module('hashmap', enabled=True)
module = self.rating_client.get_rating_module('hashmap')
self.assertTrue(module['enabled'])
self.rating_client.update_rating_module('hashmap', enabled=False)
module = self.rating_client.get_rating_module('hashmap')
self.assertFalse(module['enabled'])
@decorators.idempotent_id('daeef22b-d52d-4e89-abb0-ae492e4648d4')
def test_reload_rating_modules(self):
self.rating_client.reload_rating_modules()
@base.skipIf('skip_rating_tests',
'Rating role was not given to CloudKitty')
@decorators.idempotent_id('e439019e-9e8a-4bcd-aa83-95bdba6e6115')
def test_get_rated_tenants(self):
rated_tenants = self.rating_client.get_rated_tenants()['body']
self.assertGreater(len(rated_tenants), 0)
class CloudkittyPrimaryAPITest(base.BaseRatingTest):
credentials = ['primary']
@decorators.idempotent_id('3285bccf-d043-4ad1-b64f-af4db8317cf9')
def test_get_config(self):
self.rating_client.get_config()
@decorators.idempotent_id('43b03099-0493-4291-9749-85cd8d512811')
def test_get_services(self):
self.rating_client.get_service()
@decorators.idempotent_id('64ecae87-0138-41bd-829f-91302dae7802')
def test_get_service(self):
self.rating_client.get_service('compute')
@decorators.idempotent_id('cccbff8a-24b2-4251-8f7b-ea941d048b9d')
def test_report_summary(self):
self.rating_client.get_report_summary()
@decorators.idempotent_id('2492dfd7-0688-4957-93ac-8c91933c28f5')
def test_report_total(self):
self.rating_client.get_report_total()
@decorators.idempotent_id('e233139b-3c75-4b70-b1f5-0776ef32c916')
def test_get_storage_dataframes(self):
self.rating_client.get_storage_dataframes()

View File

@ -0,0 +1,328 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from cloudkitty_tempest_plugin.tests.api import base
class CloudkittyHashmapAPITest(base.BaseRatingTest):
credentials = ['admin']
@decorators.idempotent_id('7037a3f8-b462-4243-a0bc-ffa3b4700397')
def test_get_hashmap_rating_types(self):
self.rating_client.get_hashmap_mapping_types()
def _setup_dummy_service(self):
service = self.rating_client.create_hashmap_service(
data_utils.rand_name('service'),
)
self._created_resources['hashmap_service'].append(
service['service_id'])
return service['service_id']
@decorators.idempotent_id('9e968284-7209-46e1-9742-4882b6e2cf2f')
def test_create_delete_hashmap_service(self):
service_id = self._setup_dummy_service()
self.rating_client.delete_hashmap_service(service_id)
@decorators.idempotent_id('9e9a67d1-e53d-46cf-8e13-8a332c40c32f')
def test_get_hashmap_services(self):
self.rating_client.get_hashmap_service()
@decorators.idempotent_id('6c4260c0-8701-4959-b00e-4789d31715a7')
def test_get_hashmap_service(self):
service_id = self._setup_dummy_service()
self.rating_client.get_hashmap_service(service_id=service_id)
def _setup_dummy_fields(self, service_id):
field_ids = list()
for i in range(3):
field = self.rating_client.create_hashmap_field(
data_utils.rand_name('hashmap_field'),
service_id,
)
field_ids.append(field['field_id'])
self._created_resources['hashmap_field'].append(field['field_id'])
return field_ids
@decorators.idempotent_id('974669f9-392e-4aec-8e15-d24db23f08d4')
def test_create_delete_hashmap_field(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
for field_id in field_ids:
self.rating_client.delete_hashmap_field(field_id)
self.rating_client.delete_hashmap_service(service_id)
@decorators.idempotent_id('9ae98590-ff55-47f9-885e-baa4da1957d1')
def test_get_hashmap_field(self):
service_id = self._setup_dummy_service()
self._setup_dummy_fields(service_id)
fields = self.rating_client.get_hashmap_fields(service_id)
for field in fields['fields']:
field_info = self.rating_client.get_hashmap_field(
field['field_id'],
)
self.assertEqual(field_info['field_id'], field['field_id'])
self.assertEqual(field_info['service_id'], field['service_id'])
self.assertEqual(field_info['name'], field['name'])
def _find_item(self, haystack, needle, key):
found = False
for item in haystack:
try:
if item[key] == needle:
found = True
except KeyError:
continue
self.assertTrue(found)
@decorators.idempotent_id('4deb6914-7ba0-4219-a119-61b39bd58807')
def test_get_hashmap_mappings(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
field_mapping = self.rating_client.create_hashmap_mapping(
field_id=field_ids[0],
value='dummy mapping',
)
service_mapping = self.rating_client.create_hashmap_mapping(
service_id=service_id,
)
self._created_resources['hashmap_mapping'].append(
field_mapping['mapping_id'],
)
self._created_resources['hashmap_mapping'].append(
service_mapping['mapping_id'],
)
service_filtered_mappings = self.rating_client.get_hashmap_mappings(
service_id=service_id,
)
field_filtered_mappings = self.rating_client.get_hashmap_mappings(
field_id=field_ids[0],
)
self._find_item(service_filtered_mappings['mappings'],
service_mapping['mapping_id'],
'mapping_id')
self._find_item(field_filtered_mappings['mappings'],
field_mapping['mapping_id'],
'mapping_id')
@decorators.idempotent_id('b7ad24e5-c72c-469a-886b-db43aab8f328')
def test_create_delete_hashmap_mapping(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
mapping_ids = list()
for field_id in field_ids:
mapping = self.rating_client.create_hashmap_mapping(
field_id=field_id,
value='dummy mapping',
)
self.assertEqual('dummy mapping', mapping['value'])
mapping_ids.append(mapping['mapping_id'])
mapping = self.rating_client.create_hashmap_mapping(
service_id=service_id,
)
mapping_ids.append(mapping['mapping_id'])
self._created_resources['hashmap_mapping'] += mapping_ids
for mapping_id in mapping_ids:
self.rating_client.delete_hashmap_mapping(mapping_id)
@decorators.idempotent_id('6a04634d-2d3a-406e-980a-e7c7c9cc081b')
def test_update_hashmap_mapping(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
mapping = self.rating_client.create_hashmap_mapping(
field_id=field_ids[0],
value='dummy field',
)
self._created_resources['hashmap_mapping'].append(
mapping['mapping_id'],
)
self.assertEqual('dummy field', mapping['value'])
self.rating_client.update_hashmap_mapping(
mapping['mapping_id'],
value='new value',
)
mapping = self.rating_client.get_hashmap_mapping(
mapping['mapping_id']
)
self.assertEqual('new value', mapping['value'])
@decorators.idempotent_id('0f9200ab-146b-4349-a579-ce12062f465b')
def test_create_delete_hashmap_group(self):
group = self.rating_client.create_hashmap_group(
data_utils.rand_name('dummy_group'),
)
self._created_resources['hashmap_group'].append(group['group_id'])
self.rating_client.delete_hashmap_group(group['group_id'])
@decorators.idempotent_id('858a019a-fb64-4656-b7a6-c92917f641ab')
def test_get_hashmap_group(self):
group = self.rating_client.create_hashmap_group(
data_utils.rand_name('dummy_group'),
)
self._created_resources['hashmap_group'].append(group['group_id'])
group_name = group['name']
groups = self.rating_client.get_hashmap_group()
self._find_item(groups['groups'], group_name, 'name')
group = self.rating_client.get_hashmap_group(group['group_id'])
self.assertEqual(group['name'], group_name)
@decorators.idempotent_id('98ca42dd-a9e2-477a-8e42-16389aed1f44')
def test_get_hashmap_mapping_group(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
group = self.rating_client.create_hashmap_group(
data_utils.rand_name('dummy_group'),
)
group_name = group['name']
self._created_resources['hashmap_group'].append(group['group_id'])
mapping = self.rating_client.create_hashmap_mapping(
field_id=field_ids[0],
group_id=group['group_id'],
value='dummy mapping',
)
self._created_resources['hashmap_mapping'].append(
mapping['mapping_id'],
)
group = self.rating_client.get_hashmap_mapping_group(
mapping['mapping_id'],
)
self.assertEqual(group['name'], group_name)
@decorators.idempotent_id('92860fc8-596a-42fd-b0d5-97e0f5a7bd2c')
def test_get_hashmap_group_mappings(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
group = self.rating_client.create_hashmap_group(
data_utils.rand_name('dummy_group'),
)
self._created_resources['hashmap_group'].append(group['group_id'])
for i in range(3):
mapping = self.rating_client.create_hashmap_mapping(
field_id=field_ids[i],
group_id=group['group_id'],
value='dummy mapping {}'.format(i),
)
self._created_resources['hashmap_mapping'].append(
mapping['mapping_id']
)
mappings = self.rating_client.get_hashmap_group_mappings(
group['group_id'],
)
for i in range(3):
self._find_item(mappings['mappings'],
'dummy mapping {}'.format(i),
'value')
@decorators.idempotent_id('d2b3dba3-91df-4aa7-9ae2-96d971df2dbf')
def test_create_delete_update_hashmap_threshold(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
thresholds = list()
thresholds.append(self.rating_client.create_hashmap_threshold(
service_id=service_id,
level=0.95,
cost=12,
))
self._created_resources['hashmap_threshold'].append(
thresholds[-1]['threshold_id']
)
for idx, field_id in enumerate(field_ids):
thresholds.append(self.rating_client.create_hashmap_threshold(
field_id=field_id,
level=0.95 * (idx + 1),
cost=12 * (idx + 1),
))
self._created_resources['hashmap_threshold'].append(
thresholds[-1]['threshold_id']
)
for threshold in thresholds:
self.rating_client.update_hashmap_threshold(
threshold['threshold_id'],
cost=42,
level=1.23,
)
for threshold in thresholds:
self.rating_client.delete_hashmap_threshold(
threshold['threshold_id'],
)
@decorators.idempotent_id('dc463432-3b92-44ac-8caf-b789857f9db7')
def test_get_hashmap_threshold(self):
service_id = self._setup_dummy_service()
self._setup_dummy_fields(service_id)
created_threshold = self.rating_client.create_hashmap_threshold(
service_id=service_id,
level=1.95,
cost=42,
)
self._created_resources['hashmap_threshold'].append(
created_threshold['threshold_id'],
)
threshold = self.rating_client.get_hashmap_threshold(
created_threshold['threshold_id'],
)
self.assertEqual(threshold['level'], created_threshold['level'])
self.rating_client.delete_hashmap_threshold(
created_threshold['threshold_id'],
)
@decorators.idempotent_id('d04caad5-eb18-40ce-817e-13c257633cca')
def test_get_hashmap_thresholds(self):
service_id = self._setup_dummy_service()
field_ids = self._setup_dummy_fields(service_id)
group = self.rating_client.create_hashmap_group(
data_utils.rand_name('dummy_group'),
)
self._created_resources['hashmap_group'].append(group['group_id'])
field_threshold = self.rating_client.create_hashmap_threshold(
group_id=group['group_id'],
field_id=field_ids[0],
level=1.95,
cost=42,
)
self._created_resources['hashmap_threshold'].append(
field_threshold['threshold_id'],
)
service_threshold = self.rating_client.create_hashmap_threshold(
service_id=service_id,
group_id=group['group_id'],
level=1.95,
cost=42,
)
self._created_resources['hashmap_threshold'].append(
service_threshold['threshold_id'],
)
thresholds = self.rating_client.get_hashmap_thresholds(
service_id=service_id,
)
self._find_item(thresholds['thresholds'],
group['group_id'],
'group_id')
thresholds = self.rating_client.get_hashmap_thresholds(
field_id=field_ids[0],
)
self._find_item(thresholds['thresholds'],
group['group_id'],
'group_id')
thresholds = self.rating_client.get_hashmap_thresholds(
group_id=group['group_id']
)
self._find_item(thresholds['thresholds'], service_id, 'service_id')
self._find_item(thresholds['thresholds'], field_ids[0], 'field_id')

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from cloudkitty_tempest_plugin.tests.api import base
SCRIPT_DATA_ONE = """
def dumbfunc():
return 0
data = dumbfunc()
"""
SCRIPT_DATA_TWO = """
def dumbfunc():
return 0
data = dumbfunc() + 2
"""
class CloudkittyPyscriptAPITest(base.BaseRatingTest):
credentials = ['admin']
@decorators.idempotent_id('2015c966-b707-40f7-b84d-9aa6550b9e41')
def test_get_pyscripts(self):
self.rating_client.get_pyscripts()
@decorators.idempotent_id('9e78cccc-ca85-42ce-8648-4cd9682375df')
def test_create_update_delete_pyscript(self):
pyscript = self.rating_client.create_pyscript(
data_utils.rand_name('dummy_script'),
SCRIPT_DATA_ONE,
)
self._created_resources['pyscript'].append(pyscript['script_id'])
self.assertEqual(pyscript['data'], SCRIPT_DATA_ONE)
self.rating_client.update_pyscript(pyscript['script_id'],
data=SCRIPT_DATA_TWO,
name=pyscript['name'])
pyscript = self.rating_client.get_pyscript(pyscript['script_id'])
self.assertEqual(pyscript['data'], SCRIPT_DATA_TWO)
self.rating_client.delete_pyscript(pyscript['script_id'])
@decorators.idempotent_id('3fbaf8b4-c472-4509-8d73-55dc4a87a442')
def test_get_pyscript(self):
pyscript = self.rating_client.create_pyscript(
data_utils.rand_name('dummy_script'),
SCRIPT_DATA_ONE,
)
self.assertEqual(pyscript['data'], SCRIPT_DATA_ONE)
self._created_resources['pyscript'].append(pyscript['script_id'])
pyscript = self.rating_client.get_pyscript(pyscript['script_id'])
class CloudkittyPyscriptAPITestNegative(base.BaseRatingTest):
credentials = ['admin']
@decorators.idempotent_id('999c97cc-1d71-43b8-988f-d89b8fac4040')
@decorators.attr(type=['negative'])
def test_update_script_negative(self):
pyscript = self.rating_client.create_pyscript(
data_utils.rand_name('dummy_script'),
SCRIPT_DATA_ONE,
)
self._created_resources['pyscript'].append(pyscript['script_id'])
fake_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
self.rating_client.get_pyscript,
fake_id)

11
requirements.txt Normal file
View File

@ -0,0 +1,11 @@
# 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.
keystoneauth1>=2.18.0 # Apache-2.0
oslo.config>=3.18.0 # Apache-2.0
oslo.serialization>=2.14.0 # Apache-2.0
pbr>=2.0 # Apache-2.0
python-keystoneclient>=3.6.0 # Apache-2.0
six>=1.9.0 # MIT
tempest>=15.0.0 # Apache-2.0

28
setup.cfg Normal file
View File

@ -0,0 +1,28 @@
[metadata]
name = cloudkitty_tempest_plugin
summary = Tempest plugin for CloudKitty
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
[files]
packages =
cloudkitty_tempest_plugin
[entry_points]
tempest.test_plugins =
cloudkitty_tests = cloudkitty_tempest_plugin.plugin:CloudkittyTempestPlugin

29
setup.py Normal file
View File

@ -0,0 +1,29 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

15
test-requirements.txt Normal file
View File

@ -0,0 +1,15 @@
# 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>=0.12.0,<0.13 # Apache-2.0
coverage>=4.0,!=4.4 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx>=1.6.2 # BSD
oslotest>=1.10.0 # Apache-2.0
stestr>=1.0.0 # Apache-2.0
testtools>=1.4.0 # MIT
openstackdocstheme>=1.11.0 # Apache-2.0
# releasenotes
reno>=1.8.0 # Apache-2.0

43
tox.ini Normal file
View File

@ -0,0 +1,43 @@
[tox]
minversion = 2.0
envlist = py34,py27,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning
OS_STDOUT_CAPTURE=1
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=60
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
[testenv:pep8]
commands = flake8 {posargs}
[testenv:venv]
commands = {posargs}
[testenv:cover]
setenv =
VIRTUAL_ENV={envdir}
PYTHON=coverage run --source cloudkitty_tempest_plugin --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
[testenv:debug]
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build