Patch service endpoint
Implements: blueprint patch-service Change-Id: I2fe9355713a4bf63f515f3e6dad4611722c57fdc
This commit is contained in:
parent
7fe0197d2b
commit
8a2c731f92
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,3 +42,6 @@ venv
|
||||
docker_rsa*
|
||||
|
||||
Dockerfile
|
||||
|
||||
# patch files
|
||||
*.patch
|
||||
|
@ -27,3 +27,8 @@ class InvalidOperation(Exception):
|
||||
class BadProviderDetail(Exception):
|
||||
|
||||
"""Raised when attempted a non existent operation."""
|
||||
|
||||
|
||||
class ServiceStatusNotDeployed(Exception):
|
||||
|
||||
"""Raised when attempted to update service who status is not deployed."""
|
||||
|
@ -29,23 +29,26 @@ class ProviderWrapper(object):
|
||||
|
||||
return ext.obj.service_controller.create(service_obj)
|
||||
|
||||
def update(self, ext, provider_details, service_json):
|
||||
def update(self, ext, provider_details, service_old, service_updates,
|
||||
service_obj):
|
||||
"""Update a provider
|
||||
|
||||
:param ext
|
||||
:param provider_details
|
||||
:param service_json
|
||||
:param service_old
|
||||
:param service_updates
|
||||
:param service_obj
|
||||
"""
|
||||
|
||||
try:
|
||||
provider_detail = provider_details[ext.provider_name]
|
||||
provider_detail = provider_details[ext.obj.provider_name]
|
||||
except KeyError:
|
||||
raise errors.BadProviderDetail(
|
||||
"No provider detail information."
|
||||
"Perhaps service has not been created")
|
||||
provider_service_id = provider_detail.provider_service_id
|
||||
return ext.obj.service_controller.update(
|
||||
provider_service_id,
|
||||
service_json)
|
||||
provider_service_id, service_old, service_updates, service_obj)
|
||||
|
||||
def delete(self, ext, provider_details):
|
||||
try:
|
||||
|
@ -0,0 +1,65 @@
|
||||
# Copyright (c) 2014 Rackspace, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from poppy.model.helpers import provider_details
|
||||
from poppy.openstack.common import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def update_worker(service_controller, project_id, service_name,
|
||||
service_old, service_updates, service_obj):
|
||||
responders = []
|
||||
# update service with each provider present in provider_details
|
||||
for provider in service_old.provider_details:
|
||||
LOG.info(u'Starting to update service from {0}'.format(provider))
|
||||
responder = service_controller.provider_wrapper.update(
|
||||
service_controller._driver.providers[provider.lower()],
|
||||
service_old.provider_details, service_old, service_updates,
|
||||
service_obj)
|
||||
responders.append(responder)
|
||||
LOG.info(u'Updating service from {0} complete'.format(provider))
|
||||
|
||||
# gather links and status for service from providers
|
||||
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'
|
||||
|
||||
# update the service object
|
||||
service_controller.storage_controller.update(project_id, service_name,
|
||||
service_obj)
|
||||
# update the provider details
|
||||
service_controller.storage_controller.update_provider_details(
|
||||
project_id,
|
||||
service_name,
|
||||
provider_details_dict)
|
@ -13,12 +13,15 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import multiprocessing
|
||||
|
||||
from poppy.common import errors
|
||||
from poppy.manager import base
|
||||
from poppy.manager.default.service_async_workers import create_service_worker
|
||||
from poppy.manager.default.service_async_workers import delete_service_worker
|
||||
from poppy.manager.default.service_async_workers import purge_service_worker
|
||||
from poppy.manager.default.service_async_workers import update_service_worker
|
||||
|
||||
|
||||
class DefaultServicesController(base.ServicesController):
|
||||
@ -30,6 +33,16 @@ class DefaultServicesController(base.ServicesController):
|
||||
self.storage_controller = self._driver.storage.services_controller
|
||||
self.flavor_controller = self._driver.storage.flavors_controller
|
||||
|
||||
def _get_provider_details(self, project_id, service_name):
|
||||
try:
|
||||
provider_details = self.storage_controller.get_provider_details(
|
||||
project_id,
|
||||
service_name)
|
||||
except Exception:
|
||||
raise LookupError(u'Service {0} does not exist'.format(
|
||||
service_name))
|
||||
return provider_details
|
||||
|
||||
def list(self, project_id, marker=None, limit=None):
|
||||
"""list.
|
||||
|
||||
@ -61,7 +74,6 @@ class DefaultServicesController(base.ServicesController):
|
||||
# raise a lookup error if the flavor is not found
|
||||
except LookupError as e:
|
||||
raise e
|
||||
|
||||
providers = [p.provider_id for p in flavor.providers]
|
||||
service_name = service_obj.name
|
||||
|
||||
@ -88,26 +100,56 @@ class DefaultServicesController(base.ServicesController):
|
||||
p.start()
|
||||
return
|
||||
|
||||
def update(self, project_id, service_name, service_obj):
|
||||
def update(self, project_id, service_name, service_updates):
|
||||
"""update.
|
||||
|
||||
:param project_id
|
||||
:param service_name
|
||||
:param service_obj
|
||||
:param service_updates
|
||||
"""
|
||||
self.storage_controller.update(
|
||||
# get the current service object
|
||||
service_old = self.storage_controller.get(project_id, service_name)
|
||||
if service_old.status != u'deployed':
|
||||
raise errors.ServiceStatusNotDeployed(
|
||||
u'Service {0} not deployed'.format(service_name))
|
||||
|
||||
service_obj = copy.deepcopy(service_old)
|
||||
|
||||
# update service object
|
||||
if service_updates.name:
|
||||
raise Exception(u'Currently this operation is not supported')
|
||||
if service_updates.domains:
|
||||
service_obj.domains = service_updates.domains
|
||||
if service_updates.origins:
|
||||
service_obj.origins = service_updates.origins
|
||||
if service_updates.caching:
|
||||
raise Exception(u'Currently this operation is not supported')
|
||||
if service_updates.restrictions:
|
||||
raise Exception(u'Currently this operation is not supported')
|
||||
if service_updates.flavor_ref:
|
||||
raise Exception(u'Currently this operation is not supported')
|
||||
|
||||
# get provider details for this service
|
||||
provider_details = self._get_provider_details(project_id, service_name)
|
||||
|
||||
# set status in provider details to u'update_in_progress'
|
||||
for provider in provider_details:
|
||||
provider_details[provider].status = u'update_in_progress'
|
||||
self.storage_controller.update_provider_details(
|
||||
project_id,
|
||||
service_name,
|
||||
service_obj
|
||||
)
|
||||
provider_details)
|
||||
|
||||
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)
|
||||
self.storage_controller._driver.close_connection()
|
||||
p = multiprocessing.Process(
|
||||
name=('Process: update poppy service {0} for project id: {1}'
|
||||
.format(service_name, project_id)),
|
||||
target=update_service_worker.update_worker,
|
||||
args=(self, project_id, service_name, service_old, service_updates,
|
||||
service_obj))
|
||||
p.start()
|
||||
|
||||
return
|
||||
|
||||
def delete(self, project_id, service_name):
|
||||
"""delete.
|
||||
@ -116,12 +158,7 @@ class DefaultServicesController(base.ServicesController):
|
||||
:param service_name
|
||||
:raises LookupError
|
||||
"""
|
||||
try:
|
||||
provider_details = self.storage_controller.get_provider_details(
|
||||
project_id,
|
||||
service_name)
|
||||
except Exception:
|
||||
raise LookupError('Service %s does not exist' % service_name)
|
||||
provider_details = self._get_provider_details(project_id, service_name)
|
||||
|
||||
# change each provider detail's status to delete_in_progress
|
||||
# TODO(tonytan4ever): what if this provider is in 'failed' status?
|
||||
@ -151,12 +188,7 @@ class DefaultServicesController(base.ServicesController):
|
||||
|
||||
def purge(self, project_id, service_name, purge_url=None):
|
||||
'''If purge_url is none, all content of this service will be purge.'''
|
||||
try:
|
||||
provider_details = self.storage_controller.get_provider_details(
|
||||
project_id,
|
||||
service_name)
|
||||
except Exception:
|
||||
raise LookupError('Service %s does not exist' % service_name)
|
||||
provider_details = self._get_provider_details(project_id, service_name)
|
||||
|
||||
# possible validation of purge url here...
|
||||
self.storage_controller._driver.close_connection()
|
||||
|
@ -17,6 +17,7 @@
|
||||
VALID_STATUSES = [
|
||||
u'deploy_in_progress',
|
||||
u'deployed',
|
||||
u'update_in_progress',
|
||||
u'delete_in_progress',
|
||||
u'failed']
|
||||
|
||||
|
@ -16,10 +16,12 @@
|
||||
from poppy.model import common
|
||||
|
||||
|
||||
VALID_STATUSES = [u'create_in_progress', u'deployed', u'delete_in_progress']
|
||||
VALID_STATUSES = [u'create_in_progress', u'deployed', u'update_in_progress',
|
||||
u'delete_in_progress', u'failed']
|
||||
|
||||
|
||||
class Service(common.DictSerializableModel):
|
||||
|
||||
"""Service Class."""
|
||||
|
||||
def __init__(self,
|
||||
@ -100,7 +102,7 @@ class Service(common.DictSerializableModel):
|
||||
|
||||
:returns boolean
|
||||
"""
|
||||
# derived fiedls of service status:
|
||||
# service status is a derived field
|
||||
# service will be in creating during service creation
|
||||
# if any of the provider services are still in 'deploy_in_progress'
|
||||
# status or 'failed' status, the poppy service is still in
|
||||
@ -111,16 +113,21 @@ class Service(common.DictSerializableModel):
|
||||
# the poppy service will be in 'delete_in_progress' status
|
||||
for provider_name in self.provider_details:
|
||||
provider_detail = self.provider_details[provider_name]
|
||||
if provider_detail.status == 'delete_in_progress':
|
||||
self._status = 'delete_in_progress'
|
||||
if provider_detail.status == u'failed':
|
||||
self._status = u'failed'
|
||||
break
|
||||
elif provider_detail.status == 'deploy_in_progress' or (
|
||||
self._status == 'failed'
|
||||
):
|
||||
elif provider_detail.status == u'delete_in_progress':
|
||||
self._status = u'delete_in_progress'
|
||||
break
|
||||
elif provider_detail.status == u'update_in_progress':
|
||||
self._status = u'update_in_progress'
|
||||
elif provider_detail.status == u'deploy_in_progress':
|
||||
self._status = u'create_in_progress'
|
||||
else:
|
||||
if self.provider_details != {}:
|
||||
is_not_updating = (self._status != u'update_in_progress')
|
||||
if is_not_updating and self.provider_details != {}:
|
||||
self._status = 'deployed'
|
||||
|
||||
return self._status
|
||||
|
||||
@status.setter
|
||||
|
@ -66,17 +66,19 @@ class Responder(object):
|
||||
self.provider: provider_response
|
||||
}
|
||||
|
||||
def updated(self, provider_service_id):
|
||||
def updated(self, provider_service_id, links):
|
||||
"""updated.
|
||||
|
||||
:param provider_service_id
|
||||
:returns provider msg{provider service id}
|
||||
"""
|
||||
# TODO(tonytan4ever): May need to add link information as return
|
||||
provider_response = {
|
||||
"id": provider_service_id,
|
||||
"links": links
|
||||
}
|
||||
|
||||
return {
|
||||
self.provider: {
|
||||
'id': provider_service_id
|
||||
}
|
||||
self.provider: provider_response
|
||||
}
|
||||
|
||||
def deleted(self, provider_service_id):
|
||||
|
@ -31,7 +31,7 @@ class ServicesControllerBase(controller.ProviderControllerBase):
|
||||
self.responder = responder.Responder(driver.provider_name)
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, provider_service_id, service_json):
|
||||
def update(self, service_name, service_old, service_updates, service_obj):
|
||||
"""update.
|
||||
|
||||
:raises NotImplementedError
|
||||
|
@ -38,9 +38,10 @@ class ServiceController(base.ServiceBase):
|
||||
def get(self, service_name):
|
||||
return {'domains': [], 'origins': [], 'caching': []}
|
||||
|
||||
# TODo(obulpathi): update service
|
||||
def update(self, service_name, service_obj):
|
||||
return self.responder.updated(service_name)
|
||||
# TODO(obulpathi): update service
|
||||
def update(self, service_name, service_old, service_updates, service_obj):
|
||||
links = {}
|
||||
return self.responder.updated(service_name, links)
|
||||
|
||||
def create(self, service_obj):
|
||||
# TODO(obulpathi): create a single distribution for multiple origins
|
||||
|
@ -31,63 +31,74 @@ class ServiceController(base.ServiceBase):
|
||||
|
||||
self.driver = driver
|
||||
|
||||
def update(self, provider_service_id, service_obj):
|
||||
return self.responder.updated(provider_service_id)
|
||||
def _create_new_service_version(self, service, service_obj):
|
||||
# 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_obj.domains:
|
||||
domain = self.client.create_domain(service.id,
|
||||
service_version.number,
|
||||
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.
|
||||
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_check in domain_checks]
|
||||
|
||||
for origin in service_obj.origins:
|
||||
# Create the origins for this domain
|
||||
self.client.create_backend(service.id,
|
||||
service_version.number,
|
||||
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 links
|
||||
|
||||
def create(self, service_obj):
|
||||
|
||||
try:
|
||||
# Create a new service
|
||||
service = self.client.create_service(self.current_customer.id,
|
||||
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_obj.domains:
|
||||
domain = self.client.create_domain(service.id,
|
||||
service_version.number,
|
||||
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.
|
||||
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_check in domain_checks]
|
||||
|
||||
for origin in service_obj.origins:
|
||||
# Create the origins for this domain
|
||||
self.client.create_backend(service.id,
|
||||
service_version.number,
|
||||
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)
|
||||
|
||||
links = self._create_new_service_version(service, service_obj)
|
||||
return self.responder.created(service.id, links)
|
||||
|
||||
except fastly.FastlyError:
|
||||
return self.responder.failed("failed to create service")
|
||||
except Exception:
|
||||
return self.responder.failed("failed to create service")
|
||||
|
||||
def update(self,
|
||||
provider_service_id,
|
||||
service_old,
|
||||
service_updates,
|
||||
service_obj):
|
||||
try:
|
||||
service = self.client.get_service_details(provider_service_id)
|
||||
links = self._create_new_service_version(service, service_obj)
|
||||
return self.responder.updated(service.id, links)
|
||||
except fastly.FastlyError:
|
||||
return self.responder.failed('failed to create service')
|
||||
except Exception:
|
||||
return self.responder.failed('failed to create service')
|
||||
|
||||
def delete(self, provider_service_id):
|
||||
try:
|
||||
# Delete the service
|
||||
|
@ -36,7 +36,7 @@ class ServiceController(base.ServiceBase):
|
||||
|
||||
self.driver = driver
|
||||
|
||||
def update(self, pullzone_id, service_obj):
|
||||
def update(self, pullzone_id, service_old, service_updates, service_obj):
|
||||
'''MaxCDN update.
|
||||
|
||||
manager needs to pass in pullzone id to delete.
|
||||
@ -49,8 +49,9 @@ class ServiceController(base.ServiceBase):
|
||||
params=service_obj.to_dict())
|
||||
if update_response['code'] != 200:
|
||||
return self.responder.failed('failed to update service')
|
||||
links = {}
|
||||
return self.responder.updated(
|
||||
update_response['data']['pullzone']['id'])
|
||||
update_response['data']['pullzone']['id'], links)
|
||||
except Exception:
|
||||
# this exception branch will most likely for a network failure
|
||||
return self.responder.failed('failed to update service')
|
||||
|
@ -28,8 +28,9 @@ class ServiceController(base.ServiceBase):
|
||||
def __init__(self, driver):
|
||||
super(ServiceController, self).__init__(driver)
|
||||
|
||||
def update(self, provider_service_id, service_obj):
|
||||
return self.responder.updated(provider_service_id)
|
||||
def update(self, service_name, service_old, service_updates, service_obj):
|
||||
links = {}
|
||||
return self.responder.updated(service_name, links)
|
||||
|
||||
def create(self, service_obj):
|
||||
# We generate a fake id here
|
||||
|
@ -87,6 +87,8 @@ CQL_CREATE_SERVICE = '''
|
||||
%(provider_details)s)
|
||||
'''
|
||||
|
||||
CQL_UPDATE_SERVICE = CQL_CREATE_SERVICE
|
||||
|
||||
CQL_UPDATE_DOMAINS = '''
|
||||
UPDATE services
|
||||
SET domains = %(domains)s
|
||||
@ -179,6 +181,7 @@ class ServicesController(base.ServicesController):
|
||||
# at this point, it is certain that there's exactly 1 result in
|
||||
# results.
|
||||
result = results[0]
|
||||
|
||||
return self.format_result(result)
|
||||
|
||||
def create(self, project_id, service_obj):
|
||||
@ -225,12 +228,29 @@ class ServicesController(base.ServicesController):
|
||||
self.session.execute(CQL_CREATE_SERVICE, args)
|
||||
|
||||
def update(self, project_id, service_name, service_obj):
|
||||
# update configuration in storage
|
||||
|
||||
# determine what changed.
|
||||
domains = [json.dumps(domain.to_dict())
|
||||
for domain in service_obj.domains]
|
||||
origins = [json.dumps(origin.to_dict())
|
||||
for origin in service_obj.origins]
|
||||
caching_rules = [json.dumps(caching_rule.to_dict())
|
||||
for caching_rule in service_obj.caching]
|
||||
restrictions = [json.dumps(restriction)
|
||||
for restriction in service_obj.restrictions]
|
||||
|
||||
# update those columns provided only.
|
||||
pass
|
||||
# updates an existing new service
|
||||
args = {
|
||||
'project_id': project_id,
|
||||
'service_name': service_name,
|
||||
'flavor_id': service_obj.flavor_ref,
|
||||
'domains': domains,
|
||||
'origins': origins,
|
||||
'caching_rules': caching_rules,
|
||||
'restrictions': restrictions,
|
||||
'provider_details': {}
|
||||
}
|
||||
|
||||
self.session.execute(CQL_UPDATE_SERVICE, args)
|
||||
|
||||
def delete(self, project_id, service_name):
|
||||
"""delete.
|
||||
|
@ -18,6 +18,7 @@ import json
|
||||
from oslo.config import cfg
|
||||
import pecan
|
||||
|
||||
from poppy.common import errors
|
||||
from poppy.common import uri
|
||||
from poppy.transport.pecan.controllers import base
|
||||
from poppy.transport.pecan.models.request import service as req_service_model
|
||||
@ -169,10 +170,33 @@ class ServicesController(base.Controller):
|
||||
helpers.abort_with_message,
|
||||
stoplight_helpers.pecan_getter))
|
||||
def patch_one(self, service_name):
|
||||
service_json_dict = json.loads(pecan.request.body.decode('utf-8'))
|
||||
|
||||
# TODO(obulpathi): remove these restrictions, once cachingrule and
|
||||
# restrictions models are implemented is implemented
|
||||
if 'caching' in service_json_dict:
|
||||
pecan.abort(400, detail='This operation is yet not supported')
|
||||
elif 'restrictions' in service_json_dict:
|
||||
pecan.abort(400, detail='This operation is yet not supported')
|
||||
|
||||
# if service_json is empty, abort
|
||||
if not service_json_dict:
|
||||
pecan.abort(400, detail='No details provided to update')
|
||||
|
||||
services_controller = self._driver.manager.services_controller
|
||||
service_json = json.loads(pecan.request.body.decode('utf-8'))
|
||||
# TODO(tonytan4ever): convert service_json into a partial service model
|
||||
# under poppy.models.helpers.service.py
|
||||
# and pass service_json to update
|
||||
return services_controller.update(self.project_id, service_name,
|
||||
service_json)
|
||||
service_updates = req_service_model.load_from_json(service_json_dict)
|
||||
|
||||
try:
|
||||
services_controller.update(
|
||||
self.project_id, service_name, service_updates)
|
||||
except errors.ServiceStatusNotDeployed as e:
|
||||
pecan.abort(400, detail=str(e))
|
||||
except Exception as e:
|
||||
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
|
||||
|
@ -136,8 +136,16 @@ class ServiceSchema(schema_base.SchemaBase):
|
||||
'PATCH': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'service_name': {
|
||||
'type': 'string',
|
||||
'required': False,
|
||||
'minLength': 3,
|
||||
'maxLength': 256
|
||||
},
|
||||
'domains': {
|
||||
'type': 'array',
|
||||
'required': False,
|
||||
'minItems': 1,
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
@ -154,6 +162,8 @@ class ServiceSchema(schema_base.SchemaBase):
|
||||
},
|
||||
'origins': {
|
||||
'type': 'array',
|
||||
'required': False,
|
||||
'minItems': 1,
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -7,13 +7,6 @@
|
||||
"origins": [{"origin": "www.wooozneworigin.com",
|
||||
"port": 443, "ssl": false}]
|
||||
},
|
||||
"update_caching_list": {
|
||||
"caching": [{"newname": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 1200,
|
||||
"rules": [{"name" : "index",
|
||||
"request_url" : "/index.htm"}]}]
|
||||
},
|
||||
"update_multiple_values": {
|
||||
"domains": [{"domain": "wooozymynewwebsite.com"},
|
||||
{"domain": "wooozynewblog.mywebsite.com"}],
|
||||
|
@ -22,18 +22,9 @@
|
||||
"rules": [{"name" : "index",
|
||||
"request_url" : "/index.htm"}]}]
|
||||
},
|
||||
"invalid_domain_value": {
|
||||
"domains": [{"domain": "ftp://BOOOOOOM"},
|
||||
{"domain": "abc://BAAAAAAM"}]
|
||||
},
|
||||
"empty_origin_list": {
|
||||
"origins": []
|
||||
},
|
||||
"invalid_origin_value": {
|
||||
"origins": [{"origin": "^%}invalid_origin_value",
|
||||
"port": 443,
|
||||
"ssl": false}]
|
||||
},
|
||||
"non_numeric_origin_port": {
|
||||
"service_name": "non_numeric_origin_port",
|
||||
"domains": [{"domain": "mywebsite.com"},
|
||||
|
@ -244,7 +244,7 @@ class TestServiceActions(base.TestBase):
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
body = resp.json()
|
||||
self.assertEqual(body['status'], 'updating')
|
||||
self.assertEqual(body['status'], u'update_in_progress')
|
||||
self.client.wait_for_service_status(
|
||||
service_name=self.service_name,
|
||||
status='deployed',
|
||||
|
@ -211,7 +211,7 @@ class ServiceControllerTest(base.FunctionalTest):
|
||||
expect_errors=True)
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
def test_update(self):
|
||||
def test_update_with_bad_input(self):
|
||||
# update with erroneous data
|
||||
response = self.app.patch('/v1.0/services/' + self.service_name,
|
||||
params=json.dumps({
|
||||
@ -231,6 +231,12 @@ class ServiceControllerTest(base.FunctionalTest):
|
||||
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
def test_update_with_good_input(self):
|
||||
response = self.app.get(
|
||||
'/v1.0/services/' + self.service_name,
|
||||
headers={'X-Project-ID': self.project_id})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
# update with good data
|
||||
response = self.app.patch('/v1.0/services/' + self.service_name,
|
||||
params=json.dumps({
|
||||
@ -246,7 +252,7 @@ class ServiceControllerTest(base.FunctionalTest):
|
||||
'Content-Type': 'application/json',
|
||||
'X-Project-ID': self.project_id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
||||
def test_patch_non_exist(self):
|
||||
# This is for coverage 100%
|
||||
|
15
tests/unit/manager/default/data_service.json
Normal file
15
tests/unit/manager/default/data_service.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"service_json": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mydomain.com", "ssl": false, "port": 80},
|
||||
{"origin": "youdomain.com", "ssl": true, "port": 443}
|
||||
],
|
||||
"flavor_ref" : "standard",
|
||||
"status" : "deployed"
|
||||
}
|
||||
}
|
12
tests/unit/manager/default/service_update.json
Normal file
12
tests/unit/manager/default/service_update.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"service_json": {
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mydomain.com", "ssl": false, "port": 80},
|
||||
{"origin": "youdomain.com", "ssl": true, "port": 443}
|
||||
]
|
||||
}
|
||||
}
|
@ -38,17 +38,17 @@ class TestProviderWrapper(base.TestCase):
|
||||
mock_ext = mock.Mock(provider_name="no_existent_provider")
|
||||
self.assertRaises(errors.BadProviderDetail,
|
||||
self.provider_wrapper_obj.update,
|
||||
mock_ext, self.fake_provider_details, {})
|
||||
mock_ext, self.fake_provider_details, {}, {}, {})
|
||||
|
||||
def test_update(self):
|
||||
mock_ext = mock.Mock(provider_name="Fastly",
|
||||
obj=mock.Mock())
|
||||
fastly_provider_detail = self.fake_provider_details["Fastly"]
|
||||
mock_obj = mock.Mock(provider_name='Fastly')
|
||||
mock_ext = mock.Mock(obj=mock_obj)
|
||||
fastly_provider_detail = self.fake_provider_details['Fastly']
|
||||
self.provider_wrapper_obj.update(mock_ext,
|
||||
self.fake_provider_details, {})
|
||||
self.fake_provider_details,
|
||||
{}, {}, {})
|
||||
mock_ext.obj.service_controller.update.assert_called_once_with(
|
||||
fastly_provider_detail.provider_service_id,
|
||||
{})
|
||||
fastly_provider_detail.provider_service_id, {}, {}, {})
|
||||
|
||||
def test_delete_with_keyerror(self):
|
||||
mock_ext = mock.Mock(obj=mock.Mock(
|
||||
|
@ -64,7 +64,7 @@ class DefaultManagerServiceTests(base.TestCase):
|
||||
self.project_id = 'mock_id'
|
||||
self.service_name = 'mock_service'
|
||||
self.service_json = {
|
||||
"name": "fake_service_name",
|
||||
"name": "mock_service",
|
||||
"domains": [
|
||||
{"domain": "www.mywebsite.com"},
|
||||
{"domain": "blog.mywebsite.com"},
|
||||
@ -244,39 +244,42 @@ class DefaultManagerServiceTests(base.TestCase):
|
||||
service_obj)
|
||||
self.assertTrue(res is None)
|
||||
|
||||
@ddt.file_data('data_provider_details.json')
|
||||
def test_update(self, provider_details_json):
|
||||
self.provider_details = {}
|
||||
for provider_name in provider_details_json:
|
||||
provider_detail_dict = json.loads(
|
||||
provider_details_json[provider_name]
|
||||
)
|
||||
provider_service_id = provider_detail_dict.get("id", None)
|
||||
access_urls = provider_detail_dict.get("access_urls", None)
|
||||
status = provider_detail_dict.get("status", u'unknown')
|
||||
@ddt.file_data('service_update.json')
|
||||
def test_update(self, update_json):
|
||||
provider_details_dict = {
|
||||
"MaxCDN": {"id": 11942, "access_urls": ["mypullzone.netdata.com"]},
|
||||
"Mock": {"id": 73242, "access_urls": ["mycdn.mock.com"]},
|
||||
"CloudFront": {
|
||||
"id": "5ABC892", "access_urls": ["cf123.cloudcf.com"]},
|
||||
"Fastly": {
|
||||
"id": 3488, "access_urls": ["mockcf123.fastly.prod.com"]}
|
||||
}
|
||||
providers_details = {}
|
||||
for name in provider_details_dict:
|
||||
details = provider_details_dict[name]
|
||||
provider_detail_obj = provider_details.ProviderDetail(
|
||||
provider_service_id=provider_service_id,
|
||||
access_urls=access_urls,
|
||||
status=status)
|
||||
self.provider_details[provider_name] = provider_detail_obj
|
||||
provider_service_id=details['id'],
|
||||
access_urls=details['access_urls'],
|
||||
status=details.get('status', u'unknown'))
|
||||
providers_details[name] = provider_detail_obj
|
||||
|
||||
providers = self.sc._driver.providers
|
||||
|
||||
self.sc.storage_controller.get_provider_details.return_value = (
|
||||
self.provider_details
|
||||
providers_details
|
||||
)
|
||||
|
||||
self.sc.update(self.project_id, self.service_name, self.service_json)
|
||||
service_obj = service.load_from_json(self.service_json)
|
||||
service_obj.status = u'deployed'
|
||||
self.sc.storage_controller.get.return_value = service_obj
|
||||
service_update_obj = service.load_from_json(update_json)
|
||||
self.sc.update(self.project_id, self.service_name, service_update_obj)
|
||||
|
||||
# ensure the manager calls the storage driver with the appropriate data
|
||||
self.sc.storage_controller.update.assert_called_once_with(
|
||||
self.project_id,
|
||||
self.service_name,
|
||||
self.service_json)
|
||||
self.sc.storage_controller.update.assert_called_once()
|
||||
|
||||
# and that the providers are notified.
|
||||
providers.map.assert_called_once_with(self.sc.provider_wrapper.update,
|
||||
self.provider_details,
|
||||
self.service_json)
|
||||
providers.map.assert_called_once()
|
||||
|
||||
@ddt.file_data('data_provider_details.json')
|
||||
def test_delete(self, provider_details_json):
|
||||
|
@ -33,6 +33,7 @@ class TestServices(base.TestCase):
|
||||
super(TestServices, self).setUp()
|
||||
|
||||
self.service_name = uuid.uuid1()
|
||||
self.provider_service_id = uuid.uuid1()
|
||||
self.mock_get_client = mock_get_client
|
||||
self.driver = MockDriver()
|
||||
self.controller = services.ServiceController(self.driver)
|
||||
@ -97,7 +98,10 @@ class TestServices(base.TestCase):
|
||||
@ddt.file_data('data_service.json')
|
||||
def test_update(self, service_json):
|
||||
service_obj = service.load_from_json(service_json)
|
||||
resp = self.controller.update(self.service_name, service_obj)
|
||||
service_old = service_obj
|
||||
service_updates = service_obj
|
||||
resp = self.controller.update(self.provider_service_id, service_old,
|
||||
service_updates, service_obj)
|
||||
self.assertIn('id', resp[self.driver.provider_name])
|
||||
|
||||
def test_delete_exceptions(self):
|
||||
|
@ -234,7 +234,10 @@ class TestServices(base.TestCase):
|
||||
def test_update(self, service_json):
|
||||
provider_service_id = uuid.uuid1()
|
||||
controller = services.ServiceController(self.driver)
|
||||
resp = controller.update(provider_service_id, service_json)
|
||||
controller.client.list_versions.return_value = [self.version]
|
||||
service_obj = service.load_from_json(service_json)
|
||||
resp = controller.update(
|
||||
provider_service_id, service_obj, service_obj, service_obj)
|
||||
self.assertIn('id', resp[self.driver.provider_name])
|
||||
|
||||
def test_purge_with_exception(self):
|
||||
|
@ -14,6 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
@ -90,8 +92,8 @@ class TestServices(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestServices, self).setUp()
|
||||
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.provider_service_id = uuid.uuid1()
|
||||
|
||||
@mock.patch.object(driver.CDNProvider, 'client',
|
||||
new=fake_maxcdn_api_client())
|
||||
@ -154,8 +156,10 @@ class TestServices(base.TestCase):
|
||||
controller = services.ServiceController(new_driver)
|
||||
# test create, everything goes through successfully
|
||||
service_obj = service.load_from_json(service_json)
|
||||
service_name = 'test_service_name'
|
||||
resp = controller.update(service_name, service_obj)
|
||||
service_old = service_obj
|
||||
service_updates = service_obj
|
||||
resp = controller.update(self.provider_service_id, service_old,
|
||||
service_updates, service_obj)
|
||||
self.assertIn('id', resp[new_driver.provider_name])
|
||||
|
||||
@ddt.file_data('data_service.json')
|
||||
@ -170,14 +174,14 @@ class TestServices(base.TestCase):
|
||||
fake_maxcdn_client_get_return_value
|
||||
})
|
||||
|
||||
service_name = 'test_service_name'
|
||||
|
||||
controller_with_update_exception = services.ServiceController(driver)
|
||||
controller_with_update_exception.client.configure_mock(**{
|
||||
'put.side_effect':
|
||||
RuntimeError('Updating service mysteriously failed.')})
|
||||
resp = controller_with_update_exception.update(
|
||||
service_name,
|
||||
self.provider_service_id,
|
||||
service_json,
|
||||
service_json,
|
||||
service_json)
|
||||
self.assertIn('error', resp[driver.provider_name])
|
||||
|
||||
@ -188,7 +192,9 @@ class TestServices(base.TestCase):
|
||||
})
|
||||
service_obj = service.load_from_json(service_json)
|
||||
resp = controller_with_update_exception.update(
|
||||
service_name,
|
||||
self.provider_service_id,
|
||||
service_obj,
|
||||
service_obj,
|
||||
service_obj)
|
||||
self.assertIn('error', resp[driver.provider_name])
|
||||
|
||||
|
@ -37,7 +37,10 @@ class MockProviderServicesTest(base.TestCase):
|
||||
@ddt.file_data('data_service.json')
|
||||
def test_update(self, service_json):
|
||||
service_obj = service.load_from_json(service_json)
|
||||
response = self.sc.update(self.test_provider_service_id, service_obj)
|
||||
service_old = service_obj
|
||||
service_updates = service_obj
|
||||
response = self.sc.update(self.test_provider_service_id, service_old,
|
||||
service_updates, service_obj)
|
||||
self.assertTrue(response is not None)
|
||||
|
||||
def test_delete(self):
|
||||
|
@ -127,11 +127,12 @@ class CassandraStorageServiceTests(base.TestCase):
|
||||
@ddt.file_data('../data/data_update_service.json')
|
||||
@mock.patch.object(services.ServicesController, 'session')
|
||||
@mock.patch.object(cassandra.cluster.Session, 'execute')
|
||||
def test_update_service(self, value, mock_session, mock_execute):
|
||||
def test_update_service(self, service_json, mock_session, mock_execute):
|
||||
# mock the response from cassandra
|
||||
service_obj = req_service.load_from_json(service_json)
|
||||
actual_response = self.sc.update(self.project_id,
|
||||
self.service_name,
|
||||
value)
|
||||
service_obj)
|
||||
|
||||
# Expect the response to be None as there are no providers passed
|
||||
# into the driver to respond to this call
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"every_field": {
|
||||
"flavor_ref": "standard",
|
||||
"domains": [
|
||||
{"domain": "test.mocksite.com" },
|
||||
{"domain": "blog.mocksite.com"}
|
||||
@ -29,4 +30,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user