Added gabbi API tests

Added tests on all the core API.

Change-Id: I6610ddbe5071ce1231c80319ab1b43c9cd626de4
Depends-On: I2231521708f163d80538fe2e7e5559d4c06cf0c3
Depends-On: Ib05d533eecd32159c1bc7a639db1a49df678f726
Depends-On: I3302617c266111ba1d705d04f1acf20b9348614d
Depends-On: I0ab0f1b2a75d39b8d371c54a264856db69adc2fd
This commit is contained in:
Stéphane Albert 2015-08-28 18:18:34 +02:00
parent 23458597ab
commit 04a9f0cd43
17 changed files with 1046 additions and 13 deletions

View File

@ -2,3 +2,4 @@
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkitty/tests $LISTOPT $IDOPTION test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./cloudkitty/tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE test_id_option=--load-list $IDFILE
test_list_option=--list test_list_option=--list
group_regex=gabbi\.driver\.(test_[^_]+_[^_]+)

View File

@ -48,12 +48,13 @@ auth_opts = [
api_opts = [ api_opts = [
cfg.StrOpt('host_ip', cfg.StrOpt('host_ip',
default="0.0.0.0", default="0.0.0.0",
help="Host serving the API." help="Host serving the API."),
),
cfg.IntOpt('port', cfg.IntOpt('port',
default=8888, default=8888,
help="Host port serving the API." help="Host port serving the API."),
), cfg.BoolOpt('pecan_debug',
default=False,
help='Toggle Pecan Debug Middleware.'),
] ]
CONF = cfg.CONF CONF = cfg.CONF
@ -87,7 +88,7 @@ def setup_app(pecan_config=None, extra_hooks=None):
app_conf.app.root, app_conf.app.root,
static_root=app_conf.app.static_root, static_root=app_conf.app.static_root,
template_path=app_conf.app.template_path, template_path=app_conf.app.template_path,
debug=CONF.debug, debug=CONF.api.pecan_debug,
force_canonical=getattr(app_conf.app, 'force_canonical', True), force_canonical=getattr(app_conf.app, 'force_canonical', True),
hooks=app_hooks, hooks=app_hooks,
guess_content_type_from_ext=False guess_content_type_from_ext=False

View File

@ -102,7 +102,7 @@ class ModulesController(rest.RestController, RatingModulesMixin):
with lock: with lock:
module = self.extensions[module_id] module = self.extensions[module_id]
except KeyError: except KeyError:
pecan.abort(404, 'Module not found.') pecan.abort(400, 'Module not found.')
infos = module.obj.module_info.copy() infos = module.obj.module_info.copy()
infos['module_id'] = infos.pop('name') infos['module_id'] = infos.pop('name')
return rating_models.CloudkittyModule(**infos) return rating_models.CloudkittyModule(**infos)
@ -218,3 +218,4 @@ class RatingController(rest.RestController):
policy.enforce(pecan.request.context, 'rating:module_config', {}) policy.enforce(pecan.request.context, 'rating:module_config', {})
self.modules.reload_extensions() self.modules.reload_extensions()
self.module_config.reload_extensions() self.module_config.reload_extensions()
self.module_config.expose_modules()

View File

@ -54,25 +54,28 @@ class DataFramesController(rest.RestController):
backend = pecan.request.storage_backend backend = pecan.request.storage_backend
dataframes = [] dataframes = []
try: try:
frames = backend.get_time_frame(begin_ts, end_ts, frames = backend.get_time_frame(begin_ts,
end_ts,
tenant_id=tenant_id, tenant_id=tenant_id,
res_type=resource_type) res_type=resource_type)
for frame in frames: for frame in frames:
for service, data_list in frame['usage'].items(): for service, data_list in frame['usage'].items():
frame_tenant = None
resources = [] resources = []
for data in data_list: for data in data_list:
desc = data['desc'] if data['desc'] else {} desc = data['desc'] if data['desc'] else {}
price = decimal.Decimal(data['rating']['price']) price = decimal.Decimal(str(data['rating']['price']))
resource = storage_models.RatedResource( resource = storage_models.RatedResource(
service=service, service=service,
desc=desc, desc=desc,
volume=data['vol']['qty'], volume=data['vol']['qty'],
rating=price) rating=price)
frame_tenant = data['tenant_id']
resources.append(resource) resources.append(resource)
dataframe = storage_models.DataFrame( dataframe = storage_models.DataFrame(
begin=ck_utils.iso2dt(frame['period']['begin']), begin=ck_utils.iso2dt(frame['period']['begin']),
end=ck_utils.iso2dt(frame['period']['end']), end=ck_utils.iso2dt(frame['period']['end']),
tenant_id=tenant_id, # FIXME tenant_id=frame_tenant,
resources=resources) resources=resources)
dataframes.append(dataframe) dataframes.append(dataframe)
except ck_storage.NoTimeFrame: except ck_storage.NoTimeFrame:

View File

@ -60,7 +60,7 @@ class RatedDataFrame(Base, models.ModelBase):
# Volume informations # Volume informations
vol_dict = {} vol_dict = {}
vol_dict['qty'] = self.qty vol_dict['qty'] = self.qty.normalize()
vol_dict['unit'] = self.unit vol_dict['unit'] = self.unit
res_dict = {} res_dict = {}

View File

@ -19,11 +19,20 @@ from oslo_config import fixture as config_fixture
from oslotest import base from oslotest import base
import testscenarios import testscenarios
from cloudkitty import collector
from cloudkitty import db from cloudkitty import db
from cloudkitty.db import api as ck_db_api from cloudkitty.db import api as ck_db_api
from cloudkitty import rating from cloudkitty import rating
class FakeCollectorModule(collector.BaseCollector):
collector_name = 'test_fake'
dependencies = tuple()
def __init__(self):
super(FakeCollectorModule, self).__init__([], period=3600)
class FakeRatingModule(rating.RatingProcessorBase): class FakeRatingModule(rating.RatingProcessorBase):
module_name = 'fake' module_name = 'fake'
description = 'fake rating module' description = 'fake rating module'

View File

View File

@ -0,0 +1,311 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
import abc
import decimal
import os
from gabbi import fixture
import mock
from oslo_config import cfg
from oslo_config import fixture as conf_fixture
from oslo_db.sqlalchemy import utils
import oslo_messaging as messaging
from oslo_messaging import conffixture
from oslo_policy import opts as policy_opts
import six
from stevedore import driver
from stevedore import extension
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from cloudkitty.api import app
from cloudkitty.common import rpc
from cloudkitty import db
from cloudkitty.db import api as ck_db_api
from cloudkitty import rating
from cloudkitty import storage
from cloudkitty.storage.sqlalchemy import models
from cloudkitty import tests
from cloudkitty import utils as ck_utils
INITIAL_TIMESTAMP = 1420070400
class UUIDFixture(fixture.GabbiFixture):
def start_fixture(self):
FAKE_UUID = '6c1b8a30-797f-4b7e-ad66-9879b79059fb'
patcher = mock.patch(
'oslo_utils.uuidutils.generate_uuid',
return_value=FAKE_UUID)
patcher.start()
self.patcher = patcher
def stop_fixture(self):
self.patcher.stop()
@six.add_metaclass(abc.ABCMeta)
class BaseExtensionFixture(fixture.GabbiFixture):
klass = None
namespace = None
stevedore_mgr = None
assert_args = {}
@abc.abstractmethod
def setup_fake_modules(self):
pass
def start_fixture(self):
fake_extensions = self.setup_fake_modules()
self.mock = mock.patch(self.klass)
fake_mgr = self.stevedore_mgr.make_test_instance(
fake_extensions,
self.namespace)
self.patch = self.mock.start()
self.patch.return_value = fake_mgr
def stop_fixture(self):
self.patch.assert_called_with(
self.namespace,
**self.assert_args)
self.mock.stop()
class CollectorExtensionsFixture(BaseExtensionFixture):
klass = 'stevedore.driver.DriverManager'
namespace = 'cloudkitty.collector.backends'
stevedore_mgr = driver.DriverManager
assert_args = {
'invoke_kwds': {'period': 3600},
'invoke_on_load': True}
def setup_fake_modules(self):
def fake_metric(start,
end=None,
project_id=None,
q_filter=None):
return None
fake_module1 = tests.FakeCollectorModule()
fake_module1.collector_name = 'fake1'
fake_module1.get_compute = fake_metric
fake_module2 = tests.FakeCollectorModule()
fake_module2.collector_name = 'fake2'
fake_module2.get_volume = fake_metric
fake_module3 = tests.FakeCollectorModule()
fake_module3.collector_name = 'fake3'
fake_module3.get_compute = fake_metric
fake_extensions = [
extension.Extension(
'fake1',
'cloudkitty.tests.FakeCollectorModule1',
None,
fake_module1),
extension.Extension(
'fake2',
'cloudkitty.tests.FakeCollectorModule2',
None,
fake_module2),
extension.Extension(
'fake3',
'cloudkitty.tests.FakeCollectorModule3',
None,
fake_module3)]
return fake_extensions[0]
class RatingModulesFixture(BaseExtensionFixture):
klass = 'stevedore.extension.ExtensionManager'
namespace = 'cloudkitty.rating.processors'
stevedore_mgr = extension.ExtensionManager
assert_args = {
'invoke_on_load': True}
def setup_fake_modules(self):
class FakeConfigController(rating.RatingRestControllerBase):
_custom_actions = {
'test': ['GET']
}
@wsme_pecan.wsexpose(wtypes.text)
def get_test(self):
"""Return the list of every mapping type available.
"""
return 'OK'
fake_module1 = tests.FakeRatingModule()
fake_module1.module_name = 'fake1'
fake_module1.set_priority(3)
fake_module2 = tests.FakeRatingModule()
fake_module2.module_name = 'fake2'
fake_module2.config_controller = FakeConfigController
fake_module2.set_priority(1)
fake_module3 = tests.FakeRatingModule()
fake_module3.module_name = 'fake3'
fake_module3.set_priority(2)
fake_extensions = [
extension.Extension(
'fake1',
'cloudkitty.tests.FakeRatingModule1',
None,
fake_module1),
extension.Extension(
'fake2',
'cloudkitty.tests.FakeRatingModule2',
None,
fake_module2),
extension.Extension(
'fake3',
'cloudkitty.tests.FakeRatingModule3',
None,
fake_module3)]
return fake_extensions
class ConfigFixture(fixture.GabbiFixture):
def start_fixture(self):
self.conf = None
conf = conf_fixture.Config().conf
policy_opts.set_defaults(conf)
msg_conf = conffixture.ConfFixture(conf)
msg_conf.transport_driver = 'fake'
conf.import_group('api', 'cloudkitty.api.app')
conf.set_override('auth_strategy', 'noauth')
conf.set_override('connection', 'sqlite:///', 'database')
conf.set_override('policy_file',
os.path.abspath('etc/cloudkitty/policy.json'),
group='oslo_policy')
conf.import_group('storage', 'cloudkitty.storage')
conf.set_override('backend', 'sqlalchemy', 'storage')
self.conf = conf
self.conn = ck_db_api.get_instance()
migration = self.conn.get_migration()
migration.upgrade('head')
def stop_fixture(self):
if self.conf:
self.conf.reset()
db.get_engine().dispose()
class BaseFakeRPC(fixture.GabbiFixture):
endpoint = None
def start_fixture(self):
rpc.init()
target = messaging.Target(topic='cloudkitty',
server=cfg.CONF.host,
version='1.0')
endpoints = [
self.endpoint()
]
self.server = rpc.get_server(target, endpoints)
self.server.start()
def stop_fixture(self):
self.server.stop()
class QuoteFakeRPC(BaseFakeRPC):
class FakeRPCEndpoint(object):
target = messaging.Target(namespace='rating',
version='1.0')
def quote(self, ctxt, res_data):
return str(1.0)
endpoint = FakeRPCEndpoint
class BaseStorageDataFixture(fixture.GabbiFixture):
def create_fake_data(self, begin, end):
data = [{
"period": {
"begin": begin,
"end": end},
"usage": {
"compute": [
{
"desc": {
"dummy": True,
"fake_meta": 1.0},
"vol": {
"qty": 1,
"unit": "nothing"},
"rating": {
"price": decimal.Decimal('1.337')}}]}}]
return data
def start_fixture(self):
self.storage = storage.get_storage()
self.storage.init()
self.initialize_data()
def stop_fixture(self):
model = models.RatedDataFrame
session = db.get_session()
q = utils.model_query(
model,
session)
q.delete()
class StorageDataFixture(BaseStorageDataFixture):
def initialize_data(self):
nodata_duration = (24 * 3 + 12) * 3600
tenant_list = ['8f82cc70-e50c-466e-8624-24bdea811375',
'7606a24a-b8ad-4ae0-be6c-3d7a41334a2e']
for tenant in tenant_list:
for i in range(INITIAL_TIMESTAMP,
INITIAL_TIMESTAMP + nodata_duration,
3600):
self.storage.nodata(i, i + 3600, tenant)
data_ts = INITIAL_TIMESTAMP + nodata_duration + 3600
data_duration = (24 * 2 + 8) * 3600
for i in range(data_ts,
data_ts + data_duration,
3600):
data = self.create_fake_data(i, i + 3600)
self.storage.append(data, tenant_list[0])
half_duration = int(data_duration / 2)
for i in range(data_ts,
data_ts + half_duration,
3600):
data = self.create_fake_data(i, i + 3600)
self.storage.append(data, tenant_list[1])
for i in range(data_ts + half_duration + 3600,
data_ts + data_duration,
3600):
self.storage.nodata(i, i + 3600, tenant_list[1])
class NowStorageDataFixture(BaseStorageDataFixture):
def initialize_data(self):
begin = ck_utils.get_month_start_timestamp()
for i in range(begin,
begin + 3600 * 12,
3600):
data = self.create_fake_data(i, i + 3600)
self.storage.append(data,
'3d9a1b33-482f-42fd-aef9-b575a3da9369')
def setup_app():
rpc.init()
return app.setup_app()

View File

@ -0,0 +1,158 @@
fixtures:
- ConfigFixture
- RatingModulesFixture
- QuoteFakeRPC
tests:
- name: reload list of modules available
url: /v1/billing/reload_modules
status: 204
- name: list all modules available
url: /v1/billing/modules
status: 200
response_json_paths:
$.modules.`len`: 3
$.modules[0].priority: 3
$.modules[0].module_id: "fake1"
$.modules[0].enabled: false
$.modules[0].description: "fake rating module"
$.modules[0].hot-config: false
$.modules[1].priority: 1
$.modules[1].module_id: "fake2"
$.modules[1].enabled: false
$.modules[1].description: "fake rating module"
$.modules[1].hot-config: false
$.modules[2].priority: 2
$.modules[2].module_id: "fake3"
$.modules[2].enabled: false
$.modules[2].description: "fake rating module"
$.modules[2].hot-config: false
- name: get information of one module
url: /v1/billing/modules/fake2
status: 200
response_json_paths:
$.priority: 1
$.module_id: "fake2"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: get information of a unknown module
url: /v1/billing/modules/fakb
status: 400
response_strings:
- "Module not found."
- name: change priority of a module
url: /v1/billing/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
priority: 5
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/billing/modules/fake3"
- name: get information of the modified module (priority)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 5
$.module_id: "fake3"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: change enabled status of a module
url: /v1/billing/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
enabled: true
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/billing/modules/fake3"
- name: get information of the modified module (status)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 5
$.module_id: "fake3"
$.enabled: true
$.description: "fake rating module"
$.hot-config: false
- name: change status and priority of a module
url: /v1/billing/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
priority: 3
enabled: false
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/billing/modules/fake3"
- name: get information of the modified module (both)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 3
$.module_id: "fake3"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: get a quote for a resource description
url: /v1/billing/quote
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
resources:
- service: "compute"
volume: "1.0"
desc:
test: 1
status: 200
response_strings:
- "1.0"
- name: module without custom API should use notconfigurable controller (GET)
url: /v1/billing/module_config/fake1
status: 409
response_strings:
- "Module is not configurable"
- name: module without custom API should use notconfigurable controller (POST)
url: /v1/billing/module_config/fake1
method: POST
status: 409
response_strings:
- "Module is not configurable"
- name: module without custom API should use notconfigurable controller (PUT)
url: /v1/billing/module_config/fake1
method: PUT
status: 409
response_strings:
- "Module is not configurable"
- name: verify module exposes its custom API
url: /v1/billing/module_config/fake2/test
status: 200
response_strings:
- "OK"

View File

@ -0,0 +1,138 @@
fixtures:
- ConfigFixture
tests:
# States
- name: check collector is disabled by default
url: /v1/collector/fake1/states
status: 200
response_json_paths:
$.enabled: false
$.name: "fake1"
- name: enable collector
url: /v1/collector/fake1/states
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
name: "fake1"
enabled: true
status: 200
response_json_paths:
$.enabled: true
$.name: "fake1"
- name: check collector state isolation
url: /v1/collector/fake2/states
status: 200
response_json_paths:
$.enabled: false
$.name: "fake2"
- name: disable collector
url: /v1/collector/fake1/states
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
name: "fake1"
enabled: false
status: 200
response_json_paths:
$.enabled: false
$.name: "fake1"
# Mappings
- name: get all mappings (empty)
url: /v1/collector/mappings
status: 200
response_json_paths:
$.mappings: []
- name: try to get an unknown mapping
url: /v1/collector/mappings/notfound
status: 400
response_strings:
- "No mapping for service: notfound"
- name: try to delete an unknown mapping
url: /v1/collector/mappings/notfound
method: DELETE
status: 400
response_strings:
- "No mapping for service: notfound"
- name: create mapping
url: /v1/collector/mappings/fake1/metric1
method: POST
request_headers:
content-type: application/json
x-roles: admin
status: 200
response_json_paths:
$.collector: "fake1"
$.service: "metric1"
- name: get all mappings
url: /v1/collector/mappings
status: 200
response_json_paths:
$.mappings[0].collector: "fake1"
$.mappings[0].service: "metric1"
- name: create second mapping
url: /v1/collector/mappings/fake2/metric8
method: POST
request_headers:
content-type: application/json
x-roles: admin
status: 200
response_json_paths:
$.collector: "fake2"
$.service: "metric8"
- name: get all mappings filtering on collector fake1
url: /v1/collector/mappings?collector=fake1
status: 200
response_json_paths:
$.mappings[0].collector: "fake1"
$.mappings[0].service: "metric1"
- name: get all mappings filtering on collector fake2
url: /v1/collector/mappings?collector=fake2
status: 200
response_json_paths:
$.mappings[0].collector: "fake2"
$.mappings[0].service: "metric8"
- name: get all mappings with no filtering
url: /v1/collector/mappings
status: 200
response_json_paths:
$.mappings.`len`: 2
$.mappings[0].collector: "fake1"
$.mappings[0].service: "metric1"
$.mappings[1].collector: "fake2"
$.mappings[1].service: "metric8"
- name: get a mapping filtering on service metric8
url: /v1/collector/mappings/metric8
status: 200
response_json_paths:
$.collector: "fake2"
$.service: "metric8"
- name: delete a mapping
url: /v1/collector/mappings/metric1
method: DELETE
status: 204
- name: check the mapping got deleted
url: /v1/collector/mappings/metric1
status: 400
response_strings:
- "No mapping for service: metric1"

View File

@ -0,0 +1,158 @@
fixtures:
- ConfigFixture
- RatingModulesFixture
- QuoteFakeRPC
tests:
- name: reload list of modules available
url: /v1/rating/reload_modules
status: 204
- name: list all modules available
url: /v1/rating/modules
status: 200
response_json_paths:
$.modules.`len`: 3
$.modules[0].priority: 3
$.modules[0].module_id: "fake1"
$.modules[0].enabled: false
$.modules[0].description: "fake rating module"
$.modules[0].hot-config: false
$.modules[1].priority: 1
$.modules[1].module_id: "fake2"
$.modules[1].enabled: false
$.modules[1].description: "fake rating module"
$.modules[1].hot-config: false
$.modules[2].priority: 2
$.modules[2].module_id: "fake3"
$.modules[2].enabled: false
$.modules[2].description: "fake rating module"
$.modules[2].hot-config: false
- name: get information of one module
url: /v1/rating/modules/fake2
status: 200
response_json_paths:
$.priority: 1
$.module_id: "fake2"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: get information of a unknown module
url: /v1/rating/modules/fakb
status: 400
response_strings:
- "Module not found."
- name: change priority of a module
url: /v1/rating/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
priority: 5
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/rating/modules/fake3"
- name: get information of the modified module (priority)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 5
$.module_id: "fake3"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: change enabled status of a module
url: /v1/rating/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
enabled: true
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/rating/modules/fake3"
- name: get information of the modified module (status)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 5
$.module_id: "fake3"
$.enabled: true
$.description: "fake rating module"
$.hot-config: false
- name: change status and priority of a module
url: /v1/rating/modules/fake3
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
module_id: "fake3"
priority: 3
enabled: false
status: 302
response_headers:
location: "$SCHEME://$NETLOC/v1/rating/modules/fake3"
- name: get information of the modified module (both)
url: $LOCATION
status: 200
response_json_paths:
$.priority: 3
$.module_id: "fake3"
$.enabled: false
$.description: "fake rating module"
$.hot-config: false
- name: get a quote for a resource description
url: /v1/rating/quote
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
resources:
- service: "compute"
volume: "1.0"
desc:
test: 1
status: 200
response_strings:
- "1.0"
- name: module without custom API should use notconfigurable controller (GET)
url: /v1/rating/module_config/fake1
status: 409
response_strings:
- "Module is not configurable"
- name: module without custom API should use notconfigurable controller (POST)
url: /v1/rating/module_config/fake1
method: POST
status: 409
response_strings:
- "Module is not configurable"
- name: module without custom API should use notconfigurable controller (PUT)
url: /v1/rating/module_config/fake1
method: PUT
status: 409
response_strings:
- "Module is not configurable"
- name: verify module exposes its custom API
url: /v1/rating/module_config/fake2/test
status: 200
response_strings:
- "OK"

View File

@ -0,0 +1,68 @@
fixtures:
- ConfigFixture
- StorageDataFixture
- NowStorageDataFixture
tests:
- name: get period with two tenants
url: /v1/report/tenants
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-01-04T00:00:00"
status: 200
response_strings:
- "8f82cc70-e50c-466e-8624-24bdea811375"
- "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
- name: by default give tenants for the current month
url: /v1/report/tenants
status: 200
response_strings:
- "3d9a1b33-482f-42fd-aef9-b575a3da9369"
- name: get period with no tenants
url: /v1/report/tenants
query_parameters:
begin: "2015-02-01T00:00:00"
end: "2015-02-02T00:00:00"
status: 200
response_strings:
- "[]"
- name: get total for a period
url: /v1/report/total
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-02-04T00:00:00"
status: 200
response_strings:
- "110.971"
- name: get total for a period filtering on first tenant
url: /v1/report/total
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-02-04T00:00:00"
tenant_id: "8f82cc70-e50c-466e-8624-24bdea811375"
status: 200
response_strings:
- "73.535"
- name: get total for a period filtering on second tenant
url: /v1/report/total
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-02-04T00:00:00"
tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
status: 200
response_strings:
- "37.436"
- name: get total for a period with no data
url: /v1/report/total
query_parameters:
begin: "2015-02-01T00:00:00"
end: "2015-02-02T00:00:00"
status: 200
response_strings:
- "0"

View File

@ -0,0 +1,135 @@
fixtures:
- ConfigFixture
- StorageDataFixture
tests:
- name: fetch period with no data
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-01-04T00:00:00"
status: 200
response_json_paths:
$.dataframes.`len`: 0
- name: fetch period with no data filtering on tenant_id
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-01T00:00:00"
end: "2015-01-04T00:00:00"
tenant_id: "8f82cc70-e50c-466e-8624-24bdea811375"
status: 200
response_json_paths:
$.dataframes.`len`: 0
- name: check begin is mandatory for dataframes
url: /v1/storage/dataframes
query_parameters:
end: "2015-01-04T00:00:00"
status: 400
response_strings:
- "Missing argument: \\\"begin\\\""
- name: check end is mandatory for dataframes
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T00:00:00"
status: 400
response_strings:
- "Missing argument: \\\"end\\\""
- name: fetch data for the first tenant
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T13:00:00"
end: "2015-01-04T14:00:00"
tenant_id: "8f82cc70-e50c-466e-8624-24bdea811375"
status: 200
response_json_paths:
$.dataframes.`len`: 1
$.dataframes[0].tenant_id: "8f82cc70-e50c-466e-8624-24bdea811375"
$.dataframes[0].begin: "2015-01-04T13:00:00"
$.dataframes[0].end: "2015-01-04T14:00:00"
$.dataframes[0].resources.`len`: 1
$.dataframes[0].resources[0].volume: "1"
$.dataframes[0].resources[0].rating: "1.337"
$.dataframes[0].resources[0].service: "compute"
$.dataframes[0].resources[0].desc.dummy: true
$.dataframes[0].resources[0].desc.fake_meta: 1.0
- name: fetch data for the second tenant
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T13:00:00"
end: "2015-01-04T14:00:00"
tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
status: 200
response_json_paths:
$.dataframes.`len`: 1
$.dataframes[0].tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
$.dataframes[0].begin: "2015-01-04T13:00:00"
$.dataframes[0].end: "2015-01-04T14:00:00"
$.dataframes[0].resources.`len`: 1
$.dataframes[0].resources[0].volume: "1"
$.dataframes[0].resources[0].rating: "1.337"
$.dataframes[0].resources[0].service: "compute"
$.dataframes[0].resources[0].desc.dummy: true
$.dataframes[0].resources[0].desc.fake_meta: 1.0
- name: fetch data for multiple tenants
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T13:00:00"
end: "2015-01-04T14:00:00"
status: 200
response_json_paths:
$.dataframes.`len`: 2
$.dataframes[0].tenant_id: "8f82cc70-e50c-466e-8624-24bdea811375"
$.dataframes[0].begin: "2015-01-04T13:00:00"
$.dataframes[0].end: "2015-01-04T14:00:00"
$.dataframes[0].resources.`len`: 1
$.dataframes[0].resources[0].volume: "1"
$.dataframes[0].resources[0].rating: "1.337"
$.dataframes[0].resources[0].service: "compute"
$.dataframes[0].resources[0].desc.dummy: true
$.dataframes[0].resources[0].desc.fake_meta: 1.0
$.dataframes[1].tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
$.dataframes[1].begin: "2015-01-04T13:00:00"
$.dataframes[1].end: "2015-01-04T14:00:00"
$.dataframes[1].resources.`len`: 1
$.dataframes[1].resources[0].volume: "1"
$.dataframes[1].resources[0].rating: "1.337"
$.dataframes[1].resources[0].service: "compute"
$.dataframes[1].resources[0].desc.dummy: true
$.dataframes[1].resources[0].desc.fake_meta: 1.0
- name: fetch data filtering on service and tenant
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T13:00:00"
end: "2015-01-04T14:00:00"
resource_type: "compute"
tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
status: 200
response_json_paths:
$.dataframes.`len`: 1
$.dataframes[0].tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
$.dataframes[0].begin: "2015-01-04T13:00:00"
$.dataframes[0].end: "2015-01-04T14:00:00"
$.dataframes[0].resources.`len`: 1
$.dataframes[0].resources[0].volume: "1"
$.dataframes[0].resources[0].rating: "1.337"
$.dataframes[0].resources[0].service: "compute"
$.dataframes[0].resources[0].desc.dummy: true
$.dataframes[0].resources[0].desc.fake_meta: 1.0
- name: fetch data filtering on service with no data and tenant
url: /v1/storage/dataframes
query_parameters:
begin: "2015-01-04T13:00:00"
end: "2015-01-04T14:00:00"
resource_type: "image"
tenant_id: "7606a24a-b8ad-4ae0-be6c-3d7a41334a2e"
status: 200
response_json_paths:
$.dataframes.`len`: 0

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
import os
from gabbi import driver
from cloudkitty.tests.gabbi import fixtures
TESTS_DIR = 'gabbits'
def load_tests(loader, tests, pattern):
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
return driver.build_tests(test_dir,
loader,
host=None,
intercept=fixtures.setup_app,
fixture_module=fixtures)

View File

@ -26,6 +26,7 @@ import datetime
import sys import sys
from oslo_utils import timeutils from oslo_utils import timeutils
from six import moves
from stevedore import extension from stevedore import extension
@ -128,8 +129,20 @@ def get_next_month_timestamp(dt=None):
def refresh_stevedore(namespace=None): def refresh_stevedore(namespace=None):
# Trigger reload of entry points """Trigger reload of entry points.
reload(sys.modules['pkg_resources'])
Useful to have dynamic loading/unloading of stevedore modules.
"""
# NOTE(sheeprine): pkg_resources doesn't support reload on python3 due to
# defining basestring which is still there on reload hence executing
# python2 related code.
try:
del sys.modules['pkg_resources'].basestring
except AttributeError:
# python2, do nothing
pass
# Force working_set reload
moves.reload_module(sys.modules['pkg_resources'])
# Clear stevedore cache # Clear stevedore cache
cache = extension.ExtensionManager.ENTRY_POINT_CACHE cache = extension.ExtensionManager.ENTRY_POINT_CACHE
if namespace: if namespace:

View File

@ -1,6 +1,9 @@
[DEFAULT] [DEFAULT]
output_file = etc/cloudkitty/cloudkitty.conf.sample output_file = etc/cloudkitty/cloudkitty.conf.sample
namespace = cloudkitty.common.config namespace = cloudkitty.common.config
namespace = oslo.messaging namespace = oslo.concurrency
namespace = oslo.db namespace = oslo.db
namespace = oslo.log
namespace = oslo.messaging
namespace = oslo.policy
namespace = keystonemiddleware.auth_token namespace = keystonemiddleware.auth_token

View File

@ -4,6 +4,7 @@
hacking<0.10,>=0.9.2 hacking<0.10,>=0.9.2
coverage>=3.6 coverage>=3.6
discover discover
gabbi>=0.12.0 # Apache-2.0
testscenarios>=0.4 testscenarios>=0.4
testrepository>=0.0.18 testrepository>=0.0.18
mock>=1.2 mock>=1.2