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:
Jason Dillaman
2013-07-31 13:51:17 -04:00
parent 6b29aca811
commit ac3972b0bc
26 changed files with 431 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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