Added gabbi tests for hashmap module

Added new gabbi handler to store data into environment variables (useful
for tests requiring UUID of other tests).
Fixed some bugs with hashmap API.
Added more checks on hashmap objects.
Fixed error codes returned by the API.

Change-Id: I08f9a112df77d7c9aaa5c604edcbea6dabecab79
This commit is contained in:
Stéphane Albert 2016-04-14 15:58:42 +02:00
parent e2a842046f
commit 78c3f4fb4a
16 changed files with 971 additions and 197 deletions

View File

@ -83,8 +83,10 @@ class HashMapFieldsController(rating.RatingRestControllerBase):
pecan.response.location += field_db.field_id
return field_models.Field(
**field_db.export_model())
except (db_api.FieldAlreadyExists, db_api.NoSuchService) as e:
except db_api.FieldAlreadyExists as e:
pecan.abort(409, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),
@ -97,5 +99,5 @@ class HashMapFieldsController(rating.RatingRestControllerBase):
hashmap = db_api.get_instance()
try:
hashmap.delete_field(uuid=field_id)
except db_api.NoSuchService as e:
except db_api.NoSuchField as e:
pecan.abort(404, six.text_type(e))

View File

@ -118,6 +118,8 @@ class HashMapGroupsController(rating.RatingRestControllerBase):
**group_db.export_model())
except db_api.GroupAlreadyExists as e:
pecan.abort(409, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),

View File

@ -120,6 +120,8 @@ class HashMapMappingsController(rating.RatingRestControllerBase):
**mapping_db.export_model())
except db_api.MappingAlreadyExists as e:
pecan.abort(409, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),
@ -141,10 +143,10 @@ class HashMapMappingsController(rating.RatingRestControllerBase):
map_type=mapping.map_type,
group_id=mapping.group_id)
pecan.response.headers['Location'] = pecan.request.path
except (db_api.NoSuchService,
db_api.NoSuchField,
db_api.NoSuchMapping) as e:
except db_api.NoSuchMapping as e:
pecan.abort(404, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),
@ -157,7 +159,5 @@ class HashMapMappingsController(rating.RatingRestControllerBase):
hashmap = db_api.get_instance()
try:
hashmap.delete_mapping(uuid=mapping_id)
except (db_api.NoSuchService,
db_api.NoSuchField,
db_api.NoSuchMapping) as e:
except db_api.NoSuchMapping as e:
pecan.abort(404, six.text_type(e))

View File

@ -120,6 +120,8 @@ class HashMapThresholdsController(rating.RatingRestControllerBase):
**threshold_db.export_model())
except db_api.ThresholdAlreadyExists as e:
pecan.abort(409, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),
@ -141,10 +143,10 @@ class HashMapThresholdsController(rating.RatingRestControllerBase):
map_type=threshold.map_type,
group_id=threshold.group_id)
pecan.response.headers['Location'] = pecan.request.path
except (db_api.NoSuchService,
db_api.NoSuchField,
db_api.NoSuchThreshold) as e:
except db_api.NoSuchThreshold as e:
pecan.abort(404, six.text_type(e))
except db_api.ClientHashMapError as e:
pecan.abort(400, six.text_type(e))
@wsme_pecan.wsexpose(None,
ck_types.UuidType(),
@ -157,7 +159,5 @@ class HashMapThresholdsController(rating.RatingRestControllerBase):
hashmap = db_api.get_instance()
try:
hashmap.delete_threshold(uuid=threshold_id)
except (db_api.NoSuchService,
db_api.NoSuchField,
db_api.NoSuchThreshold) as e:
except db_api.NoSuchThreshold as e:
pecan.abort(404, six.text_type(e))

View File

@ -32,7 +32,15 @@ def get_instance():
return IMPL
class NoSuchService(Exception):
class BaseHashMapError(Exception):
"""Base class for HashMap errors."""
class ClientHashMapError(BaseHashMapError):
"""Base class for client side errors."""
class NoSuchService(ClientHashMapError):
"""Raised when the service doesn't exist."""
def __init__(self, name=None, uuid=None):
@ -42,7 +50,7 @@ class NoSuchService(Exception):
self.uuid = uuid
class NoSuchField(Exception):
class NoSuchField(ClientHashMapError):
"""Raised when the field doesn't exist for the service."""
def __init__(self, uuid):
@ -51,7 +59,7 @@ class NoSuchField(Exception):
self.uuid = uuid
class NoSuchGroup(Exception):
class NoSuchGroup(ClientHashMapError):
"""Raised when the group doesn't exist."""
def __init__(self, name=None, uuid=None):
@ -61,7 +69,7 @@ class NoSuchGroup(Exception):
self.uuid = uuid
class NoSuchMapping(Exception):
class NoSuchMapping(ClientHashMapError):
"""Raised when the mapping doesn't exist."""
def __init__(self, uuid):
@ -70,7 +78,7 @@ class NoSuchMapping(Exception):
self.uuid = uuid
class NoSuchThreshold(Exception):
class NoSuchThreshold(ClientHashMapError):
"""Raised when the threshold doesn't exist."""
def __init__(self, uuid):
@ -79,7 +87,7 @@ class NoSuchThreshold(Exception):
self.uuid = uuid
class NoSuchType(Exception):
class NoSuchType(ClientHashMapError):
"""Raised when a mapping type is not handled."""
def __init__(self, map_type):
@ -89,7 +97,7 @@ class NoSuchType(Exception):
self.map_type = map_type
class ServiceAlreadyExists(Exception):
class ServiceAlreadyExists(ClientHashMapError):
"""Raised when the service already exists."""
def __init__(self, name, uuid):
@ -99,7 +107,7 @@ class ServiceAlreadyExists(Exception):
self.uuid = uuid
class FieldAlreadyExists(Exception):
class FieldAlreadyExists(ClientHashMapError):
"""Raised when the field already exists."""
def __init__(self, field, uuid):
@ -109,7 +117,7 @@ class FieldAlreadyExists(Exception):
self.uuid = uuid
class GroupAlreadyExists(Exception):
class GroupAlreadyExists(ClientHashMapError):
"""Raised when the group already exists."""
def __init__(self, name, uuid):
@ -119,27 +127,38 @@ class GroupAlreadyExists(Exception):
self.uuid = uuid
class MappingAlreadyExists(Exception):
class MappingAlreadyExists(ClientHashMapError):
"""Raised when the mapping already exists."""
def __init__(self, mapping, uuid):
def __init__(self, mapping, parent_id=None, parent_type=None, uuid=None):
# TODO(sheeprine): UUID is deprecated
parent_id = parent_id if parent_id else uuid
super(MappingAlreadyExists, self).__init__(
"Mapping %s already exists (UUID: %s)" % (mapping, uuid))
"Mapping '%s' already exists for %s '%s'" % (mapping,
parent_type,
parent_id))
self.mapping = mapping
self.uuid = uuid
self.uuid = parent_id
self.parent_id = parent_id
self.parent_type = parent_type
class ThresholdAlreadyExists(Exception):
class ThresholdAlreadyExists(ClientHashMapError):
"""Raised when the threshold already exists."""
def __init__(self, threshold, uuid):
def __init__(self, threshold, parent_id=None, parent_type=None, uuid=None):
# TODO(sheeprine): UUID is deprecated
parent_id = parent_id if parent_id else uuid
super(ThresholdAlreadyExists, self).__init__(
"Threshold %s already exists (UUID: %s)" % (threshold, uuid))
"Threshold '%s' already exists for %s '%s'"
% (threshold, parent_type, parent_id))
self.threshold = threshold
self.uuid = uuid
self.uuid = parent_id
self.parent_id = parent_id
self.parent_type = parent_type
class MappingHasNoGroup(Exception):
class MappingHasNoGroup(ClientHashMapError):
"""Raised when the mapping is not attached to a group."""
def __init__(self, uuid):
@ -148,7 +167,7 @@ class MappingHasNoGroup(Exception):
self.uuid = uuid
class ThresholdHasNoGroup(Exception):
class ThresholdHasNoGroup(ClientHashMapError):
"""Raised when the threshold is not attached to a group."""
def __init__(self, uuid):
@ -185,10 +204,11 @@ class HashMap(object):
"""
@abc.abstractmethod
def get_group(self, uuid):
def get_group(self, uuid=None, name=None):
"""Return a group object.
:param uuid: UUID of the group to get.
:param name: Name of the group to get.
"""
@abc.abstractmethod

View File

@ -47,7 +47,8 @@ class HashMap(api.HashMap):
q = q.filter(
models.HashMapService.service_id == uuid)
else:
raise ValueError('You must specify either name or uuid.')
raise api.ClientHashMapError(
'You must specify either name or uuid.')
res = q.one()
return res
except sqlalchemy.orm.exc.NoResultFound:
@ -67,23 +68,28 @@ class HashMap(api.HashMap):
models.HashMapService.service_id == service_uuid,
models.HashMapField.name == name)
else:
raise ValueError('You must specify either an uuid'
' or a service_uuid and a name.')
raise api.ClientHashMapError(
'You must specify either an uuid'
' or a service_uuid and a name.')
res = q.one()
return res
except sqlalchemy.orm.exc.NoResultFound:
raise api.NoSuchField(uuid)
def get_group(self, uuid):
def get_group(self, uuid=None, name=None):
session = db.get_session()
try:
q = session.query(models.HashMapGroup)
q = q.filter(
models.HashMapGroup.group_id == uuid)
if uuid:
q = q.filter(
models.HashMapGroup.group_id == uuid)
if name:
q = q.filter(
models.HashMapGroup.name == name)
res = q.one()
return res
except sqlalchemy.orm.exc.NoResultFound:
raise api.NoSuchGroup(uuid=uuid)
raise api.NoSuchGroup(name, uuid)
def get_mapping(self, uuid):
session = db.get_session()
@ -179,8 +185,9 @@ class HashMap(api.HashMap):
models.HashMapMapping.group)
q = q.filter(models.HashMapGroup.group_id == group_uuid)
elif not service_uuid and not field_uuid:
raise ValueError('You must specify either service_uuid,'
' field_uuid or group_uuid.')
raise api.ClientHashMapError(
'You must specify either service_uuid,'
' field_uuid or group_uuid.')
elif no_group:
q = q.filter(models.HashMapMapping.group_id == None) # noqa
res = q.values(
@ -209,8 +216,9 @@ class HashMap(api.HashMap):
models.HashMapThreshold.group)
q = q.filter(models.HashMapGroup.group_id == group_uuid)
elif not service_uuid and not field_uuid:
raise ValueError('You must specify either service_uuid,'
' field_uuid or group_uuid.')
raise api.ClientHashMapError(
'You must specify either service_uuid,'
' field_uuid or group_uuid.')
elif no_group:
q = q.filter(models.HashMapThreshold.group_id == None) # noqa
res = q.values(
@ -243,9 +251,9 @@ class HashMap(api.HashMap):
session.add(field_db)
# FIXME(sheeprine): backref are not populated as they used to be.
# Querying the item again to get backref.
field_db = self.get_field(service_uuid=service_uuid,
name=name)
field_db = self.get_field(service_uuid=service_uuid, name=name)
except exception.DBDuplicateEntry:
field_db = self.get_field(service_uuid=service_uuid, name=name)
raise api.FieldAlreadyExists(field_db.name, field_db.field_id)
else:
return field_db
@ -260,6 +268,7 @@ class HashMap(api.HashMap):
session.add(group_db)
return group_db
except exception.DBDuplicateEntry:
group_db = self.get_group(name=name)
raise api.GroupAlreadyExists(name, group_db.group_id)
def create_mapping(self,
@ -270,13 +279,17 @@ class HashMap(api.HashMap):
field_id=None,
group_id=None):
if field_id and service_id:
raise ValueError('You can only specify one parent.')
if not value and not service_id:
raise ValueError('You must either specify a value'
' or a service_id')
raise api.ClientHashMapError('You can only specify one parent.')
elif not service_id and not field_id:
raise api.ClientHashMapError('You must specify one parent.')
elif value and service_id:
raise ValueError('You can\'t specify a value'
' and a service_id.')
raise api.ClientHashMapError(
'You can\'t specify a value'
' and a service_id.')
elif not value and field_id:
raise api.ClientHashMapError(
'You must specify a value'
' for a field mapping.')
field_fk = None
if field_id:
field_db = self.get_field(uuid=field_id)
@ -303,7 +316,13 @@ class HashMap(api.HashMap):
field_map.group_id = group_fk
session.add(field_map)
except exception.DBDuplicateEntry:
raise api.MappingAlreadyExists(value, field_map.field_id)
if field_id:
puuid = field_id
ptype = 'field'
else:
puuid = service_id
ptype = 'service'
raise api.MappingAlreadyExists(value, puuid, ptype)
except exception.DBError:
raise api.NoSuchType(map_type)
# FIXME(sheeprine): backref are not populated as they used to be.
@ -319,7 +338,9 @@ class HashMap(api.HashMap):
field_id=None,
group_id=None):
if field_id and service_id:
raise ValueError('You can only specify one parent.')
raise api.ClientHashMapError('You can only specify one parent.')
elif not service_id and not field_id:
raise api.ClientHashMapError('You must specify one parent.')
field_fk = None
if field_id:
field_db = self.get_field(uuid=field_id)
@ -346,7 +367,13 @@ class HashMap(api.HashMap):
threshold_db.group_id = group_fk
session.add(threshold_db)
except exception.DBDuplicateEntry:
raise api.ThresholdAlreadyExists(level, threshold_db.field_id)
if field_id:
puuid = field_id
ptype = 'field'
else:
puuid = service_id
ptype = 'service'
raise api.ThresholdAlreadyExists(level, puuid, ptype)
except exception.DBError:
raise api.NoSuchType(map_type)
# FIXME(sheeprine): backref are not populated as they used to be.
@ -360,10 +387,15 @@ class HashMap(api.HashMap):
with session.begin():
q = session.query(models.HashMapMapping)
q = q.filter(
models.HashMapMapping.mapping_id == uuid
)
models.HashMapMapping.mapping_id == uuid)
mapping_db = q.with_lockmode('update').one()
if kwargs:
# NOTE(sheeprine): We want to check that value is not set
# to a None value.
if mapping_db.field_id and not kwargs.get('value', 'GOOD'):
raise api.ClientHashMapError(
'You must specify a value'
' for a field mapping.')
# Resolve FK
if 'group_id' in kwargs:
group_id = kwargs.pop('group_id')
@ -379,10 +411,11 @@ class HashMap(api.HashMap):
if hasattr(mapping_db, attribute):
setattr(mapping_db, attribute, value)
else:
raise ValueError('No such attribute: {}'.format(
attribute))
raise api.ClientHashMapError(
'No such attribute: {}'.format(
attribute))
else:
raise ValueError('No attribute to update.')
raise api.ClientHashMapError('No attribute to update.')
return mapping_db
except sqlalchemy.orm.exc.NoResultFound:
raise api.NoSuchMapping(uuid)
@ -411,10 +444,11 @@ class HashMap(api.HashMap):
if hasattr(threshold_db, attribute):
setattr(threshold_db, attribute, value)
else:
raise ValueError('No such attribute: {}'.format(
attribute))
raise api.ClientHashMapError(
'No such attribute: {}'.format(
attribute))
else:
raise ValueError('No attribute to update.')
raise api.ClientHashMapError('No attribute to update.')
return threshold_db
except sqlalchemy.orm.exc.NoResultFound:
raise api.NoSuchThreshold(uuid)
@ -429,7 +463,8 @@ class HashMap(api.HashMap):
elif uuid:
q = q.filter(models.HashMapService.service_id == uuid)
else:
raise ValueError('You must specify either name or uuid.')
raise api.ClientHashMapError(
'You must specify either name or uuid.')
r = q.delete()
if not r:
raise api.NoSuchService(name, uuid)

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 handlers
class EnvironStoreHandler(handlers.ResponseHandler):
"""Hackish response handler used to store data in environment.
Store data into an environment variable to implement some kind of variable
use in gabbi.
"""
test_key_suffix = 'store_environ'
test_key_value = {}
def action(self, test, key, value=None):
data = test.extract_json_path_value(test.json_data, value)
os.environ[key] = test.replace_template(data)

View File

@ -0,0 +1,28 @@
# -*- 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
#
from cloudkitty.tests.gabbi.fixtures import * # noqa
from cloudkitty.rating.hash.db import api as hashmap_db
class HashMapConfigFixture(ConfigFixture):
def start_fixture(self):
super(HashMapConfigFixture, self).start_fixture()
self.conn = hashmap_db.get_instance()
migration = self.conn.get_migration()
migration.upgrade('head')

View File

@ -0,0 +1,257 @@
fixtures:
- HashMapConfigFixture
tests:
- name: check hashmap module is loaded
url: /v1/rating/modules
response_strings:
- '"module_id": "hashmap"'
- '"description": "HashMap rating module."'
- name: create a service
url: /v1/rating/module_config/hashmap/services
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "compute"
status: 201
response_json_paths:
$.name: "compute"
response_store_environ:
hash_service_id: $.service_id
- name: get a service
url: /v1/rating/module_config/hashmap/services/$RESPONSE['$.service_id']
status: 200
response_json_paths:
$.service_id: $RESPONSE['$.service_id']
$.name: "compute"
- name: create a flat service mapping
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $RESPONSE['$.service_id']
type: "flat"
cost: "0.10000000"
status: 201
response_json_paths:
$.service_id: $RESPONSE['$.service_id']
$.type: "flat"
$.cost: "0.10000000"
- name: delete a flat service mapping
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
method: DELETE
status: 204
- name: list services
url: /v1/rating/module_config/hashmap/services
status: 200
response_json_paths:
$.services.`len`: 1
$.services[0].name: "compute"
- name: create a rate service mapping
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $RESPONSE['$.services[0].service_id']
type: "rate"
cost: "0.2"
status: 201
response_json_paths:
$.service_id: $RESPONSE['$.services[0].service_id']
$.type: "rate"
$.cost: "0.20000000"
- name: create a field
url: /v1/rating/module_config/hashmap/fields
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $RESPONSE['$.service_id']
name: "flavor_id"
status: 201
response_json_paths:
$.service_id: $RESPONSE['$.service_id']
$.name: "flavor_id"
response_store_environ:
hash_field_id: $.field_id
- name: get a field
url: /v1/rating/module_config/hashmap/fields/$RESPONSE['$.field_id']
status: 200
response_json_paths:
$.service_id: $RESPONSE['$.service_id']
$.name: "flavor_id"
$.field_id: $RESPONSE['$.field_id']
- name: create a flat field mapping
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: $RESPONSE['$.field_id']
type: "rate"
cost: "0.2"
value: "e2083e22-0004-11e6-82bd-2f02489b068b"
status: 201
response_json_paths:
$.field_id: $RESPONSE['$.field_id']
$.type: "rate"
$.cost: "0.20000000"
- name: delete a flat field mapping
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
method: DELETE
status: 204
- name: list fields
url: /v1/rating/module_config/hashmap/fields?service_id=$ENVIRON['hash_service_id']
status: 200
response_json_paths:
$.fields.`len`: 1
$.fields[0].service_id: $ENVIRON['hash_service_id']
$.fields[0].field_id: $ENVIRON['hash_field_id']
$.fields[0].name: "flavor_id"
- name: create a rate field mapping
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: $RESPONSE['$.fields[0].field_id']
type: "rate"
cost: "0.2"
value: "f17a0674-0004-11e6-a16b-cf941f4668c4"
status: 201
response_json_paths:
$.field_id: $RESPONSE['$.fields[0].field_id']
$.type: "rate"
$.cost: "0.20000000"
response_store_environ:
hash_rate_mapping_id: $.mapping_id
- name: change the cost of a mapping
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
type: "rate"
cost: "0.3"
value: "f17a0674-0004-11e6-a16b-cf941f4668c4"
status: 302
- name: check updated mapping
url: /v1/rating/module_config/hashmap/mappings/$ENVIRON['hash_rate_mapping_id']
status: 200
response_json_paths:
$.mapping_id: $ENVIRON['hash_rate_mapping_id']
$.field_id: $ENVIRON['hash_field_id']
$.type: "rate"
$.cost: "0.30000000"
$.value: "f17a0674-0004-11e6-a16b-cf941f4668c4"
- name: delete a field
url: /v1/rating/module_config/hashmap/fields/$ENVIRON['hash_field_id']
method: DELETE
status: 204
- name: check field got deleted
url: /v1/rating/module_config/hashmap/fields/$ENVIRON['hash_field_id']
method: DELETE
status: 404
response_strings:
- "No such field: $ENVIRON['hash_field_id']"
- name: check child mappings got deleted
url: /v1/rating/module_config/hashmap/mappings/?field_id=$ENVIRON['hash_field_id']
status: 200
response_json_paths:
$.mappings.`len`: 0
- name: delete a service
url: /v1/rating/module_config/hashmap/services/$ENVIRON['hash_service_id']
method: DELETE
status: 204
- name: check service got deleted
url: /v1/rating/module_config/hashmap/services/$ENVIRON['hash_service_id']
method: DELETE
status: 404
response_strings:
- "No such service: None (UUID: $ENVIRON['hash_service_id'])"
- name: create a service for recursive delete
url: /v1/rating/module_config/hashmap/services
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "service"
status: 201
response_store_environ:
hash_service_id: $.service_id
- name: create a field for recursive delete
url: /v1/rating/module_config/hashmap/fields
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $RESPONSE['$.service_id']
name: "flavor_id"
status: 201
response_store_environ:
hash_field_id: $.field_id
- name: create a field mapping for recursive delete
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: $RESPONSE['$.field_id']
value: "flavor_id"
cost: "0.1"
status: 201
response_store_environ:
hash_mapping_id: $.mapping_id
- name: delete a service with recursive
url: /v1/rating/module_config/hashmap/services/$ENVIRON['hash_service_id']
method: DELETE
status: 204
- name: check mapping got recursively deleted
url: /v1/rating/module_config/hashmap/mappings/$ENVIRON['hash_mapping_id']
status: 404
response_strings:
- "No such mapping: $ENVIRON['hash_mapping_id']"
- name: check field got recursively deleted
url: /v1/rating/module_config/hashmap/fields/$ENVIRON['hash_field_id']
status: 404
response_strings:
- "No such field: $ENVIRON['hash_field_id']"

View File

@ -0,0 +1,28 @@
fixtures:
- HashMapConfigFixture
tests:
- name: list services (empty)
url: /v1/rating/module_config/hashmap/services
status: 200
response_strings:
- "[]"
- name: list fields from invalid service (empty)
url: /v1/rating/module_config/hashmap/fields?service_id=d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 200
response_strings:
- "[]"
- name: list mappings from invalid service (empty)
url: /v1/rating/module_config/hashmap/mappings?service_id=d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 200
response_strings:
- "[]"
- name: list mappings from invalid field (empty)
url: /v1/rating/module_config/hashmap/mappings?field_id=d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 200
response_strings:
- "[]"

View File

@ -0,0 +1,318 @@
fixtures:
- HashMapConfigFixture
tests:
- name: get an invalid service
url: /v1/rating/module_config/hashmap/services/d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 404
response_strings:
- "No such service: None (UUID: d28490b2-fb3c-11e5-988b-eb9539c935dc)"
- name: get an invalid field
url: /v1/rating/module_config/hashmap/fields/d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 404
response_strings:
- "No such field: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: get an invalid mapping
url: /v1/rating/module_config/hashmap/mappings/d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 404
response_strings:
- "No such mapping: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: get an invalid threshold
url: /v1/rating/module_config/hashmap/thresholds/d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 404
response_strings:
- "No such threshold: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: get an invalid group
url: /v1/rating/module_config/hashmap/groups/d28490b2-fb3c-11e5-988b-eb9539c935dc
status: 404
response_strings:
- "No such group: None (UUID: d28490b2-fb3c-11e5-988b-eb9539c935dc)"
- name: create a service
url: /v1/rating/module_config/hashmap/services
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "compute"
status: 201
response_json_paths:
$.name: "compute"
response_store_environ:
hash_error_service_id: $.service_id
- name: create a duplicate service
url: /v1/rating/module_config/hashmap/services
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "compute"
status: 409
response_strings:
- "Service compute already exists (UUID: $RESPONSE['$.service_id'])"
- name: create a service mapping with an invalid type
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "371bcd08-009f-11e6-91de-8745729038b2"
type: "fail"
cost: "0.2"
status: 400
response_strings:
- "Invalid input for field/attribute type. Value: 'fail'. Value should be one of: "
- name: create a field
url: /v1/rating/module_config/hashmap/fields
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $ENVIRON['hash_error_service_id']
name: "flavor_id"
status: 201
response_json_paths:
$.service_id: $ENVIRON['hash_error_service_id']
$.name: "flavor_id"
response_store_environ:
hash_error_field_id: $.field_id
- name: create a duplicate field
url: /v1/rating/module_config/hashmap/fields
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $RESPONSE['$.service_id']
name: "flavor_id"
status: 409
response_strings:
- "Field $RESPONSE['$.name'] already exists (UUID: $RESPONSE['$.field_id'])"
- name: modify unknown mapping
url: /v1/rating/module_config/hashmap/mappings/42
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "cf1e7d1e-fcc4-11e5-9c93-b7775ce62e3c"
type: "flat"
cost: "0.10000000"
status: 404
response_strings:
- "No such mapping: 42"
- name: create a field mapping to check updates
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: $ENVIRON['hash_error_field_id']
type: "flat"
cost: "0.2"
value: "fail"
status: 201
- name: remove the value of a field mapping
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
type: "rate"
cost: "0.2"
value: ''
status: 400
response_strings:
- "You must specify a value for a field mapping."
- name: create a service mapping with an invalid service_id
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
status: 400
response_strings:
- "No such service: None (UUID: de23e3fe-0097-11e6-a44d-2b09512e61d9)"
- name: create a field mapping with an invalid field_id
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
value: "fail"
status: 400
response_strings:
- "No such field: de23e3fe-0097-11e6-a44d-2b09512e61d9"
- name: create a service threshold with an invalid service_id
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
level: "1.0"
status: 400
response_strings:
- "No such service: None (UUID: de23e3fe-0097-11e6-a44d-2b09512e61d9)"
- name: create a field threshold with an invalid field_id
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
level: "1.0"
status: 400
response_strings:
- "No such field: de23e3fe-0097-11e6-a44d-2b09512e61d9"
- name: create a mapping with both parent id set
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
field_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
status: 400
response_strings:
- "You can only specify one parent."
- name: create a mapping with a value and no parent
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
type: "flat"
cost: "0.2"
value: "fail"
status: 400
response_strings:
- "You must specify one parent."
- name: create a field mapping with a parent and no value
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: $ENVIRON['hash_error_field_id']
type: "flat"
cost: "0.2"
status: 400
response_strings:
- "You must specify a value for a field mapping."
- name: create a threshold with both parent id set
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
field_id: "de23e3fe-0097-11e6-a44d-2b09512e61d9"
type: "flat"
cost: "0.2"
level: "1.0"
status: 400
response_strings:
- "You can only specify one parent."
- name: create a threshold with no parent
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
type: "flat"
cost: "0.2"
level: "1.0"
status: 400
response_strings:
- "You must specify one parent."
- name: create a service threshold with a parent and no level
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: $ENVIRON['hash_error_service_id']
type: "flat"
cost: "0.2"
status: 400
response_strings:
- "Invalid input for field/attribute level. Value: 'None'. Mandatory field missing."
- name: delete unknown threshold
url: /v1/rating/module_config/hashmap/thresholds/d28490b2-fb3c-11e5-988b-eb9539c935dc
method: DELETE
status: 404
response_strings:
- "No such threshold: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: delete unknown mapping
url: /v1/rating/module_config/hashmap/mappings/d28490b2-fb3c-11e5-988b-eb9539c935dc
method: DELETE
status: 404
response_strings:
- "No such mapping: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: delete unknown field
url: /v1/rating/module_config/hashmap/fields/d28490b2-fb3c-11e5-988b-eb9539c935dc
method: DELETE
status: 404
response_strings:
- "No such field: d28490b2-fb3c-11e5-988b-eb9539c935dc"
- name: delete unknown service
url: /v1/rating/module_config/hashmap/services/d28490b2-fb3c-11e5-988b-eb9539c935dc
method: DELETE
status: 404
response_strings:
- "No such service: None (UUID: d28490b2-fb3c-11e5-988b-eb9539c935dc)"

View File

@ -0,0 +1,144 @@
fixtures:
- HashMapConfigFixture
- UUIDFixture
tests:
- name: check redirect on service creation
url: /v1/rating/module_config/hashmap/services
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "compute"
status: 201
response_json_paths:
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "compute"
response_headers:
location: $SCHEME://$NETLOC/v1/rating/module_config/hashmap/services/6c1b8a30-797f-4b7e-ad66-9879b79059fb
- name: check redirect on service mapping creation
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
type: "flat"
cost: "0.10000000"
status: 201
response_json_paths:
$.mapping_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.type: "flat"
$.cost: "0.10000000"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/mappings/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: delete test mapping
url: /v1/rating/module_config/hashmap/mappings/6c1b8a30-797f-4b7e-ad66-9879b79059fb
method: DELETE
status: 204
- name: check redirect on service threshold creation
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
level: "2"
type: "flat"
cost: "0.10000000"
status: 201
response_json_paths:
$.threshold_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.level: "2.00000000"
$.type: "flat"
$.cost: "0.10000000"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/thresholds/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: delete test threshold
url: /v1/rating/module_config/hashmap/thresholds/6c1b8a30-797f-4b7e-ad66-9879b79059fb
method: DELETE
status: 204
- name: check redirect on field creation
url: /v1/rating/module_config/hashmap/fields
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
name: "flavor_id"
status: 201
response_json_paths:
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "flavor_id"
$.field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/fields/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: check redirect on field mapping creation
url: /v1/rating/module_config/hashmap/mappings
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
value: "04774238-fcad-11e5-a90e-6391fd56aab2"
type: "flat"
cost: "0.10000000"
status: 201
response_json_paths:
$.mapping_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.value: "04774238-fcad-11e5-a90e-6391fd56aab2"
$.type: "flat"
$.cost: "0.10000000"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/mappings/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: check redirect on field threshold creation
url: /v1/rating/module_config/hashmap/thresholds
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
level: "2"
type: "flat"
cost: "0.10000000"
status: 201
response_json_paths:
$.threshold_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.level: "2.00000000"
$.type: "flat"
$.cost: "0.10000000"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/thresholds/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: check redirect on group creation
url: /v1/rating/module_config/hashmap/groups
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "compute_uptime"
status: 201
response_json_paths:
$.group_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "compute_uptime"
response_headers:
location: $SCHEME://$NETLOC/v1/rating/module_config/hashmap/groups/6c1b8a30-797f-4b7e-ad66-9879b79059fb

View File

@ -0,0 +1,37 @@
# -*- 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
from cloudkitty.tests.gabbi import handlers
from cloudkitty.tests.gabbi.rating.hash import fixtures as hash_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=hash_fixtures,
response_handlers=[
handlers.EnvironStoreHandler])

View File

@ -1,131 +0,0 @@
fixtures:
- PyScriptsConfigFixture
- UUIDFixture
tests:
- name: typo of script
url: /v1/rating/module_config/pyscripts/script
status: 405
- name: list scripts (empty)
url: /v1/rating/module_config/pyscripts/scripts
status: 200
response_strings:
- "[]"
- name: create policy script
url: /v1/rating/module_config/pyscripts/scripts
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "policy1"
data: "a = 0"
status: 201
response_json_paths:
$.script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "policy1"
$.data: "a = 0"
$.checksum: "5ae340c1b3bb81955db1cb593cdc78540082c526"
response_headers:
location: '$SCHEME://$NETLOC/v1/rating/module_config/pyscripts/scripts/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
- name: create duplicate policy script
url: /v1/rating/module_config/pyscripts/scripts
method: POST
request_headers:
content-type: application/json
x-roles: admin
data:
name: "policy1"
data: "a = 0"
status: 409
response_strings:
- "Script policy1 already exists (UUID: 6c1b8a30-797f-4b7e-ad66-9879b79059fb)"
- name: list scripts
url: /v1/rating/module_config/pyscripts/scripts
status: 200
response_json_paths:
$.scripts[0].script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.scripts[0].name: "policy1"
$.scripts[0].data: "a = 0"
$.scripts[0].checksum: "5ae340c1b3bb81955db1cb593cdc78540082c526"
- name: list scripts excluding data
url: /v1/rating/module_config/pyscripts/scripts?no_data=true
status: 200
response_json_paths:
$.scripts[0].script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.scripts[0].name: "policy1"
$.scripts[0].checksum: "5ae340c1b3bb81955db1cb593cdc78540082c526"
- name: get script
url: /v1/rating/module_config/pyscripts/scripts/6c1b8a30-797f-4b7e-ad66-9879b79059fb
status: 200
response_json_paths:
$.script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "policy1"
$.data: "a = 0"
$.checksum: "5ae340c1b3bb81955db1cb593cdc78540082c526"
- name: modify script
url: /v1/rating/module_config/pyscripts/scripts/6c1b8a30-797f-4b7e-ad66-9879b79059fb
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
name: "policy1"
data: "a = 1"
status: 201
response_json_paths:
$.script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "policy1"
$.data: "a = 1"
$.checksum: "b88f1ec0c9fe96fde96a6f9dabcbeee661dd7afe"
- name: modify unknown script
url: /v1/rating/module_config/pyscripts/scripts/42
method: PUT
request_headers:
content-type: application/json
x-roles: admin
data:
name: "policy1"
data: "a = 1"
status: 404
response_strings:
- "No such script: None (UUID: 42)"
- name: check updated script
url: /v1/rating/module_config/pyscripts/scripts/6c1b8a30-797f-4b7e-ad66-9879b79059fb
request_headers:
content-type: application/json
x-roles: admin
status: 200
response_json_paths:
$.script_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
$.name: "policy1"
$.data: "a = 1"
$.checksum: "b88f1ec0c9fe96fde96a6f9dabcbeee661dd7afe"
- name: delete script
url: /v1/rating/module_config/pyscripts/scripts/6c1b8a30-797f-4b7e-ad66-9879b79059fb
method: DELETE
status: 204
- name: get unknown script
url: /v1/rating/module_config/pyscripts/scripts/42
status: 404
response_strings:
- "No such script: None (UUID: 42)"
- name: delete unknown script
url: /v1/rating/module_config/pyscripts/scripts/42
method: DELETE
status: 404
response_strings:
- "No such script: None (UUID: 42)"

View File

@ -350,7 +350,7 @@ class HashMapRatingTest(tests.TestCase):
service_db = self._db_api.create_service('compute')
field_db = self._db_api.create_field(service_db.service_id,
'flavor')
self.assertRaises(ValueError,
self.assertRaises(api.ClientHashMapError,
self._db_api.create_mapping,
value='m1.tiny',
cost='1.337',