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]
exclude_lines =
pragma: no cover
# Don't complain if tests don't hit defensive assertion code
raise NotImplementedError

View File

@ -52,4 +52,13 @@ keyspace = poppy
database = poppy
[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):
def create(self, ext, service_name, service_json):
return ext.obj.service_controller.create(service_name, service_json)
def create(self, ext, service_obj):
return ext.obj.service_controller.create(service_obj)
def update(self, ext, provider_details, service_json):
try:
@ -28,7 +28,7 @@ class ProviderWrapper(object):
raise errors.BadProviderDetail(
"No provider detail information."
"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(
provider_service_id,
service_json)
@ -40,5 +40,5 @@ class ProviderWrapper(object):
raise errors.BadProviderDetail(
"No provider detail information."
"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)

View File

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

View File

@ -14,6 +14,7 @@
# limitations under the License.
from poppy.manager import base
from poppy.model.helpers import provider_details
class DefaultServicesController(base.ServicesController):
@ -21,45 +22,91 @@ class DefaultServicesController(base.ServicesController):
def __init__(self, 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):
return self.storage.list(project_id, marker, limit)
return self.storage_controller.list(project_id, marker, limit)
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):
self.storage.create(
project_id,
service_name,
service_obj)
def create(self, project_id, service_obj):
try:
flavor = self.flavor_controller.get(service_obj.flavorRef)
# raise a lookup error if the flavor is not found
except LookupError as e:
raise e
# TODO(tonytan4ever): need to update provider_detail info in storage
return self._driver.providers.map(
self.provider_wrapper.create,
service_name,
service_obj)
providers = [p.provider_id for p in flavor.providers]
service_name = service_obj.name
try:
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):
self.storage.update(
self.storage_controller.update(
project_id,
service_name,
service_obj
)
provider_details = self.storage.get_provider_details(project_id,
service_name)
provider_details = self.storage_controller.get_provider_details(
project_id,
service_name)
return self._driver.providers.map(
self.provider_wrapper.update,
provider_details,
service_obj)
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,
service_name)
provider_details = self.storage_controller.get_provider_details(
project_id,
service_name)
return self._driver.providers.map(
self.provider_wrapper.delete,
provider_details)

View File

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

View File

@ -21,27 +21,33 @@ class ProviderDetail(object):
'''ProviderDetail object for each provider.'''
def __init__(self, id=None, access_url=None, status=u"unknown", name=None):
self._id = id
self._access_url = access_url
def __init__(self, provider_service_id=None, access_urls=[],
status=u"unknown", name=None, error_info=None):
self._provider_service_id = provider_service_id
self._access_urls = access_urls
self._status = status
self._name = name
self._error_info = error_info
@property
def id(self):
return self._id
def provider_service_id(self):
return self._provider_service_id
@id.setter
def id(self, value):
self._id = value
@provider_service_id.setter
def provider_service_id(self, value):
self._provider_service_id = value
@property
def access_url(self):
return self._access_url
def access_urls(self):
return self._access_urls
@access_url.setter
def access_url(self, value):
self._access_url = value
@property
def name(self):
return self._name
@access_urls.setter
def access_urls(self, value):
self._access_urls = value
@property
def status(self):
@ -57,3 +63,11 @@ class ProviderDetail(object):
value,
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,
domains,
origins,
flavorRef,
caching=[],
restrictions=[]):
self._name = name
self._domains = domains
self._origins = origins
self._flavorRef = flavorRef
self._caching = caching
self._restrictions = restrictions
self._status = u'unknown'
@ -58,6 +60,14 @@ class Service(common.DictSerializableModel):
def origins(self, value):
self._origins = value
@property
def flavorRef(self):
return self._flavorRef
@flavorRef.setter
def flavorRef(self, value):
self._flavorRef = value
@property
def caching(self):
return self._caching
@ -92,7 +102,7 @@ class Service(common.DictSerializableModel):
)
@classmethod
def init_from_dict(cls, dict):
def init_from_dict(cls, input_dict):
"""Construct a model instance from a dictionary.
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,
use to_dict.
"""
o = cls("unnamed", [], [])
o.from_dict(dict)
o = cls('unnamed', [], [], 'unnamed')
o.from_dict(input_dict)
return o

View File

@ -31,16 +31,19 @@ class Responder(object):
return {
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 {
self.provider: {
"id": provider_service_id,
"links": links
}
self.provider: provider_response
}
def updated(self, provider_service_id):

View File

@ -45,3 +45,21 @@ class ServicesControllerBase(controller.ProviderControllerBase):
def get(self, service_name):
"""Get details of the service, as stored by the provider."""
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):
return 'CloudFront'
@property
def client(self):
return self.cloudfront_client

View File

@ -15,8 +15,12 @@
from boto import cloudfront
from poppy.common import decorators
from poppy.openstack.common import log
from poppy.provider import base
LOG = log.getLogger(__name__)
class ServiceController(base.ServiceBase):
@ -28,36 +32,46 @@ class ServiceController(base.ServiceBase):
super(ServiceController, self).__init__(driver)
self.driver = driver
self.current_customer = self.client.get_current_customer()
# TODO(obulpathi): get service
def get(self, service_name):
return {'domains': [], 'origins': [], 'caching': []}
# TODo(obulpathi): update service
def update(self, service_name, service_json):
def update(self, service_name, service_obj):
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
origin = service_json['origins'][0]
origin = service_obj.origins[0]
LOG.info('Start creating cloudfront config for %s' % service_obj.name)
try:
# Create the origin for this domain:
aws_origin = cloudfront.origin.CustomOrigin(
dns_name=origin['origin'],
http_port=origin['port'],
https_port=origin['ssl'],
dns_name=origin.origin,
http_port=origin.port,
# cannot specify ssl like this yet, CF takes a port #
# https_port=origin.ssl,
origin_protocol_policy='match-viewer')
distribution = self.client.create_distribution(
origin=aws_origin,
enabled=True)
if distribution.status == 'InProgress':
status = 'in_progress'
elif distribution.status == 'Deployed':
status = 'deployed'
else:
status = 'unknown'
except cloudfront.exception.CloudFrontServerError as e:
return self.responder.failed(str(e))
except Exception as e:
return self.responder.failed(str(e))
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):
# NOTE(obulpathi): distribution_id is the equivalent of service_name
@ -68,3 +82,8 @@ class ServiceController(base.ServiceBase):
return self.responder.deleted(distribution_id)
except Exception as 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
from poppy.common import decorators
from poppy.provider import base
@ -28,47 +29,57 @@ class ServiceController(base.ServiceBase):
super(ServiceController, self).__init__(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)
def create(self, service_name, service_json):
def create(self, service_obj):
try:
# Create a new service
service = self.client.create_service(self.current_customer.id,
service_name)
service_obj.name)
# Create a new version of the service.
service_version = self.client.create_version(service.id)
# 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,
service_version.number,
domain["domain"])
domain.domain)
# TODO(tonytan4ever): what if check_domains fail ?
# For right now we fail the who create process.
# But do we want to fail the whole service create ? probably not.
# 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'}
for domain_dict, suffix, enabled in
self.client.check_domains(service.id,
service_version.number)
if enabled]
for domain_check in domain_checks]
for origin in service_json["origins"]:
for origin in service_obj.origins:
# Create the origins for this domain
self.client.create_backend(service.id,
service_version.number,
origin["origin"],
origin["origin"],
origin["ssl"],
origin["port"]
origin.origin.replace(":", "-"),
origin.origin,
origin.ssl,
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)
except fastly.FastlyError:
@ -122,3 +133,7 @@ class ServiceController(base.ServiceBase):
return self.responder.failed("failed to GET service")
except Exception:
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
# limitations under the License.
import hashlib
import re
from poppy.common import decorators
from poppy.provider import base
MAXCDN_NAMING_REGEX = re.compile('^[a-zA-Z0-9-]{3,32}$')
class ServiceController(base.ServiceBase):
@ -32,21 +38,17 @@ class ServiceController(base.ServiceBase):
self.driver = driver
# This returns the current customer account info
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):
def update(self, pullzone_id, service_obj):
'''MaxCDN update.
manager needs to pass in pullzone id to delete.
'''
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'
% pullzone_id,
params=service_json)
params=service_obj.to_dict())
if update_response['code'] != 200:
return self.responder.failed('failed to update service')
return self.responder.updated(
@ -55,19 +57,21 @@ class ServiceController(base.ServiceBase):
# this exception branch will most likely for a network failure
return self.responder.failed('failed to update service')
def create(self, service_name, service_json):
def create(self, service_obj):
'''MaxCDN create.
manager needs to pass in a service name to create.
'''
try:
# 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={
'name': service_name,
'url': origin['origin'],
'port': origin.get('port', 80),
'sslshared': 1 if origin['ssl'] else 0,
'name': self._map_service_name(service_obj.name),
# TODO(tonytan4ever): maxcdn takes origin with
# 'http://' or 'https://' prefix.
'url': ''.join([origin_prefix, origin.origin]),
'port': getattr(origin, 'port', 80),
})
if create_response['code'] != 201:
@ -77,12 +81,12 @@ class ServiceController(base.ServiceBase):
# Add custom domains to this service
links = []
for domain in service_json['domains']:
custom_domain_response = self.client.post(
for domain in service_obj.domains:
self.client.post(
'/zones/pull/%s/customdomains.json'
% created_zone_info['id'],
{'custom_domain': domain['domain']})
links.append(custom_domain_response)
{'custom_domain': domain.domain})
links.append({'href': domain.domain, "rel": "access_url"})
# TODO(tonytan4ever): What if it fails during add domains ?
return self.responder.created(created_zone_info['id'], links)
except Exception:
@ -108,3 +112,23 @@ class ServiceController(base.ServiceBase):
def get(self, service_name):
'''Get details of the service, as stored by the provider.'''
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
from poppy.common import decorators
from poppy.openstack.common import log
from poppy.provider import base
@ -26,17 +27,23 @@ class ServiceController(base.ServiceBase):
def __init__(self, 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)
def create(self, service_name, service_json):
LOG.debug("Mock creating service: %s" % service_name)
def create(self, service_obj):
# We generate a fake id here
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):
return self.responder.deleted(provider_service_id)
def get(self, service_name):
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
def get_provider_details(self, project_id, service_name):
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):
return flavors[0]
else:
raise LookupError("More than one flavor was retrieved.")
raise LookupError("More than one flavor/no record was retrieved.")
def add(self, 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)
for p in flavor.providers)

View File

@ -52,16 +52,21 @@ CQL_DELETE_SERVICE = '''
CQL_CREATE_SERVICE = '''
INSERT INTO services (project_id,
service_name,
flavor_id,
domains,
origins,
caching_rules,
restrictions)
restrictions,
provider_details
)
VALUES (%(project_id)s,
%(service_name)s,
%(flavor_id)s,
%(domains)s,
%(origins)s,
%(caching_rules)s,
%(restrictions)s)
%(restrictions)s,
%(provider_details)s)
'''
CQL_UPDATE_DOMAINS = '''
@ -94,6 +99,12 @@ CQL_GET_PROVIDER_DETAILS = '''
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):
@ -115,7 +126,7 @@ class ServicesController(base.ServicesController):
# TODO(amitgandhinz): return services instead once its formatted.
services = []
for r in results:
name = r.get("name", "unnamed")
name = r.get("service_name")
origins = r.get("origins", [])
domains = r.get("domains", [])
origins = [origin.Origin(json.loads(o)['origin'],
@ -123,7 +134,8 @@ class ServicesController(base.ServicesController):
json.loads(o).get("ssl", False))
for o in origins]
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
def get(self, project_id, service_name):
@ -135,12 +147,12 @@ class ServicesController(base.ServicesController):
results = self.session.execute(CQL_GET_SERVICE, args)
if len(results) != 1:
raise ValueError("No service or multiple service found: %s"
% service_name)
raise LookupError("No service or multiple service found: %s"
% service_name)
services = []
for r in results:
name = r.get("name", "unnamed")
name = r.get("service_name")
origins = r.get("origins", [])
domains = r.get("domains", [])
origins = [origin.Origin(json.loads(o)['origin'],
@ -148,12 +160,23 @@ class ServicesController(base.ServicesController):
json.loads(o).get("ssl", False))
for o in origins]
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]
def create(self, project_id, service_name, service_obj):
def create(self, project_id, service_obj):
# 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())
for domain in service_obj.domains]
origins = [json.dumps(origin.to_dict())
@ -167,10 +190,13 @@ class ServicesController(base.ServicesController):
args = {
'project_id': project_id,
'service_name': service_name,
'flavor_id': service_obj.flavorRef,
'domains': domains,
'origins': origins,
'caching_rules': caching_rules,
'restrictions': restrictions
'restrictions': restrictions,
# TODO(tonytan4ever): Incorporate flavor change.
'provider_details': {}
}
self.session.execute(CQL_CREATE_SERVICE, args)
@ -206,12 +232,37 @@ class ServicesController(base.ServicesController):
results = {}
for provider_name in exec_results[0]:
provider_detail_dict = json.loads(exec_results[0][provider_name])
id = provider_detail_dict.get("id", None)
access_url = provider_detail_dict.get("access_url", None)
pr_id = provider_detail_dict.get("provider_service_id", None)
access_urls = provider_detail_dict.get("access_urls", None)
status = provider_detail_dict.get("status", u'unknown')
error_info = provider_detail_dict.get("error_info", None)
provider_detail_obj = provider_details.ProviderDetail(
id=id,
access_url=access_url,
status=status)
provider_service_id=pr_id,
access_urls=access_urls,
status=status,
error_info=error_info)
results[provider_name] = provider_detail_obj
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
# limitations under the License.
from poppy.model import flavor
from poppy.storage import base
@ -23,10 +24,24 @@ class FlavorsController(base.FlavorsController):
return self._driver.database
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):
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):
pass

View File

@ -42,6 +42,7 @@ class ServicesController(base.ServicesController):
"ssl": False
}
],
"flavorRef": "standard",
"caching": [
{"name": "default", "ttl": 3600},
{
@ -76,12 +77,14 @@ class ServicesController(base.ServicesController):
services_result = []
for r in services:
name = r.get("name", "unnamed")
name = r.get("name")
origins = r.get("origins", [])
domains = r.get("domains", [])
origins = [origin.Origin(d) for d in origins]
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
@ -101,6 +104,7 @@ class ServicesController(base.ServicesController):
"ssl": False
}
],
"flavorRef": "standard",
"caching": [
{"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", [])
domains = service_dict.get("domains", [])
origins = [origin.Origin(d) for d in origins]
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
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 ""
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):
return {
"MaxCDN": provider_details.ProviderDetail(
id=11942,
provider_service_id=11942,
name='my_service_name',
access_url='my_service_name'
'.mycompanyalias.netdna-cdn.com'),
access_urls=['my_service_name'
'.mycompanyalias.netdna-cdn.com']),
"Fastly": provider_details.ProviderDetail(
id=3488,
provider_service_id=3488,
name="my_service_name",
access_url='my_service_name'
'.global.prod.fastly.net'),
access_urls=['my_service_name'
'.global.prod.fastly.net']),
"CloudFront": provider_details.ProviderDetail(
id=5892,
access_url='my_service_name'
'.gibberish.amzcf.com'),
provider_service_id=5892,
access_urls=['my_service_name'
'.gibberish.amzcf.com']),
"Mock": provider_details.ProviderDetail(
id="73242",
access_url='my_service_name.mock.com')}
provider_service_id="73242",
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')
def get_one(self, flavor_id):
flavors_controller = self.driver.manager.flavors_controller
result = flavors_controller.get(flavor_id)
if result is not None:
print (result)
print('done')
return flavor_response.Model(result, pecan.request)
try:
result = flavors_controller.get(flavor_id)
except LookupError as e:
pecan.abort(404, detail=str(e))
else:
pecan.response.status = 404
return flavor_response.Model(result, pecan.request)
@pecan.expose('json')
@decorators.validate(

View File

@ -17,6 +17,7 @@ import json
import pecan
from poppy.common import uri
from poppy.transport.pecan.controllers import base
from poppy.transport.pecan.models.request import service as req_service_model
from poppy.transport.pecan.models.response import link
@ -58,20 +59,28 @@ class ServicesController(base.Controller):
@pecan.expose('json')
@decorators.validate(
service_name=rule.Rule(
helpers.is_valid_service_name(),
helpers.abort_with_message),
request=rule.Rule(
helpers.json_matches_schema(
service.ServiceSchema.get_schema("service", "PUT")),
service.ServiceSchema.get_schema("service", "POST")),
helpers.abort_with_message,
stoplight_helpers.pecan_getter))
def put(self, service_name):
def post(self):
services_controller = self._driver.manager.services_controller
service_json_dict = json.loads(pecan.request.body.decode('utf-8'))
service_obj = req_service_model.load_from_json(service_json_dict)
return services_controller.create(self.project_id, service_name,
service_obj)
service_name = service_json_dict.get("name", None)
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')
def delete(self, service_name):

View File

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

View File

@ -17,7 +17,7 @@ from poppy.model.helpers import origin
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)
ssl = json_data.get("ssl", False)
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):
name = json_data.get("name", "unnamed")
name = json_data.get("name")
origins = json_data.get("origins", [])
domains = json_data.get("domains", [])
flavorRef = json_data.get("flavorRef")
origins = [origin.load_from_json(d) for d in origins]
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:
import ordereddict as collections
except ImportError:
import collections
except ImportError: # pragma: no cover
import collections # pragma: no cover
class Model(collections.OrderedDict):

View File

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

View File

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

View File

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

View File

@ -15,8 +15,8 @@
try:
import ordereddict as collections
except ImportError:
import collections
except ImportError: # pragma: no cover
import collections # pragma: no cover
from poppy.transport.pecan.models.response import domain
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:
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])
raise exceptions.ValidationFailed(json.dumps(details))
else:

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,71 +14,82 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from poppy.transport.validators import schema_base
class ServiceSchema(schema_base.SchemaBase):
"""JSON Schmema validation for /service."""
'''JSON Schmema validation for /service.'''
schema = {
'service': {
'PUT': {
'name': 'service',
'POST': {
'type': 'object',
'properties': {
"domains": {
'name': {
'type': 'string',
'required': True,
},
'domains': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"domain": {
"type": "string",
'pattern': "^(([a-zA-Z]{1})|"
"([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]))\."
"([a-zA-Z]{2,6}|"
"[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"
'type': 'object',
'properties': {
'domain': {
'type': 'string',
'pattern': re.compile(
'^(([^:/?#]+):)?'
'(//([^/?#]*))?'
'([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
)
}}},
'required': True,
"minItems": 1},
"origins": {
'minItems': 1},
'origins': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"origin": {
"type": "string",
"required": True},
"port": {
"type": "integer",
"enum": [
'type': 'object',
'properties': {
'origin': {
'type': 'string',
'pattern': re.compile(
'^(([^:/?#]+):)?'
'(//([^/?#]*))?'
'([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
),
'required': True},
'port': {
'type': 'integer',
'enum': [
80,
443]},
"ssl": {
"type": "boolean"}},
'ssl': {
'type': 'boolean'}},
},
'required': True,
"minItems": 1},
"caching": {
'minItems': 1},
'caching': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"name": {
"type": "string",
"required": True},
"ttl": {
"type": "integer",
"required": True},
"rules": {
"type": "array",
'type': 'object',
'properties': {
'name': {
'type': 'string',
'required': True},
'ttl': {
'type': 'integer',
'required': True},
'rules': {
'type': 'array',
'items': {
'type': "object",
"properties": {
'type': 'object',
'properties': {
'name': {
'type': 'string'},
'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': {
'type': 'object',
'properties': {
"domains": {
'domains': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"domain": {
"type": "string",
'pattern': "^(([a-zA-Z]{1})|"
"([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]))\."
"([a-zA-Z]{2,6}|"
"[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"
'type': 'object',
'properties': {
'domain': {
'type': 'string',
'pattern': re.compile(
'^(([^:/?#]+):)?'
'(//([^/?#]*))?'
'([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
)
}}},
},
"origins": {
'origins': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"origin": {
"type": "string",
"required": True},
"port": {
"type": "integer",
"enum": [
'type': 'object',
'properties': {
'origin': {
'type': 'string',
'pattern': re.compile(
'^(([^:/?#]+):)?'
'(//([^/?#]*))?'
'([^?#]*)(\?([^#]*))?'
'(#(.*))?$',
re.UNICODE
),
'required': True},
'port': {
'type': 'integer',
'enum': [
80,
443]},
"ssl": {
"type": "boolean"}},
'ssl': {
'type': 'boolean'}},
},
},
"caching": {
'caching': {
'type': 'array',
'items': {
'type': "object",
"properties": {
"name": {
"type": "string",
"required": True},
"ttl": {
"type": "integer",
"required": True},
"rules": {
"type": "array",
'type': 'object',
'properties': {
'name': {
'type': 'string',
'required': True},
'ttl': {
'type': 'integer',
'required': True},
'rules': {
'type': 'array',
'items': {
'type': "object",
"properties": {
'type': 'object',
'properties': {
'name': {
'type': 'string'},
'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 =
fastly = poppy.provider.fastly:Driver
mock = poppy.provider.mock:Driver
cloudfront = poppy.provider.cloudfront:Driver
maxcdn = poppy.provider.maxcdn:Driver
[wheel]
universal = 1

View File

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

View File

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

View File

@ -21,15 +21,20 @@ from cafe.engine.models import base
class CreateService(base.AutoMarshallingModel):
"""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__()
self.service_name = name
self.domain_list = domain_list or []
self.origin_list = origin_list or []
self.flavorRef = flavorRef
self.caching_list = caching_list or []
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,
"flavorRef": self.flavorRef,
"caching": self.caching_list}
return json.dumps(create_service_request)

View File

@ -11,3 +11,7 @@ apikey = "MYAPIKEY"
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

@ -52,4 +52,13 @@ keyspace = poppy
database = poppy
[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)
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,
expect_errors=True)

View File

@ -15,6 +15,7 @@
import json
import ddt
import pecan
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
@ddt.ddt
class ServiceControllerTest(base.FunctionalTest):
def test_get_all(self):
@ -45,52 +47,30 @@ class ServiceControllerTest(base.FunctionalTest):
self.assertTrue("domains" 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
self.assertRaises(app.AppError, self.app.put,
'/v1.0/0001/services/fake_service_name_2',
self.assertRaises(app.AppError, self.app.post,
'/v1.0/0001/services',
params="{", headers={
"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
self.assertRaises(app.AppError, self.app.put,
'/v1.0/0001/services/fake_service_name_2',
params=json.dumps({
"domain": "www.mytest.com"
}), headers={
self.assertRaises(app.AppError, self.app.post,
'/v1.0/0001/services',
params=json.dumps(service_json), headers={
"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):
# update with erroneous data
self.assertRaises(app.AppError, self.app.patch,

View File

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

View File

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

View File

@ -24,7 +24,7 @@ from poppy.transport.validators.stoplight import rule
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(
helpers.with_schema_falcon,
schema=testing_schema)

View File

@ -31,8 +31,8 @@ class TestProviderWrapper(base.TestCase):
# fake a provider details to work with unittest
self.fake_provider_details = {
"Fastly": provider_details.ProviderDetail(
id=uuid.uuid1(),
access_url='mydummywebsite.prod.fastly.com')}
provider_service_id=uuid.uuid1(),
access_urls='mydummywebsite.prod.fastly.com')}
def test_update_with_keyerror(self):
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.fake_provider_details, {})
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):
@ -62,4 +62,4 @@ class TestProviderWrapper(base.TestCase):
fastly_provider_detail = self.fake_provider_details["Fastly"]
self.provider_wrapper_obj.delete(mock_ext, self.fake_provider_details)
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 services
from poppy.model import flavor
from poppy.model.helpers import provider_details
from poppy.transport.pecan.models.request import service
from tests.unit import base
@ -44,21 +46,98 @@ class DefaultManagerServiceTests(base.TestCase):
self.project_id = 'mock_id'
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):
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
self.sc.storage.create.assert_called_once_with(self.project_id,
self.service_name,
self.service_json)
# 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)
self.sc.storage_controller.create.assert_called_once_with(
self.project_id,
service_obj)
@ddt.file_data('data_provider_details.json')
def test_update(self, provider_details_json):
@ -67,27 +146,29 @@ class DefaultManagerServiceTests(base.TestCase):
provider_detail_dict = json.loads(
provider_details_json[provider_name]
)
id = provider_detail_dict.get("id", None)
access_url = provider_detail_dict.get("access_url", None)
provider_service_id = provider_detail_dict.get(
"provider_service_id", None)
access_urls = provider_detail_dict.get("access_url", None)
status = provider_detail_dict.get("status", u'unknown')
provider_detail_obj = provider_details.ProviderDetail(
id=id,
access_url=access_url,
provider_service_id=provider_service_id,
access_urls=access_urls,
status=status)
self.provider_details[provider_name] = provider_detail_obj
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.sc.update(self.project_id, self.service_name, self.service_json)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage.update.assert_called_once_with(self.project_id,
self.service_name,
self.service_json)
self.sc.storage_controller.update.assert_called_once_with(
self.project_id,
self.service_name,
self.service_json)
# and that the providers are notified.
providers.map.assert_called_once_with(self.sc.provider_wrapper.update,
self.provider_details,
@ -100,24 +181,27 @@ class DefaultManagerServiceTests(base.TestCase):
provider_detail_dict = json.loads(
provider_details_json[provider_name]
)
id = provider_detail_dict.get("id", None)
access_url = provider_detail_dict.get("access_url", None)
provider_service_id = provider_detail_dict.get(
"provider_service_id", None)
access_urls = provider_detail_dict.get("access_urls", None)
status = provider_detail_dict.get("status", u'unknown')
provider_detail_obj = provider_details.ProviderDetail(
id=id,
access_url=access_url,
provider_service_id=provider_service_id,
access_urls=access_urls,
status=status)
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.sc.delete(self.project_id, self.service_name)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage.delete.assert_called_once_with(self.project_id,
self.service_name)
self.sc.storage_controller.delete.assert_called_once_with(
self.project_id,
self.service_name
)
# and that the providers are notified.
providers = self.sc._driver.providers
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):
myservice = service.Service(
self.service_name, self.mydomains, self.myorigins,
self.service_name, self.mydomains, self.myorigins, self.flavorRef,
self.mycaching, self.myrestrictions)
# test all properties
@ -76,6 +76,11 @@ class TestServiceModel(base.TestCase):
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)
myservice.restrictions = []
self.assertEqual(myservice.restrictions, [])
@ -88,12 +93,24 @@ class TestServiceModel(base.TestCase):
# status
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')
def test_set_invalid_status(self, status):
myservice = service.Service(
self.service_name,
self.mydomains,
self.myorigins)
self.myorigins,
self.flavorRef)
self.assertRaises(ValueError, setattr, myservice, 'status', status)
@ -102,7 +119,8 @@ class TestServiceModel(base.TestCase):
myservice = service.Service(
self.service_name,
self.mydomains,
self.myorigins)
self.myorigins,
self.flavorRef)
myservice.status = status

View File

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

View File

@ -19,8 +19,8 @@ from boto import cloudfront
import ddt
import mock
from poppy.provider.cloudfront import services
from poppy.transport.pecan.models.request import service
from tests.unit import base
@ -46,30 +46,58 @@ class TestServices(base.TestCase):
@ddt.file_data('data_service.json')
def test_create_server_error(self, service_json):
# create_distribution: CloudFrontServerError
service_obj = service.load_from_json(service_json)
side_effect = cloudfront.exception.CloudFrontServerError(
503, 'Service Unavailable')
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])
@ddt.file_data('data_service.json')
def test_create_exception(self, service_json):
# generic exception: Exception
service_obj = service.load_from_json(service_json)
self.controller.client.create_distribution.side_effect = 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])
@ddt.file_data('data_service.json')
def test_create(self, service_json):
def test_create_service_in_progress(self, service_json):
# 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])
@ddt.file_data('data_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])
def test_delete_exceptions(self):
@ -85,3 +113,8 @@ class TestServices(base.TestCase):
def test_client(self):
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": {
"name" : "mysite.com",
"domains": [
{"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"}
],
"origins": [
{"origin": "mockdomain.com", "ssl": false, "port": 80}
]
],
"flavorRef" : "standard"
}
}

View File

@ -21,6 +21,7 @@ import fastly
import mock
from poppy.provider.fastly import services
from poppy.transport.pecan.models.request import service
from tests.unit import base
@ -59,91 +60,118 @@ class TestServices(base.TestCase):
def test_create_with_create_service_exception(self, service_json):
# ASSERTIONS
# create_service
service_obj = service.load_from_json(service_json)
self.controller.client.create_service.side_effect = fastly.FastlyError(
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])
@ddt.file_data('data_service.json')
def test_create_with_create_version_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.create_service.side_effect = None
service_obj = service.load_from_json(service_json)
# create_version
self.controller.client.create_version.side_effect = fastly.FastlyError(
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])
@ddt.file_data('data_service.json')
def test_create_with_create_domain_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.create_version.side_effect = None
service_obj = service.load_from_json(service_json)
# create domains
self.controller.client.create_domain.side_effect = fastly.FastlyError(
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])
@ddt.file_data('data_service.json')
def test_create_with_create_backend_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.create_domain.side_effect = None
service_obj = service.load_from_json(service_json)
# create backends
self.controller.client.create_backend.side_effect = fastly.FastlyError(
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])
@ddt.file_data('data_service.json')
def test_create_with_check_domains_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.check_domains.side_effect = fastly.FastlyError(
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])
@ddt.file_data('data_service.json')
def test_create_with_general_exception(self, service_json):
self.controller.client.reset_mock()
self.controller.client.check_domains.side_effect = None
service_obj = service.load_from_json(service_json)
# test a general exception
self.controller.client.create_service.side_effect = Exception(
'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])
@ddt.file_data('data_service.json')
def test_create(self, service_json):
# instantiate
# 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.client.create_service.return_value = self.service_instance
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 = [
[{
"name": "www.example.com",
"comment": "",
"service_id": "<fake_id>",
"version": "1",
"locked": True
},
"global.prod.fastly.net.",
True
]
mock.Mock(domain=fastly_fake_domain_check)
]
resp = controller.create(self.service_name, service_json)
resp = controller.create(service_obj)
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(
self.service_instance.id)
@ -151,22 +179,22 @@ class TestServices(base.TestCase):
controller.client.create_domain.assert_any_call(
self.service_instance.id,
self.version.number,
service_json['domains'][0]['domain'])
service_obj.domains[0].domain)
controller.client.create_domain.assert_any_call(
self.service_instance.id,
self.version.number,
service_json['domains'][1]['domain'])
service_obj.domains[1].domain)
controller.client.check_domains.assert_called_once_with(
self.service_instance.id, self.version.number)
controller.client.create_backend.assert_has_any_call(
self.service_instance.id, 1,
service_json['origins'][0]['origin'],
service_json['origins'][0]['origin'],
service_json['origins'][0]['ssl'],
service_json['origins'][0]['port'])
service_obj.origins[0].origin.replace(":", "-"),
service_obj.origins[0].origin,
service_obj.origins[0].ssl,
service_obj.origins[0].port)
self.assertIn('links', resp[self.driver.provider_name])
@ -231,10 +259,13 @@ class TestProviderValidation(base.TestCase):
self.controller = services.ServiceController(self.driver)
self.client = mock_client
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",
"ssl": False, "port": 80}]}
self.controller.create(self.service_name, service_json)
"ssl": False, "port": 80}],
"flavorRef": "standard"}
service_obj = service.load_from_json(service_json)
self.controller.create(service_obj)
@mock.patch('fastly.FastlyService')
def test_get_service_details(self, mock_service):

View File

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

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc.
#
# 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 services
from poppy.transport.pecan.models.request import service
from tests.unit import base
@ -45,7 +47,7 @@ fake_maxcdn_client_get_return_value = {u'code': 200,
fake_maxcdn_client_400_return_value = {
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):
return {u'code': 201,
u'data': {
u"pullzone": {
u"cdn_url": u"newpullzone1.alias.netdna-cdn.com",
u'pullzone': {
u'cdn_url': u'newpullzone1.alias.netdna-cdn.com',
u'name': u'newpullzone1',
u'id': u'97312'
}}}
@ -72,8 +74,8 @@ class fake_maxcdn_api_client:
def put(self, url=None, params=None):
return {u'code': 200,
u'data': {
u"pullzone": {
u"cdn_url": u"newpullzone1.alias.netdna-cdn.com",
u'pullzone': {
u'cdn_url': u'newpullzone1.alias.netdna-cdn.com',
u'name': u'newpullzone1',
u'id': u'97312'
}}}
@ -91,19 +93,13 @@ class TestServices(base.TestCase):
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',
new=fake_maxcdn_api_client())
def test_get(self):
new_driver = driver.CDNProvider(self.conf)
# instantiate
controller = services.ServiceController(new_driver)
service_name = "test_service_name"
service_name = 'test_service_name'
self.assertTrue(controller.get(service_name) is not None)
@ddt.file_data('data_service.json')
@ -114,8 +110,8 @@ class TestServices(base.TestCase):
# instantiate
controller = services.ServiceController(new_driver)
# test create, everything goes through successfully
service_name = "test_service_name"
resp = controller.create(service_name, service_json)
service_obj = service.load_from_json(service_json)
resp = controller.create(service_obj)
self.assertIn('id', 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':
fake_maxcdn_client_get_return_value
})
service_name = "test_service_name"
service_obj = service.load_from_json(service_json)
controller_with_create_exception = services.ServiceController(driver)
controller_with_create_exception.client.configure_mock(**{
"post.side_effect":
'post.side_effect':
RuntimeError('Creating service mysteriously failed.')})
resp = controller_with_create_exception.create(
service_name,
service_json)
service_obj)
self.assertIn('error', resp[driver.provider_name])
controller_with_create_exception.client.reset_mock()
controller_with_create_exception.client.configure_mock(**{
'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(
service_name,
service_json)
service_obj)
self.assertIn('error', resp[driver.provider_name])
@ddt.file_data('data_service.json')
@ -160,8 +153,9 @@ class TestServices(base.TestCase):
# instantiate
controller = services.ServiceController(new_driver)
# test create, everything goes through successfully
service_name = "test_service_name"
resp = controller.update(service_name, service_json)
service_obj = service.load_from_json(service_json)
service_name = 'test_service_name'
resp = controller.update(service_name, service_obj)
self.assertIn('id', resp[new_driver.provider_name])
@ddt.file_data('data_service.json')
@ -176,11 +170,11 @@ class TestServices(base.TestCase):
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.client.configure_mock(**{
"put.side_effect":
'put.side_effect':
RuntimeError('Updating service mysteriously failed.')})
resp = controller_with_update_exception.update(
service_name,
@ -189,12 +183,13 @@ class TestServices(base.TestCase):
controller_with_update_exception.client.reset_mock()
controller_with_update_exception.client.configure_mock(**{
"put.side_effect": None,
"put.return_value": fake_maxcdn_client_400_return_value
'put.side_effect': None,
'put.return_value': fake_maxcdn_client_400_return_value
})
service_obj = service.load_from_json(service_json)
resp = controller_with_update_exception.update(
service_name,
service_json)
service_obj)
self.assertIn('error', resp[driver.provider_name])
@mock.patch.object(driver.CDNProvider, 'client',
@ -204,7 +199,7 @@ class TestServices(base.TestCase):
# instantiate
controller = services.ServiceController(new_driver)
# test create, everything goes through successfully
service_name = "test_service_name"
service_name = 'test_service_name'
resp = controller.delete(service_name)
self.assertIn('id', resp[new_driver.provider_name])
@ -219,11 +214,11 @@ class TestServices(base.TestCase):
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.client.configure_mock(**{
"delete.side_effect":
'delete.side_effect':
RuntimeError('Deleting service mysteriously failed.')})
resp = controller_with_delete_exception.delete(service_name)
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.configure_mock(**{
"delete.side_effect": None,
"delete.return_value": fake_maxcdn_client_400_return_value
'delete.side_effect': None,
'delete.return_value': fake_maxcdn_client_400_return_value
})
resp = controller_with_delete_exception.delete(service_name)
self.assertEqual(resp[driver.provider_name]['error'],
'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": {
"name" : "mysite.com",
"domains": [
{"domain": "parsely.sage.com"},
{"domain": "rosemary.thyme.net"}
],
"origins": [
{"origin": "mockdomain.com", "ssl": false, "port": 80}
]
],
"flavorRef" : "standard"
}
}

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -19,6 +20,7 @@ import ddt
import mock
from poppy.provider.mock import services
from poppy.transport.pecan.models.request import service
from tests.unit import base
@ -34,7 +36,8 @@ class MockProviderServicesTest(base.TestCase):
@ddt.file_data('data_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)
def test_delete(self):
@ -47,5 +50,14 @@ class MockProviderServicesTest(base.TestCase):
@ddt.file_data('data_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)
@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": [
{
"name" : "mocksite",
"service_name" : "mocksite",
"domains": [
"{\"domain\": \"www.mocksite.com\"}",
"{\"domain\": \"blog.mocksite.com\"}"
@ -12,6 +12,7 @@
"caching": [
"{\"name\": \"default\", \"ttl\": 3600}"
],
"flavor_id" : "standard",
"restrictions": [
"{\"rules\": [{\"http_host\": \"www.mocksite.com\", \"name\": \"mocksite.com\"}], \"name\": \"website only\"}"
]

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import ddt
import mock
from oslo.config import cfg
from poppy.model import flavor as model_flavor
from poppy.storage.cassandra import driver
from poppy.storage.cassandra import flavors
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(cassandra.cluster.Session, '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_execute.execute.return_value = value
@ -79,6 +84,20 @@ class CassandraStorageFlavorsTests(base.TestCase):
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')
@mock.patch.object(flavors.FlavorsController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')

View File

@ -13,12 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import cassandra
import ddt
import mock
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 services
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_execute.execute.return_value = []
self.assertRaises(ValueError, self.sc.get,
self.assertRaises(LookupError, self.sc.get,
self.project_id, self.service_name)
@ddt.file_data('../data/data_create_service.json')
@ -71,10 +73,8 @@ class CassandraStorageServiceTests(base.TestCase):
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_create_service(self, value, mock_session, mock_execute):
value.update({'name': self.service_name})
request_service = req_service.load_from_json(value)
service_obj = service.Service.init_from_dict(request_service.to_dict())
responses = self.sc.create(self.project_id, self.service_name,
service_obj)
service_obj = req_service.load_from_json(value)
responses = self.sc.create(self.project_id, service_obj)
# Expect the response to be None as there are no providers passed
# 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.
@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')
@mock.patch.object(services.ServicesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
@ -133,6 +145,47 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertTrue("CloudFront" 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')
def test_session(self, mock_service_database):
session = self.sc.session

View File

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

View File

@ -45,7 +45,7 @@ class MockDBStorageFlavorsTests(base.TestCase):
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')
@ddt.file_data('../data/data_create_flavor.json')
@ -61,7 +61,8 @@ class MockDBStorageFlavorsTests(base.TestCase):
actual_response = self.fc.list()
# 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')
def test_delete_flavor(self, mock_session):