Patch service endpoint

Implements: blueprint patch-service
Change-Id: I2fe9355713a4bf63f515f3e6dad4611722c57fdc
This commit is contained in:
Obulpathi 2014-10-15 11:33:36 -04:00
parent 7fe0197d2b
commit 8a2c731f92
30 changed files with 393 additions and 169 deletions

3
.gitignore vendored
View File

@ -42,3 +42,6 @@ venv
docker_rsa*
Dockerfile
# patch files
*.patch

View File

@ -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."""

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -17,6 +17,7 @@
VALID_STATUSES = [
u'deploy_in_progress',
u'deployed',
u'update_in_progress',
u'delete_in_progress',
u'failed']

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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': {

View File

@ -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"}],

View File

@ -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"},

View File

@ -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',

View File

@ -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%

View 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"
}
}

View 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}
]
}
}

View File

@ -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(

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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])

View File

@ -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):

View File

@ -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

View File

@ -1,5 +1,6 @@
{
"every_field": {
"flavor_ref": "standard",
"domains": [
{"domain": "test.mocksite.com" },
{"domain": "blog.mocksite.com"}
@ -29,4 +30,4 @@
}
]
}
}
}