Create Service

Change-Id: Id5c8da6b3b3bfc8aad6495b441e294a64598c260
This commit is contained in:
tonytan4ever 2014-09-05 15:07:23 -04:00
parent 3bed3f2ec2
commit 4735284a41
63 changed files with 1188 additions and 409 deletions

View File

@ -3,5 +3,6 @@ omit = *poppy/openstack*
[report] [report]
exclude_lines = exclude_lines =
pragma: no cover
# Don't complain if tests don't hit defensive assertion code # Don't complain if tests don't hit defensive assertion code
raise NotImplementedError raise NotImplementedError

View File

@ -52,4 +52,13 @@ keyspace = poppy
database = poppy database = poppy
[drivers:provider:fastly] [drivers:provider:fastly]
apikey = "MYAPIKEY" apikey = "MYAPIKEY"
[drivers:provider:maxcdn]
alias = "MYALIAS"
consumer_secret = "MYCONSUMER_SECRET"
consumer_key = "MYCONSUMERKEY"
[drivers:provider:cloudfront]
aws_access_key_id = "MY_AWS_ACCESS_KEY_ID"
aws_secret_access_key = "MY_AWS_SECRET_ACCESS_KEY"

View File

@ -18,8 +18,8 @@ from poppy.common import errors
class ProviderWrapper(object): class ProviderWrapper(object):
def create(self, ext, service_name, service_json): def create(self, ext, service_obj):
return ext.obj.service_controller.create(service_name, service_json) return ext.obj.service_controller.create(service_obj)
def update(self, ext, provider_details, service_json): def update(self, ext, provider_details, service_json):
try: try:
@ -28,7 +28,7 @@ class ProviderWrapper(object):
raise errors.BadProviderDetail( raise errors.BadProviderDetail(
"No provider detail information." "No provider detail information."
"Perhaps service has not been created") "Perhaps service has not been created")
provider_service_id = provider_detail.id provider_service_id = provider_detail.provider_service_id
return ext.obj.service_controller.update( return ext.obj.service_controller.update(
provider_service_id, provider_service_id,
service_json) service_json)
@ -40,5 +40,5 @@ class ProviderWrapper(object):
raise errors.BadProviderDetail( raise errors.BadProviderDetail(
"No provider detail information." "No provider detail information."
"Perhaps service has not been created") "Perhaps service has not been created")
provider_service_id = provider_detail.id provider_service_id = provider_detail.provider_service_id
return ext.obj.service_controller.delete(provider_service_id) return ext.obj.service_controller.delete(provider_service_id)

View File

@ -38,11 +38,11 @@ class ServicesControllerBase(controller.ManagerControllerBase):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
def create(self, project_id, service_name, service_json): def create(self, project_id, service_obj):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
def update(self, project_id, service_name, service_json): def update(self, project_id, service_name, service_obj):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod

View File

@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
from poppy.manager import base from poppy.manager import base
from poppy.model.helpers import provider_details
class DefaultServicesController(base.ServicesController): class DefaultServicesController(base.ServicesController):
@ -21,45 +22,91 @@ class DefaultServicesController(base.ServicesController):
def __init__(self, manager): def __init__(self, manager):
super(DefaultServicesController, self).__init__(manager) super(DefaultServicesController, self).__init__(manager)
self.storage = self._driver.storage.services_controller self.storage_controller = self._driver.storage.services_controller
self.flavor_controller = self._driver.storage.flavors_controller
def list(self, project_id, marker=None, limit=None): def list(self, project_id, marker=None, limit=None):
return self.storage.list(project_id, marker, limit) return self.storage_controller.list(project_id, marker, limit)
def get(self, project_id, service_name): def get(self, project_id, service_name):
return self.storage.get(project_id, service_name) return self.storage_controller.get(project_id, service_name)
def create(self, project_id, service_name, service_obj): def create(self, project_id, service_obj):
self.storage.create( try:
project_id, flavor = self.flavor_controller.get(service_obj.flavorRef)
service_name, # raise a lookup error if the flavor is not found
service_obj) except LookupError as e:
raise e
# TODO(tonytan4ever): need to update provider_detail info in storage providers = [p.provider_id for p in flavor.providers]
return self._driver.providers.map( service_name = service_obj.name
self.provider_wrapper.create,
service_name, try:
service_obj) self.storage_controller.create(
project_id,
service_obj)
# ValueError will be raised if the service has already existed
except ValueError as e:
raise e
responders = []
for provider in providers:
responder = self.provider_wrapper.create(
self._driver.providers[provider],
service_obj)
responders.append(responder)
provider_details_dict = {}
for responder in responders:
for provider_name in responder:
if 'error' not in responder[provider_name]:
provider_details_dict[provider_name] = (
provider_details.ProviderDetail(
provider_service_id=responder[provider_name]['id'],
access_urls=[link['href'] for link in
responder[provider_name]['links']])
)
if 'status' in responder[provider_name]:
provider_details_dict[provider_name].status = (
responder[provider_name]['status'])
else:
provider_details_dict[provider_name].status = (
'deployed')
else:
provider_details_dict[provider_name] = (
provider_details.ProviderDetail(
error_info=responder[provider_name]['error_detail']
)
)
provider_details_dict[provider_name].status = 'failed'
self.storage_controller.update_provider_details(project_id,
service_name,
provider_details_dict)
return responders
def update(self, project_id, service_name, service_obj): def update(self, project_id, service_name, service_obj):
self.storage.update( self.storage_controller.update(
project_id, project_id,
service_name, service_name,
service_obj service_obj
) )
provider_details = self.storage.get_provider_details(project_id, provider_details = self.storage_controller.get_provider_details(
service_name) project_id,
service_name)
return self._driver.providers.map( return self._driver.providers.map(
self.provider_wrapper.update, self.provider_wrapper.update,
provider_details, provider_details,
service_obj) service_obj)
def delete(self, project_id, service_name): def delete(self, project_id, service_name):
self.storage.delete(project_id, service_name) self.storage_controller.delete(project_id, service_name)
provider_details = self.storage.get_provider_details(project_id, provider_details = self.storage_controller.get_provider_details(
service_name) project_id,
service_name)
return self._driver.providers.map( return self._driver.providers.map(
self.provider_wrapper.delete, self.provider_wrapper.delete,
provider_details) provider_details)

View File

@ -16,8 +16,8 @@
import inspect import inspect
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
class DictSerializableModel(object): class DictSerializableModel(object):
@ -48,7 +48,7 @@ class DictSerializableModel(object):
return self return self
@classmethod @classmethod
def init_from_dict(cls, dict): def init_from_dict(cls, input_dict):
"""Construct a model instance from a dictionary. """Construct a model instance from a dictionary.
This is only meant to be used for converting a This is only meant to be used for converting a

View File

@ -21,27 +21,33 @@ class ProviderDetail(object):
'''ProviderDetail object for each provider.''' '''ProviderDetail object for each provider.'''
def __init__(self, id=None, access_url=None, status=u"unknown", name=None): def __init__(self, provider_service_id=None, access_urls=[],
self._id = id status=u"unknown", name=None, error_info=None):
self._access_url = access_url self._provider_service_id = provider_service_id
self._access_urls = access_urls
self._status = status self._status = status
self._name = name self._name = name
self._error_info = error_info
@property @property
def id(self): def provider_service_id(self):
return self._id return self._provider_service_id
@id.setter @provider_service_id.setter
def id(self, value): def provider_service_id(self, value):
self._id = value self._provider_service_id = value
@property @property
def access_url(self): def access_urls(self):
return self._access_url return self._access_urls
@access_url.setter @property
def access_url(self, value): def name(self):
self._access_url = value return self._name
@access_urls.setter
def access_urls(self, value):
self._access_urls = value
@property @property
def status(self): def status(self):
@ -57,3 +63,11 @@ class ProviderDetail(object):
value, value,
VALID_STATUSES) VALID_STATUSES)
) )
@property
def error_info(self):
return self._error_info
@error_info.setter
def error_info(self, value):
self._error_info = value

View File

@ -25,11 +25,13 @@ class Service(common.DictSerializableModel):
name, name,
domains, domains,
origins, origins,
flavorRef,
caching=[], caching=[],
restrictions=[]): restrictions=[]):
self._name = name self._name = name
self._domains = domains self._domains = domains
self._origins = origins self._origins = origins
self._flavorRef = flavorRef
self._caching = caching self._caching = caching
self._restrictions = restrictions self._restrictions = restrictions
self._status = u'unknown' self._status = u'unknown'
@ -58,6 +60,14 @@ class Service(common.DictSerializableModel):
def origins(self, value): def origins(self, value):
self._origins = value self._origins = value
@property
def flavorRef(self):
return self._flavorRef
@flavorRef.setter
def flavorRef(self, value):
self._flavorRef = value
@property @property
def caching(self): def caching(self):
return self._caching return self._caching
@ -92,7 +102,7 @@ class Service(common.DictSerializableModel):
) )
@classmethod @classmethod
def init_from_dict(cls, dict): def init_from_dict(cls, input_dict):
"""Construct a model instance from a dictionary. """Construct a model instance from a dictionary.
This is only meant to be used for converting a This is only meant to be used for converting a
@ -100,6 +110,6 @@ class Service(common.DictSerializableModel):
When converting a model into a request model, When converting a model into a request model,
use to_dict. use to_dict.
""" """
o = cls("unnamed", [], []) o = cls('unnamed', [], [], 'unnamed')
o.from_dict(dict) o.from_dict(input_dict)
return o return o

View File

@ -31,16 +31,19 @@ class Responder(object):
return { return {
self.provider: { self.provider: {
"error": msg "error": msg,
"error_detail": traceback.format_exc()
} }
} }
def created(self, provider_service_id, links): def created(self, provider_service_id, links, **extras):
provider_response = {
"id": provider_service_id,
"links": links
}
provider_response.update(extras)
return { return {
self.provider: { self.provider: provider_response
"id": provider_service_id,
"links": links
}
} }
def updated(self, provider_service_id): def updated(self, provider_service_id):

View File

@ -45,3 +45,21 @@ class ServicesControllerBase(controller.ProviderControllerBase):
def get(self, service_name): def get(self, service_name):
"""Get details of the service, as stored by the provider.""" """Get details of the service, as stored by the provider."""
raise NotImplementedError raise NotImplementedError
def _map_service_name(self, service_name):
"""Map poppy service name to provider's specific service name.
Map a poppy service name to a provider's service name so it
can comply provider's naming convention.
"""
return service_name
@abc.abstractmethod
def current_customer(self):
"""Return the current customer for a provider.
This will needed call each provider's customer API,
useful for certain providers ( e.g fastly) and manage
master-sub account
"""
raise NotImplementedError

View File

@ -51,6 +51,7 @@ class CDNProvider(base.Driver):
def provider_name(self): def provider_name(self):
return 'CloudFront' return 'CloudFront'
@property
def client(self): def client(self):
return self.cloudfront_client return self.cloudfront_client

View File

@ -15,8 +15,12 @@
from boto import cloudfront from boto import cloudfront
from poppy.common import decorators
from poppy.openstack.common import log
from poppy.provider import base from poppy.provider import base
LOG = log.getLogger(__name__)
class ServiceController(base.ServiceBase): class ServiceController(base.ServiceBase):
@ -28,36 +32,46 @@ class ServiceController(base.ServiceBase):
super(ServiceController, self).__init__(driver) super(ServiceController, self).__init__(driver)
self.driver = driver self.driver = driver
self.current_customer = self.client.get_current_customer()
# TODO(obulpathi): get service # TODO(obulpathi): get service
def get(self, service_name): def get(self, service_name):
return {'domains': [], 'origins': [], 'caching': []} return {'domains': [], 'origins': [], 'caching': []}
# TODo(obulpathi): update service # TODo(obulpathi): update service
def update(self, service_name, service_json): def update(self, service_name, service_obj):
return self.responder.updated(service_name) return self.responder.updated(service_name)
def create(self, service_name, service_json): def create(self, service_obj):
# TODO(obulpathi): create a single distribution for multiple origins # TODO(obulpathi): create a single distribution for multiple origins
origin = service_json['origins'][0] origin = service_obj.origins[0]
LOG.info('Start creating cloudfront config for %s' % service_obj.name)
try: try:
# Create the origin for this domain: # Create the origin for this domain:
aws_origin = cloudfront.origin.CustomOrigin( aws_origin = cloudfront.origin.CustomOrigin(
dns_name=origin['origin'], dns_name=origin.origin,
http_port=origin['port'], http_port=origin.port,
https_port=origin['ssl'], # cannot specify ssl like this yet, CF takes a port #
# https_port=origin.ssl,
origin_protocol_policy='match-viewer') origin_protocol_policy='match-viewer')
distribution = self.client.create_distribution( distribution = self.client.create_distribution(
origin=aws_origin, origin=aws_origin,
enabled=True) enabled=True)
if distribution.status == 'InProgress':
status = 'in_progress'
elif distribution.status == 'Deployed':
status = 'deployed'
else:
status = 'unknown'
except cloudfront.exception.CloudFrontServerError as e: except cloudfront.exception.CloudFrontServerError as e:
return self.responder.failed(str(e)) return self.responder.failed(str(e))
except Exception as e: except Exception as e:
return self.responder.failed(str(e)) return self.responder.failed(str(e))
links = [{'href': distribution.domain_name, 'rel': 'access_url'}] links = [{'href': distribution.domain_name, 'rel': 'access_url'}]
return self.responder.created(distribution.id, links) # extra information should be passed in here.
LOG.info('Creating cloudfront config for %s'
'successfull...' % service_obj.name)
return self.responder.created(distribution.id, links, status=status)
def delete(self, service_name): def delete(self, service_name):
# NOTE(obulpathi): distribution_id is the equivalent of service_name # NOTE(obulpathi): distribution_id is the equivalent of service_name
@ -68,3 +82,8 @@ class ServiceController(base.ServiceBase):
return self.responder.deleted(distribution_id) return self.responder.deleted(distribution_id)
except Exception as e: except Exception as e:
return self.responder.failed(str(e)) return self.responder.failed(str(e))
@decorators.lazy_property(write=False)
def current_customer(self):
# TODO(tonytan4ever/obulpathi): Implement cloudfront's current_customer
pass

View File

@ -15,6 +15,7 @@
import fastly import fastly
from poppy.common import decorators
from poppy.provider import base from poppy.provider import base
@ -28,47 +29,57 @@ class ServiceController(base.ServiceBase):
super(ServiceController, self).__init__(driver) super(ServiceController, self).__init__(driver)
self.driver = driver self.driver = driver
self.current_customer = self.client.get_current_customer()
def update(self, provider_service_id, service_json): def update(self, provider_service_id, service_obj):
return self.responder.updated(provider_service_id) return self.responder.updated(provider_service_id)
def create(self, service_name, service_json): def create(self, service_obj):
try: try:
# Create a new service # Create a new service
service = self.client.create_service(self.current_customer.id, service = self.client.create_service(self.current_customer.id,
service_name) service_obj.name)
# Create a new version of the service. # Create a new version of the service.
service_version = self.client.create_version(service.id) service_version = self.client.create_version(service.id)
# Create the domain for this service # Create the domain for this service
for domain in service_json["domains"]: for domain in service_obj.domains:
domain = self.client.create_domain(service.id, domain = self.client.create_domain(service.id,
service_version.number, service_version.number,
domain["domain"]) domain.domain)
# TODO(tonytan4ever): what if check_domains fail ? # TODO(tonytan4ever): what if check_domains fail ?
# For right now we fail the who create process. # For right now we fail the who create process.
# But do we want to fail the whole service create ? probably not. # But do we want to fail the whole service create ? probably not.
# we need to carefully divise our try_catch here. # we need to carefully divise our try_catch here.
links = [{"href": '.'.join([domain_dict['name'], suffix]), domain_checks = self.client.check_domains(service.id,
service_version.number)
links = [{"href": '.'.join([domain_check.domain.name,
"global.prod.fastly.net"]),
"rel": 'access_url'} "rel": 'access_url'}
for domain_dict, suffix, enabled in for domain_check in domain_checks]
self.client.check_domains(service.id,
service_version.number)
if enabled]
for origin in service_json["origins"]: for origin in service_obj.origins:
# Create the origins for this domain # Create the origins for this domain
self.client.create_backend(service.id, self.client.create_backend(service.id,
service_version.number, service_version.number,
origin["origin"], origin.origin.replace(":", "-"),
origin["origin"], origin.origin,
origin["ssl"], origin.ssl,
origin["port"] origin.port
) )
# TODO(tonytan4ever): To incorporate caching, restriction change
# once standarnd/limitation on these service details have been
# figured out
# activate latest version of this fastly service
service_versions = self.client.list_versions(service.id)
latest_version_number = max([version.number
for version in service_versions])
self.client.activate_version(service.id, latest_version_number)
return self.responder.created(service.id, links) return self.responder.created(service.id, links)
except fastly.FastlyError: except fastly.FastlyError:
@ -122,3 +133,7 @@ class ServiceController(base.ServiceBase):
return self.responder.failed("failed to GET service") return self.responder.failed("failed to GET service")
except Exception: except Exception:
return self.responder.failed("failed to GET service") return self.responder.failed("failed to GET service")
@decorators.lazy_property(write=False)
def current_customer(self):
return self.client.get_current_customer()

View File

@ -13,8 +13,14 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import hashlib
import re
from poppy.common import decorators
from poppy.provider import base from poppy.provider import base
MAXCDN_NAMING_REGEX = re.compile('^[a-zA-Z0-9-]{3,32}$')
class ServiceController(base.ServiceBase): class ServiceController(base.ServiceBase):
@ -32,21 +38,17 @@ class ServiceController(base.ServiceBase):
self.driver = driver self.driver = driver
# This returns the current customer account info def update(self, pullzone_id, service_obj):
account_info_return = self.client.get('/account.json')
if account_info_return['code'] != 200:
raise RuntimeError(account_info_return['error'])
self.current_customer = account_info_return['data']['account']
def update(self, pullzone_id, service_json):
'''MaxCDN update. '''MaxCDN update.
manager needs to pass in pullzone id to delete. manager needs to pass in pullzone id to delete.
''' '''
try: try:
# TODO(tonytan4ever): correctly convert and update service_obj
# to a dictionary passed into maxcdn call.
update_response = self.client.put('/zones/pull.json/%s' update_response = self.client.put('/zones/pull.json/%s'
% pullzone_id, % pullzone_id,
params=service_json) params=service_obj.to_dict())
if update_response['code'] != 200: if update_response['code'] != 200:
return self.responder.failed('failed to update service') return self.responder.failed('failed to update service')
return self.responder.updated( return self.responder.updated(
@ -55,19 +57,21 @@ class ServiceController(base.ServiceBase):
# this exception branch will most likely for a network failure # this exception branch will most likely for a network failure
return self.responder.failed('failed to update service') return self.responder.failed('failed to update service')
def create(self, service_name, service_json): def create(self, service_obj):
'''MaxCDN create. '''MaxCDN create.
manager needs to pass in a service name to create. manager needs to pass in a service name to create.
''' '''
try: try:
# Create a new pull zone: maxcdn only supports 1 origin # Create a new pull zone: maxcdn only supports 1 origin
origin = service_json['origins'][0] origin = service_obj.origins[0]
origin_prefix = 'https://' if origin.ssl else 'http://'
create_response = self.client.post('/zones/pull.json', data={ create_response = self.client.post('/zones/pull.json', data={
'name': service_name, 'name': self._map_service_name(service_obj.name),
'url': origin['origin'], # TODO(tonytan4ever): maxcdn takes origin with
'port': origin.get('port', 80), # 'http://' or 'https://' prefix.
'sslshared': 1 if origin['ssl'] else 0, 'url': ''.join([origin_prefix, origin.origin]),
'port': getattr(origin, 'port', 80),
}) })
if create_response['code'] != 201: if create_response['code'] != 201:
@ -77,12 +81,12 @@ class ServiceController(base.ServiceBase):
# Add custom domains to this service # Add custom domains to this service
links = [] links = []
for domain in service_json['domains']: for domain in service_obj.domains:
custom_domain_response = self.client.post( self.client.post(
'/zones/pull/%s/customdomains.json' '/zones/pull/%s/customdomains.json'
% created_zone_info['id'], % created_zone_info['id'],
{'custom_domain': domain['domain']}) {'custom_domain': domain.domain})
links.append(custom_domain_response) links.append({'href': domain.domain, "rel": "access_url"})
# TODO(tonytan4ever): What if it fails during add domains ? # TODO(tonytan4ever): What if it fails during add domains ?
return self.responder.created(created_zone_info['id'], links) return self.responder.created(created_zone_info['id'], links)
except Exception: except Exception:
@ -108,3 +112,23 @@ class ServiceController(base.ServiceBase):
def get(self, service_name): def get(self, service_name):
'''Get details of the service, as stored by the provider.''' '''Get details of the service, as stored by the provider.'''
return {'domains': [], 'origins': [], 'caching': []} return {'domains': [], 'origins': [], 'caching': []}
def _map_service_name(self, service_name):
"""Map poppy service name to provider's specific service name.
Map a poppy service name to a provider's service name so it
can comply provider's naming convention.
"""
if MAXCDN_NAMING_REGEX.match(service_name):
return service_name
else:
return hashlib.sha1(service_name.encode("utf-8")).hexdigest()[:30]
@decorators.lazy_property(write=False)
def current_customer(self):
# This returns the current customer account info
account_info_return = self.client.get('/account.json')
if account_info_return['code'] == 200:
return account_info_return['data']['account']
else:
raise RuntimeError("Get maxcdn current customer failed...")

View File

@ -15,6 +15,7 @@
import uuid import uuid
from poppy.common import decorators
from poppy.openstack.common import log from poppy.openstack.common import log
from poppy.provider import base from poppy.provider import base
@ -26,17 +27,23 @@ class ServiceController(base.ServiceBase):
def __init__(self, driver): def __init__(self, driver):
super(ServiceController, self).__init__(driver) super(ServiceController, self).__init__(driver)
def update(self, provider_service_id, service_json): def update(self, provider_service_id, service_obj):
return self.responder.updated(provider_service_id) return self.responder.updated(provider_service_id)
def create(self, service_name, service_json): def create(self, service_obj):
LOG.debug("Mock creating service: %s" % service_name)
# We generate a fake id here # We generate a fake id here
service_id = uuid.uuid1() service_id = uuid.uuid1()
return self.responder.created(str(service_id), {}) return self.responder.created(str(service_id), [{
"href": "www.mysite.com",
'rel': "access_url"}])
def delete(self, provider_service_id): def delete(self, provider_service_id):
return self.responder.deleted(provider_service_id) return self.responder.deleted(provider_service_id)
def get(self, service_name): def get(self, service_name):
return self.responder.get([], [], []) return self.responder.get([], [], [])
@decorators.lazy_property(write=False)
def current_customer(self):
'''return current_customer for Mock. We can return a None.'''
return None

View File

@ -49,3 +49,7 @@ class ServicesControllerBase(controller.StorageControllerBase):
@abc.abstractmethod @abc.abstractmethod
def get_provider_details(self, project_id, service_name): def get_provider_details(self, project_id, service_name):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod
def update_provider_details(self, provider_details):
raise NotImplementedError

View File

@ -83,11 +83,21 @@ class FlavorsController(base.FlavorsController):
if (len(flavors) == 1): if (len(flavors) == 1):
return flavors[0] return flavors[0]
else: else:
raise LookupError("More than one flavor was retrieved.") raise LookupError("More than one flavor/no record was retrieved.")
def add(self, flavor): def add(self, flavor):
"""Add a new flavor.""" """Add a new flavor."""
# check if the flavor already exist.
# Note: If it does, no LookupError will be raised
try:
self.get(flavor.flavor_id)
except LookupError:
pass
else:
raise ValueError("Flavor %s already exists..."
% flavor.flavor_id)
providers = dict((p.provider_id, p.provider_url) providers = dict((p.provider_id, p.provider_url)
for p in flavor.providers) for p in flavor.providers)

View File

@ -52,16 +52,21 @@ CQL_DELETE_SERVICE = '''
CQL_CREATE_SERVICE = ''' CQL_CREATE_SERVICE = '''
INSERT INTO services (project_id, INSERT INTO services (project_id,
service_name, service_name,
flavor_id,
domains, domains,
origins, origins,
caching_rules, caching_rules,
restrictions) restrictions,
provider_details
)
VALUES (%(project_id)s, VALUES (%(project_id)s,
%(service_name)s, %(service_name)s,
%(flavor_id)s,
%(domains)s, %(domains)s,
%(origins)s, %(origins)s,
%(caching_rules)s, %(caching_rules)s,
%(restrictions)s) %(restrictions)s,
%(provider_details)s)
''' '''
CQL_UPDATE_DOMAINS = ''' CQL_UPDATE_DOMAINS = '''
@ -94,6 +99,12 @@ CQL_GET_PROVIDER_DETAILS = '''
WHERE project_id = %(project_id)s AND service_name = %(service_name)s WHERE project_id = %(project_id)s AND service_name = %(service_name)s
''' '''
CQL_UPDATE_PROVIDER_DETAILS = '''
UPDATE services
set provider_details = %(provider_details)s
WHERE project_id = %(project_id)s AND service_name = %(service_name)s
'''
class ServicesController(base.ServicesController): class ServicesController(base.ServicesController):
@ -115,7 +126,7 @@ class ServicesController(base.ServicesController):
# TODO(amitgandhinz): return services instead once its formatted. # TODO(amitgandhinz): return services instead once its formatted.
services = [] services = []
for r in results: for r in results:
name = r.get("name", "unnamed") name = r.get("service_name")
origins = r.get("origins", []) origins = r.get("origins", [])
domains = r.get("domains", []) domains = r.get("domains", [])
origins = [origin.Origin(json.loads(o)['origin'], origins = [origin.Origin(json.loads(o)['origin'],
@ -123,7 +134,8 @@ class ServicesController(base.ServicesController):
json.loads(o).get("ssl", False)) json.loads(o).get("ssl", False))
for o in origins] for o in origins]
domains = [domain.Domain(json.loads(d)['domain']) for d in domains] domains = [domain.Domain(json.loads(d)['domain']) for d in domains]
services.append(service.Service(name, domains, origins)) flavorRef = r.get("flavor_id")
services.append(service.Service(name, domains, origins, flavorRef))
return services return services
def get(self, project_id, service_name): def get(self, project_id, service_name):
@ -135,12 +147,12 @@ class ServicesController(base.ServicesController):
results = self.session.execute(CQL_GET_SERVICE, args) results = self.session.execute(CQL_GET_SERVICE, args)
if len(results) != 1: if len(results) != 1:
raise ValueError("No service or multiple service found: %s" raise LookupError("No service or multiple service found: %s"
% service_name) % service_name)
services = [] services = []
for r in results: for r in results:
name = r.get("name", "unnamed") name = r.get("service_name")
origins = r.get("origins", []) origins = r.get("origins", [])
domains = r.get("domains", []) domains = r.get("domains", [])
origins = [origin.Origin(json.loads(o)['origin'], origins = [origin.Origin(json.loads(o)['origin'],
@ -148,12 +160,23 @@ class ServicesController(base.ServicesController):
json.loads(o).get("ssl", False)) json.loads(o).get("ssl", False))
for o in origins] for o in origins]
domains = [domain.Domain(json.loads(d)['domain']) for d in domains] domains = [domain.Domain(json.loads(d)['domain']) for d in domains]
services.append(service.Service(name, domains, origins)) flavorRef = r.get("flavor_id")
services.append(service.Service(name, domains, origins, flavorRef))
return services[0] return services[0]
def create(self, project_id, service_name, service_obj): def create(self, project_id, service_obj):
# create the service in storage # create the service in storage
service_name = service_obj.name
# check if the serivce already exist.
# Note: If it does, no LookupError will be raised
try:
self.get(project_id, service_name)
except LookupError:
pass
else:
raise ValueError("Service %s already exists..." % service_name)
domains = [json.dumps(domain.to_dict()) domains = [json.dumps(domain.to_dict())
for domain in service_obj.domains] for domain in service_obj.domains]
origins = [json.dumps(origin.to_dict()) origins = [json.dumps(origin.to_dict())
@ -167,10 +190,13 @@ class ServicesController(base.ServicesController):
args = { args = {
'project_id': project_id, 'project_id': project_id,
'service_name': service_name, 'service_name': service_name,
'flavor_id': service_obj.flavorRef,
'domains': domains, 'domains': domains,
'origins': origins, 'origins': origins,
'caching_rules': caching_rules, 'caching_rules': caching_rules,
'restrictions': restrictions 'restrictions': restrictions,
# TODO(tonytan4ever): Incorporate flavor change.
'provider_details': {}
} }
self.session.execute(CQL_CREATE_SERVICE, args) self.session.execute(CQL_CREATE_SERVICE, args)
@ -206,12 +232,37 @@ class ServicesController(base.ServicesController):
results = {} results = {}
for provider_name in exec_results[0]: for provider_name in exec_results[0]:
provider_detail_dict = json.loads(exec_results[0][provider_name]) provider_detail_dict = json.loads(exec_results[0][provider_name])
id = provider_detail_dict.get("id", None) pr_id = provider_detail_dict.get("provider_service_id", None)
access_url = provider_detail_dict.get("access_url", None) access_urls = provider_detail_dict.get("access_urls", None)
status = provider_detail_dict.get("status", u'unknown') status = provider_detail_dict.get("status", u'unknown')
error_info = provider_detail_dict.get("error_info", None)
provider_detail_obj = provider_details.ProviderDetail( provider_detail_obj = provider_details.ProviderDetail(
id=id, provider_service_id=pr_id,
access_url=access_url, access_urls=access_urls,
status=status) status=status,
error_info=error_info)
results[provider_name] = provider_detail_obj results[provider_name] = provider_detail_obj
return results return results
def update_provider_details(self, project_id, service_name,
provider_details):
provider_detail_dict = {}
for provider_name in provider_details:
provider_detail_dict[provider_name] = json.dumps({
"id": provider_details[provider_name].provider_service_id,
"access_urls": provider_details[provider_name].access_urls,
"status": provider_details[provider_name].status,
"name": provider_details[provider_name].name,
"error_info": provider_details[provider_name].error_info
})
args = {
'project_id': project_id,
'service_name': service_name,
'provider_details': provider_detail_dict
}
# TODO(tonytan4ever): Not sure this returns a list or a single
# dictionary.
# Needs to verify after cassandra unittest framework has been added in
# if a list, the return the first item of a list. if it is a dictionary
# returns the dictionary
self.session.execute(CQL_UPDATE_PROVIDER_DETAILS, args)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from poppy.model import flavor
from poppy.storage import base from poppy.storage import base
@ -23,10 +24,24 @@ class FlavorsController(base.FlavorsController):
return self._driver.database return self._driver.database
def list(self): def list(self):
return [] f = flavor.Flavor(
"standard",
[flavor.Provider("cloudfront", "www.cloudfront.com"),
flavor.Provider("fastly", "www.fastly.com"),
flavor.Provider("mock", "www.mock_provider.com")]
)
return [f]
def get(self, flavor_id): def get(self, flavor_id):
return None f = flavor.Flavor(
"standard",
[flavor.Provider("cloudfront", "www.cloudfront.com"),
flavor.Provider("fastly", "www.fastly.com"),
flavor.Provider("mock", "www.mock_provider.com")]
)
if flavor_id == "non_exist":
raise LookupError("More than one flavor/no record was retrieved.")
return f
def add(self, flavor): def add(self, flavor):
pass pass

View File

@ -42,6 +42,7 @@ class ServicesController(base.ServicesController):
"ssl": False "ssl": False
} }
], ],
"flavorRef": "standard",
"caching": [ "caching": [
{"name": "default", "ttl": 3600}, {"name": "default", "ttl": 3600},
{ {
@ -76,12 +77,14 @@ class ServicesController(base.ServicesController):
services_result = [] services_result = []
for r in services: for r in services:
name = r.get("name", "unnamed") name = r.get("name")
origins = r.get("origins", []) origins = r.get("origins", [])
domains = r.get("domains", []) domains = r.get("domains", [])
origins = [origin.Origin(d) for d in origins] origins = [origin.Origin(d) for d in origins]
domains = [domain.Domain(d) for d in domains] domains = [domain.Domain(d) for d in domains]
services_result.append(service.Service(name, domains, origins)) flavorRef = r.get("name").split("/")[-1]
services_result.append(service.Service(name, domains, origins,
flavorRef))
return services_result return services_result
@ -101,6 +104,7 @@ class ServicesController(base.ServicesController):
"ssl": False "ssl": False
} }
], ],
"flavorRef": "standard",
"caching": [ "caching": [
{"name": "default", "ttl": 3600}, {"name": "default", "ttl": 3600},
{ {
@ -132,16 +136,18 @@ class ServicesController(base.ServicesController):
], ],
} }
name = service_dict.get("name", "unnamed") name = service_dict.get("name")
origins = service_dict.get("origins", []) origins = service_dict.get("origins", [])
domains = service_dict.get("domains", []) domains = service_dict.get("domains", [])
origins = [origin.Origin(d) for d in origins] origins = [origin.Origin(d) for d in origins]
domains = [domain.Domain(d) for d in domains] domains = [domain.Domain(d) for d in domains]
services_result = service.Service(name, domains, origins) flavorRef = service_dict.get("name").split("/")[-1]
services_result = service.Service(name, domains, origins, flavorRef)
return services_result return services_result
def create(self, project_id, service_name, service_json): def create(self, project_id, service_obj):
if service_obj.name == "mockdb1_service_name":
raise ValueError("Service %s already exists..." % service_obj.name)
return "" return ""
def update(self, project_id, service_name, service_json): def update(self, project_id, service_name, service_json):
@ -156,19 +162,23 @@ class ServicesController(base.ServicesController):
def get_provider_details(self, project_id, service_name): def get_provider_details(self, project_id, service_name):
return { return {
"MaxCDN": provider_details.ProviderDetail( "MaxCDN": provider_details.ProviderDetail(
id=11942, provider_service_id=11942,
name='my_service_name', name='my_service_name',
access_url='my_service_name' access_urls=['my_service_name'
'.mycompanyalias.netdna-cdn.com'), '.mycompanyalias.netdna-cdn.com']),
"Fastly": provider_details.ProviderDetail( "Fastly": provider_details.ProviderDetail(
id=3488, provider_service_id=3488,
name="my_service_name", name="my_service_name",
access_url='my_service_name' access_urls=['my_service_name'
'.global.prod.fastly.net'), '.global.prod.fastly.net']),
"CloudFront": provider_details.ProviderDetail( "CloudFront": provider_details.ProviderDetail(
id=5892, provider_service_id=5892,
access_url='my_service_name' access_urls=['my_service_name'
'.gibberish.amzcf.com'), '.gibberish.amzcf.com']),
"Mock": provider_details.ProviderDetail( "Mock": provider_details.ProviderDetail(
id="73242", provider_service_id="73242",
access_url='my_service_name.mock.com')} access_urls=['my_service_name.mock.com'])}
def update_provider_details(self, project_id, service_name,
provider_details):
pass

View File

@ -43,14 +43,12 @@ class FlavorsController(base.Controller):
@pecan.expose('json') @pecan.expose('json')
def get_one(self, flavor_id): def get_one(self, flavor_id):
flavors_controller = self.driver.manager.flavors_controller flavors_controller = self.driver.manager.flavors_controller
result = flavors_controller.get(flavor_id) try:
result = flavors_controller.get(flavor_id)
if result is not None: except LookupError as e:
print (result) pecan.abort(404, detail=str(e))
print('done')
return flavor_response.Model(result, pecan.request)
else: else:
pecan.response.status = 404 return flavor_response.Model(result, pecan.request)
@pecan.expose('json') @pecan.expose('json')
@decorators.validate( @decorators.validate(

View File

@ -17,6 +17,7 @@ import json
import pecan import pecan
from poppy.common import uri
from poppy.transport.pecan.controllers import base from poppy.transport.pecan.controllers import base
from poppy.transport.pecan.models.request import service as req_service_model from poppy.transport.pecan.models.request import service as req_service_model
from poppy.transport.pecan.models.response import link from poppy.transport.pecan.models.response import link
@ -58,20 +59,28 @@ class ServicesController(base.Controller):
@pecan.expose('json') @pecan.expose('json')
@decorators.validate( @decorators.validate(
service_name=rule.Rule(
helpers.is_valid_service_name(),
helpers.abort_with_message),
request=rule.Rule( request=rule.Rule(
helpers.json_matches_schema( helpers.json_matches_schema(
service.ServiceSchema.get_schema("service", "PUT")), service.ServiceSchema.get_schema("service", "POST")),
helpers.abort_with_message, helpers.abort_with_message,
stoplight_helpers.pecan_getter)) stoplight_helpers.pecan_getter))
def put(self, service_name): def post(self):
services_controller = self._driver.manager.services_controller services_controller = self._driver.manager.services_controller
service_json_dict = json.loads(pecan.request.body.decode('utf-8')) service_json_dict = json.loads(pecan.request.body.decode('utf-8'))
service_obj = req_service_model.load_from_json(service_json_dict) service_obj = req_service_model.load_from_json(service_json_dict)
return services_controller.create(self.project_id, service_name, service_name = service_json_dict.get("name", None)
service_obj) try:
services_controller.create(self.project_id, service_obj)
except LookupError as e: # error handler for no flavor
pecan.abort(400, detail=str(e))
except ValueError as e: # error handler for existing service name
pecan.abort(400, detail=str(e))
service_url = str(
uri.encode(u'{0}/v1.0/services/{1}'.format(
pecan.request.host_url,
service_name)))
pecan.response.status = 202
pecan.response.headers["Location"] = service_url
@pecan.expose('json') @pecan.expose('json')
def delete(self, service_name): def delete(self, service_name):

View File

@ -17,5 +17,5 @@ from poppy.model.helpers import domain
def load_from_json(json_data): def load_from_json(json_data):
domain_name = json_data.get("domain", None) domain_name = json_data.get("domain")
return domain.Domain(domain_name) return domain.Domain(domain_name)

View File

@ -17,7 +17,7 @@ from poppy.model.helpers import origin
def load_from_json(json_data): def load_from_json(json_data):
origin_name = json_data.get("origin", "unnamed") origin_name = json_data.get("origin")
port = json_data.get("port", 80) port = json_data.get("port", 80)
ssl = json_data.get("ssl", False) ssl = json_data.get("ssl", False)
return origin.Origin(origin_name, port, ssl) return origin.Origin(origin_name, port, ssl)

View File

@ -19,9 +19,10 @@ from poppy.transport.pecan.models.request import origin
def load_from_json(json_data): def load_from_json(json_data):
name = json_data.get("name", "unnamed") name = json_data.get("name")
origins = json_data.get("origins", []) origins = json_data.get("origins", [])
domains = json_data.get("domains", []) domains = json_data.get("domains", [])
flavorRef = json_data.get("flavorRef")
origins = [origin.load_from_json(d) for d in origins] origins = [origin.load_from_json(d) for d in origins]
domains = [domain.load_from_json(d) for d in domains] domains = [domain.load_from_json(d) for d in domains]
return service.Service(name, origins, domains) return service.Service(name, domains, origins, flavorRef)

View File

@ -15,8 +15,8 @@
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
class Model(collections.OrderedDict): class Model(collections.OrderedDict):

View File

@ -15,8 +15,8 @@
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
from poppy.transport.pecan.models.response import link from poppy.transport.pecan.models.response import link

View File

@ -15,8 +15,8 @@
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
class Model(collections.OrderedDict): class Model(collections.OrderedDict):

View File

@ -15,8 +15,8 @@
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
class Model(collections.OrderedDict): class Model(collections.OrderedDict):

View File

@ -15,8 +15,8 @@
try: try:
import ordereddict as collections import ordereddict as collections
except ImportError: except ImportError: # pragma: no cover
import collections import collections # pragma: no cover
from poppy.transport.pecan.models.response import domain from poppy.transport.pecan.models.response import domain
from poppy.transport.pecan.models.response import link from poppy.transport.pecan.models.response import link

View File

@ -178,7 +178,10 @@ def json_matches_schema_inner(request, schema=None):
if len(errors_list) > 0: if len(errors_list) > 0:
details = dict(errors=[{ details = dict(errors=[{
'message': str(getattr(error, "message", error))} 'message': '-'.join([
"[%s]" % "][".join(repr(p) for p in error.path),
str(getattr(error, "message", error))
])}
for error in errors_list]) for error in errors_list])
raise exceptions.ValidationFailed(json.dumps(details)) raise exceptions.ValidationFailed(json.dumps(details))
else: else:

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc. # Copyright (c) 2014 Rackspace, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -13,71 +14,82 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import re
from poppy.transport.validators import schema_base from poppy.transport.validators import schema_base
class ServiceSchema(schema_base.SchemaBase): class ServiceSchema(schema_base.SchemaBase):
"""JSON Schmema validation for /service.""" '''JSON Schmema validation for /service.'''
schema = { schema = {
'service': { 'service': {
'PUT': { 'POST': {
'name': 'service',
'type': 'object', 'type': 'object',
'properties': { 'properties': {
"domains": { 'name': {
'type': 'string',
'required': True,
},
'domains': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"domain": { 'domain': {
"type": "string", 'type': 'string',
'pattern': "^(([a-zA-Z]{1})|" 'pattern': re.compile(
"([a-zA-Z]{1}[a-zA-Z]{1})|" '^(([^:/?#]+):)?'
"([a-zA-Z]{1}[0-9]{1})" '(//([^/?#]*))?'
"|([0-9]{1}[a-zA-Z]{1})|" '([^?#]*)(\?([^#]*))?'
"([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}" '(#(.*))?$',
"[a-zA-Z0-9]))\." re.UNICODE
"([a-zA-Z]{2,6}|" )
"[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"
}}}, }}},
'required': True, 'required': True,
"minItems": 1}, 'minItems': 1},
"origins": { 'origins': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"origin": { 'origin': {
"type": "string", 'type': 'string',
"required": True}, 'pattern': re.compile(
"port": { '^(([^:/?#]+):)?'
"type": "integer", '(//([^/?#]*))?'
"enum": [ '([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
),
'required': True},
'port': {
'type': 'integer',
'enum': [
80, 80,
443]}, 443]},
"ssl": { 'ssl': {
"type": "boolean"}}, 'type': 'boolean'}},
}, },
'required': True, 'required': True,
"minItems": 1}, 'minItems': 1},
"caching": { 'caching': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"name": { 'name': {
"type": "string", 'type': 'string',
"required": True}, 'required': True},
"ttl": { 'ttl': {
"type": "integer", 'type': 'integer',
"required": True}, 'required': True},
"rules": { 'rules': {
"type": "array", 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
'name': { 'name': {
'type': 'string'}, 'type': 'string'},
'request_url': { 'request_url': {
@ -85,60 +97,101 @@ class ServiceSchema(schema_base.SchemaBase):
}}, }},
}, },
}, },
'restrictions': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'required': True},
'rules': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string'},
'request_url': {
'type': 'string'},
'http_host': {
'type': 'string'},
'client_ip': {
'type': 'string'},
'http_method': {
'type': 'string',
'enum': [
'GET',
'PUT',
'POST',
'PATCH']}}},
}},
},
},
'flavorRef': {
'type': 'string',
'required': True,
}
}}, }},
'PATCH': { 'PATCH': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
"domains": { 'domains': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"domain": { 'domain': {
"type": "string", 'type': 'string',
'pattern': "^(([a-zA-Z]{1})|" 'pattern': re.compile(
"([a-zA-Z]{1}[a-zA-Z]{1})|" '^(([^:/?#]+):)?'
"([a-zA-Z]{1}[0-9]{1})" '(//([^/?#]*))?'
"|([0-9]{1}[a-zA-Z]{1})|" '([^?#]*)(\?([^#]*))?'
"([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}" '(#(.*))?$',
"[a-zA-Z0-9]))\." re.UNICODE
"([a-zA-Z]{2,6}|" )
"[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"
}}}, }}},
}, },
"origins": { 'origins': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"origin": { 'origin': {
"type": "string", 'type': 'string',
"required": True}, 'pattern': re.compile(
"port": { '^(([^:/?#]+):)?'
"type": "integer", '(//([^/?#]*))?'
"enum": [ '([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
),
'required': True},
'port': {
'type': 'integer',
'enum': [
80, 80,
443]}, 443]},
"ssl": { 'ssl': {
"type": "boolean"}}, 'type': 'boolean'}},
}, },
}, },
"caching": { 'caching': {
'type': 'array', 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
"name": { 'name': {
"type": "string", 'type': 'string',
"required": True}, 'required': True},
"ttl": { 'ttl': {
"type": "integer", 'type': 'integer',
"required": True}, 'required': True},
"rules": { 'rules': {
"type": "array", 'type': 'array',
'items': { 'items': {
'type': "object", 'type': 'object',
"properties": { 'properties': {
'name': { 'name': {
'type': 'string'}, 'type': 'string'},
'request_url': { 'request_url': {
@ -146,6 +199,40 @@ class ServiceSchema(schema_base.SchemaBase):
}}, }},
}, },
}, },
'restrictions': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'required': True},
'rules': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string'},
'request_url': {
'type': 'string'},
'http_host': {
'type': 'string'},
'client_ip': {
'type': 'string'},
'http_method': {
'type': 'string',
'enum': [
'GET',
'PUT',
'POST',
'PATCH']}}},
}},
},
},
'flavorRef': {
'type': 'string',
}
}}, }},
}, },
} }

View File

@ -49,6 +49,8 @@ poppy.storage =
poppy.provider = poppy.provider =
fastly = poppy.provider.fastly:Driver fastly = poppy.provider.fastly:Driver
mock = poppy.provider.mock:Driver mock = poppy.provider.mock:Driver
cloudfront = poppy.provider.cloudfront:Driver
maxcdn = poppy.provider.maxcdn:Driver
[wheel] [wheel]
universal = 1 universal = 1

View File

@ -1,10 +1,12 @@
{ {
"all_fields": { "all_fields": {
"name": "my_service_name",
"domain_list": [{"domain": "mywebsite.com"}, "domain_list": [{"domain": "mywebsite.com"},
{"domain": "blog.mywebsite.com"}], {"domain": "blog.mywebsite.com"}],
"origin_list": [{"origins": "mywebsite.com", "origin_list": [{"origins": "mywebsite.com",
"port": 443, "port": 443,
"ssl": false}], "ssl": false}],
"flavorRef": "standard",
"caching_list": [{"name": "default", "ttl": 3600}, "caching_list": [{"name": "default", "ttl": 3600},
{"name": "home", {"name": "home",
"ttl": 1200, "ttl": 1200,
@ -12,11 +14,13 @@
"request_url" : "/index.htm"}]}] "request_url" : "/index.htm"}]}]
}, },
"caching_empty": { "caching_empty": {
"name": "my_service_name_2",
"domain_list": [{"domain": "mywebsite.com"}, "domain_list": [{"domain": "mywebsite.com"},
{"domain": "blog.mywebsite.com"}], {"domain": "blog.mywebsite.com"}],
"origin_list": [{"origins": "mywebsite.com", "origin_list": [{"origins": "mywebsite.com",
"port": 443, "port": 443,
"ssl": false}], "ssl": false}],
"flavorRef": "standard",
"caching_list": [] "caching_list": []
} }
} }

View File

@ -78,8 +78,9 @@ class PoppyClient(client.AutoMarshallingHTTPClient):
PUT PUT
services/{service_name} services/{service_name}
""" """
url = '{0}/v1.0/services/{1}'.format(self.url, service_name) url = '{0}/v1.0/services'.format(self.url)
request_object = requests.CreateService(domain_list=domain_list, request_object = requests.CreateService(service_name=service_name,
domain_list=domain_list,
origin_list=origin_list, origin_list=origin_list,
caching_list=caching_list) caching_list=caching_list)
return self.request('PUT', url, return self.request('PUT', url,

View File

@ -21,15 +21,20 @@ from cafe.engine.models import base
class CreateService(base.AutoMarshallingModel): class CreateService(base.AutoMarshallingModel):
"""Marshalling for Create Service requests.""" """Marshalling for Create Service requests."""
def __init__(self, domain_list=None, origin_list=None, caching_list=None): def __init__(self, name=None, domain_list=None, origin_list=None,
flavorRef=None, caching_list=None):
super(CreateService, self).__init__() super(CreateService, self).__init__()
self.service_name = name
self.domain_list = domain_list or [] self.domain_list = domain_list or []
self.origin_list = origin_list or [] self.origin_list = origin_list or []
self.flavorRef = flavorRef
self.caching_list = caching_list or [] self.caching_list = caching_list or []
def _obj_to_json(self): def _obj_to_json(self):
create_service_request = {"domains": self.domain_list, create_service_request = {"name": self.service_name,
"domains": self.domain_list,
"origins": self.origin_list, "origins": self.origin_list,
"flavorRef": self.flavorRef,
"caching": self.caching_list} "caching": self.caching_list}
return json.dumps(create_service_request) return json.dumps(create_service_request)

View File

@ -11,3 +11,7 @@ apikey = "MYAPIKEY"
alias = "MYALIAS" alias = "MYALIAS"
consumer_secret = "MYCONSUMER_SECRET" consumer_secret = "MYCONSUMER_SECRET"
consumer_key = "MYCONSUMERKEY" consumer_key = "MYCONSUMERKEY"
[drivers:provider:cloudfront]
aws_access_key_id = "MY_AWS_ACCESS_KEY_ID"
aws_secret_access_key = "MY_AWS_SECRET_ACCESS_KEY"

View File

@ -52,4 +52,13 @@ keyspace = poppy
database = poppy database = poppy
[drivers:provider:fastly] [drivers:provider:fastly]
apikey = "MYAPIKEY" apikey = "MYAPIKEY"
[drivers:provider:maxcdn]
alias = "MYALIAS"
consumer_secret = "MYCONSUMER_SECRET"
consumer_key = "MYCONSUMERKEY"
[drivers:provider:cloudfront]
aws_access_key_id = "MY_AWS_ACCESS_KEY_ID"
aws_secret_access_key = "MY_AWS_SECRET_ACCESS_KEY"

View File

@ -0,0 +1,37 @@
{
"using_all_fields": {
"name": "mocksite.com",
"domains": [
{"domain": "test.mocksite.com" },
{"domain": "blog.mocksite.com"}
],
"origins": [
{
"origin": "mocksite.com",
"port": 80,
"ssl": false
}
],
"flavorRef": "standard",
"caching": [
{
"name": "default",
"ttl": 3600
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mocksite.com",
"http_host": "www.mocksite.com"
}
]
}
]
}
}

View File

@ -0,0 +1,70 @@
{
"missing_origin": {
"name": "mocksite.com",
"domain": "my_domain.com"
},
"non_existing_flavor_input": {
"name": "mocksite.com",
"domains": [
{"domain": "test.mocksite.com" },
{"domain": "blog.mocksite.com"}
],
"origins": [
{
"origin": "mocksite.com",
"port": 80,
"ssl": false
}
],
"flavorRef": "non_exist",
"caching": [
{
"name": "default",
"ttl": 3600
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mocksite.com",
"http_host": "www.mocksite.com"
}
]
}
]
},
"existing_name_service_json": {
"name": "mockdb1_service_name",
"domains": [
{"domain": "test.mocksite.com" },
{"domain": "blog.mocksite.com"}
],
"origins": [
{
"origin": "mocksite.com",
"port": 80,
"ssl": false
}
],
"flavorRef": "standard",
"caching": [
{
"name": "default",
"ttl": 3600
}
],
"restrictions": [
{
"name": "website only",
"rules": [
{
"name": "mocksite.com",
"http_host": "www.mocksite.com"
}
]
}
]
}
}

View File

@ -48,7 +48,7 @@ class FlavorControllerTest(base.FunctionalTest):
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
def test_get_not_found(self): def test_get_not_found(self):
response = self.app.get('/v1.0/flavors/{0}'.format(uuid.uuid1()), response = self.app.get('/v1.0/flavors/{0}'.format("non_exist"),
status=404, status=404,
expect_errors=True) expect_errors=True)

View File

@ -15,6 +15,7 @@
import json import json
import ddt
import pecan import pecan
from webtest import app from webtest import app
@ -22,6 +23,7 @@ from poppy.transport.pecan.controllers import base as c_base
from tests.functional.transport.pecan import base from tests.functional.transport.pecan import base
@ddt.ddt
class ServiceControllerTest(base.FunctionalTest): class ServiceControllerTest(base.FunctionalTest):
def test_get_all(self): def test_get_all(self):
@ -45,52 +47,30 @@ class ServiceControllerTest(base.FunctionalTest):
self.assertTrue("domains" in response_dict) self.assertTrue("domains" in response_dict)
self.assertTrue("origins" in response_dict) self.assertTrue("origins" in response_dict)
def test_create(self): @ddt.file_data("data_create_service.json")
def test_create(self, service_json):
# create with errorenous data: invalid json data # create with errorenous data: invalid json data
self.assertRaises(app.AppError, self.app.put, self.assertRaises(app.AppError, self.app.post,
'/v1.0/0001/services/fake_service_name_2', '/v1.0/0001/services',
params="{", headers={ params="{", headers={
"Content-Type": "application/json" "Content-Type": "application/json"
}) })
# create with good data
response = self.app.post('/v1.0/0001/services',
params=json.dumps(service_json),
headers={"Content-Type": "application/json"})
self.assertEqual(202, response.status_code)
@ddt.file_data("data_create_service_bad_input_json.json")
def test_create_with_bad_input_json(self, service_json):
# create with errorenous data # create with errorenous data
self.assertRaises(app.AppError, self.app.put, self.assertRaises(app.AppError, self.app.post,
'/v1.0/0001/services/fake_service_name_2', '/v1.0/0001/services',
params=json.dumps({ params=json.dumps(service_json), headers={
"domain": "www.mytest.com"
}), headers={
"Content-Type": "application/json" "Content-Type": "application/json"
}) })
# create with good data
response = self.app.put('/v1.0/0001/services/fake_service_name_2',
params=json.dumps({
"domains": [
{"domain": "www.mywebsite.com"},
{"domain": "blog.mywebsite.com"},
],
"origins": [{"origin": "mywebsite.com",
"port": 80,
"ssl": False},
{"origin": "mywebsite.com",
}],
"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"}]}
]}),
headers={"Content-Type": "application/json"})
self.assertEqual(200, response.status_code)
def test_update(self): def test_update(self):
# update with erroneous data # update with erroneous data
self.assertRaises(app.AppError, self.app.patch, self.assertRaises(app.AppError, self.app.patch,

View File

@ -27,7 +27,7 @@ from poppy.transport.validators.stoplight import rule
class MockPecanEndpoint(object): class MockPecanEndpoint(object):
testing_schema = service.ServiceSchema.get_schema("service", "PUT") testing_schema = service.ServiceSchema.get_schema("service", "POST")
@decorators.validation_function @decorators.validation_function
def is_valid_json(r): def is_valid_json(r):

View File

@ -48,8 +48,9 @@ class DummyRequest(object):
def __init__(self): def __init__(self):
self.headers = dict(header1='headervalue1') self.headers = dict(header1='headervalue1')
self.method = "PUT" self.method = "POST"
self.body = json.dumps({ self.body = json.dumps({
"name": "fake_service_name",
"domains": [ "domains": [
{"domain": "www.mywebsite.com"}, {"domain": "www.mywebsite.com"},
{"domain": "blog.mywebsite.com"}, {"domain": "blog.mywebsite.com"},
@ -75,7 +76,8 @@ class DummyRequest(object):
{"name": "images", {"name": "images",
"ttl": 12800, "ttl": 12800,
} }
] ],
"flavorRef": "https://www.poppycdn.io/v1.0/flavors/standard"
}) })
@ -91,6 +93,7 @@ class DummyRequestWithInvalidHeader(DummyRequest):
fake_request_good = DummyRequest() fake_request_good = DummyRequest()
fake_request_bad_missing_domain = DummyRequest() fake_request_bad_missing_domain = DummyRequest()
fake_request_bad_missing_domain.body = json.dumps({ fake_request_bad_missing_domain.body = json.dumps({
"name": "fake_service_name",
"origins": [ "origins": [
{ {
"origin": "mywebsite.com", "origin": "mywebsite.com",
@ -112,7 +115,8 @@ fake_request_bad_missing_domain.body = json.dumps({
{"name": "images", "request_url": "*.png"} {"name": "images", "request_url": "*.png"}
] ]
} }
] ],
"flavorRef": "https://www.poppycdn.io/v1.0/flavors/standard"
}) })
fake_request_bad_invalid_json_body = DummyRequest() fake_request_bad_invalid_json_body = DummyRequest()
fake_request_bad_invalid_json_body.body = "{" fake_request_bad_invalid_json_body.body = "{"

View File

@ -24,7 +24,7 @@ from poppy.transport.validators.stoplight import rule
from tests.functional.transport.validator import base from tests.functional.transport.validator import base
testing_schema = service.ServiceSchema.get_schema("service", "PUT") testing_schema = service.ServiceSchema.get_schema("service", "POST")
request_fit_schema = functools.partial( request_fit_schema = functools.partial(
helpers.with_schema_falcon, helpers.with_schema_falcon,
schema=testing_schema) schema=testing_schema)

View File

@ -31,8 +31,8 @@ class TestProviderWrapper(base.TestCase):
# fake a provider details to work with unittest # fake a provider details to work with unittest
self.fake_provider_details = { self.fake_provider_details = {
"Fastly": provider_details.ProviderDetail( "Fastly": provider_details.ProviderDetail(
id=uuid.uuid1(), provider_service_id=uuid.uuid1(),
access_url='mydummywebsite.prod.fastly.com')} access_urls='mydummywebsite.prod.fastly.com')}
def test_update_with_keyerror(self): def test_update_with_keyerror(self):
mock_ext = mock.Mock(provider_name="no_existent_provider") mock_ext = mock.Mock(provider_name="no_existent_provider")
@ -47,7 +47,7 @@ class TestProviderWrapper(base.TestCase):
self.provider_wrapper_obj.update(mock_ext, self.provider_wrapper_obj.update(mock_ext,
self.fake_provider_details, {}) self.fake_provider_details, {})
mock_ext.obj.service_controller.update.assert_called_once_with( mock_ext.obj.service_controller.update.assert_called_once_with(
fastly_provider_detail.id, fastly_provider_detail.provider_service_id,
{}) {})
def test_delete_with_keyerror(self): def test_delete_with_keyerror(self):
@ -62,4 +62,4 @@ class TestProviderWrapper(base.TestCase):
fastly_provider_detail = self.fake_provider_details["Fastly"] fastly_provider_detail = self.fake_provider_details["Fastly"]
self.provider_wrapper_obj.delete(mock_ext, self.fake_provider_details) self.provider_wrapper_obj.delete(mock_ext, self.fake_provider_details)
mock_ext.obj.service_controller.delete.assert_called_once_with( mock_ext.obj.service_controller.delete.assert_called_once_with(
fastly_provider_detail.id) fastly_provider_detail.provider_service_id)

View File

@ -21,7 +21,9 @@ from oslo.config import cfg
from poppy.manager.default import driver from poppy.manager.default import driver
from poppy.manager.default import services from poppy.manager.default import services
from poppy.model import flavor
from poppy.model.helpers import provider_details from poppy.model.helpers import provider_details
from poppy.transport.pecan.models.request import service
from tests.unit import base from tests.unit import base
@ -44,21 +46,98 @@ class DefaultManagerServiceTests(base.TestCase):
self.project_id = 'mock_id' self.project_id = 'mock_id'
self.service_name = 'mock_service' self.service_name = 'mock_service'
self.service_json = '' self.service_json = {
"name": "fake_service_name",
"domains": [
{"domain": "www.mywebsite.com"},
{"domain": "blog.mywebsite.com"},
],
"origins": [
{
"origin": "mywebsite.com",
"port": 80,
"ssl": False
},
{
"origin": "mywebsite.com",
}
],
"caching": [
{"name": "default", "ttl": 3600},
{"name": "home",
"ttl": 17200,
"rules": [
{"name": "index", "request_url": "/index.htm"}
]
},
{"name": "images",
"ttl": 12800,
}
],
"flavorRef": "https://www.poppycdn.io/v1.0/flavors/standard"
}
def test_create(self): def test_create(self):
service_obj = service.load_from_json(self.service_json)
# fake one return value
self.sc.flavor_controller.get.return_value = flavor.Flavor(
"standard",
[flavor.Provider("cloudfront", "www.cloudfront.com"),
flavor.Provider("fastly", "www.fastly.com"),
flavor.Provider("mock", "www.mock_provider.com")]
)
self.sc.create(self.project_id, self.service_name, self.service_json) providers = self.sc._driver.providers
# mock responses from provider_wrapper.create call
# to get code coverage
def get_provider_extension_by_name(name):
if name == "cloudfront":
return mock.Mock(
obj=mock.Mock(
service_controller=mock.Mock(
create=mock.Mock(
return_value={
'Cloudfront': {
'id':
'08d2e326-377e-11e4-b531-3c15c2b8d2d6',
'links': [
{
'href': 'www.mysite.com',
'rel': 'access_url'}],
'status': "in_progress"}}),
)))
elif name == "fastly":
return mock.Mock(obj=mock.Mock(service_controller=mock.Mock(
create=mock.Mock(return_value={
'Fastly': {'error': "fail to create servcice",
'error_detail': 'Fastly Create failed'
' because of XYZ'}})
)
))
else:
return mock.Mock(
obj=mock.Mock(
service_controller=mock.Mock(
create=mock.Mock(
return_value={
name.title(): {
'id':
'08d2e326-377e-11e4-b531-3c15c2b8d2d6',
'links': [
{
'href': 'www.mysite.com',
'rel': 'access_url'}]}}),
)))
providers.__getitem__.side_effect = get_provider_extension_by_name
self.sc.create(self.project_id, service_obj)
# ensure the manager calls the storage driver with the appropriate data # ensure the manager calls the storage driver with the appropriate data
self.sc.storage.create.assert_called_once_with(self.project_id, self.sc.storage_controller.create.assert_called_once_with(
self.service_name, self.project_id,
self.service_json) service_obj)
# and that the providers are notified.
providers = self.sc._driver.providers
providers.map.assert_called_once_with(self.sc.provider_wrapper.create,
self.service_name,
self.service_json)
@ddt.file_data('data_provider_details.json') @ddt.file_data('data_provider_details.json')
def test_update(self, provider_details_json): def test_update(self, provider_details_json):
@ -67,27 +146,29 @@ class DefaultManagerServiceTests(base.TestCase):
provider_detail_dict = json.loads( provider_detail_dict = json.loads(
provider_details_json[provider_name] provider_details_json[provider_name]
) )
id = provider_detail_dict.get("id", None) provider_service_id = provider_detail_dict.get(
access_url = provider_detail_dict.get("access_url", None) "provider_service_id", None)
access_urls = provider_detail_dict.get("access_url", None)
status = provider_detail_dict.get("status", u'unknown') status = provider_detail_dict.get("status", u'unknown')
provider_detail_obj = provider_details.ProviderDetail( provider_detail_obj = provider_details.ProviderDetail(
id=id, provider_service_id=provider_service_id,
access_url=access_url, access_urls=access_urls,
status=status) status=status)
self.provider_details[provider_name] = provider_detail_obj self.provider_details[provider_name] = provider_detail_obj
providers = self.sc._driver.providers providers = self.sc._driver.providers
self.sc.storage.get_provider_details.return_value = ( self.sc.storage_controller.get_provider_details.return_value = (
self.provider_details self.provider_details
) )
self.sc.update(self.project_id, self.service_name, self.service_json) self.sc.update(self.project_id, self.service_name, self.service_json)
# ensure the manager calls the storage driver with the appropriate data # ensure the manager calls the storage driver with the appropriate data
self.sc.storage.update.assert_called_once_with(self.project_id, self.sc.storage_controller.update.assert_called_once_with(
self.service_name, self.project_id,
self.service_json) self.service_name,
self.service_json)
# and that the providers are notified. # and that the providers are notified.
providers.map.assert_called_once_with(self.sc.provider_wrapper.update, providers.map.assert_called_once_with(self.sc.provider_wrapper.update,
self.provider_details, self.provider_details,
@ -100,24 +181,27 @@ class DefaultManagerServiceTests(base.TestCase):
provider_detail_dict = json.loads( provider_detail_dict = json.loads(
provider_details_json[provider_name] provider_details_json[provider_name]
) )
id = provider_detail_dict.get("id", None) provider_service_id = provider_detail_dict.get(
access_url = provider_detail_dict.get("access_url", None) "provider_service_id", None)
access_urls = provider_detail_dict.get("access_urls", None)
status = provider_detail_dict.get("status", u'unknown') status = provider_detail_dict.get("status", u'unknown')
provider_detail_obj = provider_details.ProviderDetail( provider_detail_obj = provider_details.ProviderDetail(
id=id, provider_service_id=provider_service_id,
access_url=access_url, access_urls=access_urls,
status=status) status=status)
self.provider_details[provider_name] = provider_detail_obj self.provider_details[provider_name] = provider_detail_obj
self.sc.storage.get_provider_details.return_value = ( self.sc.storage_controller.get_provider_details.return_value = (
self.provider_details self.provider_details
) )
self.sc.delete(self.project_id, self.service_name) self.sc.delete(self.project_id, self.service_name)
# ensure the manager calls the storage driver with the appropriate data # ensure the manager calls the storage driver with the appropriate data
self.sc.storage.delete.assert_called_once_with(self.project_id, self.sc.storage_controller.delete.assert_called_once_with(
self.service_name) self.project_id,
self.service_name
)
# and that the providers are notified. # and that the providers are notified.
providers = self.sc._driver.providers providers = self.sc._driver.providers
providers.map.assert_called_once_with(self.sc.provider_wrapper.delete, providers.map.assert_called_once_with(self.sc.provider_wrapper.delete,

View File

@ -53,7 +53,7 @@ class TestServiceModel(base.TestCase):
def test_create(self): def test_create(self):
myservice = service.Service( myservice = service.Service(
self.service_name, self.mydomains, self.myorigins, self.service_name, self.mydomains, self.myorigins, self.flavorRef,
self.mycaching, self.myrestrictions) self.mycaching, self.myrestrictions)
# test all properties # test all properties
@ -76,6 +76,11 @@ class TestServiceModel(base.TestCase):
myservice.origins = [] myservice.origins = []
self.assertEqual(myservice.origins, []) self.assertEqual(myservice.origins, [])
# flavorRef
self.assertEqual(myservice.flavorRef, self.flavorRef)
myservice.flavorRef = "standard"
self.assertEqual(myservice.flavorRef, "standard")
self.assertEqual(myservice.restrictions, self.myrestrictions) self.assertEqual(myservice.restrictions, self.myrestrictions)
myservice.restrictions = [] myservice.restrictions = []
self.assertEqual(myservice.restrictions, []) self.assertEqual(myservice.restrictions, [])
@ -88,12 +93,24 @@ class TestServiceModel(base.TestCase):
# status # status
self.assertEqual(myservice.status, u'unknown') self.assertEqual(myservice.status, u'unknown')
def test_init_from_dict_method(self):
# this should generate a service copy from my service
myservice = service.Service(
self.service_name, self.mydomains, self.myorigins, self.flavorRef,
self.mycaching, self.myrestrictions)
cloned_service = service.Service.init_from_dict(myservice.to_dict())
self.assertEqual(cloned_service.domains, myservice.domains)
self.assertEqual(cloned_service.origins, myservice.origins)
self.assertEqual(cloned_service.flavorRef, myservice.flavorRef)
@ddt.data(u'', u'apple') @ddt.data(u'', u'apple')
def test_set_invalid_status(self, status): def test_set_invalid_status(self, status):
myservice = service.Service( myservice = service.Service(
self.service_name, self.service_name,
self.mydomains, self.mydomains,
self.myorigins) self.myorigins,
self.flavorRef)
self.assertRaises(ValueError, setattr, myservice, 'status', status) self.assertRaises(ValueError, setattr, myservice, 'status', status)
@ -102,7 +119,8 @@ class TestServiceModel(base.TestCase):
myservice = service.Service( myservice = service.Service(
self.service_name, self.service_name,
self.mydomains, self.mydomains,
self.myorigins) self.myorigins,
self.flavorRef)
myservice.status = status myservice.status = status

View File

@ -1,5 +1,6 @@
{ {
"service_json": { "service_json": {
"name" : "mysite.com",
"domains": [ "domains": [
{"domain": "parsely.sage.com"}, {"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"} {"domain": "rosemary.thyme.net"}
@ -7,6 +8,7 @@
"origins": [ "origins": [
{"origin": "mydomain.com", "ssl": false, "port": 80}, {"origin": "mydomain.com", "ssl": false, "port": 80},
{"origin": "youdomain.com", "ssl": true, "port": 443} {"origin": "youdomain.com", "ssl": true, "port": 443}
] ],
"flavorRef" : "standard"
} }
} }

View File

@ -19,8 +19,8 @@ from boto import cloudfront
import ddt import ddt
import mock import mock
from poppy.provider.cloudfront import services from poppy.provider.cloudfront import services
from poppy.transport.pecan.models.request import service
from tests.unit import base from tests.unit import base
@ -46,30 +46,58 @@ class TestServices(base.TestCase):
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_server_error(self, service_json): def test_create_server_error(self, service_json):
# create_distribution: CloudFrontServerError # create_distribution: CloudFrontServerError
service_obj = service.load_from_json(service_json)
side_effect = cloudfront.exception.CloudFrontServerError( side_effect = cloudfront.exception.CloudFrontServerError(
503, 'Service Unavailable') 503, 'Service Unavailable')
self.controller.client.create_distribution.side_effect = side_effect self.controller.client.create_distribution.side_effect = side_effect
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_exception(self, service_json): def test_create_exception(self, service_json):
# generic exception: Exception # generic exception: Exception
service_obj = service.load_from_json(service_json)
self.controller.client.create_distribution.side_effect = Exception( self.controller.client.create_distribution.side_effect = Exception(
'Creating service failed.') 'Creating service failed.')
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create(self, service_json): def test_create_service_in_progress(self, service_json):
# clear run # clear run
resp = self.controller.create(self.service_name, service_json) service_obj = service.load_from_json(service_json)
self.controller.client.create_distribution.return_value = (
mock.Mock(domain_name="jibberish.cloudfront.com",
id=uuid.uuid1(),
status="InProgress")
)
resp = self.controller.create(service_obj)
self.assertIn('links', resp[self.driver.provider_name])
@ddt.file_data('data_service.json')
def test_create_service_deployed(self, service_json):
# clear run
service_obj = service.load_from_json(service_json)
self.controller.client.create_distribution.return_value = (
mock.Mock(domain_name="jibberish.cloudfront.com",
id=uuid.uuid1(),
status="Deployed")
)
resp = self.controller.create(service_obj)
self.assertIn('links', resp[self.driver.provider_name])
@ddt.file_data('data_service.json')
def test_create_service_unknown(self, service_json):
# clear run
service_obj = service.load_from_json(service_json)
resp = self.controller.create(service_obj)
self.assertIn('links', resp[self.driver.provider_name]) self.assertIn('links', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_update(self, service_json): def test_update(self, service_json):
resp = self.controller.update(self.service_name, service_json) service_obj = service.load_from_json(service_json)
resp = self.controller.update(self.service_name, service_obj)
self.assertIn('id', resp[self.driver.provider_name]) self.assertIn('id', resp[self.driver.provider_name])
def test_delete_exceptions(self): def test_delete_exceptions(self):
@ -85,3 +113,8 @@ class TestServices(base.TestCase):
def test_client(self): def test_client(self):
self.assertNotEqual(self.controller.client(), None) self.assertNotEqual(self.controller.client(), None)
def test_current_customer(self):
# TODO(tonytan4ever/obulpathi): fill in once correct
# current_customer logic is done
self.assertTrue(self.controller.current_customer is None)

View File

@ -1,11 +1,13 @@
{ {
"service_json": { "service_json": {
"name" : "mysite.com",
"domains": [ "domains": [
{"domain": "parsely.sage.com"}, {"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"} {"domain": "rosemary.thyme.net"}
], ],
"origins": [ "origins": [
{"origin": "mockdomain.com", "ssl": false, "port": 80} {"origin": "mockdomain.com", "ssl": false, "port": 80}
] ],
"flavorRef" : "standard"
} }
} }

View File

@ -21,6 +21,7 @@ import fastly
import mock import mock
from poppy.provider.fastly import services from poppy.provider.fastly import services
from poppy.transport.pecan.models.request import service
from tests.unit import base from tests.unit import base
@ -59,91 +60,118 @@ class TestServices(base.TestCase):
def test_create_with_create_service_exception(self, service_json): def test_create_with_create_service_exception(self, service_json):
# ASSERTIONS # ASSERTIONS
# create_service # create_service
service_obj = service.load_from_json(service_json)
self.controller.client.create_service.side_effect = fastly.FastlyError( self.controller.client.create_service.side_effect = fastly.FastlyError(
Exception('Creating service failed.')) Exception('Creating service failed.'))
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_with_create_version_exception(self, service_json): def test_create_with_create_version_exception(self, service_json):
self.controller.client.reset_mock() self.controller.client.reset_mock()
self.controller.client.create_service.side_effect = None self.controller.client.create_service.side_effect = None
service_obj = service.load_from_json(service_json)
# create_version # create_version
self.controller.client.create_version.side_effect = fastly.FastlyError( self.controller.client.create_version.side_effect = fastly.FastlyError(
Exception('Creating version failed.')) Exception('Creating version failed.'))
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_with_create_domain_exception(self, service_json): def test_create_with_create_domain_exception(self, service_json):
self.controller.client.reset_mock() self.controller.client.reset_mock()
self.controller.client.create_version.side_effect = None self.controller.client.create_version.side_effect = None
service_obj = service.load_from_json(service_json)
# create domains # create domains
self.controller.client.create_domain.side_effect = fastly.FastlyError( self.controller.client.create_domain.side_effect = fastly.FastlyError(
Exception('Creating domains failed.')) Exception('Creating domains failed.'))
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_with_create_backend_exception(self, service_json): def test_create_with_create_backend_exception(self, service_json):
self.controller.client.reset_mock() self.controller.client.reset_mock()
self.controller.client.create_domain.side_effect = None self.controller.client.create_domain.side_effect = None
service_obj = service.load_from_json(service_json)
# create backends # create backends
self.controller.client.create_backend.side_effect = fastly.FastlyError( self.controller.client.create_backend.side_effect = fastly.FastlyError(
Exception('Creating backend failed.')) Exception('Creating backend failed.'))
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_with_check_domains_exception(self, service_json): def test_create_with_check_domains_exception(self, service_json):
self.controller.client.reset_mock() self.controller.client.reset_mock()
self.controller.client.create_backend.side_effect = None self.controller.client.create_backend.side_effect = None
service_obj = service.load_from_json(service_json)
self.controller.client.check_domains.side_effect = fastly.FastlyError( self.controller.client.check_domains.side_effect = fastly.FastlyError(
Exception('Check_domains failed.')) Exception('Check_domains failed.'))
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json')
def test_create_with_list_versions_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.create_backend.side_effect = None
service_obj = service.load_from_json(service_json)
self.controller.client.list_versions.side_effect = fastly.FastlyError(
Exception('List_versions failed.'))
resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json')
def test_create_with_activate_version_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.create_backend.side_effect = None
service_obj = service.load_from_json(service_json)
self.controller.client.active_version.side_effect = fastly.FastlyError(
Exception('Active_version failed.'))
resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create_with_general_exception(self, service_json): def test_create_with_general_exception(self, service_json):
self.controller.client.reset_mock() self.controller.client.reset_mock()
self.controller.client.check_domains.side_effect = None self.controller.client.check_domains.side_effect = None
service_obj = service.load_from_json(service_json)
# test a general exception # test a general exception
self.controller.client.create_service.side_effect = Exception( self.controller.client.create_service.side_effect = Exception(
'Wild exception occurred.') 'Wild exception occurred.')
resp = self.controller.create(self.service_name, service_json) resp = self.controller.create(service_obj)
self.assertIn('error', resp[self.driver.provider_name]) self.assertIn('error', resp[self.driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create(self, service_json): def test_create(self, service_json):
# instantiate # instantiate
# this case needs to set all return value for each call # this case needs to set all return value for each call
service_obj = service.load_from_json(service_json)
controller = services.ServiceController(self.driver) controller = services.ServiceController(self.driver)
controller.client.create_service.return_value = self.service_instance controller.client.create_service.return_value = self.service_instance
controller.client.create_version.return_value = self.version controller.client.create_version.return_value = self.version
controller.client.list_versions.return_value = [self.version]
controller.client.active_version.return_value = self.version
fastly_fake_domain_check = type(
'FastlyDomain', (object,), {
'name': 'fake_domain.global.prod.fastly.net'})
controller.client.check_domains.return_value = [ controller.client.check_domains.return_value = [
[{ mock.Mock(domain=fastly_fake_domain_check)
"name": "www.example.com",
"comment": "",
"service_id": "<fake_id>",
"version": "1",
"locked": True
},
"global.prod.fastly.net.",
True
]
] ]
resp = controller.create(self.service_name, service_json) resp = controller.create(service_obj)
controller.client.create_service.assert_called_once_with( controller.client.create_service.assert_called_once_with(
controller.current_customer.id, self.service_name) controller.current_customer.id, service_obj.name)
controller.client.create_version.assert_called_once_with( controller.client.create_version.assert_called_once_with(
self.service_instance.id) self.service_instance.id)
@ -151,22 +179,22 @@ class TestServices(base.TestCase):
controller.client.create_domain.assert_any_call( controller.client.create_domain.assert_any_call(
self.service_instance.id, self.service_instance.id,
self.version.number, self.version.number,
service_json['domains'][0]['domain']) service_obj.domains[0].domain)
controller.client.create_domain.assert_any_call( controller.client.create_domain.assert_any_call(
self.service_instance.id, self.service_instance.id,
self.version.number, self.version.number,
service_json['domains'][1]['domain']) service_obj.domains[1].domain)
controller.client.check_domains.assert_called_once_with( controller.client.check_domains.assert_called_once_with(
self.service_instance.id, self.version.number) self.service_instance.id, self.version.number)
controller.client.create_backend.assert_has_any_call( controller.client.create_backend.assert_has_any_call(
self.service_instance.id, 1, self.service_instance.id, 1,
service_json['origins'][0]['origin'], service_obj.origins[0].origin.replace(":", "-"),
service_json['origins'][0]['origin'], service_obj.origins[0].origin,
service_json['origins'][0]['ssl'], service_obj.origins[0].ssl,
service_json['origins'][0]['port']) service_obj.origins[0].port)
self.assertIn('links', resp[self.driver.provider_name]) self.assertIn('links', resp[self.driver.provider_name])
@ -231,10 +259,13 @@ class TestProviderValidation(base.TestCase):
self.controller = services.ServiceController(self.driver) self.controller = services.ServiceController(self.driver)
self.client = mock_client self.client = mock_client
self.service_name = uuid.uuid1() self.service_name = uuid.uuid1()
service_json = {"domains": [{"domain": "parsely.sage.com"}], service_json = {"name": "mocksite.com",
"domains": [{"domain": "parsely.sage.com"}],
"origins": [{"origin": "mockdomain.com", "origins": [{"origin": "mockdomain.com",
"ssl": False, "port": 80}]} "ssl": False, "port": 80}],
self.controller.create(self.service_name, service_json) "flavorRef": "standard"}
service_obj = service.load_from_json(service_json)
self.controller.create(service_obj)
@mock.patch('fastly.FastlyService') @mock.patch('fastly.FastlyService')
def test_get_service_details(self, mock_service): def test_get_service_details(self, mock_service):

View File

@ -1,11 +1,13 @@
{ {
"service_json": { "service_json": {
"name" : "mysite.com",
"domains": [ "domains": [
{"domain": "parsely.sage.com"}, {"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"} {"domain": "rosemary.thyme.net"}
], ],
"origins": [ "origins": [
{"origin": "mockdomain.com", "ssl": false, "port": 80} {"origin": "mockdomain.com", "ssl": false, "port": 80}
] ],
"flavorRef" : "standard"
} }
} }

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc. # Copyright (c) 2014 Rackspace, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -19,6 +20,7 @@ from oslo.config import cfg
from poppy.provider.maxcdn import driver from poppy.provider.maxcdn import driver
from poppy.provider.maxcdn import services from poppy.provider.maxcdn import services
from poppy.transport.pecan.models.request import service
from tests.unit import base from tests.unit import base
@ -45,7 +47,7 @@ fake_maxcdn_client_get_return_value = {u'code': 200,
fake_maxcdn_client_400_return_value = { fake_maxcdn_client_400_return_value = {
u'code': 400, u'code': 400,
u'message': "operation PUT/GET/POST failed due to technical difficulties.." u'message': 'operation PUT/GET/POST failed due to technical difficulties..'
} }
@ -63,8 +65,8 @@ class fake_maxcdn_api_client:
def post(self, url=None, data=None): def post(self, url=None, data=None):
return {u'code': 201, return {u'code': 201,
u'data': { u'data': {
u"pullzone": { u'pullzone': {
u"cdn_url": u"newpullzone1.alias.netdna-cdn.com", u'cdn_url': u'newpullzone1.alias.netdna-cdn.com',
u'name': u'newpullzone1', u'name': u'newpullzone1',
u'id': u'97312' u'id': u'97312'
}}} }}}
@ -72,8 +74,8 @@ class fake_maxcdn_api_client:
def put(self, url=None, params=None): def put(self, url=None, params=None):
return {u'code': 200, return {u'code': 200,
u'data': { u'data': {
u"pullzone": { u'pullzone': {
u"cdn_url": u"newpullzone1.alias.netdna-cdn.com", u'cdn_url': u'newpullzone1.alias.netdna-cdn.com',
u'name': u'newpullzone1', u'name': u'newpullzone1',
u'id': u'97312' u'id': u'97312'
}}} }}}
@ -91,19 +93,13 @@ class TestServices(base.TestCase):
self.conf = cfg.ConfigOpts() self.conf = cfg.ConfigOpts()
@mock.patch.object(driver, 'MAXCDN_OPTIONS', new=MAXCDN_OPTIONS)
def test_init(self):
provider = driver.CDNProvider(self.conf)
# instantiate will get
self.assertRaises(RuntimeError, services.ServiceController, provider)
@mock.patch.object(driver.CDNProvider, 'client', @mock.patch.object(driver.CDNProvider, 'client',
new=fake_maxcdn_api_client()) new=fake_maxcdn_api_client())
def test_get(self): def test_get(self):
new_driver = driver.CDNProvider(self.conf) new_driver = driver.CDNProvider(self.conf)
# instantiate # instantiate
controller = services.ServiceController(new_driver) controller = services.ServiceController(new_driver)
service_name = "test_service_name" service_name = 'test_service_name'
self.assertTrue(controller.get(service_name) is not None) self.assertTrue(controller.get(service_name) is not None)
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
@ -114,8 +110,8 @@ class TestServices(base.TestCase):
# instantiate # instantiate
controller = services.ServiceController(new_driver) controller = services.ServiceController(new_driver)
# test create, everything goes through successfully # test create, everything goes through successfully
service_name = "test_service_name" service_obj = service.load_from_json(service_json)
resp = controller.create(service_name, service_json) resp = controller.create(service_obj)
self.assertIn('id', resp[new_driver.provider_name]) self.assertIn('id', resp[new_driver.provider_name])
self.assertIn('links', resp[new_driver.provider_name]) self.assertIn('links', resp[new_driver.provider_name])
@ -130,26 +126,23 @@ class TestServices(base.TestCase):
driver.client.configure_mock(**{'get.return_value': driver.client.configure_mock(**{'get.return_value':
fake_maxcdn_client_get_return_value fake_maxcdn_client_get_return_value
}) })
service_obj = service.load_from_json(service_json)
service_name = "test_service_name"
controller_with_create_exception = services.ServiceController(driver) controller_with_create_exception = services.ServiceController(driver)
controller_with_create_exception.client.configure_mock(**{ controller_with_create_exception.client.configure_mock(**{
"post.side_effect": 'post.side_effect':
RuntimeError('Creating service mysteriously failed.')}) RuntimeError('Creating service mysteriously failed.')})
resp = controller_with_create_exception.create( resp = controller_with_create_exception.create(
service_name, service_obj)
service_json)
self.assertIn('error', resp[driver.provider_name]) self.assertIn('error', resp[driver.provider_name])
controller_with_create_exception.client.reset_mock() controller_with_create_exception.client.reset_mock()
controller_with_create_exception.client.configure_mock(**{ controller_with_create_exception.client.configure_mock(**{
'post.side_effect': None, 'post.side_effect': None,
"post.return_value": fake_maxcdn_client_400_return_value 'post.return_value': fake_maxcdn_client_400_return_value
}) })
resp = controller_with_create_exception.create( resp = controller_with_create_exception.create(
service_name, service_obj)
service_json)
self.assertIn('error', resp[driver.provider_name]) self.assertIn('error', resp[driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
@ -160,8 +153,9 @@ class TestServices(base.TestCase):
# instantiate # instantiate
controller = services.ServiceController(new_driver) controller = services.ServiceController(new_driver)
# test create, everything goes through successfully # test create, everything goes through successfully
service_name = "test_service_name" service_obj = service.load_from_json(service_json)
resp = controller.update(service_name, service_json) service_name = 'test_service_name'
resp = controller.update(service_name, service_obj)
self.assertIn('id', resp[new_driver.provider_name]) self.assertIn('id', resp[new_driver.provider_name])
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
@ -176,11 +170,11 @@ class TestServices(base.TestCase):
fake_maxcdn_client_get_return_value fake_maxcdn_client_get_return_value
}) })
service_name = "test_service_name" service_name = 'test_service_name'
controller_with_update_exception = services.ServiceController(driver) controller_with_update_exception = services.ServiceController(driver)
controller_with_update_exception.client.configure_mock(**{ controller_with_update_exception.client.configure_mock(**{
"put.side_effect": 'put.side_effect':
RuntimeError('Updating service mysteriously failed.')}) RuntimeError('Updating service mysteriously failed.')})
resp = controller_with_update_exception.update( resp = controller_with_update_exception.update(
service_name, service_name,
@ -189,12 +183,13 @@ class TestServices(base.TestCase):
controller_with_update_exception.client.reset_mock() controller_with_update_exception.client.reset_mock()
controller_with_update_exception.client.configure_mock(**{ controller_with_update_exception.client.configure_mock(**{
"put.side_effect": None, 'put.side_effect': None,
"put.return_value": fake_maxcdn_client_400_return_value 'put.return_value': fake_maxcdn_client_400_return_value
}) })
service_obj = service.load_from_json(service_json)
resp = controller_with_update_exception.update( resp = controller_with_update_exception.update(
service_name, service_name,
service_json) service_obj)
self.assertIn('error', resp[driver.provider_name]) self.assertIn('error', resp[driver.provider_name])
@mock.patch.object(driver.CDNProvider, 'client', @mock.patch.object(driver.CDNProvider, 'client',
@ -204,7 +199,7 @@ class TestServices(base.TestCase):
# instantiate # instantiate
controller = services.ServiceController(new_driver) controller = services.ServiceController(new_driver)
# test create, everything goes through successfully # test create, everything goes through successfully
service_name = "test_service_name" service_name = 'test_service_name'
resp = controller.delete(service_name) resp = controller.delete(service_name)
self.assertIn('id', resp[new_driver.provider_name]) self.assertIn('id', resp[new_driver.provider_name])
@ -219,11 +214,11 @@ class TestServices(base.TestCase):
fake_maxcdn_client_get_return_value fake_maxcdn_client_get_return_value
}) })
service_name = "test_service_name" service_name = 'test_service_name'
controller_with_delete_exception = services.ServiceController(driver) controller_with_delete_exception = services.ServiceController(driver)
controller_with_delete_exception.client.configure_mock(**{ controller_with_delete_exception.client.configure_mock(**{
"delete.side_effect": 'delete.side_effect':
RuntimeError('Deleting service mysteriously failed.')}) RuntimeError('Deleting service mysteriously failed.')})
resp = controller_with_delete_exception.delete(service_name) resp = controller_with_delete_exception.delete(service_name)
self.assertEqual(resp[driver.provider_name]['error'], self.assertEqual(resp[driver.provider_name]['error'],
@ -231,9 +226,57 @@ class TestServices(base.TestCase):
controller_with_delete_exception.client.reset_mock() controller_with_delete_exception.client.reset_mock()
controller_with_delete_exception.client.configure_mock(**{ controller_with_delete_exception.client.configure_mock(**{
"delete.side_effect": None, 'delete.side_effect': None,
"delete.return_value": fake_maxcdn_client_400_return_value 'delete.return_value': fake_maxcdn_client_400_return_value
}) })
resp = controller_with_delete_exception.delete(service_name) resp = controller_with_delete_exception.delete(service_name)
self.assertEqual(resp[driver.provider_name]['error'], self.assertEqual(resp[driver.provider_name]['error'],
'failed to delete service') 'failed to delete service')
@ddt.data('good-service-name', 'yahooservice')
@mock.patch.object(driver.CDNProvider, 'client',
new=fake_maxcdn_api_client())
def test_map_service_name_no_hash(self, service_name):
maxcdn_driver = driver.CDNProvider(self.conf)
controller = services.ServiceController(maxcdn_driver)
self.assertEqual(controller._map_service_name(service_name),
service_name)
@ddt.data(u'www.düsseldorf-Lörick.com', 'yahoo%_notvalid')
@mock.patch.object(driver.CDNProvider, 'client',
new=fake_maxcdn_api_client())
def test_map_service_name_with_hash(self, service_name):
maxcdn_driver = driver.CDNProvider(self.conf)
controller = services.ServiceController(maxcdn_driver)
# test hashed
self.assertNotEqual(controller._map_service_name(service_name),
service_name)
# test deterministic-ity
self.assertEqual(controller._map_service_name(service_name),
controller._map_service_name(service_name))
@mock.patch.object(driver.CDNProvider, 'client',
new=fake_maxcdn_api_client())
def test_current_customer(self):
new_driver = driver.CDNProvider(self.conf)
# instantiate
controller = services.ServiceController(new_driver)
self.assertTrue(controller.current_customer['name'] ==
u'<My_fake_company_alias>')
@mock.patch('poppy.provider.maxcdn.driver.CDNProvider.client')
@mock.patch('poppy.provider.maxcdn.driver.CDNProvider')
def test_current_customer_error(self, mock_controllerclient, mock_driver):
# test create with exceptions
driver = mock_driver()
driver.attach_mock(mock_controllerclient, 'client')
driver.client.configure_mock(**{'get.return_value':
fake_maxcdn_client_400_return_value
})
controller = services.ServiceController(mock_driver)
# for some reason self.assertRaises doesn't work
try:
controller.current_customer
except RuntimeError as e:
self.assertTrue(str(e) == "Get maxcdn current customer failed...")

View File

@ -1,11 +1,13 @@
{ {
"service_json": { "service_json": {
"name" : "mysite.com",
"domains": [ "domains": [
{"domain": "parsely.sage.com"}, {"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"} {"domain": "rosemary.thyme.net"}
], ],
"origins": [ "origins": [
{"origin": "mockdomain.com", "ssl": false, "port": 80} {"origin": "mockdomain.com", "ssl": false, "port": 80}
] ],
"flavorRef" : "standard"
} }
} }

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc. # Copyright (c) 2014 Rackspace, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -19,6 +20,7 @@ import ddt
import mock import mock
from poppy.provider.mock import services from poppy.provider.mock import services
from poppy.transport.pecan.models.request import service
from tests.unit import base from tests.unit import base
@ -34,7 +36,8 @@ class MockProviderServicesTest(base.TestCase):
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_update(self, service_json): def test_update(self, service_json):
response = self.sc.update(self.test_provider_service_id, service_json) service_obj = service.load_from_json(service_json)
response = self.sc.update(self.test_provider_service_id, service_obj)
self.assertTrue(response is not None) self.assertTrue(response is not None)
def test_delete(self): def test_delete(self):
@ -47,5 +50,14 @@ class MockProviderServicesTest(base.TestCase):
@ddt.file_data('data_service.json') @ddt.file_data('data_service.json')
def test_create(self, service_json): def test_create(self, service_json):
response = self.sc.create("mock_name", service_json) service_obj = service.load_from_json(service_json)
response = self.sc.create(service_obj)
self.assertTrue(response is not None) self.assertTrue(response is not None)
@ddt.data("my_mock_service.com", u'www.düsseldorf-Lörick.com')
def test__map_service_name(self, service_name):
self.assertTrue(self.sc._map_service_name(service_name),
service_name)
def test_current_customer(self):
self.assertTrue(self.sc.current_customer is None)

View File

@ -1,7 +1,7 @@
{ {
"using_all_fields": [ "using_all_fields": [
{ {
"name" : "mocksite", "service_name" : "mocksite",
"domains": [ "domains": [
"{\"domain\": \"www.mocksite.com\"}", "{\"domain\": \"www.mocksite.com\"}",
"{\"domain\": \"blog.mocksite.com\"}" "{\"domain\": \"blog.mocksite.com\"}"
@ -12,6 +12,7 @@
"caching": [ "caching": [
"{\"name\": \"default\", \"ttl\": 3600}" "{\"name\": \"default\", \"ttl\": 3600}"
], ],
"flavor_id" : "standard",
"restrictions": [ "restrictions": [
"{\"rules\": [{\"http_host\": \"www.mocksite.com\", \"name\": \"mocksite.com\"}], \"name\": \"website only\"}" "{\"rules\": [{\"http_host\": \"www.mocksite.com\", \"name\": \"mocksite.com\"}], \"name\": \"website only\"}"
] ]

View File

@ -1,7 +1,7 @@
{ {
"using_all_fields": [ "using_all_fields": [
{ {
"name": "mocksite", "service_name": "mocksite",
"domain": [ "domain": [
"{\"domain\": \"www.mocksite.com\"}", "{\"domain\": \"www.mocksite.com\"}",
"{\"domain\": \"blog.mocksite.com\"}" "{\"domain\": \"blog.mocksite.com\"}"
@ -9,6 +9,7 @@
"origin": [ "origin": [
"{\"origin\": \"mocksite.com\", \"ssl\": false, \"port\": 80}" "{\"origin\": \"mocksite.com\", \"ssl\": false, \"port\": 80}"
], ],
"flavor_id" : "standard",
"caching": [ "caching": [
"{\"name\": \"default\", \"ttl\": 3600}" "{\"name\": \"default\", \"ttl\": 3600}"
], ],
@ -17,7 +18,7 @@
] ]
}, },
{ {
"name": "another_mocksite", "service_name": "another_mocksite",
"domain": [ "domain": [
"{\"domain\": \"www.mocksite.co.uk\"}", "{\"domain\": \"www.mocksite.co.uk\"}",
"{\"domain\": \"blog.mocksite.co.uk\"}" "{\"domain\": \"blog.mocksite.co.uk\"}"
@ -25,6 +26,7 @@
"origin": [ "origin": [
"{\"origin\": \"mocksite.co.uk\", \"ssl\": false, \"port\": 80}" "{\"origin\": \"mocksite.co.uk\", \"ssl\": false, \"port\": 80}"
], ],
"flavor_id" : "premium",
"caching":[ "caching":[
"{\"name\": \"default\", \"ttl\": 3600}" "{\"name\": \"default\", \"ttl\": 3600}"
], ],

View File

@ -1,9 +1,9 @@
{ {
"provider_details": "provider_details":
{ {
"MaxCDN": "{\"id\": 11942, \"access_url\": \"mypullzone.netdata.com\"}", "MaxCDN": "{\"provider_service_id\": 11942, \"access_urls\": [\"mypullzone.netdata.com\"]}",
"Mock": "{\"id\": 73242, \"access_url\": \"mycdn.mock.com\"}", "Mock": "{\"provider_service_id\": 73242, \"access_urls\": [\"mycdn.mock.com\"]}",
"CloudFront": "{\"id\": \"5ABC892\", \"access_url\": \"cf123.cloudcf.com\"}", "CloudFront": "{\"provider_service_id\": \"5ABC892\", \"access_urls\": [\"cf123.cloudcf.com\"]}",
"Fastly": "{\"id\": 3488, \"access_url\": \"mockcf123.fastly.prod.com\"}" "Fastly": "{\"provider_service_id\": 3488, \"access_urls\": [\"mockcf123.fastly.prod.com\"]}"
} }
} }

View File

@ -20,6 +20,7 @@ import ddt
import mock import mock
from oslo.config import cfg from oslo.config import cfg
from poppy.model import flavor as model_flavor
from poppy.storage.cassandra import driver from poppy.storage.cassandra import driver
from poppy.storage.cassandra import flavors from poppy.storage.cassandra import flavors
from poppy.transport.pecan.models.request import flavor from poppy.transport.pecan.models.request import flavor
@ -70,6 +71,10 @@ class CassandraStorageFlavorsTests(base.TestCase):
@mock.patch.object(flavors.FlavorsController, 'session') @mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute') @mock.patch.object(cassandra.cluster.Session, 'execute')
def test_add_flavor(self, value, mock_session, mock_execute): def test_add_flavor(self, value, mock_session, mock_execute):
self.fc.get = mock.Mock(side_effect=LookupError(
"More than one flavor/no record was retrieved."
))
# mock the response from cassandra # mock the response from cassandra
mock_execute.execute.return_value = value mock_execute.execute.return_value = value
@ -79,6 +84,20 @@ class CassandraStorageFlavorsTests(base.TestCase):
self.assertEqual(actual_response, None) self.assertEqual(actual_response, None)
@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_exist(self, value, mock_session, mock_execute):
self.fc.get = mock.Mock(return_value=model_flavor.Flavor(
flavor_id=value['id']
))
# mock the response from cassandra
mock_execute.execute.return_value = value
new_flavor = flavor.load_from_json(value)
self.assertRaises(ValueError, self.fc.add, new_flavor)
@ddt.file_data('data_list_flavors.json') @ddt.file_data('data_list_flavors.json')
@mock.patch.object(flavors.FlavorsController, 'session') @mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute') @mock.patch.object(cassandra.cluster.Session, 'execute')

View File

@ -13,12 +13,14 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
import cassandra import cassandra
import ddt import ddt
import mock import mock
from oslo.config import cfg from oslo.config import cfg
from poppy.model import service from poppy.model.helpers import provider_details
from poppy.storage.cassandra import driver from poppy.storage.cassandra import driver
from poppy.storage.cassandra import services from poppy.storage.cassandra import services
from poppy.transport.pecan.models.request import service as req_service from poppy.transport.pecan.models.request import service as req_service
@ -63,7 +65,7 @@ class CassandraStorageServiceTests(base.TestCase):
# mock the response from cassandra # mock the response from cassandra
mock_execute.execute.return_value = [] mock_execute.execute.return_value = []
self.assertRaises(ValueError, self.sc.get, self.assertRaises(LookupError, self.sc.get,
self.project_id, self.service_name) self.project_id, self.service_name)
@ddt.file_data('../data/data_create_service.json') @ddt.file_data('../data/data_create_service.json')
@ -71,10 +73,8 @@ class CassandraStorageServiceTests(base.TestCase):
@mock.patch.object(cassandra.cluster.Session, 'execute') @mock.patch.object(cassandra.cluster.Session, 'execute')
def test_create_service(self, value, mock_session, mock_execute): def test_create_service(self, value, mock_session, mock_execute):
value.update({'name': self.service_name}) value.update({'name': self.service_name})
request_service = req_service.load_from_json(value) service_obj = req_service.load_from_json(value)
service_obj = service.Service.init_from_dict(request_service.to_dict()) responses = self.sc.create(self.project_id, service_obj)
responses = self.sc.create(self.project_id, self.service_name,
service_obj)
# Expect the response to be None as there are no providers passed # Expect the response to be None as there are no providers passed
# into the driver to respond to this call # into the driver to respond to this call
@ -82,6 +82,18 @@ class CassandraStorageServiceTests(base.TestCase):
# TODO(amitgandhinz): need to validate the create to cassandra worked. # TODO(amitgandhinz): need to validate the create to cassandra worked.
@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_exist(self, value, mock_session, mock_execute):
value.update({'name': self.service_name})
service_obj = req_service.load_from_json(value)
self.sc.get = mock.Mock(return_value=service_obj)
self.assertRaises(ValueError,
self.sc.create,
self.project_id, service_obj)
@ddt.file_data('data_list_services.json') @ddt.file_data('data_list_services.json')
@mock.patch.object(services.ServicesController, 'session') @mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute') @mock.patch.object(cassandra.cluster.Session, 'execute')
@ -133,6 +145,47 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertTrue("CloudFront" in actual_response) self.assertTrue("CloudFront" in actual_response)
self.assertTrue("Fastly" in actual_response) self.assertTrue("Fastly" in actual_response)
@ddt.file_data('data_provider_details.json')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_update_provider_details(self, provider_details_json,
mock_session, mock_execute):
provider_details_dict = {}
for k, v in provider_details_json.items():
provider_detail_dict = json.loads(v)
provider_details_dict[k] = provider_details.ProviderDetail(
provider_service_id=(
provider_detail_dict["provider_service_id"]),
access_urls=provider_detail_dict["access_urls"])
# mock the response from cassandra
mock_execute.execute.return_value = None
self.sc.update_provider_details(
self.project_id,
self.service_name,
provider_details_dict)
# this is for update_provider_details unittest code coverage
arg_provider_details_dict = {}
for provider_name in provider_details_dict:
arg_provider_details_dict[provider_name] = json.dumps({
"id": provider_details_dict[provider_name].provider_service_id,
"access_urls": (
provider_details_dict[provider_name].access_urls),
"status": provider_details_dict[provider_name].status,
"name": provider_details_dict[provider_name].name,
"error_info": None
})
args = {
'project_id': self.project_id,
'service_name': self.service_name,
'provider_details': arg_provider_details_dict
}
mock_execute.execute.assert_called_once_with(
services.CQL_UPDATE_PROVIDER_DETAILS, args)
@mock.patch.object(cassandra.cluster.Cluster, 'connect') @mock.patch.object(cassandra.cluster.Cluster, 'connect')
def test_session(self, mock_service_database): def test_session(self, mock_service_database):
session = self.sc.session session = self.sc.session

View File

@ -1,5 +1,6 @@
{ {
"using_all_fields": { "using_all_fields": {
"name": "mocksite.com",
"domains": [ "domains": [
{"domain": "test.mocksite.com" }, {"domain": "test.mocksite.com" },
{"domain": "blog.mocksite.com"} {"domain": "blog.mocksite.com"}
@ -11,6 +12,7 @@
"ssl": false "ssl": false
} }
], ],
"flavorRef": "standard",
"caching": [ "caching": [
{ {
"name": "default", "name": "default",

View File

@ -45,7 +45,7 @@ class MockDBStorageFlavorsTests(base.TestCase):
actual_response = self.fc.get(self.flavor_id) actual_response = self.fc.get(self.flavor_id)
self.assertEqual(actual_response, None) self.assertEqual(actual_response.flavor_id, "standard")
@mock.patch.object(flavors.FlavorsController, 'session') @mock.patch.object(flavors.FlavorsController, 'session')
@ddt.file_data('../data/data_create_flavor.json') @ddt.file_data('../data/data_create_flavor.json')
@ -61,7 +61,8 @@ class MockDBStorageFlavorsTests(base.TestCase):
actual_response = self.fc.list() actual_response = self.fc.list()
# confirm the correct number of results are returned # confirm the correct number of results are returned
self.assertEqual(actual_response, []) self.assertEqual(len(actual_response), 1)
self.assertEqual(actual_response[0].flavor_id, "standard")
@mock.patch.object(flavors.FlavorsController, 'session') @mock.patch.object(flavors.FlavorsController, 'session')
def test_delete_flavor(self, mock_session): def test_delete_flavor(self, mock_session):