diff --git a/poppy/manager/default/services.py b/poppy/manager/default/services.py index bb029ff7..7a1713d1 100644 --- a/poppy/manager/default/services.py +++ b/poppy/manager/default/services.py @@ -174,7 +174,8 @@ class DefaultServicesController(base.ServicesController): """create. :param project_id - :param service_obj + :param auth_token + :param service_json :raises LookupError, ValueError """ try: @@ -236,12 +237,15 @@ class DefaultServicesController(base.ServicesController): return service_obj - def update(self, project_id, service_id, auth_token, service_updates): + def update(self, project_id, service_id, + auth_token, service_updates, force_update=False): """update. :param project_id :param service_id + :param auth_token :param service_updates + :param force_update :raises LookupError, ValueError """ # get the current service object @@ -254,7 +258,10 @@ class DefaultServicesController(base.ServicesController): raise errors.ServiceStatusDisabled( u'Service {0} is disabled'.format(service_id)) - if service_old.status not in [u'deployed', u'failed']: + if ( + service_old.status not in [u'deployed', u'failed'] and + force_update is False + ): raise errors.ServiceStatusNeitherDeployedNorFailed( u'Service {0} neither deployed nor failed'.format(service_id)) @@ -434,11 +441,23 @@ class DefaultServicesController(base.ServicesController): project_id=project_id, project_limit=limit) - def set_service_provider_details(self, project_id, service_id, status): + def set_service_provider_details(self, project_id, service_id, + auth_token, status): + old_service = self.storage_controller.get(project_id, service_id) + + if ( + old_service.status == 'create_in_progress' and + old_service.provider_details == {} + ): + self.update( + project_id, service_id, auth_token, [], force_update=True) + return 202 self.storage_controller.set_service_provider_details( project_id, service_id, - status) + status + ) + return 201 def get_services_limit(self, project_id): limit = self.storage_controller.get_service_limit( diff --git a/poppy/storage/cassandra/driver.py b/poppy/storage/cassandra/driver.py index 509b0fbf..18993489 100644 --- a/poppy/storage/cassandra/driver.py +++ b/poppy/storage/cassandra/driver.py @@ -191,7 +191,7 @@ class CassandraStorageDriver(base.Driver): """Health check for Cassandra.""" try: - self.session.execute( + self.database.execute( "SELECT cluster_name, data_center FROM system.local;") except Exception: return False diff --git a/poppy/transport/pecan/controllers/v1/admin.py b/poppy/transport/pecan/controllers/v1/admin.py index e2a34dc7..c4f4e1f5 100644 --- a/poppy/transport/pecan/controllers/v1/admin.py +++ b/poppy/transport/pecan/controllers/v1/admin.py @@ -390,10 +390,14 @@ class ServiceStatusController(base.Controller, hooks.HookController): status = service_state_json['status'] services_controller = self._driver.manager.services_controller + status_code = None try: - services_controller.set_service_provider_details(project_id, - service_id, - status) + status_code = services_controller.set_service_provider_details( + project_id, + service_id, + self.auth_token, + status + ) except Exception as e: pecan.abort(404, detail=( 'Setting state of service {0} on tenant: {1} ' @@ -403,7 +407,7 @@ class ServiceStatusController(base.Controller, hooks.HookController): status, str(e)))) - return pecan.Response(None, 201) + return pecan.Response(None, status_code) class AdminCertController(base.Controller, hooks.HookController): diff --git a/poppy/transport/validators/helpers.py b/poppy/transport/validators/helpers.py index 634277b1..45624997 100644 --- a/poppy/transport/validators/helpers.py +++ b/poppy/transport/validators/helpers.py @@ -580,7 +580,7 @@ def is_valid_service_status(request): # supported statuses VALID_STATUSES = [ - u'deploy_in_progress', + u'create_in_progress', u'deployed', u'update_in_progress', u'delete_in_progress', diff --git a/tests/functional/transport/pecan/controllers/test_get_service_status.py b/tests/functional/transport/pecan/controllers/test_get_service_status.py index 99a7bbc1..7b1c8c52 100644 --- a/tests/functional/transport/pecan/controllers/test_get_service_status.py +++ b/tests/functional/transport/pecan/controllers/test_get_service_status.py @@ -53,9 +53,9 @@ class TestGetServiceStatus(base.FunctionalTest): self.assertEqual(response.status_code, 400) - @ddt.data('deploy_in_progress', 'deployed', 'update_in_progress', + @ddt.data('create_in_progress', 'deployed', 'update_in_progress', 'delete_in_progress', 'failed') - def test_get_service_status_valid_queryparam(self, status): + def test_get_service_status_valid_query_param(self, status): # valid status with mock.patch.object(DefaultServicesController, 'get_services_by_status'): diff --git a/tests/functional/transport/pecan/controllers/test_set_service_status.py b/tests/functional/transport/pecan/controllers/test_set_service_status.py index 539823f6..5e2ec220 100644 --- a/tests/functional/transport/pecan/controllers/test_set_service_status.py +++ b/tests/functional/transport/pecan/controllers/test_set_service_status.py @@ -30,7 +30,78 @@ class TestServicesState(base.FunctionalTest): super(TestServicesState, self).setUp() self.project_id = str(uuid.uuid4()) - self.service_id = str(uuid.uuid4()) + self.service_name = str(uuid.uuid1()) + self.flavor_id = str(uuid.uuid1()) + + # create a mock flavor to be used by new service creations + flavor_json = { + "id": self.flavor_id, + "providers": [ + { + "provider": "mock", + "links": [ + { + "href": "http://mock.cdn", + "rel": "provider_url" + } + ] + } + ] + } + response = self.app.post('/v1.0/flavors', + params=json.dumps(flavor_json), + headers={ + "Content-Type": "application/json", + "X-Project-ID": self.project_id}) + + self.assertEqual(201, response.status_code) + + # create an initial service to be used by the tests + self.service_json = { + "name": self.service_name, + "domains": [ + {"domain": "test.mocksite.com"}, + {"domain": "blog.mocksite.com"} + ], + "origins": [ + { + "origin": "mocksite.com", + "port": 80, + "ssl": False + } + ], + "flavor_id": self.flavor_id, + "caching": [ + { + "name": "default", + "ttl": 3600 + } + ], + "restrictions": [ + { + "name": "website only", + "type": "whitelist", + "rules": [ + { + "name": "mocksite.com", + "referrer": "www.mocksite.com" + } + ] + } + ] + } + + response = self.app.post('/v1.0/services', + params=json.dumps(self.service_json), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': self.project_id}) + self.assertEqual(202, response.status_code) + self.assertTrue('Location' in response.headers) + + self.service_id = (response.headers['Location'] + [response.headers['Location'].rfind('/') + 1:]) + self.req_body = { 'project_id': self.project_id, 'service_id': self.service_id, @@ -38,6 +109,13 @@ class TestServicesState(base.FunctionalTest): @ddt.data(u'deployed', u'failed') def test_services_state_valid_states(self, status): + response = self.app.get( + '/v1.0/services/{0}'.format(self.service_id), + headers={'X-Project-ID': self.project_id} + ) + + self.assertEqual(200, response.status_code) + self.req_body['status'] = status response = self.app.post( '/v1.0/admin/services/status', diff --git a/tests/unit/manager/default/test_services.py b/tests/unit/manager/default/test_services.py index 6578a6af..5c1e8839 100644 --- a/tests/unit/manager/default/test_services.py +++ b/tests/unit/manager/default/test_services.py @@ -20,6 +20,7 @@ import uuid import ddt import mock from oslo_config import cfg +from oslo_context import context import requests import six @@ -103,8 +104,8 @@ class DefaultManagerServiceTests(base.TestCase): @mock.patch('poppy.storage.base.driver.StorageDriverBase') @mock.patch('poppy.distributed_task.base.driver.DistributedTaskDriverBase') @mock.patch('poppy.metrics.base.driver.MetricsDriverBase') - def setUp(self, mock_distributed_task, mock_storage, - mock_dns, mock_notification, mock_bootstrap, mock_metrics): + def setUp(self, mock_metrics, mock_distributed_task, mock_storage, + mock_dns, mock_notification, mock_bootstrap): # NOTE(TheSriram): the mock.patch decorator applies mocks # in the reverse order of the arguments present super(DefaultManagerServiceTests, self).setUp() @@ -216,6 +217,9 @@ class DefaultManagerServiceTests(base.TestCase): self.service_obj = service.load_from_json(self.service_json) + self.mock_storage = mock_storage + self.mock_distributed_task = mock_distributed_task + @mock.patch('poppy.bootstrap.Bootstrap') def mock_purge_service(self, mock_bootstrap, hard=False): mock_bootstrap.return_value = self.bootstrap_obj @@ -1005,3 +1009,58 @@ class DefaultManagerServiceTests(base.TestCase): memoized_controllers.task_controllers): self.mock_purge_service(hard=True) self.mock_purge_service(hard=False) + + def test_set_service_provider_details_missing_provider_details(self): + context.RequestContext(overwrite=True) + mock_service_obj = mock.Mock() + mock_service_obj.to_dict.return_value = { + 'name': 'name', + 'flavor_id': 'flavor_id', + 'service_id': 'service_id', + 'status': 'status', + 'operator_status': 'operator_status', + 'provider_details': 'provider_details', + 'domains': [ + {'domain': 'www.test.com'} + ], + 'origins': [ + { + "origin": "www.tester.com", + "port": 80, + "ssl": False, + "rules": [ + { + "name": "default", + "request_url": "/*" + } + ], + "hostheadertype": "domain" + } + ] + } + + type(mock_service_obj).service_id = mock.PropertyMock( + return_value='service_id' + ) + type(mock_service_obj).status = mock.PropertyMock( + return_value="create_in_progress" + ) + type(mock_service_obj).provider_details = mock.PropertyMock( + return_value=dict() + ) + type(mock_service_obj).domains = mock.PropertyMock( + return_value=list() + ) + + self.mock_storage.services_controller.get.return_value = ( + mock_service_obj + ) + + self.sc.set_service_provider_details( + "project_id", "service_id", "auth_token", "deployed" + ) + self.assertTrue(self.mock_storage.services_controller.get.called) + self.assertTrue(self.mock_storage.services_controller.update.called) + self.assertTrue( + self.mock_distributed_task.services_controller.submit_task.called + )