diff --git a/.coveragerc b/.coveragerc index 07df79df..eacc9023 100644 --- a/.coveragerc +++ b/.coveragerc @@ -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 diff --git a/etc/poppy.conf b/etc/poppy.conf index 298d5113..7cebedd9 100644 --- a/etc/poppy.conf +++ b/etc/poppy.conf @@ -52,4 +52,13 @@ keyspace = poppy database = poppy [drivers:provider:fastly] -apikey = "MYAPIKEY" \ No newline at end of file +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" diff --git a/poppy/manager/base/providers.py b/poppy/manager/base/providers.py index a62f98e9..e33e1f12 100644 --- a/poppy/manager/base/providers.py +++ b/poppy/manager/base/providers.py @@ -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) diff --git a/poppy/manager/base/services.py b/poppy/manager/base/services.py index b85ca6f9..080f70c1 100644 --- a/poppy/manager/base/services.py +++ b/poppy/manager/base/services.py @@ -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 diff --git a/poppy/manager/default/services.py b/poppy/manager/default/services.py index 3d21cb42..90920f44 100644 --- a/poppy/manager/default/services.py +++ b/poppy/manager/default/services.py @@ -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) diff --git a/poppy/model/common.py b/poppy/model/common.py index d7a498fd..e5e4ec35 100644 --- a/poppy/model/common.py +++ b/poppy/model/common.py @@ -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 diff --git a/poppy/model/helpers/provider_details.py b/poppy/model/helpers/provider_details.py index b92833b9..cd6a9551 100644 --- a/poppy/model/helpers/provider_details.py +++ b/poppy/model/helpers/provider_details.py @@ -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 diff --git a/poppy/model/service.py b/poppy/model/service.py index f794a846..1d3d50b1 100644 --- a/poppy/model/service.py +++ b/poppy/model/service.py @@ -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 diff --git a/poppy/provider/base/responder.py b/poppy/provider/base/responder.py index 069f971f..5140c108 100644 --- a/poppy/provider/base/responder.py +++ b/poppy/provider/base/responder.py @@ -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): diff --git a/poppy/provider/base/services.py b/poppy/provider/base/services.py index 928046a7..c4385d40 100644 --- a/poppy/provider/base/services.py +++ b/poppy/provider/base/services.py @@ -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 diff --git a/poppy/provider/cloudfront/driver.py b/poppy/provider/cloudfront/driver.py index d4b92e79..8b6e7f79 100644 --- a/poppy/provider/cloudfront/driver.py +++ b/poppy/provider/cloudfront/driver.py @@ -51,6 +51,7 @@ class CDNProvider(base.Driver): def provider_name(self): return 'CloudFront' + @property def client(self): return self.cloudfront_client diff --git a/poppy/provider/cloudfront/services.py b/poppy/provider/cloudfront/services.py index dbcbec00..e826e0b6 100644 --- a/poppy/provider/cloudfront/services.py +++ b/poppy/provider/cloudfront/services.py @@ -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 diff --git a/poppy/provider/fastly/services.py b/poppy/provider/fastly/services.py index 40525040..84875c21 100644 --- a/poppy/provider/fastly/services.py +++ b/poppy/provider/fastly/services.py @@ -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() diff --git a/poppy/provider/maxcdn/services.py b/poppy/provider/maxcdn/services.py index 24d948f5..6988a18b 100644 --- a/poppy/provider/maxcdn/services.py +++ b/poppy/provider/maxcdn/services.py @@ -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...") diff --git a/poppy/provider/mock/services.py b/poppy/provider/mock/services.py index e4486ae3..bd23b3dc 100644 --- a/poppy/provider/mock/services.py +++ b/poppy/provider/mock/services.py @@ -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 diff --git a/poppy/storage/base/services.py b/poppy/storage/base/services.py index edb58ca9..700d2bac 100644 --- a/poppy/storage/base/services.py +++ b/poppy/storage/base/services.py @@ -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 diff --git a/poppy/storage/cassandra/flavors.py b/poppy/storage/cassandra/flavors.py index 28380e15..c97af01d 100644 --- a/poppy/storage/cassandra/flavors.py +++ b/poppy/storage/cassandra/flavors.py @@ -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) diff --git a/poppy/storage/cassandra/services.py b/poppy/storage/cassandra/services.py index c5652108..268b094e 100644 --- a/poppy/storage/cassandra/services.py +++ b/poppy/storage/cassandra/services.py @@ -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) diff --git a/poppy/storage/mockdb/flavors.py b/poppy/storage/mockdb/flavors.py index ce23a7f2..b5989943 100644 --- a/poppy/storage/mockdb/flavors.py +++ b/poppy/storage/mockdb/flavors.py @@ -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 diff --git a/poppy/storage/mockdb/services.py b/poppy/storage/mockdb/services.py index 400d9e24..96b71b97 100644 --- a/poppy/storage/mockdb/services.py +++ b/poppy/storage/mockdb/services.py @@ -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 diff --git a/poppy/transport/pecan/controllers/v1/flavors.py b/poppy/transport/pecan/controllers/v1/flavors.py index 02ce5a26..9049907b 100644 --- a/poppy/transport/pecan/controllers/v1/flavors.py +++ b/poppy/transport/pecan/controllers/v1/flavors.py @@ -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( diff --git a/poppy/transport/pecan/controllers/v1/services.py b/poppy/transport/pecan/controllers/v1/services.py index 743014a9..be267140 100644 --- a/poppy/transport/pecan/controllers/v1/services.py +++ b/poppy/transport/pecan/controllers/v1/services.py @@ -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): diff --git a/poppy/transport/pecan/models/request/domain.py b/poppy/transport/pecan/models/request/domain.py index 3893be9f..0df87990 100644 --- a/poppy/transport/pecan/models/request/domain.py +++ b/poppy/transport/pecan/models/request/domain.py @@ -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) diff --git a/poppy/transport/pecan/models/request/origin.py b/poppy/transport/pecan/models/request/origin.py index b7066c65..68bcd740 100644 --- a/poppy/transport/pecan/models/request/origin.py +++ b/poppy/transport/pecan/models/request/origin.py @@ -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) diff --git a/poppy/transport/pecan/models/request/service.py b/poppy/transport/pecan/models/request/service.py index 613576e3..80e7daa7 100644 --- a/poppy/transport/pecan/models/request/service.py +++ b/poppy/transport/pecan/models/request/service.py @@ -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) diff --git a/poppy/transport/pecan/models/response/domain.py b/poppy/transport/pecan/models/response/domain.py index 381d048b..ec62cd61 100644 --- a/poppy/transport/pecan/models/response/domain.py +++ b/poppy/transport/pecan/models/response/domain.py @@ -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): diff --git a/poppy/transport/pecan/models/response/flavor.py b/poppy/transport/pecan/models/response/flavor.py index a844bb16..644225bd 100644 --- a/poppy/transport/pecan/models/response/flavor.py +++ b/poppy/transport/pecan/models/response/flavor.py @@ -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 diff --git a/poppy/transport/pecan/models/response/link.py b/poppy/transport/pecan/models/response/link.py index be0548a1..d7e65b11 100644 --- a/poppy/transport/pecan/models/response/link.py +++ b/poppy/transport/pecan/models/response/link.py @@ -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): diff --git a/poppy/transport/pecan/models/response/origin.py b/poppy/transport/pecan/models/response/origin.py index e247d629..be6d89fd 100644 --- a/poppy/transport/pecan/models/response/origin.py +++ b/poppy/transport/pecan/models/response/origin.py @@ -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): diff --git a/poppy/transport/pecan/models/response/service.py b/poppy/transport/pecan/models/response/service.py index 30b428e7..499425fb 100644 --- a/poppy/transport/pecan/models/response/service.py +++ b/poppy/transport/pecan/models/response/service.py @@ -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 diff --git a/poppy/transport/validators/helpers.py b/poppy/transport/validators/helpers.py index 555553a6..4f651671 100644 --- a/poppy/transport/validators/helpers.py +++ b/poppy/transport/validators/helpers.py @@ -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: diff --git a/poppy/transport/validators/schemas/service.py b/poppy/transport/validators/schemas/service.py index e8f8dea2..59b382a1 100644 --- a/poppy/transport/validators/schemas/service.py +++ b/poppy/transport/validators/schemas/service.py @@ -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', + } }}, }, } diff --git a/setup.cfg b/setup.cfg index 4798740a..555c1bb5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tests/api/services/data_create_service.json b/tests/api/services/data_create_service.json index ea9d9417..1c33bcaf 100644 --- a/tests/api/services/data_create_service.json +++ b/tests/api/services/data_create_service.json @@ -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": [] } } \ No newline at end of file diff --git a/tests/api/utils/client.py b/tests/api/utils/client.py index a73b3b61..b570cf91 100644 --- a/tests/api/utils/client.py +++ b/tests/api/utils/client.py @@ -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, diff --git a/tests/api/utils/models/requests.py b/tests/api/utils/models/requests.py index 0133f37d..dd5cae61 100644 --- a/tests/api/utils/models/requests.py +++ b/tests/api/utils/models/requests.py @@ -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) diff --git a/tests/etc/default_functional.conf b/tests/etc/default_functional.conf index 93bf3710..b2d384cf 100644 --- a/tests/etc/default_functional.conf +++ b/tests/etc/default_functional.conf @@ -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" diff --git a/tests/etc/poppy_mockdb.conf b/tests/etc/poppy_mockdb.conf index 298d5113..7cebedd9 100644 --- a/tests/etc/poppy_mockdb.conf +++ b/tests/etc/poppy_mockdb.conf @@ -52,4 +52,13 @@ keyspace = poppy database = poppy [drivers:provider:fastly] -apikey = "MYAPIKEY" \ No newline at end of file +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" diff --git a/tests/functional/transport/pecan/controllers/data_create_service.json b/tests/functional/transport/pecan/controllers/data_create_service.json new file mode 100644 index 00000000..09134ba4 --- /dev/null +++ b/tests/functional/transport/pecan/controllers/data_create_service.json @@ -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" + } + ] + } + ] + } +} + + + diff --git a/tests/functional/transport/pecan/controllers/data_create_service_bad_input_json.json b/tests/functional/transport/pecan/controllers/data_create_service_bad_input_json.json new file mode 100644 index 00000000..95d6f825 --- /dev/null +++ b/tests/functional/transport/pecan/controllers/data_create_service_bad_input_json.json @@ -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" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/functional/transport/pecan/controllers/test_flavors.py b/tests/functional/transport/pecan/controllers/test_flavors.py index 47ba772a..bc2db70f 100644 --- a/tests/functional/transport/pecan/controllers/test_flavors.py +++ b/tests/functional/transport/pecan/controllers/test_flavors.py @@ -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) diff --git a/tests/functional/transport/pecan/controllers/test_services.py b/tests/functional/transport/pecan/controllers/test_services.py index 3942bb5e..7430a514 100644 --- a/tests/functional/transport/pecan/controllers/test_services.py +++ b/tests/functional/transport/pecan/controllers/test_services.py @@ -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, diff --git a/tests/functional/transport/pecan/mock.py b/tests/functional/transport/pecan/mock.py index 43e0822c..7da65e67 100644 --- a/tests/functional/transport/pecan/mock.py +++ b/tests/functional/transport/pecan/mock.py @@ -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): diff --git a/tests/functional/transport/validator/base.py b/tests/functional/transport/validator/base.py index edf0ffe1..0bfa3c6e 100644 --- a/tests/functional/transport/validator/base.py +++ b/tests/functional/transport/validator/base.py @@ -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 = "{" diff --git a/tests/functional/transport/validator/test_falcon_validation.py b/tests/functional/transport/validator/test_falcon_validation.py index 24e4f5fc..1d8133f0 100644 --- a/tests/functional/transport/validator/test_falcon_validation.py +++ b/tests/functional/transport/validator/test_falcon_validation.py @@ -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) diff --git a/tests/unit/manager/default/test_provider_wrapper.py b/tests/unit/manager/default/test_provider_wrapper.py index 41068f3f..26170984 100644 --- a/tests/unit/manager/default/test_provider_wrapper.py +++ b/tests/unit/manager/default/test_provider_wrapper.py @@ -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) diff --git a/tests/unit/manager/default/test_services.py b/tests/unit/manager/default/test_services.py index 61bbcd60..1d9ed68f 100644 --- a/tests/unit/manager/default/test_services.py +++ b/tests/unit/manager/default/test_services.py @@ -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, diff --git a/tests/unit/model/test_service.py b/tests/unit/model/test_service.py index 558ae199..08b4ba03 100644 --- a/tests/unit/model/test_service.py +++ b/tests/unit/model/test_service.py @@ -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 diff --git a/tests/unit/provider/cloudfront/data_service.json b/tests/unit/provider/cloudfront/data_service.json index e542bf0a..ef1d6a14 100644 --- a/tests/unit/provider/cloudfront/data_service.json +++ b/tests/unit/provider/cloudfront/data_service.json @@ -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" } } diff --git a/tests/unit/provider/cloudfront/test_services.py b/tests/unit/provider/cloudfront/test_services.py index 9db8aac3..4d359c08 100644 --- a/tests/unit/provider/cloudfront/test_services.py +++ b/tests/unit/provider/cloudfront/test_services.py @@ -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) diff --git a/tests/unit/provider/fastly/data_service.json b/tests/unit/provider/fastly/data_service.json index 75d8da86..358ecda2 100644 --- a/tests/unit/provider/fastly/data_service.json +++ b/tests/unit/provider/fastly/data_service.json @@ -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" } } diff --git a/tests/unit/provider/fastly/test_services.py b/tests/unit/provider/fastly/test_services.py index 82c0a83d..1117a349 100644 --- a/tests/unit/provider/fastly/test_services.py +++ b/tests/unit/provider/fastly/test_services.py @@ -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": "", - "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): diff --git a/tests/unit/provider/maxcdn/data_service.json b/tests/unit/provider/maxcdn/data_service.json index d2a0fec7..6bc8aaa0 100644 --- a/tests/unit/provider/maxcdn/data_service.json +++ b/tests/unit/provider/maxcdn/data_service.json @@ -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" } } diff --git a/tests/unit/provider/maxcdn/test_services.py b/tests/unit/provider/maxcdn/test_services.py index 9efbac28..17f4285f 100644 --- a/tests/unit/provider/maxcdn/test_services.py +++ b/tests/unit/provider/maxcdn/test_services.py @@ -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'') + + @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...") diff --git a/tests/unit/provider/mock/data_service.json b/tests/unit/provider/mock/data_service.json index d2a0fec7..6bc8aaa0 100644 --- a/tests/unit/provider/mock/data_service.json +++ b/tests/unit/provider/mock/data_service.json @@ -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" } } diff --git a/tests/unit/provider/mock/test_services.py b/tests/unit/provider/mock/test_services.py index 7b1962f2..db64c717 100644 --- a/tests/unit/provider/mock/test_services.py +++ b/tests/unit/provider/mock/test_services.py @@ -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) diff --git a/tests/unit/storage/cassandra/data_get_service.json b/tests/unit/storage/cassandra/data_get_service.json index a683a726..765ce4f1 100644 --- a/tests/unit/storage/cassandra/data_get_service.json +++ b/tests/unit/storage/cassandra/data_get_service.json @@ -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\"}" ] diff --git a/tests/unit/storage/cassandra/data_list_services.json b/tests/unit/storage/cassandra/data_list_services.json index 1a070304..79c01f89 100644 --- a/tests/unit/storage/cassandra/data_list_services.json +++ b/tests/unit/storage/cassandra/data_list_services.json @@ -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}" ], diff --git a/tests/unit/storage/cassandra/data_provider_details.json b/tests/unit/storage/cassandra/data_provider_details.json index 55b76f7d..5563c92a 100644 --- a/tests/unit/storage/cassandra/data_provider_details.json +++ b/tests/unit/storage/cassandra/data_provider_details.json @@ -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\"]}" } } \ No newline at end of file diff --git a/tests/unit/storage/cassandra/test_flavors.py b/tests/unit/storage/cassandra/test_flavors.py index c099e18d..c2518391 100644 --- a/tests/unit/storage/cassandra/test_flavors.py +++ b/tests/unit/storage/cassandra/test_flavors.py @@ -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') diff --git a/tests/unit/storage/cassandra/test_services.py b/tests/unit/storage/cassandra/test_services.py index 6b3bd460..e87d6654 100644 --- a/tests/unit/storage/cassandra/test_services.py +++ b/tests/unit/storage/cassandra/test_services.py @@ -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 diff --git a/tests/unit/storage/data/data_create_service.json b/tests/unit/storage/data/data_create_service.json index 29bd60d8..125c36e9 100644 --- a/tests/unit/storage/data/data_create_service.json +++ b/tests/unit/storage/data/data_create_service.json @@ -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", diff --git a/tests/unit/storage/mockdb/test_flavors.py b/tests/unit/storage/mockdb/test_flavors.py index 5dc2db34..dbf9f56c 100644 --- a/tests/unit/storage/mockdb/test_flavors.py +++ b/tests/unit/storage/mockdb/test_flavors.py @@ -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):