diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index fa780a5562c5..b688cb9d3000 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.json
+++ b/doc/api_samples/all_extensions/extensions-get-resp.json
@@ -623,6 +623,14 @@
"name": "Volumes",
"namespace": "http://docs.openstack.org/compute/ext/volumes/api/v1.1",
"updated": "2011-03-25T00:00:00+00:00"
+ },
+ {
+ "alias": "os-extended-services-delete",
+ "description": "Services deletion support.",
+ "links": [],
+ "name": "ExtendedServicesDelete",
+ "namespace": "http://docs.openstack.org/compute/ext/extended_services_delete/api/v2",
+ "updated": "2013-12-10T00:00:00+00:00"
}
]
}
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index d12e74d80a56..80337434dc29 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.xml
+++ b/doc/api_samples/all_extensions/extensions-get-resp.xml
@@ -254,4 +254,7 @@
Volumes support.
+
+ Services deletion support.
+
diff --git a/doc/api_samples/os-extended-services-delete/services-get-resp.json b/doc/api_samples/os-extended-services-delete/services-get-resp.json
new file mode 100644
index 000000000000..fefb0474c294
--- /dev/null
+++ b/doc/api_samples/os-extended-services-delete/services-get-resp.json
@@ -0,0 +1,40 @@
+{
+ "services": [
+ {
+ "id": 1,
+ "binary": "nova-scheduler",
+ "host": "host1",
+ "state": "up",
+ "status": "disabled",
+ "updated_at": "2012-10-29T13:42:02.000000",
+ "zone": "internal"
+ },
+ {
+ "id": 2,
+ "binary": "nova-compute",
+ "host": "host1",
+ "state": "up",
+ "status": "disabled",
+ "updated_at": "2012-10-29T13:42:05.000000",
+ "zone": "nova"
+ },
+ {
+ "id": 3,
+ "binary": "nova-scheduler",
+ "host": "host2",
+ "state": "down",
+ "status": "enabled",
+ "updated_at": "2012-09-19T06:55:34.000000",
+ "zone": "internal"
+ },
+ {
+ "id": 4,
+ "binary": "nova-compute",
+ "host": "host2",
+ "state": "down",
+ "status": "disabled",
+ "updated_at": "2012-09-18T08:03:38.000000",
+ "zone": "nova"
+ }
+ ]
+}
diff --git a/doc/api_samples/os-extended-services-delete/services-get-resp.xml b/doc/api_samples/os-extended-services-delete/services-get-resp.xml
new file mode 100644
index 000000000000..bb5b46770ac5
--- /dev/null
+++ b/doc/api_samples/os-extended-services-delete/services-get-resp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/doc/v3/api_samples/os-services/services-list-get-resp.json b/doc/v3/api_samples/os-services/services-list-get-resp.json
index 40b3bf2521af..1b2c5bf3438b 100644
--- a/doc/v3/api_samples/os-services/services-list-get-resp.json
+++ b/doc/v3/api_samples/os-services/services-list-get-resp.json
@@ -1,6 +1,7 @@
{
"services": [
{
+ "id": 1,
"binary": "nova-scheduler",
"disabled_reason": "test1",
"host": "host1",
@@ -10,6 +11,7 @@
"zone": "internal"
},
{
+ "id": 2,
"binary": "nova-compute",
"disabled_reason": "test2",
"host": "host1",
@@ -19,6 +21,7 @@
"zone": "nova"
},
{
+ "id": 3,
"binary": "nova-scheduler",
"disabled_reason": "",
"host": "host2",
@@ -28,6 +31,7 @@
"zone": "internal"
},
{
+ "id": 4,
"binary": "nova-compute",
"disabled_reason": "test4",
"host": "host2",
@@ -37,4 +41,4 @@
"zone": "nova"
}
]
-}
\ No newline at end of file
+}
diff --git a/nova/api/openstack/compute/contrib/extended_services_delete.py b/nova/api/openstack/compute/contrib/extended_services_delete.py
new file mode 100644
index 000000000000..a7b3bd38230a
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/extended_services_delete.py
@@ -0,0 +1,23 @@
+# 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 nova.api.openstack import extensions
+
+
+class Extended_services_delete(extensions.ExtensionDescriptor):
+ """Extended services deletion support."""
+
+ name = "ExtendedServicesDelete"
+ alias = "os-extended-services-delete"
+ namespace = ("http://docs.openstack.org/compute/ext/"
+ "extended_services_delete/api/v2")
+ updated = "2013-12-10T00:00:00"
diff --git a/nova/api/openstack/compute/contrib/services.py b/nova/api/openstack/compute/contrib/services.py
index 373582763a41..601255ec7b14 100644
--- a/nova/api/openstack/compute/contrib/services.py
+++ b/nova/api/openstack/compute/contrib/services.py
@@ -33,6 +33,7 @@ class ServicesIndexTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('services')
elem = xmlutil.SubTemplateElement(root, 'service', selector='services')
+ elem.set('id')
elem.set('binary')
elem.set('host')
elem.set('zone')
@@ -106,6 +107,8 @@ class ServiceController(object):
'zone': svc['availability_zone'],
'status': active, 'state': state,
'updated_at': svc['updated_at']}
+ if self.ext_mgr.is_loaded('os-extended-services-delete'):
+ service_detail['id'] = svc['id']
if detailed:
service_detail['disabled_reason'] = svc['disabled_reason']
@@ -128,6 +131,21 @@ class ServiceController(object):
return True
+ @wsgi.response(204)
+ def delete(self, req, id):
+ """Deletes the specified service."""
+ if not self.ext_mgr.is_loaded('os-extended-services-delete'):
+ raise webob.exc.HTTPMethodNotAllowed()
+
+ context = req.environ['nova.context']
+ authorize(context)
+
+ try:
+ self.host_api.service_delete(context, id)
+ except exception.ServiceNotFound:
+ explanation = _("Service %s not found.") % id
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
@wsgi.serializers(xml=ServicesIndexTemplate)
def index(self, req):
"""
diff --git a/nova/api/openstack/compute/plugins/v3/services.py b/nova/api/openstack/compute/plugins/v3/services.py
index 6c4ebf65f5f7..54456614d83b 100644
--- a/nova/api/openstack/compute/plugins/v3/services.py
+++ b/nova/api/openstack/compute/plugins/v3/services.py
@@ -16,6 +16,7 @@ from oslo.config import cfg
import webob.exc
from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
from nova import compute
from nova import exception
from nova.openstack.common.gettextutils import _
@@ -28,7 +29,7 @@ CONF = cfg.CONF
CONF.import_opt('service_down_time', 'nova.service')
-class ServiceController(object):
+class ServiceController(wsgi.Controller):
def __init__(self):
self.host_api = compute.HostAPI()
@@ -60,6 +61,7 @@ class ServiceController(object):
if svc['disabled']:
active = 'disabled'
service_detail = {'binary': svc['binary'], 'host': svc['host'],
+ 'id': svc['id'],
'zone': svc['availability_zone'],
'status': active, 'state': state,
'updated_at': svc['updated_at'],
@@ -84,6 +86,19 @@ class ServiceController(object):
return True
+ @wsgi.response(204)
+ @extensions.expected_errors((400, 404))
+ def delete(self, req, id):
+ """Deletes the specified service."""
+ context = req.environ['nova.context']
+ authorize(context)
+
+ try:
+ self.host_api.service_delete(context, id)
+ except exception.ServiceNotFound:
+ explanation = _("Service %s not found.") % id
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
@extensions.expected_errors(())
def index(self, req):
"""
diff --git a/nova/cells/manager.py b/nova/cells/manager.py
index 7e7cd7fde389..b35be14c6647 100644
--- a/nova/cells/manager.py
+++ b/nova/cells/manager.py
@@ -71,7 +71,7 @@ class CellsManager(manager.Manager):
Scheduling requests get passed to the scheduler class.
"""
- target = oslo_messaging.Target(version='1.25')
+ target = oslo_messaging.Target(version='1.26')
def __init__(self, *args, **kwargs):
LOG.warn(_('The cells feature of Nova is considered experimental '
@@ -303,6 +303,12 @@ class CellsManager(manager.Manager):
cells_utils.add_cell_to_service(service, response.cell_name)
return service
+ def service_delete(self, ctxt, cell_service_id):
+ """Deletes the specified service."""
+ cell_name, service_id = cells_utils.split_cell_and_item(
+ cell_service_id)
+ self.msg_runner.service_delete(ctxt, cell_name, service_id)
+
def proxy_rpc_to_manager(self, ctxt, topic, rpc_message, call, timeout):
"""Proxy an RPC message as-is to a manager."""
compute_topic = CONF.compute_topic
diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py
index 4db07c465bfa..377cae6c5084 100644
--- a/nova/cells/messaging.py
+++ b/nova/cells/messaging.py
@@ -757,6 +757,10 @@ class _TargetedMessageMethods(_BaseMessageMethods):
self.host_api.service_update(message.ctxt, host_name, binary,
params_to_update))
+ def service_delete(self, message, service_id):
+ """Deletes the specified service."""
+ self.host_api.service_delete(message.ctxt, service_id)
+
def proxy_rpc_to_manager(self, message, host_name, rpc_message,
topic, timeout):
"""Proxy RPC to the given compute topic."""
@@ -1535,6 +1539,15 @@ class MessageRunner(object):
need_response=True)
return message.process()
+ def service_delete(self, ctxt, cell_name, service_id):
+ """Deletes the specified service."""
+ method_kwargs = {'service_id': service_id}
+ message = _TargetedMessage(self, ctxt,
+ 'service_delete',
+ method_kwargs, 'down', cell_name,
+ need_response=True)
+ message.process()
+
def proxy_rpc_to_manager(self, ctxt, cell_name, host_name, topic,
rpc_message, call, timeout):
method_kwargs = {'host_name': host_name,
diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py
index 70ef10993c19..cfa3f3a98a86 100644
--- a/nova/cells/rpcapi.py
+++ b/nova/cells/rpcapi.py
@@ -88,6 +88,7 @@ class CellsAPI(object):
handle the version_cap being set to 1.24.
1.25 - Adds rebuild_instance()
+ 1.26 - Adds service_delete()
'''
VERSION_ALIASES = {
@@ -258,6 +259,12 @@ class CellsAPI(object):
binary=binary,
params_to_update=params_to_update)
+ def service_delete(self, ctxt, cell_service_id):
+ """Deletes the specified service."""
+ cctxt = self.client.prepare(version='1.26')
+ cctxt.call(ctxt, 'service_delete',
+ cell_service_id=cell_service_id)
+
def proxy_rpc_to_manager(self, ctxt, rpc_message, topic, call=False,
timeout=None):
"""Proxy RPC to a compute manager. The host in the topic
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 83d3db56bf44..b87b9ace8508 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -3152,6 +3152,10 @@ class HostAPI(base.Base):
service.save()
return service
+ def service_delete(self, context, service_id):
+ """Deletes the specified service."""
+ service_obj.Service.get_by_id(context, service_id).destroy()
+
def instance_get_all_by_host(self, context, host_name):
"""Return all instances on the given host."""
return self.db.instance_get_all_by_host(context, host_name)
diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py
index 0f31c2428688..afa3a5b54d82 100644
--- a/nova/compute/cells_api.py
+++ b/nova/compute/cells_api.py
@@ -558,6 +558,10 @@ class HostAPI(compute_api.HostAPI):
service_obj.Service(),
db_service)
+ def service_delete(self, context, service_id):
+ """Deletes the specified service."""
+ self.cells_rpcapi.service_delete(context, service_id)
+
def instance_get_all_by_host(self, context, host_name):
"""Get all instances by host. Host might have a cell prepended
to it, so we'll need to strip it out. We don't need to proxy
diff --git a/nova/tests/api/openstack/compute/contrib/test_services.py b/nova/tests/api/openstack/compute/contrib/test_services.py
index d9b877e34841..9d0006488335 100644
--- a/nova/tests/api/openstack/compute/contrib/test_services.py
+++ b/nova/tests/api/openstack/compute/contrib/test_services.py
@@ -15,6 +15,8 @@
import calendar
import datetime
+
+import mock
import webob.exc
from nova.api.openstack.compute.contrib import services
@@ -332,6 +334,44 @@ class ServicesTest(test.TestCase):
'disabled_reason': 'test2'}]}
self.assertEqual(res_dict, response)
+ def test_services_detail_with_delete_extension(self):
+ self.ext_mgr.extensions['os-extended-services-delete'] = True
+ self.controller = services.ServiceController(self.ext_mgr)
+ with mock.patch.object(self.controller.host_api, 'service_get_all',
+ side_effect=fake_host_api_service_get_all):
+ req = FakeRequest()
+ res_dict = self.controller.index(req)
+ response = {'services': [
+ {'binary': 'nova-scheduler',
+ 'host': 'host1',
+ 'id': 1,
+ 'zone': 'internal',
+ 'status': 'disabled',
+ 'state': 'up',
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)},
+ {'binary': 'nova-compute',
+ 'host': 'host1',
+ 'id': 2,
+ 'zone': 'nova',
+ 'status': 'disabled',
+ 'state': 'up',
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)},
+ {'binary': 'nova-scheduler',
+ 'host': 'host2',
+ 'id': 3,
+ 'zone': 'internal',
+ 'status': 'enabled',
+ 'state': 'down',
+ 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34)},
+ {'binary': 'nova-compute',
+ 'host': 'host2',
+ 'id': 4,
+ 'zone': 'nova',
+ 'status': 'disabled',
+ 'state': 'down',
+ 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]}
+ self.assertEqual(res_dict, response)
+
def test_services_enable(self):
def _service_update(context, service_id, values):
self.assertIsNone(values['disabled_reason'])
@@ -396,3 +436,35 @@ class ServicesTest(test.TestCase):
self.assertFalse(self.controller._is_valid_as_reason(reason))
reason = 'it\'s a valid reason.'
self.assertTrue(self.controller._is_valid_as_reason(reason))
+
+ def test_services_delete(self):
+ self.ext_mgr.extensions['os-extended-services-delete'] = True
+ self.controller = services.ServiceController(self.ext_mgr)
+
+ request = fakes.HTTPRequest.blank('/v2/fakes/os-services/1',
+ use_admin_context=True)
+ request.method = 'DELETE'
+
+ with mock.patch.object(self.controller.host_api,
+ 'service_delete') as service_delete:
+ self.controller.delete(request, '1')
+ service_delete.assert_called_once_with(
+ request.environ['nova.context'], '1')
+ self.assertEqual(self.controller.delete.wsgi_code, 204)
+
+ def test_services_delete_not_found(self):
+ self.ext_mgr.extensions['os-extended-services-delete'] = True
+ self.controller = services.ServiceController(self.ext_mgr)
+
+ request = fakes.HTTPRequest.blank('/v2/fakes/os-services/abc',
+ use_admin_context=True)
+ request.method = 'DELETE'
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.delete, request, 'abc')
+
+ def test_services_delete_not_enabled(self):
+ request = fakes.HTTPRequest.blank('/v2/fakes/os-services/300',
+ use_admin_context=True)
+ request.method = 'DELETE'
+ self.assertRaises(webob.exc.HTTPMethodNotAllowed,
+ self.controller.delete, request, '300')
diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_services.py b/nova/tests/api/openstack/compute/plugins/v3/test_services.py
index ce80e62b13d7..966a6e84b13b 100644
--- a/nova/tests/api/openstack/compute/plugins/v3/test_services.py
+++ b/nova/tests/api/openstack/compute/plugins/v3/test_services.py
@@ -14,6 +14,8 @@
import calendar
import datetime
+
+import mock
import webob.exc
from nova.api.openstack.compute.plugins.v3 import services
@@ -150,6 +152,7 @@ class ServicesTest(test.TestCase):
res_dict = self.controller.index(req)
response = {'services': [
{'binary': 'nova-scheduler',
+ 'id': 1,
'host': 'host1',
'zone': 'internal',
'status': 'disabled',
@@ -158,6 +161,7 @@ class ServicesTest(test.TestCase):
'disabled_reason': 'test1'},
{'binary': 'nova-compute',
'host': 'host1',
+ 'id': 2,
'zone': 'nova',
'status': 'disabled',
'state': 'up',
@@ -165,6 +169,7 @@ class ServicesTest(test.TestCase):
'disabled_reason': 'test2'},
{'binary': 'nova-scheduler',
'host': 'host2',
+ 'id': 3,
'zone': 'internal',
'status': 'enabled',
'state': 'down',
@@ -172,6 +177,7 @@ class ServicesTest(test.TestCase):
'disabled_reason': ''},
{'binary': 'nova-compute',
'host': 'host2',
+ 'id': 4,
'zone': 'nova',
'status': 'disabled',
'state': 'down',
@@ -187,6 +193,7 @@ class ServicesTest(test.TestCase):
response = {'services': [
{'binary': 'nova-scheduler',
'host': 'host1',
+ 'id': 1,
'zone': 'internal',
'status': 'disabled',
'state': 'up',
@@ -194,6 +201,7 @@ class ServicesTest(test.TestCase):
'disabled_reason': 'test1'},
{'binary': 'nova-compute',
'host': 'host1',
+ 'id': 2,
'zone': 'nova',
'status': 'disabled',
'state': 'up',
@@ -209,6 +217,7 @@ class ServicesTest(test.TestCase):
response = {'services': [
{'binary': 'nova-compute',
'host': 'host1',
+ 'id': 2,
'zone': 'nova',
'status': 'disabled',
'state': 'up',
@@ -216,6 +225,7 @@ class ServicesTest(test.TestCase):
'disabled_reason': 'test2'},
{'binary': 'nova-compute',
'host': 'host2',
+ 'id': 4,
'zone': 'nova',
'status': 'disabled',
'state': 'down',
@@ -231,6 +241,7 @@ class ServicesTest(test.TestCase):
response = {'services': [
{'binary': 'nova-compute',
'host': 'host1',
+ 'id': 2,
'zone': 'nova',
'status': 'disabled',
'state': 'up',
@@ -299,3 +310,22 @@ class ServicesTest(test.TestCase):
self.assertFalse(self.controller._is_valid_as_reason(reason))
reason = 'it\'s a valid reason.'
self.assertTrue(self.controller._is_valid_as_reason(reason))
+
+ def test_services_delete(self):
+ request = fakes.HTTPRequestV3.blank('/v3/os-services/1',
+ use_admin_context=True)
+ request.method = 'DELETE'
+
+ with mock.patch.object(self.controller.host_api,
+ 'service_delete') as service_delete:
+ response = self.controller.delete(request, '1')
+ service_delete.assert_called_once_with(
+ request.environ['nova.context'], '1')
+ self.assertEqual(self.controller.delete.wsgi_code, 204)
+
+ def test_services_delete_not_found(self):
+ request = fakes.HTTPRequestV3.blank('/v3/os-services/abc',
+ use_admin_context=True)
+ request.method = 'DELETE'
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.delete, request, 'abc')
diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py
index adf804da1e00..46203e02c1df 100644
--- a/nova/tests/cells/test_cells_manager.py
+++ b/nova/tests/cells/test_cells_manager.py
@@ -18,6 +18,7 @@ Tests For CellsManager
import copy
import datetime
+import mock
from oslo.config import cfg
from nova.cells import messaging
@@ -341,6 +342,17 @@ class CellsManagerClassTestCase(test.NoDBTestCase):
params_to_update=params_to_update)
self.assertEqual(expected_response, response)
+ def test_service_delete(self):
+ fake_cell = 'fake-cell'
+ service_id = '1'
+ cell_service_id = cells_utils.cell_with_item(fake_cell, service_id)
+
+ with mock.patch.object(self.msg_runner,
+ 'service_delete') as service_delete:
+ self.cells_manager.service_delete(self.ctxt, cell_service_id)
+ service_delete.assert_called_once_with(
+ self.ctxt, fake_cell, service_id)
+
def test_proxy_rpc_to_manager(self):
self.mox.StubOutWithMock(self.msg_runner,
'proxy_rpc_to_manager')
diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py
index 28d7c14a0beb..b992ce6b6b3e 100644
--- a/nova/tests/cells/test_cells_messaging.py
+++ b/nova/tests/cells/test_cells_messaging.py
@@ -898,6 +898,18 @@ class CellsTargetedMethodsTestCase(test.TestCase):
topic='compute')
self.assertEqual(expected_result, result)
+ def test_service_delete(self):
+ fake_service = dict(id=42, host='fake_host', binary='nova-compute',
+ topic='compute')
+
+ ctxt = self.ctxt.elevated()
+ db.service_create(ctxt, fake_service)
+
+ self.src_msg_runner.service_delete(
+ ctxt, self.tgt_cell_name, fake_service['id'])
+ self.assertRaises(exception.ServiceNotFound,
+ db.service_get, ctxt, fake_service['id'])
+
def test_proxy_rpc_to_manager_call(self):
fake_topic = 'fake-topic'
fake_rpc_message = {'method': 'fake_rpc_method', 'args': {}}
diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py
index a1768ea1267d..4c0727b91f4c 100644
--- a/nova/tests/cells/test_cells_rpcapi.py
+++ b/nova/tests/cells/test_cells_rpcapi.py
@@ -310,6 +310,16 @@ class CellsAPITestCase(test.NoDBTestCase):
version='1.7')
self.assertEqual(result, 'fake_response')
+ def test_service_delete(self):
+ call_info = self._stub_rpc_method('call', None)
+ cell_service_id = 'cell@id'
+ result = self.cells_rpcapi.service_delete(
+ self.fake_context, cell_service_id=cell_service_id)
+ expected_args = {'cell_service_id': cell_service_id}
+ self._check_result(call_info, 'service_delete',
+ expected_args, version='1.26')
+ self.assertIsNone(result)
+
def test_proxy_rpc_to_manager(self):
call_info = self._stub_rpc_method('call', 'fake_response')
result = self.cells_rpcapi.proxy_rpc_to_manager(
diff --git a/nova/tests/compute/test_host_api.py b/nova/tests/compute/test_host_api.py
index 8ed4eaf003c8..50bfe417cbbb 100644
--- a/nova/tests/compute/test_host_api.py
+++ b/nova/tests/compute/test_host_api.py
@@ -14,10 +14,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import contextlib
+
+import mock
+
from nova.cells import utils as cells_utils
from nova import compute
from nova import context
from nova import exception
+from nova.objects import service as service_obj
from nova import test
from nova.tests import fake_notifier
from nova.tests.objects import test_objects
@@ -305,6 +310,18 @@ class ComputeHostAPITestCase(test.TestCase):
state='fake-state')
self.assertEqual('fake-response', result)
+ def test_service_delete(self):
+ with contextlib.nested(
+ mock.patch.object(service_obj.Service, 'get_by_id',
+ return_value=service_obj.Service()),
+ mock.patch.object(service_obj.Service, 'destroy')
+ ) as (
+ get_by_id, destroy
+ ):
+ self.host_api.service_delete(self.ctxt, 1)
+ get_by_id.assert_called_once_with(self.ctxt, 1)
+ destroy.assert_called_once_with()
+
class ComputeHostAPICellsTestCase(ComputeHostAPITestCase):
def setUp(self):
@@ -413,6 +430,14 @@ class ComputeHostAPICellsTestCase(ComputeHostAPITestCase):
self.ctxt, host_name, binary, params_to_update)
self._compare_obj(result, expected_result)
+ def test_service_delete(self):
+ cell_service_id = cells_utils.cell_with_item('cell1', 1)
+ with mock.patch.object(self.host_api.cells_rpcapi,
+ 'service_delete') as service_delete:
+ self.host_api.service_delete(self.ctxt, cell_service_id)
+ service_delete.assert_called_once_with(
+ self.ctxt, cell_service_id)
+
def test_instance_get_all_by_host(self):
instances = [dict(id=1, cell_name='cell1', host='host1'),
dict(id=2, cell_name='cell2', host='host1'),
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
index c3ae0280047d..2c90e2524ce5 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
@@ -623,6 +623,14 @@
"name": "PreserveEphemeralOnRebuild",
"namespace": "http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2",
"updated": "%(timestamp)s"
+ },
+ {
+ "alias": "os-extended-services-delete",
+ "description": "%(text)s",
+ "links": [],
+ "name": "ExtendedServicesDelete",
+ "namespace": "http://docs.openstack.org/compute/ext/extended_services_delete/api/v2",
+ "updated": "%(timestamp)s"
}
]
}
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
index e8715ee8f77d..95504f416315 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
@@ -233,4 +233,7 @@
%(text)s
+
+ %(text)s
+
diff --git a/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.json.tpl
new file mode 100644
index 000000000000..93091e5a3696
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.json.tpl
@@ -0,0 +1,40 @@
+{
+ "services": [
+ {
+ "id": 1,
+ "binary": "nova-scheduler",
+ "host": "host1",
+ "state": "up",
+ "status": "disabled",
+ "updated_at": "%(timestamp)s",
+ "zone": "internal"
+ },
+ {
+ "id": 2,
+ "binary": "nova-compute",
+ "host": "host1",
+ "state": "up",
+ "status": "disabled",
+ "updated_at": "%(timestamp)s",
+ "zone": "nova"
+ },
+ {
+ "id": 3,
+ "binary": "nova-scheduler",
+ "host": "host2",
+ "state": "down",
+ "status": "enabled",
+ "updated_at": "%(timestamp)s",
+ "zone": "internal"
+ },
+ {
+ "id": 4,
+ "binary": "nova-compute",
+ "host": "host2",
+ "state": "down",
+ "status": "disabled",
+ "updated_at": "%(timestamp)s",
+ "zone": "nova"
+ }
+ ]
+}
diff --git a/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.xml.tpl
new file mode 100644
index 000000000000..1b1a3cf74c33
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-extended-services-delete/services-get-resp.xml.tpl
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index fab6009346d8..4069484b5573 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -23,6 +23,7 @@ import urllib
import uuid as uuid_lib
from lxml import etree
+import mock
from oslo.config import cfg
from nova.api.metadata import password
@@ -1653,8 +1654,8 @@ class ServicesJsonTest(ApiSampleTestBaseV2):
super(ServicesJsonTest, self).tearDown()
timeutils.clear_time_override()
- def fake_load(self, *args):
- return True
+ def fake_load(self, service_name):
+ return service_name == 'os-extended-services'
def test_services_list(self):
"""Return a list of all agent builds."""
@@ -1738,6 +1739,51 @@ class ExtendedServicesXmlTest(ExtendedServicesJsonTest):
ctype = 'xml'
+@mock.patch.object(db, 'service_get_all',
+ side_effect=test_services.fake_db_api_service_get_all)
+@mock.patch.object(db, 'service_get_by_args',
+ side_effect=test_services.fake_service_get_by_host_binary)
+class ExtendedServicesDeleteJsonTest(ApiSampleTestBaseV2):
+ extends_name = ("nova.api.openstack.compute.contrib.services.Services")
+ extension_name = ("nova.api.openstack.compute.contrib."
+ "extended_services_delete.Extended_services_delete")
+
+ def setUp(self):
+ super(ExtendedServicesDeleteJsonTest, self).setUp()
+ timeutils.set_time_override(test_services.fake_utcnow())
+
+ def tearDown(self):
+ super(ExtendedServicesDeleteJsonTest, self).tearDown()
+ timeutils.clear_time_override()
+
+ def test_service_detail(self, *mocks):
+ """
+ Return a list of all running services with the disable reason
+ information if that exists.
+ """
+ response = self._do_get('os-services')
+ self.assertEqual(response.status, 200)
+ subs = {'id': 1,
+ 'binary': 'nova-compute',
+ 'host': 'host1',
+ 'zone': 'nova',
+ 'status': 'disabled',
+ 'state': 'up'}
+ subs.update(self._get_regexes())
+ return self._verify_response('services-get-resp',
+ subs, response, 200)
+
+ def test_service_delete(self, *mocks):
+ response = self._do_delete('os-services/1')
+ self.assertEqual(response.status, 204)
+ self.assertEqual(response.read(), "")
+
+
+class ExtendedServicesDeleteXmlTest(ExtendedServicesDeleteJsonTest):
+ """This extension is tested in the ExtendedServicesDeleteJsonTest class."""
+ ctype = 'xml'
+
+
class SimpleTenantUsageSampleJsonTest(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib.simple_tenant_usage."
"Simple_tenant_usage")
diff --git a/nova/tests/integrated/v3/api_samples/os-services/services-list-get-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-services/services-list-get-resp.json.tpl
index 4f921736e498..ee7f4181e3f0 100644
--- a/nova/tests/integrated/v3/api_samples/os-services/services-list-get-resp.json.tpl
+++ b/nova/tests/integrated/v3/api_samples/os-services/services-list-get-resp.json.tpl
@@ -4,6 +4,7 @@
"binary": "nova-scheduler",
"disabled_reason": "test1",
"host": "host1",
+ "id": 1,
"state": "up",
"status": "disabled",
"updated_at": "%(timestamp)s",
@@ -13,6 +14,7 @@
"binary": "nova-compute",
"disabled_reason": "test2",
"host": "host1",
+ "id": 2,
"state": "up",
"status": "disabled",
"updated_at": "%(timestamp)s",
@@ -22,6 +24,7 @@
"binary": "nova-scheduler",
"disabled_reason": "",
"host": "host2",
+ "id": 3,
"state": "down",
"status": "enabled",
"updated_at": "%(timestamp)s",
@@ -31,6 +34,7 @@
"binary": "nova-compute",
"disabled_reason": "test4",
"host": "host2",
+ "id": 4,
"state": "down",
"status": "disabled",
"updated_at": "%(timestamp)s",
diff --git a/nova/tests/integrated/v3/test_services.py b/nova/tests/integrated/v3/test_services.py
index 13a3174e7d81..ec2bcc2f677d 100644
--- a/nova/tests/integrated/v3/test_services.py
+++ b/nova/tests/integrated/v3/test_services.py
@@ -78,3 +78,9 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV3):
'service-disable-log-put-req', subs)
return self._verify_response('service-disable-log-put-resp',
subs, response, 200)
+
+ def test_service_delete(self):
+ """Delete an existing service."""
+ response = self._do_delete('os-services/1')
+ self.assertEqual(response.status, 204)
+ self.assertEqual(response.read(), "")