Add a new compute API method for deleting retired services
Services and related compute nodes cannot currently be deleted from the command-line. If a node is retired, the service list will continue to show the retired services. A delete service REST method has been added to the compute APIs to support the command-line removal of retired services. Blueprint: remove-nova-compute Change-Id: I655a7f818bb59c8971feb5841feeefafc3a4580a Flags: DocImpact
This commit is contained in:
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -254,4 +254,7 @@
|
||||
<extension alias="os-volumes" updated="2011-03-25T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
|
||||
<description>Volumes support.</description>
|
||||
</extension>
|
||||
<extension alias="os-extended-services-delete" updated="2013-12-10T00:00:00" namespace="http://docs.openstack.org/compute/ext/extended_services_delete/api/v2" name="ExtendedServicesDelete">
|
||||
<description>Services deletion support.</description>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<services>
|
||||
<service status="disabled" binary="nova-scheduler" zone="internal" state="up" host="host1" updated_at="2012-10-29T13:42:02.000000" id="1"/>
|
||||
<service status="disabled" binary="nova-compute" zone="nova" state="up" host="host1" updated_at="2012-10-29T13:42:05.000000" id="2"/>
|
||||
<service status="enabled" binary="nova-scheduler" zone="internal" state="down" host="host2" updated_at="2012-09-19T06:55:34.000000" id="3"/>
|
||||
<service status="disabled" binary="nova-compute" zone="nova" state="down" host="host2" updated_at="2012-09-18T08:03:38.000000" id="4"/>
|
||||
</services>
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
@@ -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):
|
||||
"""
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
|
@@ -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')
|
||||
|
@@ -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')
|
||||
|
@@ -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': {}}
|
||||
|
@@ -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(
|
||||
|
@@ -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'),
|
||||
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -233,4 +233,7 @@
|
||||
<extension alias="os-preserve-ephemeral-rebuild" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2" name="PreserveEphemeralOnRebuild">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-extended-services-delete" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/extended_services_delete/api/v2" name="ExtendedServicesDelete">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<services>
|
||||
<service status="disabled" binary="nova-scheduler" zone="internal" state="up" updated_at="%(timestamp)s" host="host1" id="1"/>
|
||||
<service status="disabled" binary="nova-compute" zone="nova" state="up" updated_at="%(timestamp)s" host="host1" id="2"/>
|
||||
<service status="enabled" binary="nova-scheduler" zone="internal" state="down" updated_at="%(timestamp)s" host="host2" id="3"/>
|
||||
<service status="disabled" binary="nova-compute" zone="nova" state="down" updated_at="%(timestamp)s" host="host2" id="4"/>
|
||||
</services>
|
@@ -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")
|
||||
|
@@ -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",
|
||||
|
@@ -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(), "")
|
||||
|
Reference in New Issue
Block a user