merge from upstream

This commit is contained in:
amitgandhinz 2014-07-25 16:44:26 -04:00
commit e74e34b7d5
57 changed files with 1123 additions and 448 deletions

View File

@ -25,6 +25,8 @@ LOG = log.getLogger(__name__)
_DRIVER_OPTIONS = [
cfg.StrOpt('transport', default='falcon',
help='Transport driver to use'),
cfg.StrOpt('manager', default='default',
help='Manager driver to use'),
cfg.StrOpt('storage', default='mockdb',
help='Storage driver to use'),
]
@ -72,7 +74,7 @@ class Bootstrap(object):
storage_type = 'cdn.storage'
storage_name = self.driver_conf.storage
args = [self.conf, self.provider]
args = [self.conf]
try:
mgr = driver.DriverManager(namespace=storage_type,
@ -83,6 +85,25 @@ class Bootstrap(object):
except RuntimeError as exc:
LOG.exception(exc)
@decorators.lazy_property(write=False)
def manager(self):
LOG.debug((u'Loading manager driver'))
# create the driver manager to load the appropriate drivers
manager_type = 'cdn.manager'
manager_name = self.driver_conf.manager
args = [self.conf, self.storage, self.provider]
try:
mgr = driver.DriverManager(namespace=manager_type,
name=manager_name,
invoke_on_load=True,
invoke_args=args)
return mgr.driver
except RuntimeError as exc:
LOG.exception(exc)
@decorators.lazy_property(write=False)
def transport(self):
LOG.debug("loading transport")
@ -91,7 +112,7 @@ class Bootstrap(object):
transport_type = 'cdn.transport'
transport_name = self.driver_conf.transport
args = [self.conf, self.storage]
args = [self.conf, self.manager]
LOG.debug((u'Loading transport driver: %s'), transport_name)

View File

@ -1,4 +1,4 @@
# Copyright (c) 2013 Red Hat, Inc.
# 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.
@ -15,16 +15,22 @@
import functools
import msgpack
def cached_getattr(meth):
"""Caches attributes returned by __getattr__
import cdn.openstack.common.log as logging
It can be used to cache results from
LOG = logging.getLogger(__name__)
def memoized_getattr(meth):
"""Memoizes attributes returned by __getattr__
It can be used to remember the results from
__getattr__ and reduce the debt of calling
it again when the same attribute is accessed.
This decorator caches attributes by setting
them in the object itself.
This decorator memoizes attributes by setting
them on the object itself.
The wrapper returned by this decorator won't alter
the returned value.
@ -40,6 +46,91 @@ def cached_getattr(meth):
return wrapper
def caches(keygen, ttl, cond=None):
"""Flags a getter method as being cached using oslo.cache.
It is assumed that the containing class defines an attribute
named `_cache` that is an instance of an oslo.cache backend.
The getter should raise an exception if the value can't be
loaded, which will skip the caching step. Otherwise, the
getter must return a value that can be encoded with
msgpack.
Note that you can also flag a remover method such that it
will purge an associated item from the cache, e.g.:
def project_cache_key(user, project=None):
return user + ':' + str(project)
class Project(object):
def __init__(self, db, cache):
self._db = db
self._cache = cache
@decorators.caches(project_cache_key, 60)
def get_project(self, user, project=None):
return self._db.get_project(user, project)
@get_project.purges
def del_project(self, user, project=None):
self._db.delete_project(user, project)
:param keygen: A static key generator function. This function
must accept the same arguments as the getter, sans `self`.
:param ttl: TTL for the cache entry, in seconds.
:param cond: Conditional for whether or not to cache the
value. Must be a function that takes a single value, and
returns True or False.
"""
def purges_prop(remover):
@functools.wraps(remover)
def wrapper(self, *args, **kwargs):
# First, purge from cache
key = keygen(*args, **kwargs)
del self._cache[key]
# Remove/delete from origin
remover(self, *args, **kwargs)
return wrapper
def prop(getter):
@functools.wraps(getter)
def wrapper(self, *args, **kwargs):
key = keygen(*args, **kwargs)
packed_value = self._cache.get(key)
if packed_value is None:
value = getter(self, *args, **kwargs)
# Cache new value if desired
if cond is None or cond(value):
# NOTE(kgriffs): Setting use_bin_type is essential
# for being able to distinguish between Unicode
# and binary strings when decoding; otherwise,
# both types are normalized to the MessagePack
# str format family.
packed_value = msgpack.packb(value, use_bin_type=True)
if not self._cache.set(key, packed_value, ttl):
LOG.warn('Failed to cache key: ' + key)
else:
# NOTE(kgriffs): unpackb does not default to UTF-8,
# so we have to explicitly ask for it.
value = msgpack.unpackb(packed_value, encoding='utf-8')
return value
wrapper.purges = purges_prop
return wrapper
return prop
def lazy_property(write=False, delete=True):
"""Creates a lazy property.

View File

@ -0,0 +1,22 @@
# 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 cdn.manager.base import driver
from cdn.manager.base import services
Driver = driver.ManagerDriverBase
ServicesController = services.ServicesControllerBase

View File

@ -0,0 +1,30 @@
# 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
@six.add_metaclass(abc.ABCMeta)
class ManagerControllerBase(object):
"""Top-level class for controllers.
:param driver: Instance of the driver
instantiating this controller.
"""
def __init__(self, driver):
self._driver = driver

View File

@ -0,0 +1,38 @@
# 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
@six.add_metaclass(abc.ABCMeta)
class ManagerDriverBase(object):
def __init__(self, conf, storage, providers):
self._conf = conf
self._storage = storage
self._providers = providers
@property
def storage(self):
return self._storage
@property
def providers(self):
return self._providers
@abc.abstractproperty
def services_controller(self):
"""Returns the driver's services controller."""
raise NotImplementedError

View File

@ -0,0 +1,25 @@
# 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 ProviderWrapper(object):
def create(self, ext, service_name, service_json):
return ext.obj.service_controller.create(service_name, service_json)
def update(self, ext, service_name, service_json):
return ext.obj.service_controller.update(service_name, service_json)
def delete(self, ext, service_name):
return ext.obj.service_controller.delete(service_name)

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 cdn.manager.base import controller
from cdn.manager.base import providers
@six.add_metaclass(abc.ABCMeta)
class ServicesControllerBase(controller.ManagerControllerBase):
def __init__(self, manager):
super(ServicesControllerBase, self).__init__(manager)
self.provider_wrapper = providers.ProviderWrapper()
@abc.abstractmethod
def list(self, project_id, marker=None, limit=None):
raise NotImplementedError
@abc.abstractmethod
def get(self, project_id, service_name):
raise NotImplementedError
@abc.abstractmethod
def create(self, project_id, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def update(self, project_id, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def delete(self, project_id, service_name):
raise NotImplementedError

View File

@ -0,0 +1,22 @@
# 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.
"""Default Manager Driver"""
from cdn.manager.default import driver
# Hoist classes into package namespace
Driver = driver.DefaultManagerDriver

View File

@ -0,0 +1,19 @@
# 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 cdn.manager.default import services
Services = services.DefaultServicesController

View File

@ -0,0 +1,30 @@
# 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.
"""Default manager driver implementation."""
from cdn.common import decorators
from cdn.manager import base
from cdn.manager.default import controllers
class DefaultManagerDriver(base.Driver):
def __init__(self, conf, storage, providers):
super(DefaultManagerDriver, self).__init__(conf, storage, providers)
@decorators.lazy_property(write=False)
def services_controller(self):
return controllers.Services(self)

View File

@ -0,0 +1,59 @@
# 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 cdn.manager import base
class DefaultServicesController(base.ServicesController):
def __init__(self, manager):
super(DefaultServicesController, self).__init__(manager)
self.storage = self._driver.storage.service_controller
def list(self, project_id, marker=None, limit=None):
return self.storage.list(project_id, marker, limit)
def get(self, project_id, service_name):
return self.storage.get(project_id, service_name)
def create(self, project_id, service_name, service_json):
self.storage.create(
project_id,
service_name,
service_json)
return self._driver.providers.map(
self.provider_wrapper.create,
service_name,
service_json)
def update(self, project_id, service_name, service_json):
self.storage.update(
project_id,
service_name,
service_json
)
return self._driver.providers.map(
self.provider_wrapper.update,
service_name,
service_json)
def delete(self, project_id, service_name):
self.storage.delete(project_id, service_name)
return self._driver.providers.map(
self.provider_wrapper.delete,
service_name)

View File

@ -0,0 +1,21 @@
# 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 cdn.provider.base import driver
from cdn.provider.base import services
Driver = driver.ProviderDriverBase
ServiceBase = services.ServicesControllerBase

View File

@ -0,0 +1,30 @@
# 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
@six.add_metaclass(abc.ABCMeta)
class ProviderControllerBase(object):
"""Top-level class for controllers.
:param driver: Instance of the driver
instantiating this controller.
"""
def __init__(self, driver):
self._driver = driver

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
@six.add_metaclass(abc.ABCMeta)
class ProviderDriverBase(object):
"""Interface definition for storage drivers.
Data plane storage drivers are responsible for implementing the
core functionality of the system.
Connection information and driver-specific options are
loaded from the config file.
:param conf: Configuration containing options for this driver.
:type conf: `oslo.config.ConfigOpts`
"""
def __init__(self, conf):
self._conf = conf
@abc.abstractmethod
def is_alive(self):
"""Check whether the storage is ready."""
raise NotImplementedError
@abc.abstractproperty
def provider_name(self):
raise NotImplementedError
@abc.abstractproperty
def service_controller(self):
"""Returns the driver's hostname controller."""
raise NotImplementedError

View File

@ -0,0 +1,57 @@
# 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 __future__ import print_function
import sys
import traceback
class Responder(object):
def __init__(self, provider_type):
self.provider = provider_type
def failed(self, msg):
ex_type, ex, tb = sys.exc_info()
print("error: {0} {1} {2} {3}".format(self.provider, msg, ex_type, ex))
traceback.print_tb(tb)
return {
self.provider: {
"error": msg
}
}
def created(self, domain):
return {
self.provider: {
"domain": domain
}
}
def updated(self, domain):
return {
self.provider: {
"domain": domain
}
}
def deleted(self, domain):
return {
self.provider: {
"domain": domain
}
}

View File

@ -0,0 +1,41 @@
# 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 cdn.provider.base import controller
from cdn.provider.base import responder
@six.add_metaclass(abc.ABCMeta)
class ServicesControllerBase(controller.ProviderControllerBase):
def __init__(self, driver):
super(ServicesControllerBase, self).__init__(driver)
self.responder = responder.Responder(driver.provider_name)
@abc.abstractmethod
def update(self, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def create(self, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def delete(self, service_name):
raise NotImplementedError

View File

@ -3,4 +3,4 @@
from cdn.provider.fastly import driver
# Hoist classes into package namespace
CDNProvider = driver.CDNProvider
Driver = driver.CDNProvider

View File

@ -15,7 +15,6 @@
"""Fastly CDN Provider implementation."""
from cdn.common import decorators
from cdn.openstack.common import log as logging
from cdn.provider import base
from cdn.provider.fastly import controllers
@ -38,15 +37,19 @@ class CDNProvider(base.Driver):
def __init__(self, conf):
super(CDNProvider, self).__init__(conf)
self.conf.register_opts(FASTLY_OPTIONS,
group=FASTLY_GROUP)
self.fastly_conf = self.conf[FASTLY_GROUP]
self._conf.register_opts(FASTLY_OPTIONS,
group=FASTLY_GROUP)
self.fastly_conf = self._conf[FASTLY_GROUP]
self.fastly_client = fastly.connect(self.fastly_conf.apikey)
def is_alive(self):
return True
@property
def provider_name(self):
return "Fastly"
@property
def client(self):
return self.fastly_client

View File

@ -31,7 +31,7 @@ class ServiceController(base.ServiceBase):
self.current_customer = self.client.get_current_customer()
def update(self, service_name, service_json):
print("update services")
return self.responder.updated(service_name)
def create(self, service_name, service_json):

View File

@ -3,4 +3,4 @@
from cdn.provider.mock import driver
# Hoist classes into package namespace
CDNProvider = driver.CDNProvider
Driver = driver.CDNProvider

View File

@ -15,7 +15,6 @@
"""CDN Provider implementation."""
from cdn.common import decorators
from cdn.openstack.common import log as logging
from cdn.provider import base
from cdn.provider.mock import controllers
@ -31,6 +30,10 @@ class CDNProvider(base.Driver):
def is_alive(self):
return True
@decorators.lazy_property(write=False)
@property
def provider_name(self):
return "Mock"
@property
def service_controller(self):
return controllers.ServiceController()
return controllers.ServiceController(self)

View File

@ -18,8 +18,8 @@ from cdn.provider import base
class ServiceController(base.ServiceBase):
def __init__(self):
super(ServiceController, self).__init__()
def __init__(self, driver):
super(ServiceController, self).__init__(driver)
def update(self, service_name, service_json):
return self.responder.updated(service_name)

View File

@ -1,7 +0,0 @@
"""CDN Storage Drivers"""
from cdn.storage import base
# Hoist classes into package namespace
StorageDriverBase = base.StorageDriverBase
ServicesBase = base.ServicesBase

View File

@ -1,149 +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 abc
import six
from oslo.config import cfg
_LIMITS_OPTIONS = [
cfg.IntOpt('default_services_paging', default=10,
help='Default services pagination size')
]
_LIMITS_GROUP = 'limits:storage'
@six.add_metaclass(abc.ABCMeta)
class DriverBase(object):
"""Base class for both data and control plane drivers
:param conf: Configuration containing options for this driver.
:type conf: `oslo.config.ConfigOpts`
"""
def __init__(self, conf, providers):
self.conf = conf
self.providers = providers
def providers(self):
return self.providers
@six.add_metaclass(abc.ABCMeta)
class StorageDriverBase(DriverBase):
"""Interface definition for storage drivers.
Data plane storage drivers are responsible for implementing the
core functionality of the system.
Connection information and driver-specific options are
loaded from the config file.
:param conf: Configuration containing options for this driver.
:type conf: `oslo.config.ConfigOpts`
"""
def __init__(self, conf, providers):
super(StorageDriverBase, self).__init__(conf, providers)
self.conf.register_opts(_LIMITS_OPTIONS, group=_LIMITS_GROUP)
self.limits_conf = self.conf[_LIMITS_GROUP]
@abc.abstractmethod
def is_alive(self):
"""Check whether the storage is ready."""
raise NotImplementedError
@abc.abstractproperty
def service_controller(self):
"""Returns the driver's hostname controller."""
raise NotImplementedError
class ControllerBase(object):
"""Top-level class for controllers.
:param driver: Instance of the driver
instantiating this controller.
"""
def __init__(self, driver):
self.driver = driver
@six.add_metaclass(abc.ABCMeta)
class ServicesBase(ControllerBase):
"""This class is responsible for managing Services
"""
__metaclass__ = abc.ABCMeta
def __init__(self, driver):
super(ServicesBase, self).__init__(driver)
self.wrapper = ProviderWrapper()
@abc.abstractmethod
def list(self, project_id):
raise NotImplementedError
@abc.abstractmethod
def create(self, project_id, service_name, service_json):
if (self.driver.providers is not None):
return self.driver.providers.map(
self.wrapper.create,
service_name,
service_json)
else:
return None
@abc.abstractmethod
def update(self, project_id, service_name, service_json):
if (self.driver.providers is not None):
return self.driver.providers.map(
self.wrapper.update,
service_name,
service_json)
else:
return None
@abc.abstractmethod
def delete(self, project_id, service_name):
if (self.driver.providers is not None):
return self.driver.providers.map(
self.wrapper.delete,
service_name)
else:
return None
@abc.abstractmethod
def get(self):
raise NotImplementedError
class ProviderWrapper(object):
def create(self, ext, service_name, service_json):
return ext.obj.service_controller.create(service_name, service_json)
def update(self, ext, service_name):
return ext.obj.service_controller.update(service_name)
def delete(self, ext, service_name):
return ext.obj.service_controller.delete(service_name)

View File

@ -0,0 +1,22 @@
# 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 cdn.storage.base import driver
from cdn.storage.base import services
Driver = driver.StorageDriverBase
ServicesController = services.ServicesControllerBase

View File

@ -0,0 +1,30 @@
# 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
@six.add_metaclass(abc.ABCMeta)
class StorageControllerBase(object):
"""Top-level class for controllers.
:param driver: Instance of the driver
instantiating this controller.
"""
def __init__(self, driver):
self._driver = driver

View File

@ -0,0 +1,57 @@
# 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 oslo.config import cfg
_LIMITS_OPTIONS = [
cfg.IntOpt('default_services_paging', default=10,
help='Default services pagination size')
]
_LIMITS_GROUP = 'limits:storage'
@six.add_metaclass(abc.ABCMeta)
class StorageDriverBase(object):
"""Interface definition for storage drivers.
Data plane storage drivers are responsible for implementing the
core functionality of the system.
Connection information and driver-specific options are
loaded from the config file.
:param conf: Configuration containing options for this driver.
:type conf: `oslo.config.ConfigOpts`
"""
def __init__(self, conf):
self._conf = conf
self._conf.register_opts(_LIMITS_OPTIONS, group=_LIMITS_GROUP)
self.limits_conf = self._conf[_LIMITS_GROUP]
@abc.abstractmethod
def is_alive(self):
"""Check whether the storage is ready."""
raise NotImplementedError
@abc.abstractproperty
def service_controller(self):
"""Returns the driver's hostname controller."""
raise NotImplementedError

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.
import abc
import six
from cdn.storage.base import controller
@six.add_metaclass(abc.ABCMeta)
class ServicesControllerBase(controller.StorageControllerBase):
def __init__(self, driver):
super(ServicesControllerBase, self).__init__(driver)
@abc.abstractmethod
def list(self, project_id, marker=None, limit=None):
raise NotImplementedError
@abc.abstractmethod
def create(self, project_id, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def update(self, project_id, service_name, service_json):
raise NotImplementedError
@abc.abstractmethod
def delete(self, project_id, service_name):
raise NotImplementedError
@abc.abstractmethod
def get(self):
raise NotImplementedError

View File

@ -3,4 +3,4 @@
from cdn.storage.cassandra import driver
# Hoist classes into package namespace
StorageDriver = driver.StorageDriver
Driver = driver.CassandraStorageDriver

View File

@ -19,7 +19,7 @@ from cassandra.cluster import Cluster
from cdn.common import decorators
from cdn.openstack.common import log as logging
from cdn import storage
from cdn.storage import base
from cdn.storage.cassandra import controllers
from oslo.config import cfg
@ -42,14 +42,14 @@ def _connection(conf):
return session
class StorageDriver(storage.StorageDriverBase):
class CassandraStorageDriver(base.Driver):
def __init__(self, conf, providers):
super(StorageDriver, self).__init__(conf, providers)
def __init__(self, conf):
super(CassandraStorageDriver, self).__init__(conf)
self.conf.register_opts(CASSANDRA_OPTIONS,
group=CASSANDRA_GROUP)
self.cassandra_conf = self.conf[CASSANDRA_GROUP]
self._conf.register_opts(CASSANDRA_OPTIONS,
group=CASSANDRA_GROUP)
self.cassandra_conf = self._conf[CASSANDRA_GROUP]
def is_alive(self):
return True

View File

@ -84,13 +84,13 @@ CQL_UPDATE_RESTRICTIONS = '''
'''
class ServicesController(base.ServicesBase):
class ServicesController(base.ServicesController):
@property
def session(self):
return self.driver.service_database
return self._driver.service_database
def list(self, project_id):
def list(self, project_id, marker=None, limit=None):
# get all services
args = {
@ -140,23 +140,13 @@ class ServicesController(base.ServicesBase):
self.session.execute(CQL_CREATE_SERVICE, args)
# create at providers
providers = super(ServicesController, self).create(
project_id, service_name, service)
return providers
def update(self, project_id, service_name, service_json):
# update configuration in storage
# determine what changed.
# update those columns provided only.
# update at providers
return super(ServicesController, self).update(project_id,
service_name,
service_json)
pass
def delete(self, project_id, service_name):
# delete local configuration from storage
@ -165,6 +155,3 @@ class ServicesController(base.ServicesBase):
'service_name': service_name
}
self.session.execute(CQL_DELETE_SERVICE, args)
# delete from providers
return super(ServicesController, self).delete(project_id, service_name)

View File

@ -3,4 +3,4 @@
from cdn.storage.mockdb import driver
# Hoist classes into package namespace
StorageDriver = driver.StorageDriver
Driver = driver.MockDBStorageDriver

View File

@ -17,7 +17,7 @@
from cdn.common import decorators
from cdn.openstack.common import log as logging
from cdn import storage
from cdn.storage import base
from cdn.storage.mockdb import controllers
from oslo.config import cfg
@ -36,14 +36,14 @@ def _connection():
return None
class StorageDriver(storage.StorageDriverBase):
class MockDBStorageDriver(base.Driver):
def __init__(self, conf, providers):
super(StorageDriver, self).__init__(conf, providers)
def __init__(self, conf):
super(MockDBStorageDriver, self).__init__(conf)
self.conf.register_opts(MOCKDB_OPTIONS,
group=MOCKDB_GROUP)
self.mockdb_conf = self.conf[MOCKDB_GROUP]
self._conf.register_opts(MOCKDB_OPTIONS,
group=MOCKDB_GROUP)
self.mockdb_conf = self._conf[MOCKDB_GROUP]
def is_alive(self):
return True

View File

@ -16,14 +16,13 @@
from cdn.storage import base
class ServicesController(base.ServicesBase):
class ServicesController(base.ServicesController):
def __init__(self, *args, **kwargs):
super(ServicesController, self).__init__(*args, **kwargs)
@property
def session(self):
return self._driver.service_database
self._session = self.driver.service_database
def list(self, project_id):
def list(self, project_id, marker=None, limit=None):
services = {
"links": [
{
@ -88,25 +87,17 @@ class ServicesController(base.ServicesBase):
def get(self, project_id):
# get the requested service from storage
print "get service"
return ""
def create(self, project_id, service_name, service_json):
# create at providers
providers = super(ServicesController, self).create(
project_id, service_name, service_json)
return providers
return ""
def update(self, project_id, service_name, service_json):
# update configuration in storage
# update at providers
return super(ServicesController, self).update(project_id,
service_name,
service_json)
return ""
def delete(self, project_id, service_name):
# delete from providers
return super(ServicesController, self).delete(project_id, service_name)
return ""

View File

@ -3,4 +3,4 @@
from cdn.storage.mongodb import driver
# Hoist classes into package namespace
StorageDriver = driver.StorageDriver
Driver = driver.MongoDBStorageDriver

View File

@ -20,7 +20,7 @@ import pymongo.errors
from cdn.common import decorators
from cdn.openstack.common import log as logging
from cdn import storage
from cdn.storage import base
from cdn.storage.mongodb import controllers
from oslo.config import cfg
@ -63,14 +63,14 @@ def _connection(conf):
return MongoClient(conf.uri)
class StorageDriver(storage.StorageDriverBase):
class MongoDBStorageDriver(base.Driver):
def __init__(self, conf, providers):
super(StorageDriver, self).__init__(conf, providers)
def __init__(self, conf):
super(MongoDBStorageDriver, self).__init__(conf)
self.conf.register_opts(MONGODB_OPTIONS,
group=MONGODB_GROUP)
self.mongodb_conf = self.conf[MONGODB_GROUP]
self._conf.register_opts(MONGODB_OPTIONS,
group=MONGODB_GROUP)
self.mongodb_conf = self._conf[MONGODB_GROUP]
def is_alive(self):
try:

View File

@ -17,95 +17,28 @@
from cdn.storage import base
class ServicesController(base.ServicesBase):
def list(self, project_id):
services = {
"links": [
{
"rel": "next",
"href": "/v1.0/services?marker=www.test.com&limit=20"
}
],
"services": [
{
"domains": [
{
"domain": "www.mywebsite.com"
}
],
"origins": [
{
"origin": "mywebsite.com",
"port": 80,
"ssl": False
}
],
"caching": [
{"name": "default", "ttl": 3600},
{
"name": "home",
"ttl": 17200,
"rules": [
{"name": "index",
"request_url": "/index.htm"}
]
},
{
"name": "images",
"ttl": 12800,
"rules": [
{"name": "images", "request_url": "*.png"}
]
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mywebsite.com",
"http_host": "www.mywebsite.com"
}
]
}
],
"links": [
{
"href": "/v1.0/services/mywebsite",
"rel": "self"
}
]
}
]
}
class ServicesController(base.ServicesController):
@property
def session(self):
return self._driver.service_database
def list(self, project_id, marker=None, limit=None):
services = {}
return services
def get(self, project_id):
def get(self, project_id, service_name):
# get the requested service from storage
print "get service"
pass
def create(self, project_id, service_name, service_json):
# create the service in storage
service = service_json
# create at providers
return super(ServicesController, self).create(project_id,
service_name,
service)
pass
def update(self, project_id, service_name, service_json):
# update configuration in storage
# update at providers
return super(ServicesController, self).update(project_id,
service_name,
service_json)
pass
def delete(self, project_id, service_name):
# delete local configuration from storage
# delete from providers
return super(ServicesController, self).delete(project_id, service_name)
pass

View File

@ -3,5 +3,6 @@
from cdn.transport import base
# Hoist into package namespace
DriverBase = base.DriverBase
Driver = base.TransportDriverBase

View File

@ -18,16 +18,26 @@ import six
@six.add_metaclass(abc.ABCMeta)
class DriverBase(object):
class TransportDriverBase(object):
"""Base class for Transport Drivers to document the expected interface.
:param conf: configuration instance
:type conf: oslo.config.cfg.CONF
"""
def __init__(self, conf, storage):
def __init__(self, conf, manager):
self._conf = conf
self._storage = storage
self._manager = manager
self._app = None
@property
def app(self):
return self._app
@property
def manager(self):
return self._manager
@abc.abstractmethod
def listen():

View File

@ -3,4 +3,4 @@
from cdn.transport.falcon import driver
# Hoist into package namespace
TransportDriver = driver.TransportDriver
Driver = driver.TransportDriver

View File

@ -42,37 +42,36 @@ LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class TransportDriver(transport.DriverBase):
class TransportDriver(transport.Driver):
def __init__(self, conf, storage):
super(TransportDriver, self).__init__(conf, storage)
def __init__(self, conf, manager):
super(TransportDriver, self).__init__(conf, manager)
self._conf.register_opts(_WSGI_OPTIONS, group=_WSGI_GROUP)
self._wsgi_conf = self._conf[_WSGI_GROUP]
self.app = None
self._init_routes()
self._setup_app()
def _init_routes(self):
def _setup_app(self):
"""Initialize hooks and URI routes to resources."""
self.app = falcon.API()
self._app = falcon.API()
version_path = "/v1.0"
project_id = "/{project_id}"
prefix = version_path + project_id
# init the controllers
service_controller = self._storage.service_controller
service_controller = self.manager.services_controller
# setup the routes
self.app.add_route(prefix,
v1.V1Resource())
self._app.add_route(prefix,
v1.V1Resource())
self.app.add_route(prefix + '/services',
services.ServicesResource(service_controller))
self._app.add_route(prefix + '/services',
services.ServicesResource(service_controller))
self.app.add_route(prefix + '/services/{service_name}',
services.ServiceResource(service_controller))
self._app.add_route(prefix + '/services/{service_name}',
services.ServiceResource(service_controller))
def listen(self):
"""Self-host using 'bind' and 'port' from the WSGI config group."""

View File

@ -29,6 +29,9 @@ log_file = cdn.log
# Transport driver module (e.g., falcon, pecan)
transport = falcon
# Manager driver module (e.g. default)
manager = default
# Storage driver module (e.g., mongodb, sqlite, cassandra)
storage = mockdb

View File

@ -1,2 +1,2 @@
-e git+https://github.com/zebrafishlabs/fastly-python@b98a756b2a03687d76c3cfa0e024a445af85b38d#egg=fastly-python
httplib2>=0.8
cdn-fastly==1.0.4
httplib2>=0.8

View File

@ -1,4 +1,5 @@
-r common.txt
-r storage/cassandra.txt
-r transport/falcon.txt
-r transport/pecan.txt
-r provider/fastly.txt

View File

@ -0,0 +1 @@
pecan==0.6.1

View File

@ -26,17 +26,23 @@ packages =
console_scripts =
cdn-server = cdn.cmd.server:run
[wheel]
universal = 1
cdn.transport =
falcon = cdn.transport.falcon.driver:TransportDriver
falcon = cdn.transport.falcon:Driver
cdn.manager =
default = cdn.manager.default:Driver
cdn.storage =
mongodb = cdn.storage.mongodb.driver:StorageDriver
cassandra = cdn.storage.cassandra.driver:StorageDriver
mockdb = cdn.storage.mockdb.driver:StorageDriver
mongodb = cdn.storage.mongodb:Driver
cassandra = cdn.storage.cassandra:Driver
mockdb = cdn.storage.mockdb:Driver
cdn.provider =
fastly = cdn.provider.fastly.driver:CDNProvider
mock = cdn.provider.mock.driver:CDNProvider
fastly = cdn.provider.fastly:Driver
mock = cdn.provider.mock:Driver
[nosetests]
where=tests
@ -57,4 +63,3 @@ cover-inclusive = true
; method in nose/inspector.py requires a traceback-like object.
;
; detailed-errors = 1

View File

@ -15,8 +15,26 @@
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import os
import setuptools
from pip.req import parse_requirements
requirement_files = []
# walk the requirements directory and gather requirement files
for root, dirs, files in os.walk('requirements'):
for requirements_file in files:
requirements_file_path = os.path.join(root, requirements_file)
# parse_requirements() returns generator of pip.req.InstallRequirement objects
requirement_files.append(parse_requirements(requirements_file_path))
# parse all requirement files and generate requirements
requirements = set()
for requirement_file in requirement_files:
requirements.update([str(requirement.req) for requirement in requirement_file])
# convert requirements in to list
requirements = list(requirements)
setuptools.setup(
install_requires = requirements,
setup_requires=['pbr'],
pbr=True)

View File

@ -1,16 +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.
# Server Specific Configurations

View File

View File

View File

@ -0,0 +1,37 @@
# 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 mock
import unittest
from oslo.config import cfg
from cdn.manager.default import driver
from cdn.manager.default import services
class DefaultManagerDriverTests(unittest.TestCase):
@mock.patch('cdn.storage.base.driver.StorageDriverBase')
@mock.patch('cdn.provider.base.driver.ProviderDriverBase')
def setUp(self, mock_storage, mock_provider):
conf = cfg.ConfigOpts()
self.driver = driver.DefaultManagerDriver(conf,
mock_storage,
mock_provider)
def test_services_controller(self):
sc = self.driver.services_controller
self.assertIsInstance(sc, services.DefaultServicesController)

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 mock
import unittest
from oslo.config import cfg
from cdn.manager.default import driver
from cdn.manager.default import services
class DefaultManagerServiceTests(unittest.TestCase):
@mock.patch('cdn.storage.base.driver.StorageDriverBase')
@mock.patch('cdn.provider.base.driver.ProviderDriverBase')
def setUp(self, mock_driver, mock_provider):
# create mocked config and driver
conf = cfg.ConfigOpts()
manager_driver = driver.DefaultManagerDriver(conf,
mock_driver,
mock_provider)
# stubbed driver
self.sc = services.DefaultServicesController(manager_driver)
def test_create(self):
project_id = 'mock_id'
service_name = 'mock_service'
service_json = ''
self.sc.create(project_id, service_name, service_json)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage.create.assert_called_once_with(project_id,
service_name,
service_json)
# and that the providers are notified.
providers = self.sc._driver.providers
providers.map.assert_called_once_with(self.sc.provider_wrapper.create,
service_name,
service_json)
def test_update(self):
project_id = 'mock_id'
service_name = 'mock_service'
service_json = ''
self.sc.update(project_id, service_name, service_json)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage.update.assert_called_once_with(project_id,
service_name,
service_json)
# and that the providers are notified.
providers = self.sc._driver.providers
providers.map.assert_called_once_with(self.sc.provider_wrapper.update,
service_name,
service_json)
def test_delete(self):
project_id = 'mock_id'
service_name = 'mock_service'
self.sc.delete(project_id, service_name)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage.delete.assert_called_once_with(project_id,
service_name)
# and that the providers are notified.
providers = self.sc._driver.providers
providers.map.assert_called_once_with(self.sc.provider_wrapper.delete,
service_name)

View File

@ -37,7 +37,7 @@ class TestDriver(unittest.TestCase):
def test_init(self, mock_connect):
provider = driver.CDNProvider(self.conf)
mock_connect.assert_called_once_with(
provider.conf['drivers:provider:fastly'].apikey)
provider._conf['drivers:provider:fastly'].apikey)
@mock.patch.object(driver, 'FASTLY_OPTIONS', new=FASTLY_OPTIONS)
def test_is_alive(self):

View File

@ -30,99 +30,108 @@ class TestServices(unittest.TestCase):
@mock.patch('fastly.FastlyService')
@mock.patch('fastly.FastlyVersion')
@mock.patch('cdn.provider.fastly.services.ServiceController.client')
def test_create(self, service_json, MockConnection,
MockService, MockVersion, mock_controllerclient):
service_name = 'scarborough'
mock_service_id = '%020x' % random.randrange(16**20)
mockCreateVersionResp = {
u'service_id': mock_service_id, u'number': 5}
service = MockService()
service.id = mock_service_id
version = MockVersion(MockConnection, mockCreateVersionResp)
MockConnection.create_service.return_value = service
MockConnection.create_version.return_value = version
@mock.patch('cdn.provider.fastly.driver.CDNProvider')
def test_create(self, service_json, mock_connection,
mock_service, mock_version, mock_controllerclient,
mock_driver):
driver = mock_driver()
driver.provider_name = 'Fastly'
# instantiate
controller = services.ServiceController(None)
controller = services.ServiceController(driver)
service_name = 'scarborough'
mock_service_id = '%020x' % random.randrange(16 ** 20)
mock_create_version_resp = {
u'service_id': mock_service_id, u'number': 5}
service = mock_service()
service.id = mock_service_id
version = mock_version(controller.client, mock_create_version_resp)
controller.client.create_service.return_value = service
controller.client.create_version.return_value = version
# ASSERTIONS
# create_service
MockConnection.create_service.side_effect = fastly.FastlyError(
controller.client.create_service.side_effect = fastly.FastlyError(
Exception('Creating service failed.'))
resp = controller.create(service_name, service_json)
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
MockConnection.reset_mock()
MockConnection.create_service.side_effect = None
controller.client.reset_mock()
controller.client.create_service.side_effect = None
# create_version
MockConnection.create_version.side_effect = fastly.FastlyError(
controller.client.create_version.side_effect = fastly.FastlyError(
Exception('Creating version failed.'))
resp = controller.create(service_name, service_json)
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
MockConnection.reset_mock()
MockConnection.create_version.side_effect = None
controller.client.reset_mock()
controller.client.create_version.side_effect = None
# create domains
MockConnection.create_domain.side_effect = fastly.FastlyError(
controller.client.create_domain.side_effect = fastly.FastlyError(
Exception('Creating domains failed.'))
resp = controller.create(service_name, service_json)
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
MockConnection.reset_mock()
MockConnection.create_domain.side_effect = None
controller.client.reset_mock()
controller.client.create_domain.side_effect = None
# create backends
MockConnection.create_backend.side_effect = fastly.FastlyError(
controller.client.create_backend.side_effect = fastly.FastlyError(
Exception('Creating backend failed.'))
resp = controller.create(service_name, service_json)
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
MockConnection.reset_mock()
MockConnection.create_backend.side_effect = None
controller.client.reset_mock()
controller.client.create_backend.side_effect = None
# test a general exception
MockConnection.create_service.side_effect = Exception(
controller.client.create_service.side_effect = Exception(
'Wild exception occurred.')
resp = controller.create(service_name, service_json)
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
# finally, a clear run
MockConnection.reset_mock()
MockConnection.create_service.side_effect = None
controller.client.reset_mock()
controller.client.create_service.side_effect = None
resp = controller.create(service_name, service_json)
MockConnection.create_service.assert_called_once_with(
controller.client.create_service.assert_called_once_with(
controller.current_customer.id, service_name)
MockConnection.create_version.assert_called_once_with(service.id)
controller.client.create_version.assert_called_once_with(service.id)
MockConnection.create_domain.assert_any_call(
controller.client.create_domain.assert_any_call(
service.id, version.number, service_json['domains'][0]['domain'])
MockConnection.create_domain.assert_any_call(
controller.client.create_domain.assert_any_call(
service.id, version.number, service_json['domains'][1]['domain'])
MockConnection.create_backend.assert_has_any_call(
controller.client.create_backend.assert_has_any_call(
service.id, 1,
service_json['origins'][0]['origin'],
service_json['origins'][0]['origin'],
service_json['origins'][0]['ssl'],
service_json['origins'][0]['port'])
self.assertIn('domain', resp['fastly'])
self.assertIn('domain', resp[driver.provider_name])
@mock.patch('fastly.FastlyConnection')
@mock.patch('fastly.FastlyService')
@mock.patch('cdn.provider.fastly.services.ServiceController.client')
def test_delete(self, mock_connection, mock_service, mock_get_client):
@mock.patch('cdn.provider.fastly.driver.CDNProvider')
def test_delete(self, mock_connection, mock_service, mock_client,
mock_driver):
driver = mock_driver()
driver.provider_name = 'Fastly'
service_name = 'whatsitnamed'
# instantiate
controller = services.ServiceController(None)
controller = services.ServiceController(driver)
# mock.patch return values
service = mock_service()
@ -131,28 +140,34 @@ class TestServices(unittest.TestCase):
# test exception
exception = fastly.FastlyError(Exception('ding'))
mock_connection.delete_service.side_effect = exception
controller.client.delete_service.side_effect = exception
resp = controller.delete('wrongname')
self.assertIn('error', resp['fastly'])
self.assertIn('error', resp[driver.provider_name])
# clear run
mock_connection.reset_mock()
mock_connection.delete_service.side_effect = None
controller.client.reset_mock()
controller.client.delete_service.side_effect = None
resp = controller.delete(service_name)
mock_connection.get_service_by_name.assert_called_once_with(
controller.client.get_service_by_name.assert_called_once_with(
service_name)
mock_connection.delete_service.assert_called_once_with(service.id)
self.assertIn('domain', resp['fastly'])
controller.client.delete_service.assert_called_once_with(service.id)
self.assertIn('domain', resp[driver.provider_name])
@mock.patch('cdn.provider.fastly.services.ServiceController.client')
def test_update(self, mock_get_client):
controller = services.ServiceController(None)
resp = controller.update(None, None)
self.assertEqual(resp, None)
@mock.patch('cdn.provider.fastly.driver.CDNProvider')
@ddt.file_data('data_service.json')
def test_update(self, mock_get_client, mock_driver, service_json):
service_name = 'whatsitnamed'
driver = mock_driver()
controller = services.ServiceController(driver)
resp = controller.update(service_name, service_json)
self.assertIn('domain', resp[driver.provider_name])
@mock.patch('cdn.provider.fastly.driver.CDNProvider')
def test_client(self, MockDriver):
driver = MockDriver()
def test_client(self, mock_driver):
driver = mock_driver()
controller = services.ServiceController(driver)
self.assertNotEquals(controller.client(), None)

View File

@ -14,12 +14,14 @@
# limitations under the License.
import cassandra
import mock
import unittest
from oslo.config import cfg
from cdn.storage.cassandra import driver
from cdn.storage.cassandra import services
from mock import patch
from oslo.config import cfg
from unittest import TestCase
CASSANDRA_OPTIONS = [
cfg.ListOpt('cluster', default='mock_ip',
@ -29,11 +31,11 @@ CASSANDRA_OPTIONS = [
]
class CassandraStorageServiceTests(TestCase):
@patch.object(driver, 'CASSANDRA_OPTIONS', new=CASSANDRA_OPTIONS)
class CassandraStorageServiceTests(unittest.TestCase):
@mock.patch.object(driver, 'CASSANDRA_OPTIONS', new=CASSANDRA_OPTIONS)
def setUp(self):
conf = cfg.ConfigOpts()
self.cassandra_driver = driver.StorageDriver(conf, None)
self.cassandra_driver = driver.CassandraStorageDriver(conf)
def test_storage_driver(self):
# assert that the configs are set up based on what was passed in
@ -45,7 +47,7 @@ class CassandraStorageServiceTests(TestCase):
def test_is_alive(self):
self.assertEquals(self.cassandra_driver.is_alive(), True)
@patch.object(cassandra.cluster.Cluster, 'connect')
@mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_connection(self, mock_cluster):
self.cassandra_driver.connection()
mock_cluster.assert_called_with('mock_cdn')
@ -57,11 +59,7 @@ class CassandraStorageServiceTests(TestCase):
isinstance(sc, services.ServicesController),
True)
@patch.object(cassandra.cluster.Cluster, 'connect')
@mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_service_database(self, mock_cluster):
self.cassandra_driver.service_database
mock_cluster.assert_called_with('mock_cdn')
def test_providers(self):
providers = self.cassandra_driver.providers
self.assertEquals(providers, None)

View File

@ -14,19 +14,18 @@
# limitations under the License.
import cassandra
import ddt
import mock
import unittest
from oslo.config import cfg
from cdn.storage.cassandra import driver
from cdn.storage.cassandra import services
from ddt import ddt, file_data
from mock import patch
from oslo.config import cfg
from unittest import TestCase
@ddt
class CassandraStorageServiceTests(TestCase):
@ddt.ddt
class CassandraStorageServiceTests(unittest.TestCase):
def setUp(self):
# mock arguments to use
self.project_id = '123456'
@ -34,14 +33,14 @@ class CassandraStorageServiceTests(TestCase):
# create mocked config and driver
conf = cfg.ConfigOpts()
cassandra_driver = driver.StorageDriver(conf, None)
cassandra_driver = driver.CassandraStorageDriver(conf)
# stubbed cassandra driver
self.sc = services.ServicesController(cassandra_driver)
@file_data('data_get_service.json')
@patch.object(services.ServicesController, 'session')
@patch.object(cassandra.cluster.Session, 'execute')
@ddt.file_data('data_get_service.json')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_get_service(self, value, mock_session, mock_execute):
# mock the response from cassandra
@ -54,9 +53,9 @@ class CassandraStorageServiceTests(TestCase):
self.assertEqual(actual_response[0][0], self.project_id)
self.assertEqual(actual_response[0][1], self.service_name)
@file_data('data_create_service.json')
@patch.object(services.ServicesController, 'session')
@patch.object(cassandra.cluster.Session, 'execute')
@ddt.file_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):
responses = self.sc.create(self.project_id, self.service_name, value)
@ -66,9 +65,9 @@ class CassandraStorageServiceTests(TestCase):
# TODO(amitgandhinz): need to validate the create to cassandra worked.
@file_data('data_list_services.json')
@patch.object(services.ServicesController, 'session')
@patch.object(cassandra.cluster.Session, 'execute')
@ddt.file_data('data_list_services.json')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_list_services(self, value, mock_session, mock_execute):
# mock the response from cassandra
mock_execute.execute.return_value = value
@ -81,8 +80,8 @@ class CassandraStorageServiceTests(TestCase):
self.assertEqual(actual_response[0][0], self.project_id)
self.assertEqual(actual_response[0][1], "mocksite")
@patch.object(services.ServicesController, 'session')
@patch.object(cassandra.cluster.Session, 'execute')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_delete_service(self, mock_session, mock_execute):
# mock the response from cassandra
actual_response = self.sc.delete(self.project_id, self.service_name)
@ -91,9 +90,9 @@ class CassandraStorageServiceTests(TestCase):
# into the driver to respond to this call
self.assertEqual(actual_response, None)
@file_data('data_update_service.json')
@patch.object(services.ServicesController, 'session')
@patch.object(cassandra.cluster.Session, 'execute')
@ddt.file_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):
# mock the response from cassandra
actual_response = self.sc.update(self.project_id,
@ -104,7 +103,7 @@ class CassandraStorageServiceTests(TestCase):
# into the driver to respond to this call
self.assertEqual(actual_response, None)
@patch.object(cassandra.cluster.Cluster, 'connect')
@mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_session(self, mock_service_database):
session = self.sc.session
self.assertNotEquals(session, None)