Adding flavorRef to Service
Change-Id: I2389b6222737dec7fb6c3540c0a42c445f4c567f
This commit is contained in:
parent
d884f1ed44
commit
3bed3f2ec2
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,6 +28,7 @@ pip-log.txt
|
||||
.tox
|
||||
nosetests.xml
|
||||
tests/cover
|
||||
tests/logs
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -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
136
poppy/common/uri.py
Normal 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
45
poppy/common/util.py
Normal 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__)
|
@ -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
|
||||
|
@ -29,3 +29,7 @@ class ManagerControllerBase(object):
|
||||
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
return self._driver
|
||||
|
@ -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
|
||||
|
49
poppy/manager/base/flavors.py
Normal file
49
poppy/manager/base/flavors.py
Normal 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
|
@ -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):
|
@ -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
|
||||
|
@ -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)
|
||||
|
33
poppy/manager/default/flavors.py
Normal file
33
poppy/manager/default/flavors.py
Normal 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)
|
@ -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
|
||||
|
@ -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
48
poppy/model/flavor.py
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
43
poppy/storage/base/flavors.py
Normal file
43
poppy/storage/base/flavors.py
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
107
poppy/storage/cassandra/flavors.py
Normal file
107
poppy/storage/cassandra/flavors.py
Normal 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)
|
@ -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)
|
||||
);
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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 = [
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
34
poppy/storage/mongodb/flavors.py
Normal file
34
poppy/storage/mongodb/flavors.py
Normal 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 ""
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
28
poppy/transport/pecan/controllers/v1/__init__.py
Normal file
28
poppy/transport/pecan/controllers/v1/__init__.py
Normal 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
|
84
poppy/transport/pecan/controllers/v1/flavors.py
Normal file
84
poppy/transport/pecan/controllers/v1/flavors.py
Normal 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
|
@ -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()
|
@ -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',
|
||||
|
35
poppy/transport/pecan/models/request/flavor.py
Normal file
35
poppy/transport/pecan/models/request/flavor.py
Normal 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
|
46
poppy/transport/pecan/models/response/flavor.py
Normal file
46
poppy/transport/pecan/models/response/flavor.py
Normal 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'))
|
@ -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
|
||||
|
@ -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"})
|
||||
|
69
poppy/transport/validators/schemas/flavor.py
Normal file
69
poppy/transport/validators/schemas/flavor.py
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" : []
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
93
tests/functional/transport/pecan/controllers/test_flavors.py
Normal file
93
tests/functional/transport/pecan/controllers/test_flavors.py
Normal 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)
|
@ -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)
|
@ -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)
|
||||
|
44
tests/unit/common/test_uri.py
Normal file
44
tests/unit/common/test_uri.py
Normal 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')
|
@ -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)
|
||||
|
77
tests/unit/manager/default/test_flavors.py
Normal file
77
tests/unit/manager/default/test_flavors.py
Normal 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)
|
@ -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)
|
60
tests/unit/model/test_flavors.py
Normal file
60
tests/unit/model/test_flavors.py
Normal 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)
|
@ -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 = []
|
||||
|
22
tests/unit/storage/cassandra/data_get_flavor.json
Normal file
22
tests/unit/storage/cassandra/data_get_flavor.json
Normal 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" : {}
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
|
20
tests/unit/storage/cassandra/data_get_flavor_bad.json
Normal file
20
tests/unit/storage/cassandra/data_get_flavor_bad.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
28
tests/unit/storage/cassandra/data_list_flavors.json
Normal file
28
tests/unit/storage/cassandra/data_list_flavors.json
Normal 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": [
|
||||
|
||||
]
|
||||
}
|
@ -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')
|
||||
|
108
tests/unit/storage/cassandra/test_flavors.py
Normal file
108
tests/unit/storage/cassandra/test_flavors.py
Normal 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)
|
@ -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):
|
||||
|
43
tests/unit/storage/data/data_create_flavor.json
Normal file
43
tests/unit/storage/data/data_create_flavor.json
Normal 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" : []
|
||||
}
|
||||
}
|
@ -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)
|
74
tests/unit/storage/mockdb/test_flavors.py
Normal file
74
tests/unit/storage/mockdb/test_flavors.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user