Adding flavorRef to Service

Change-Id: I2389b6222737dec7fb6c3540c0a42c445f4c567f
This commit is contained in:
amitgandhinz 2014-08-15 15:08:48 -04:00
parent d884f1ed44
commit 3bed3f2ec2
69 changed files with 1589 additions and 118 deletions

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ pip-log.txt
.tox
nosetests.xml
tests/cover
tests/logs
# Translations
*.mo

View File

@ -29,8 +29,6 @@ This documentation is generated by the Sphinx toolkit and lives in the source
tree. Additional draft and project documentation on Poppy and other components of OpenStack can
be found on the `OpenStack wiki`_. Cloud administrators, refer to `docs.openstack.org`_.
.. _`OpenStack wiki`: http://wiki.openstack.org
.. _`docs.openstack.org`: http://docs.openstack.org
Concepts
========
@ -76,4 +74,5 @@ Using Poppy's API
.. toctree::
:maxdepth: 1
api
.. _`OpenStack wiki`: http://wiki.openstack.org
.. _`docs.openstack.org`: http://docs.openstack.org

136
poppy/common/uri.py Normal file
View File

@ -0,0 +1,136 @@
"""Defines URI utilities
Copyright 2014 by Rackspace Hosting, Inc.
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
# NOTE(kgriffs): See also RFC 3986
_UNRESERVED = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789'
'-._~')
# NOTE(kgriffs): See also RFC 3986
_DELIMITERS = ":/?#[]@!$&'()*+,;="
_ALL_ALLOWED = _UNRESERVED + _DELIMITERS
def _create_char_encoder(allowed_chars):
lookup = {}
for code_point in range(256):
if chr(code_point) in allowed_chars:
encoded_char = chr(code_point)
else:
encoded_char = '%{0:02X}'.format(code_point)
# NOTE(kgriffs): PY2 returns str from uri.encode, while
# PY3 returns a byte array.
key = chr(code_point) if six.PY2 else code_point
lookup[key] = encoded_char
return lookup.__getitem__
def _create_str_encoder(is_value):
allowed_chars = _UNRESERVED if is_value else _ALL_ALLOWED
encode_char = _create_char_encoder(allowed_chars)
def encoder(uri):
# PERF(kgriffs): Very fast way to check, learned from urlib.quote
if not uri.rstrip(allowed_chars):
return uri
# Convert to a byte array if it is not one already
#
# NOTE(kgriffs): Code coverage disabled since in Py3K the uri
# is always a text type, so we get a failure for that tox env.
if isinstance(uri, six.text_type): # pragma no cover
uri = uri.encode('utf-8')
# Use our map to encode each char and join the result into a new uri
#
# PERF(kgriffs): map is faster than list comp on py27, but a tiny bit
# slower on py33. Since we are already much faster than urllib on
# py33, let's optimize for py27.
return ''.join(map(encode_char, uri))
return encoder
encode = _create_str_encoder(False)
encode.__name__ = 'encode'
encode.__doc__ = """Encodes a full or relative URI according to RFC 3986.
Escapes disallowed characters by percent-encoding them according
to RFC 3986.
This function is faster in the average case than the similar
`quote` function found in urlib. It also strives to be easier
to use by assuming a sensible default of allowed characters.
RFC 3986 defines a set of "unreserved" characters as well as a
set of "reserved" characters used as delimiters.
Args:
uri: URI or part of a URI to encode. If this is a wide
string (i.e., six.text_type), it will be encoded to
a UTF-8 byte array and any multibyte sequences will
be percent-encoded as-is.
Returns:
An escaped version of `uri`, where all disallowed characters
have been percent-encoded.
"""
encode_value = _create_str_encoder(True)
encode_value.name = 'encode_value'
encode_value.__doc__ = """Encodes a value string according to RFC 3986.
Escapes disallowed characters by percent-encoding them according
to RFC 3986.
This function is faster in the average case than the similar
`quote` function found in urlib. It also strives to be easier
to use by assuming a sensible default of allowed characters.
RFC 3986 defines a set of "unreserved" characters as well as a
set of "reserved" characters used as delimiters.
This function keeps things simply by lumping all reserved
characters into a single set of "delimiters", and everything in
that set is escaped.
Args:
uri: Value to encode. It is assumed not to cross delimiter
boundaries, and so any reserved URI delimiter characters
included in it will be escaped. If `value` is a wide
string (i.e., six.text_type), it will be encoded to
a UTF-8 byte array and any multibyte sequences will
be percent-encoded as-is.
Returns:
An escaped version of `value`, where all disallowed characters
have been percent-encoded.
"""

45
poppy/common/util.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 pprint
class dict2obj(object):
"""Creates objects that behave much like a dictionaries."""
def __init__(self, d):
for k in d:
if isinstance(d[k], dict):
self.__dict__[k] = dict2obj(d[k])
elif isinstance(d[k], (list, tuple)):
l = []
for v in d[k]:
if isinstance(v, dict):
l.append(dict2obj(v))
else:
l.append(v)
self.__dict__[k] = l
else:
self.__dict__[k] = d[k]
def __getitem__(self, name):
if name in self.__dict__:
return self.__dict__[name]
def __iter__(self):
return iter(self.__dict__.keys())
def __repr__(self):
return pprint.pformat(self.__dict__)

View File

@ -14,11 +14,13 @@
# limitations under the License.
from poppy.manager.base import driver
from poppy.manager.base import flavors
from poppy.manager.base import home
from poppy.manager.base import services
from poppy.manager.base import v1
Driver = driver.ManagerDriverBase
FlavorsController = flavors.FlavorsControllerBase
ServicesController = services.ServicesControllerBase
V1Controller = v1.V1ControllerBase
HomeController = home.HomeControllerBase

View File

@ -29,3 +29,7 @@ class ManagerControllerBase(object):
def __init__(self, driver):
self._driver = driver
@property
def driver(self):
return self._driver

View File

@ -37,3 +37,8 @@ class ManagerDriverBase(object):
def services_controller(self):
"""Returns the driver's services controller."""
raise NotImplementedError
@abc.abstractproperty
def flavors_controller(self):
"""Returns the driver's flavors controller."""
raise NotImplementedError

View File

@ -0,0 +1,49 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 abc
import six
from poppy.manager.base import controller
@six.add_metaclass(abc.ABCMeta)
class FlavorsControllerBase(controller.ManagerControllerBase):
def __init__(self, manager):
super(FlavorsControllerBase, self).__init__(manager)
self._storage = self.driver.storage.flavors_controller
@property
def storage(self):
return self._storage
@abc.abstractmethod
def list(self):
raise NotImplementedError
@abc.abstractmethod
def get(self, flavor_id):
raise NotImplementedError
@abc.abstractmethod
def add(self, flavor):
raise NotImplementedError
@abc.abstractmethod
def delete(self, flavor_id, provider_id):
raise NotImplementedError

View File

@ -21,9 +21,9 @@ from poppy.manager.base import controller
@six.add_metaclass(abc.ABCMeta)
class V1ControllerBase(controller.ManagerControllerBase):
class HomeControllerBase(controller.ManagerControllerBase):
def __init__(self, manager):
super(V1ControllerBase, self).__init__(manager)
super(HomeControllerBase, self).__init__(manager)
@abc.abstractmethod
def get(self):

View File

@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from poppy.manager.default import flavors
from poppy.manager.default import home
from poppy.manager.default import services
from poppy.manager.default import v1
Home = home.DefaultHomeController
Flavors = flavors.DefaultFlavorsController
Services = services.DefaultServicesController
V1 = v1.DefaultV1Controller

View File

@ -30,5 +30,9 @@ class DefaultManagerDriver(base.Driver):
return controllers.Services(self)
@decorators.lazy_property(write=False)
def v1_controller(self):
return controllers.V1(self)
def home_controller(self):
return controllers.Home(self)
@decorators.lazy_property(write=False)
def flavors_controller(self):
return controllers.Flavors(self)

View File

@ -0,0 +1,33 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.manager import base
class DefaultFlavorsController(base.FlavorsController):
def __init__(self, manager):
super(DefaultFlavorsController, self).__init__(manager)
def list(self):
return self.storage.list()
def get(self, flavor_id):
return self.storage.get(flavor_id)
def add(self, new_flavor):
return self.storage.add(new_flavor)
def delete(self, flavor_id):
return self.storage.delete(flavor_id)

View File

@ -36,9 +36,9 @@ JSON_HOME = {
}
class DefaultV1Controller(base.V1Controller):
class DefaultHomeController(base.HomeController):
def __init__(self, manager):
super(DefaultV1Controller, self).__init__(manager)
super(DefaultHomeController, self).__init__(manager)
self.JSON_HOME = JSON_HOME

View File

@ -21,7 +21,7 @@ class DefaultServicesController(base.ServicesController):
def __init__(self, manager):
super(DefaultServicesController, self).__init__(manager)
self.storage = self._driver.storage.service_controller
self.storage = self._driver.storage.services_controller
def list(self, project_id, marker=None, limit=None):
return self.storage.list(project_id, marker, limit)

48
poppy/model/flavor.py Normal file
View File

@ -0,0 +1,48 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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.
class Flavor(object):
def __init__(self,
flavor_id, providers=[]):
self._flavor_id = flavor_id
self._providers = providers
@property
def flavor_id(self):
return self._flavor_id
@property
def providers(self):
return self._providers
class Provider(object):
def __init__(self,
provider_id,
provider_url):
self._provider_id = provider_id
self._provider_url = provider_url
@property
def provider_id(self):
return self._provider_id
@property
def provider_url(self):
return self._provider_url

View File

@ -21,7 +21,12 @@ VALID_STATUSES = [u'unknown', u'in_progress', u'deployed', u'failed']
class Service(common.DictSerializableModel):
def __init__(self, name, domains, origins, caching=[], restrictions=[]):
def __init__(self,
name,
domains,
origins,
caching=[],
restrictions=[]):
self._name = name
self._domains = domains
self._origins = origins

View File

@ -14,9 +14,11 @@
# limitations under the License.
from poppy.storage.base import driver
from poppy.storage.base import flavors
from poppy.storage.base import services
Driver = driver.StorageDriverBase
FlavorsController = flavors.FlavorsControllerBase
ServicesController = services.ServicesControllerBase

View File

@ -52,6 +52,11 @@ class StorageDriverBase(object):
raise NotImplementedError
@abc.abstractproperty
def service_controller(self):
def services_controller(self):
"""Returns the driver's hostname controller."""
raise NotImplementedError
@abc.abstractproperty
def flavors_controller(self):
"""Returns the driver's hostname controller."""
raise NotImplementedError

View File

@ -0,0 +1,43 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 abc
import six
from poppy.storage.base import controller
@six.add_metaclass(abc.ABCMeta)
class FlavorsControllerBase(controller.StorageControllerBase):
def __init__(self, driver):
super(FlavorsControllerBase, self).__init__(driver)
@abc.abstractmethod
def list(self):
raise NotImplementedError
@abc.abstractmethod
def get(self, flavor_id):
raise NotImplementedError
@abc.abstractmethod
def add(self, flavor):
raise NotImplementedError
@abc.abstractmethod
def delete(self, flavor_id):
raise NotImplementedError

View File

@ -23,6 +23,8 @@ Field Mappings:
updated and documented in each controller class.
"""
from poppy.storage.cassandra import flavors
from poppy.storage.cassandra import services
ServicesController = services.ServicesController
FlavorsController = flavors.FlavorsController

View File

@ -16,8 +16,8 @@
"""Cassandra storage driver implementation."""
from cassandra import cluster
from cassandra import query
from poppy.common import decorators
from poppy.openstack.common import log as logging
from poppy.storage import base
from poppy.storage.cassandra import controllers
@ -38,6 +38,7 @@ CASSANDRA_GROUP = 'drivers:storage:cassandra'
def _connection(conf):
cassandra_cluster = cluster.Cluster(conf.cluster)
session = cassandra_cluster.connect(conf.keyspace)
session.row_factory = query.dict_factory
return session
@ -54,15 +55,19 @@ class CassandraStorageDriver(base.Driver):
def is_alive(self):
return True
@decorators.lazy_property(write=False)
@property
def connection(self):
"""Cassandra connection instance."""
return _connection(self.cassandra_conf)
@decorators.lazy_property(write=False)
def service_controller(self):
@property
def services_controller(self):
return controllers.ServicesController(self)
@decorators.lazy_property(write=False)
def service_database(self):
@property
def flavors_controller(self):
return controllers.FlavorsController(self)
@property
def database(self):
return self.connection

View File

@ -0,0 +1,107 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.model import flavor
from poppy.storage import base
CQL_GET_ALL = '''
SELECT flavor_id,
providers
FROM flavors
'''
CQL_GET = '''
SELECT flavor_id,
providers
FROM flavors
WHERE flavor_id = %(flavor_id)s
'''
CQL_DELETE = '''
DELETE FROM flavors
WHERE flavor_id = %(flavor_id)s
'''
CQL_CREATE = '''
INSERT INTO flavors (flavor_id,
providers)
VALUES (%(flavor_id)s,
%(providers)s)
'''
class FlavorsController(base.FlavorsController):
@property
def session(self):
return self._driver.database
def list(self):
"""List the supported flavors."""
# get all
result = self.session.execute(CQL_GET_ALL)
flavors = [
flavor.Flavor(
f['flavor_id'],
[flavor.Provider(p_id, p_url)
for p_id, p_url in f['providers'].items()])
for f in result]
return flavors
def get(self, flavor_id):
"""Get the specified Flavor."""
args = {
'flavor_id': flavor_id
}
result = self.session.execute(CQL_GET, args)
flavors = [
flavor.Flavor(
f['flavor_id'],
[flavor.Provider(p_id, p_url)
for p_id, p_url in f['providers'].items()]
)
for f in result]
if (len(flavors) == 1):
return flavors[0]
else:
raise LookupError("More than one flavor was retrieved.")
def add(self, flavor):
"""Add a new flavor."""
providers = dict((p.provider_id, p.provider_url)
for p in flavor.providers)
args = {
'flavor_id': flavor.flavor_id,
'providers': providers
}
self.session.execute(CQL_CREATE, args)
def delete(self, flavor_id):
"""Delete a flavor."""
args = {
'flavor_id': flavor_id
}
self.session.execute(CQL_DELETE, args)

View File

@ -4,6 +4,7 @@ USE poppy;
CREATE TABLE services (
project_id VARCHAR,
service_name VARCHAR,
flavor_id VARCHAR,
domains LIST<TEXT>,
origins LIST<TEXT>,
caching_rules LIST<TEXT>,
@ -11,3 +12,9 @@ CREATE TABLE services (
provider_details MAP<TEXT, TEXT>,
PRIMARY KEY (project_id, service_name)
);
CREATE TABLE flavors (
flavor_id VARCHAR,
providers MAP<TEXT, TEXT>,
PRIMARY KEY (flavor_id)
);

View File

@ -99,7 +99,7 @@ class ServicesController(base.ServicesController):
@property
def session(self):
return self._driver.service_database
return self._driver.database
def list(self, project_id, marker=None, limit=None):

View File

@ -23,6 +23,8 @@ Field Mappings:
updated and documented in each controller class.
"""
from poppy.storage.mockdb import flavors
from poppy.storage.mockdb import services
ServicesController = services.ServicesController
FlavorsController = flavors.FlavorsController

View File

@ -15,7 +15,6 @@
"""Storage driver implementation."""
from poppy.common import decorators
from poppy.openstack.common import log as logging
from poppy.storage import base
from poppy.storage.mockdb import controllers
@ -48,15 +47,19 @@ class MockDBStorageDriver(base.Driver):
def is_alive(self):
return True
@decorators.lazy_property(write=False)
@property
def connection(self):
"""Connection instance."""
return _connection()
@decorators.lazy_property(write=False)
def service_controller(self):
@property
def services_controller(self):
return controllers.ServicesController(self)
@decorators.lazy_property(write=False)
def service_database(self):
@property
def flavors_controller(self):
return controllers.FlavorsController(self)
@property
def database(self):
return self.connection

View File

@ -13,19 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from poppy.model import common
from poppy.storage import base
class Link(common.DictSerializableModel):
def __init__(self, href, rel):
self._href = href
self._rel = rel
class FlavorsController(base.FlavorsController):
@property
def href(self):
return self._href
def session(self):
return self._driver.database
@property
def rel(self):
return self._rel
def list(self):
return []
def get(self, flavor_id):
return None
def add(self, flavor):
pass
def delete(self, flavor_id):
pass

View File

@ -24,7 +24,7 @@ class ServicesController(base.ServicesController):
@property
def session(self):
return self._driver.service_database
return self._driver.database
def list(self, project_id, marker=None, limit=None):
services = [

View File

@ -22,6 +22,8 @@ Field Mappings:
updated and documented in each controller class.
"""
from poppy.storage.mongodb import flavors
from poppy.storage.mongodb import services
ServicesController = services.ServicesController
FlavorsController = flavors.FlavorsController

View File

@ -85,8 +85,12 @@ class MongoDBStorageDriver(base.Driver):
return _connection(self.mongodb_conf)
@decorators.lazy_property(write=False)
def service_controller(self):
return controllers.ServicesController(self.providers)
def services_controller(self):
return controllers.ServicesController(self)
@decorators.lazy_property(write=False)
def flavors_controller(self):
return controllers.FlavorsController(self)
@decorators.lazy_property(write=False)
def service_database(self):
@ -97,3 +101,13 @@ class MongoDBStorageDriver(base.Driver):
name = self.mongodb_conf.database + '_services'
return self.connection[name]
@decorators.lazy_property(write=False)
def flavor_database(self):
"""Database dedicated to the "services" collection.
The services collection is separated out into its own database.
"""
name = self.mongodb_conf.database + '_flavors'
return self.connection[name]

View File

@ -0,0 +1,34 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.storage import base
class FlavorsController(base.FlavorsController):
@property
def session(self):
return self._driver.flavor_database
def list(self):
return ""
def get(self, flavor_id):
return ""
def add(self, flavor):
return ""
def delete(self, flavor_id, provider_id):
return ""

View File

@ -15,14 +15,8 @@
"""Pecan Controllers"""
from poppy.transport.pecan.controllers import ping
from poppy.transport.pecan.controllers import root
from poppy.transport.pecan.controllers import services
from poppy.transport.pecan.controllers import v1
# Hoist into package namespace
Root = root.RootController
Ping = ping.PingController
Services = services.ServicesController
V1 = v1.ControllerV1

View File

@ -24,6 +24,10 @@ class Controller(rest.RestController):
def __init__(self, driver):
self._driver = driver
@property
def driver(self):
return self._driver
def add_controller(self, path, controller):
setattr(self, path, controller)

View File

@ -0,0 +1,28 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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.
"""Pecan v1.0 Controllers"""
from poppy.transport.pecan.controllers.v1 import flavors
from poppy.transport.pecan.controllers.v1 import home
from poppy.transport.pecan.controllers.v1 import ping
from poppy.transport.pecan.controllers.v1 import services
# Hoist into package namespace
Home = home.HomeController
Services = services.ServicesController
Flavors = flavors.FlavorsController
Ping = ping.PingController

View File

@ -0,0 +1,84 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 json
import pecan
from poppy.common import uri
from poppy.transport.pecan.controllers import base
from poppy.transport.pecan.models.request import flavor as flavor_request
from poppy.transport.pecan.models.response import flavor as flavor_response
from poppy.transport.validators import helpers
from poppy.transport.validators.schemas import flavor as schema
from poppy.transport.validators.stoplight import decorators
from poppy.transport.validators.stoplight import helpers as stoplight_helpers
from poppy.transport.validators.stoplight import rule
class FlavorsController(base.Controller):
@pecan.expose('json')
def get_all(self):
flavors_controller = self.driver.manager.flavors_controller
result = flavors_controller.list()
flavor_list = [
flavor_response.Model(item, pecan.request) for item in result]
return flavor_list
@pecan.expose('json')
def get_one(self, flavor_id):
flavors_controller = self.driver.manager.flavors_controller
result = flavors_controller.get(flavor_id)
if result is not None:
print (result)
print('done')
return flavor_response.Model(result, pecan.request)
else:
pecan.response.status = 404
@pecan.expose('json')
@decorators.validate(
request=rule.Rule(
helpers.json_matches_schema(
schema.FlavorSchema.get_schema("flavor", "POST")),
helpers.abort_with_message,
stoplight_helpers.pecan_getter))
def post(self):
flavors_controller = self.driver.manager.flavors_controller
flavor_json = json.loads(pecan.request.body.decode('utf-8'))
try:
new_flavor = flavor_request.load_from_json(flavor_json)
flavors_controller.add(new_flavor)
# form the success response
flavor_url = str(
uri.encode(u'{0}/v1.0/flavors/{1}'.format(
pecan.request.host_url,
new_flavor.flavor_id)))
pecan.response.status = 204
pecan.response.headers["Location"] = flavor_url
except Exception:
pecan.response.status = 400
@pecan.expose('json')
def delete(self, flavor_id):
flavors_controller = self.driver.manager.flavors_controller
flavors_controller.delete(flavor_id)
pecan.response.status = 204

View File

@ -18,9 +18,9 @@ import pecan
from poppy.transport.pecan.controllers import base
class ControllerV1(base.Controller):
class HomeController(base.Controller):
@pecan.expose('json')
def get(self):
v1_controller = self._driver.manager.v1_controller
return v1_controller.get()
home_controller = self._driver.manager.home_controller
return home_controller.get()

View File

@ -21,6 +21,7 @@ import pecan
from poppy.openstack.common import log
from poppy import transport
from poppy.transport.pecan import controllers
from poppy.transport.pecan.controllers import v1
from poppy.transport.pecan import hooks
@ -49,16 +50,17 @@ class PecanTransportDriver(transport.Driver):
def _setup_app(self):
root_controller = controllers.Root(self)
home_controller = v1.Home(self)
root_controller.add_controller('v1.0', home_controller)
home_controller.add_controller('ping', v1.Ping(self))
home_controller.add_controller('services', v1.Services(self))
home_controller.add_controller('flavors', v1.Flavors(self))
pecan_hooks = [hooks.Context()]
self._app = pecan.make_app(root_controller, hooks=pecan_hooks)
controller_v1 = controllers.V1(self)
root_controller.add_controller('v1.0', controller_v1)
controller_v1.add_controller('ping', controllers.Ping(self))
controller_v1.add_controller('services', controllers.Services(self))
def listen(self):
LOG.info(
'Serving on host %(bind)s:%(port)s',

View File

@ -0,0 +1,35 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.model import flavor
def load_from_json(json_data):
flavor_id = json_data['id']
providers = []
for p in json_data['providers']:
provider_id = p['provider']
provider_url = [item['href']
for item in p['links']
if item['rel'] == 'provider_url'][0]
provider = flavor.Provider(provider_id, provider_url)
providers.append(provider)
new_flavor = flavor.Flavor(flavor_id, providers)
return new_flavor

View File

@ -0,0 +1,46 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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.
try:
import ordereddict as collections
except ImportError:
import collections
from poppy.transport.pecan.models.response import link
class Model(collections.OrderedDict):
def __init__(self, flavor, request):
super(Model, self).__init__()
self['id'] = flavor.flavor_id
self['providers'] = []
for x in flavor.providers:
provider = collections.OrderedDict()
provider['provider'] = x.provider_id
provider['links'] = []
provider['links'].append(
link.Model(x.provider_url, 'provider_url'))
self['providers'].append(provider)
self['links'] = []
self['links'].append(
link.Model(
u'{0}/v1.0/flavors/{1}'.format(request.host_url,
flavor.flavor_id),
'self'))

View File

@ -24,4 +24,4 @@ class Model(collections.OrderedDict):
def __init__(self, href, rel):
super(Model, self).__init__()
self['href'] = href
self['rel'] = rel
self['rel'] = rel

View File

@ -196,6 +196,11 @@ def is_valid_service_name(service_name):
pass
@decorators.validation_function
def is_valid_flavor_id(flavor_id):
pass
def abort_with_message(error_info):
pecan.abort(400, detail=getattr(error_info, "message", ""),
headers={'Content-Type': "application/json"})

View File

@ -0,0 +1,69 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.transport.validators import schema_base
class FlavorSchema(schema_base.SchemaBase):
"""JSON Schmema validation for /flavor."""
schema = {
"flavor": {
"POST": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 3,
"maxLength": 64,
"required": True
},
"providers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"provider": {
"type": "string",
"required": True
},
"links": {
"type": "array",
"items": {
"type": "object",
"properties": {
"href": {
"type": "string",
"format": "uri",
"required": True
},
"rel": {
"type": "string",
"enum": ["provider_url"],
"required": True
}
}
},
"minItems": 1
}
}
},
"minItems": 0
}
}
}
}
}

View File

@ -5,6 +5,7 @@ netaddr>=0.7.6
jsonschema>=1.3.0,!=1.4.0
iso8601>=0.1.8
msgpack-python
ordereddict
python-keystoneclient>=0.4.1
WebOb>=1.2.3,<1.3

View File

@ -1,6 +1,7 @@
-r common.txt
-r docs.txt
-r storage/cassandra.txt
-r transport/pecan.txt
-r storage/cassandra.txt
-r provider/cloudfront.txt
-r provider/fastly.txt
-r provider/maxcdn.txt

View File

@ -35,6 +35,6 @@ class BaseFunctionalTest(base.TestCase):
cfg.CONF(args=[], default_config_files=[conf_path])
poppy_wsgi = bootstrap.Bootstrap(cfg.CONF).transport.app
self.app = webtest.TestApp(poppy_wsgi)
self.app = webtest.app.TestApp(poppy_wsgi)
FunctionalTest = BaseFunctionalTest

View File

@ -0,0 +1,57 @@
{
"one_provider": {
"id" : "asia",
"providers" : [
{
"provider" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "provider_url"
}
]
}
]
},
"many_providers": {
"id" : "asia",
"providers" : [
{
"provider" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "provider_url"
}
]
},
{
"provider" : "KiwiCache",
"links": [
{
"href": "http://www.cdn.co.nz",
"rel": "provider_url"
}
]
}
]
},
"unicode_provider": {
"id" : "위키백과대문",
"providers" : [
{
"provider" : "KoreanCache",
"links": [
{
"href": "http://www.위키백과대문.com",
"rel": "provider_url"
}
]
}
]
},
"no_providers": {
"id" : "asia",
"providers" : []
}
}

View File

@ -0,0 +1,21 @@
{
"no_body": {
},
"no_flavor_id": {
"x_id" : "asia"
},
"invalid_fields": {
"id" : "asia",
"providers" : [
{
"invalid_field" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "invalid_url"
}
]
}
]
}
}

View File

@ -0,0 +1,93 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 json
import uuid
import ddt
import mock
from poppy.common import uri
from poppy.manager.default import flavors as manager
from poppy.transport.pecan.models.request import flavor
from tests.functional.transport.pecan import base
@ddt.ddt
class FlavorControllerTest(base.FunctionalTest):
def test_get_all(self):
response = self.app.get('/v1.0/flavors')
self.assertEqual(200, response.status_code)
@ddt.file_data('data_create_flavor.json')
@mock.patch.object(manager.DefaultFlavorsController, 'storage')
def test_get_one(self, value, mock_manager):
return_flavor = flavor.load_from_json(value)
# mock the storage response
mock_response = return_flavor
mock_manager.get.return_value = mock_response
url = u'/v1.0/flavors/{0}'.format(uri.encode(value['id']))
response = self.app.get(url)
self.assertEqual(200, response.status_code)
def test_get_not_found(self):
response = self.app.get('/v1.0/flavors/{0}'.format(uuid.uuid1()),
status=404,
expect_errors=True)
self.assertEqual(404, response.status_code)
@ddt.file_data('data_create_flavor_bad.json')
def test_create_bad_data(self, value):
response = self.app.post('/v1.0/flavors',
params=json.dumps(value),
headers={"Content-Type": "application/json"},
status=400,
expect_errors=True)
self.assertEqual(400, response.status_code)
@ddt.file_data('data_create_flavor.json')
@mock.patch.object(manager.DefaultFlavorsController, 'storage')
def test_create_exception(self, value, mock_storage):
mock_storage.add.side_effect = Exception()
# create with good data
response = self.app.post('/v1.0/flavors',
params=json.dumps(value),
headers={"Content-Type": "application/json"},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ddt.file_data('data_create_flavor.json')
def test_create(self, value):
# create with good data
response = self.app.post('/v1.0/flavors',
params=json.dumps(value),
headers={"Content-Type": "application/json"})
self.assertEqual(204, response.status_code)
def test_delete(self):
response = self.app.delete('/v1.0/flavors/{0}'.format(uuid.uuid1()))
self.assertEqual(204, response.status_code)

View File

@ -13,15 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from poppy.manager.default import v1
from poppy.manager.default import home
from tests.functional.transport.pecan import base
class V1ControllerTest(base.FunctionalTest):
class HomeControllerTest(base.FunctionalTest):
def test_get_all(self):
response = self.app.get('/v1.0/00001')
self.assertEqual(200, response.status_code)
# Temporary until actual implementation
self.assertEqual(v1.JSON_HOME, response.json)
self.assertEqual(home.JSON_HOME, response.json)

View File

@ -15,7 +15,7 @@
import uuid
from poppy.manager.default import v1
from poppy.manager.default import home
from tests.functional.transport.pecan import base
@ -33,11 +33,11 @@ class ContextHookTest(base.FunctionalTest):
self.assertEqual(200, response.status_code)
# Temporary until actual implementation
self.assertEqual(v1.JSON_HOME, response.json)
self.assertEqual(home.JSON_HOME, response.json)
def test_project_id_in_url(self):
response = self.app.get('/v1.0/000001', headers=self.headers)
self.assertEqual(200, response.status_code)
# Temporary until actual implementation
self.assertEqual(v1.JSON_HOME, response.json)
self.assertEqual(home.JSON_HOME, response.json)

View File

@ -0,0 +1,44 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 poppy.common import uri
from tests.unit import base
class URITest(base.TestCase):
def test_uri_encode(self):
url = 'http://example.com/v1/fizbit/messages?limit=3&echo=true'
self.assertEqual(uri.encode(url), url)
url = 'http://example.com/v1/fiz bit/messages'
expected = 'http://example.com/v1/fiz%20bit/messages'
self.assertEqual(uri.encode(url), expected)
url = u'http://example.com/v1/fizbit/messages?limit=3&e\u00e7ho=true'
expected = ('http://example.com/v1/fizbit/messages'
'?limit=3&e%C3%A7ho=true')
self.assertEqual(uri.encode(url), expected)
def test_uri_encode_value(self):
self.assertEqual(uri.encode_value('abcd'), 'abcd')
self.assertEqual(uri.encode_value(u'abcd'), u'abcd')
self.assertEqual(uri.encode_value(u'ab cd'), u'ab%20cd')
self.assertEqual(uri.encode_value(u'\u00e7'), '%C3%A7')
self.assertEqual(uri.encode_value(u'\u00e7\u20ac'),
'%C3%A7%E2%82%AC')
self.assertEqual(uri.encode_value('ab/cd'), 'ab%2Fcd')
self.assertEqual(uri.encode_value('ab+cd=42,9'),
'ab%2Bcd%3D42%2C9')

View File

@ -17,6 +17,7 @@ import mock
from oslo.config import cfg
from poppy.manager.default import driver
from poppy.manager.default import flavors
from poppy.manager.default import services
from tests.unit import base
@ -36,3 +37,8 @@ class DefaultManagerDriverTests(base.TestCase):
sc = self.driver.services_controller
self.assertIsInstance(sc, services.DefaultServicesController)
def test_flavors_controller(self):
sc = self.driver.flavors_controller
self.assertIsInstance(sc, flavors.DefaultFlavorsController)

View File

@ -0,0 +1,77 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 uuid
import mock
from oslo.config import cfg
from poppy.manager.default import driver
from poppy.manager.default import flavors
from poppy.model import flavor
from tests.unit import base
class DefaultManagerFlavorTests(base.TestCase):
@mock.patch('poppy.storage.base.driver.StorageDriverBase')
@mock.patch('poppy.provider.base.driver.ProviderDriverBase')
def setUp(self, mock_driver, mock_provider):
super(DefaultManagerFlavorTests, self).setUp()
# create mocked config and driver
conf = cfg.ConfigOpts()
manager_driver = driver.DefaultManagerDriver(conf,
mock_driver,
mock_provider)
# stubbed driver
self.fc = flavors.DefaultFlavorsController(manager_driver)
def test_list(self):
results = self.fc.list()
# ensure the manager calls the storage driver with the appropriate data
self.fc.storage.list.assert_called_once()
# and that a list of flavors objects are returned
[self.assertIsInstance(x, flavor.Flavor) for x in results]
def test_get(self):
flavor_id = uuid.uuid1()
self.fc.get(flavor_id)
# ensure the manager calls the storage driver with the appropriate data
self.fc.storage.get.assert_called_once_with(flavor_id)
def test_add(self):
flavor_id = uuid.uuid1()
providers = []
providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
new_flavor = flavor.Flavor(flavor_id, providers)
self.fc.add(new_flavor)
self.fc.storage.add.assert_called_once_with(new_flavor)
def test_delete(self):
flavor_id = uuid.uuid1()
self.fc.delete(flavor_id)
# ensure the manager calls the storage driver with the appropriate data
self.fc.storage.delete.assert_called_once_with(flavor_id)

View File

@ -1,39 +0,0 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 ddt
from poppy.model.helpers import link
from tests.unit import base
@ddt.ddt
class TestLink(base.TestCase):
def test_link(self):
href = 'http://www.mywebsite.com/'
rel = 'nofollow'
mylink = link.Link(href, rel)
# test all properties
# href
self.assertEqual(mylink.href, href)
self.assertRaises(AttributeError, setattr, mylink, 'href', href)
# rel
self.assertEqual(mylink.rel, rel)
self.assertRaises(AttributeError, setattr, mylink, 'href', rel)

View File

@ -0,0 +1,60 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 uuid
import ddt
from poppy.model import flavor
from tests.unit import base
@ddt.ddt
class TestFlavorModel(base.TestCase):
def setUp(self):
super(TestFlavorModel, self).setUp()
def test_create_with_providers(self):
self.flavor_id = uuid.uuid1()
self.providers = []
self.providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
self.providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
self.providers.append(flavor.Provider(uuid.uuid1(), uuid.uuid1()))
my_flavor = flavor.Flavor(self.flavor_id, self.providers)
# test all properties
self.assertEqual(my_flavor.flavor_id, self.flavor_id)
self.assertEqual(len(my_flavor.providers), len(self.providers))
def test_create_no_providers(self):
self.flavor_id = uuid.uuid1()
my_flavor = flavor.Flavor(self.flavor_id)
# test all properties
self.assertEqual(my_flavor.flavor_id, self.flavor_id)
self.assertEqual(len(my_flavor.providers), 0)
def test_provider(self):
provider_id = uuid.uuid1()
provider_url = uuid.uuid1()
new_provider = flavor.Provider(provider_id, provider_url)
self.assertEqual(new_provider.provider_id, provider_id)
self.assertEqual(new_provider.provider_url, provider_url)

View File

@ -32,6 +32,7 @@ class TestServiceModel(base.TestCase):
super(TestServiceModel, self).setUp()
self.service_name = uuid.uuid1()
self.flavorRef = "strawberry"
self.myorigins = []
self.mydomains = []
@ -62,6 +63,9 @@ class TestServiceModel(base.TestCase):
myservice.name = changed_service_name
self.assertEqual(myservice.name, changed_service_name)
# flavorRef
# self.assertEqual(myservice.flavorRef, self.flavorRef)
# domains
self.assertEqual(myservice.domains, self.mydomains)
myservice.domains = []

View File

@ -0,0 +1,22 @@
{
"one_provider": [{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net"
}
}],
"multiple_providers": [{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net",
"maxcdn" : "www.maxcdn.net"
}
}],
"no_providers": [{
"flavor_id" : "europe",
"providers" : {}
}]
}

View File

@ -0,0 +1,20 @@
{
"multiple_flavors": [
{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net"
}
},
{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net",
"maxcdn" : "www.maxcdn.net"
}
}
]
}

View File

@ -0,0 +1,28 @@
{
"one_flavor": [
{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net"
}
}
],
"multiple_flavors": [
{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net",
"maxcdn" : "www.maxcdn.net"
}
},
{
"flavor_id" : "europe",
"providers" : {
"fastly" : "www.fastly.net"
}
}
],
"no_flavors": [
]
}

View File

@ -18,6 +18,7 @@ import mock
from oslo.config import cfg
from poppy.storage.cassandra import driver
from poppy.storage.cassandra import flavors
from poppy.storage.cassandra import services
from tests.unit import base
@ -30,11 +31,11 @@ CASSANDRA_OPTIONS = [
]
class CassandraStorageServiceTests(base.TestCase):
class CassandraStorageDriverTests(base.TestCase):
@mock.patch.object(driver, 'CASSANDRA_OPTIONS', new=CASSANDRA_OPTIONS)
def setUp(self):
super(CassandraStorageServiceTests, self).setUp()
super(CassandraStorageDriverTests, self).setUp()
conf = cfg.ConfigOpts()
self.cassandra_driver = driver.CassandraStorageDriver(conf)
@ -55,13 +56,20 @@ class CassandraStorageServiceTests(base.TestCase):
mock_cluster.assert_called_with('mock_poppy')
def test_service_controller(self):
sc = self.cassandra_driver.service_controller
sc = self.cassandra_driver.services_controller
self.assertEqual(
isinstance(sc, services.ServicesController),
True)
def test_flavor_controller(self):
sc = self.cassandra_driver.flavors_controller
self.assertEqual(
isinstance(sc, flavors.FlavorsController),
True)
@mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_service_database(self, mock_cluster):
self.cassandra_driver.service_database
def test_database(self, mock_cluster):
self.cassandra_driver.database
mock_cluster.assert_called_with('mock_poppy')

View File

@ -0,0 +1,108 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 uuid
import cassandra
import ddt
import mock
from oslo.config import cfg
from poppy.storage.cassandra import driver
from poppy.storage.cassandra import flavors
from poppy.transport.pecan.models.request import flavor
from tests.unit import base
@ddt.ddt
class CassandraStorageFlavorsTests(base.TestCase):
def setUp(self):
super(CassandraStorageFlavorsTests, self).setUp()
self.flavor_id = uuid.uuid1()
# create mocked config and driver
conf = cfg.ConfigOpts()
cassandra_driver = driver.CassandraStorageDriver(conf)
# stubbed cassandra driver
self.fc = flavors.FlavorsController(cassandra_driver)
@ddt.file_data('data_get_flavor.json')
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_get_flavor(self, value, mock_session, mock_execute):
# mock the response from cassandra
mock_execute.execute.return_value = value
actual_response = self.fc.get(value[0]['flavor_id'])
self.assertEqual(actual_response.flavor_id, value[0]['flavor_id'])
self.assertEqual(
len(actual_response.providers), len(value[0]['providers']))
@ddt.file_data('data_get_flavor_bad.json')
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_get_flavor_error(self, value, mock_session, mock_execute):
# mock the response from cassandra
mock_execute.execute.return_value = value
self.assertRaises(
LookupError, lambda: self.fc.get(value[0]['flavor_id']))
@ddt.file_data('../data/data_create_flavor.json')
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_add_flavor(self, value, mock_session, mock_execute):
# mock the response from cassandra
mock_execute.execute.return_value = value
new_flavor = flavor.load_from_json(value)
actual_response = self.fc.add(new_flavor)
self.assertEqual(actual_response, None)
@ddt.file_data('data_list_flavors.json')
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_list_flavors(self, value, mock_session, mock_execute):
# mock the response from cassandra
mock_execute.execute.return_value = value
actual_response = self.fc.list()
# confirm the correct number of results are returned
self.assertEqual(len(actual_response), len(value))
# confirm the flavor id is returned for each expectation
for i, r in enumerate(actual_response):
self.assertEqual(r.flavor_id, value[i]['flavor_id'])
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_delete_flavor(self, mock_session, mock_execute):
actual_response = self.fc.delete(self.flavor_id)
self.assertEqual(actual_response, None)
@mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_session(self, mock_flavor_database):
session = self.fc.session
self.assertNotEqual(session, None)

View File

@ -66,7 +66,7 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertRaises(ValueError, self.sc.get,
self.project_id, self.service_name)
@ddt.file_data('data_create_service.json')
@ddt.file_data('../data/data_create_service.json')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_create_service(self, value, mock_session, mock_execute):
@ -106,7 +106,7 @@ class CassandraStorageServiceTests(base.TestCase):
# into the driver to respond to this call
self.assertEqual(actual_response, None)
@ddt.file_data('data_update_service.json')
@ddt.file_data('../data/data_update_service.json')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_update_service(self, value, mock_session, mock_execute):

View File

@ -0,0 +1,43 @@
{
"one_provider": {
"id" : "asia",
"providers" : [
{
"provider" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "provider_url"
}
]
}
]
},
"many_providers": {
"id" : "asia",
"providers" : [
{
"provider" : "ChinaCache",
"links": [
{
"href": "http://www.chinacache.com",
"rel": "provider_url"
}
]
},
{
"provider" : "KiwiCache",
"links": [
{
"href": "http://www.cdn.co.nz",
"rel": "provider_url"
}
]
}
]
},
"no_providers": {
"id" : "asia",
"providers" : []
}
}

View File

@ -19,11 +19,24 @@ from poppy.storage.mockdb import driver
from tests.unit import base
class MockDBDriverTest(base.TestCase):
class MockDBStorageDriverTests(base.TestCase):
def setUp(self):
super(MockDBStorageDriverTests, self).setUp()
def test_mockdb_driver_working(self):
self.mockdb_driver = driver.MockDBStorageDriver(cfg.CONF)
def test_is_alive(self):
self.assertTrue(self.mockdb_driver.is_alive())
self.assertTrue(self.mockdb_driver.service_database is None)
def test_database(self):
self.assertTrue(self.mockdb_driver.database is None)
def test_connection(self):
self.assertTrue(self.mockdb_driver.connection is None)
self.assertTrue(self.mockdb_driver.service_controller.session is None)
def test_services_controller(self):
self.assertTrue(self.mockdb_driver.services_controller.session is None)
def test_flavors_controller(self):
self.assertTrue(self.mockdb_driver.flavors_controller.session is None)

View File

@ -0,0 +1,74 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 uuid
import ddt
import mock
from oslo.config import cfg
from poppy.storage.mockdb import driver
from poppy.storage.mockdb import flavors
from poppy.transport.pecan.models.request import flavor
from tests.unit import base
@ddt.ddt
class MockDBStorageFlavorsTests(base.TestCase):
def setUp(self):
super(MockDBStorageFlavorsTests, self).setUp()
self.flavor_id = uuid.uuid1()
# create mocked config and driver
conf = cfg.ConfigOpts()
mockdb_driver = driver.MockDBStorageDriver(conf)
# stubbed driver
self.fc = flavors.FlavorsController(mockdb_driver)
@mock.patch.object(flavors.FlavorsController, 'session')
def test_get_flavor(self, mock_session):
actual_response = self.fc.get(self.flavor_id)
self.assertEqual(actual_response, None)
@mock.patch.object(flavors.FlavorsController, 'session')
@ddt.file_data('../data/data_create_flavor.json')
def test_add_flavor(self, mock_session, value):
new_flavor = flavor.load_from_json(value)
actual_response = self.fc.add(new_flavor)
self.assertEqual(actual_response, None)
@mock.patch.object(flavors.FlavorsController, 'session')
def test_list_flavors(self, mock_session):
actual_response = self.fc.list()
# confirm the correct number of results are returned
self.assertEqual(actual_response, [])
@mock.patch.object(flavors.FlavorsController, 'session')
def test_delete_flavor(self, mock_session):
actual_response = self.fc.delete(self.flavor_id)
self.assertEqual(actual_response, None)
def test_session(self):
session = self.fc.session
self.assertEqual(session, None)