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