Teach HostAPI about cells
This makes the HostAPI module know enough about cells to support the os-hypervisors, os-services, and several other API modules that deal with hosts and services. Note that this introduces a conflict where duplicate-id resources could be returned from the API because they are in different DBs with different key namespaces. For now, we return an error in the delete case where an ambiguous result could cause us to delete the wrong service. This is a fundamental problem across several of our APIs and is being worked as a separate improvement. Related to blueprint cells-aware-api Change-Id: If1e03c9343b8cc9c34bd51c2b4d25acdb21131ff
This commit is contained in:
parent
791cf06434
commit
74c5bfea6f
|
@ -108,7 +108,8 @@ class EvacuateController(wsgi.Controller):
|
|||
if host is not None:
|
||||
try:
|
||||
self.host_api.service_get_by_compute_host(context, host)
|
||||
except exception.ComputeHostNotFound:
|
||||
except (exception.ComputeHostNotFound,
|
||||
exception.HostMappingNotFound):
|
||||
msg = _("Compute host %s not found.") % host
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ from nova.api.openstack import extensions
|
|||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova import compute
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.policies import hosts as hosts_policies
|
||||
|
@ -85,7 +86,7 @@ class HostController(wsgi.Controller):
|
|||
if zone:
|
||||
filters['availability_zone'] = zone
|
||||
services = self.api.service_get_all(context, filters=filters,
|
||||
set_zones=True)
|
||||
set_zones=True, all_cells=True)
|
||||
hosts = []
|
||||
api_services = ('nova-osapi_compute', 'nova-ec2', 'nova-metadata')
|
||||
for service in services:
|
||||
|
@ -143,7 +144,7 @@ class HostController(wsgi.Controller):
|
|||
result = self.api.set_host_maintenance(context, host_name, mode)
|
||||
except NotImplementedError:
|
||||
common.raise_feature_not_supported()
|
||||
except exception.HostNotFound as e:
|
||||
except (exception.HostNotFound, exception.HostMappingNotFound) as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
except exception.ComputeServiceUnavailable as e:
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
@ -161,11 +162,10 @@ class HostController(wsgi.Controller):
|
|||
else:
|
||||
LOG.info("Disabling host %s.", host_name)
|
||||
try:
|
||||
result = self.api.set_host_enabled(context, host_name=host_name,
|
||||
enabled=enabled)
|
||||
result = self.api.set_host_enabled(context, host_name, enabled)
|
||||
except NotImplementedError:
|
||||
common.raise_feature_not_supported()
|
||||
except exception.HostNotFound as e:
|
||||
except (exception.HostNotFound, exception.HostMappingNotFound) as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
except exception.ComputeServiceUnavailable as e:
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
@ -178,11 +178,10 @@ class HostController(wsgi.Controller):
|
|||
context = req.environ['nova.context']
|
||||
context.can(hosts_policies.BASE_POLICY_NAME)
|
||||
try:
|
||||
result = self.api.host_power_action(context, host_name=host_name,
|
||||
action=action)
|
||||
result = self.api.host_power_action(context, host_name, action)
|
||||
except NotImplementedError:
|
||||
common.raise_feature_not_supported()
|
||||
except exception.HostNotFound as e:
|
||||
except (exception.HostNotFound, exception.HostMappingNotFound) as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
except exception.ComputeServiceUnavailable as e:
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
@ -265,12 +264,15 @@ class HostController(wsgi.Controller):
|
|||
context.can(hosts_policies.BASE_POLICY_NAME)
|
||||
host_name = id
|
||||
try:
|
||||
mapping = objects.HostMapping.get_by_host(context, host_name)
|
||||
nova_context.set_target_cell(context, mapping.cell_mapping)
|
||||
compute_node = (
|
||||
objects.ComputeNode.get_first_node_by_host_for_old_compat(
|
||||
context, host_name))
|
||||
except exception.ComputeHostNotFound as e:
|
||||
instances = self.api.instance_get_all_by_host(context, host_name)
|
||||
except (exception.ComputeHostNotFound,
|
||||
exception.HostMappingNotFound) as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
instances = self.api.instance_get_all_by_host(context, host_name)
|
||||
resources = [self._get_total_resources(host_name, compute_node)]
|
||||
resources.append(self._get_used_now_resources(host_name,
|
||||
compute_node))
|
||||
|
|
|
@ -211,7 +211,8 @@ class HypervisorsController(wsgi.Controller):
|
|||
uptime = self.host_api.get_host_uptime(context, host)
|
||||
except NotImplementedError:
|
||||
common.raise_feature_not_supported()
|
||||
except exception.ComputeServiceUnavailable as e:
|
||||
except (exception.ComputeServiceUnavailable,
|
||||
exception.HostMappingNotFound) as e:
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
service = self.host_api.service_get_by_compute_host(context, host)
|
||||
|
@ -246,10 +247,13 @@ class HypervisorsController(wsgi.Controller):
|
|||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
hypervisors = []
|
||||
for compute_node in compute_nodes:
|
||||
instances = self.host_api.instance_get_all_by_host(context,
|
||||
try:
|
||||
instances = self.host_api.instance_get_all_by_host(context,
|
||||
compute_node.host)
|
||||
service = self.host_api.service_get_by_compute_host(
|
||||
context, compute_node.host)
|
||||
service = self.host_api.service_get_by_compute_host(
|
||||
context, compute_node.host)
|
||||
except exception.HostMappingNotFound as e:
|
||||
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||
hyp = self._view_hypervisor(compute_node, service, False, req,
|
||||
instances)
|
||||
hypervisors.append(hyp)
|
||||
|
|
|
@ -47,7 +47,8 @@ class ServiceController(wsgi.Controller):
|
|||
|
||||
_services = [
|
||||
s
|
||||
for s in self.host_api.service_get_all(context, set_zones=True)
|
||||
for s in self.host_api.service_get_all(context, set_zones=True,
|
||||
all_cells=True)
|
||||
if s['binary'] not in api_services
|
||||
]
|
||||
|
||||
|
@ -150,7 +151,8 @@ class ServiceController(wsgi.Controller):
|
|||
"""Do the actual PUT/update"""
|
||||
try:
|
||||
self.host_api.service_update(context, host, binary, payload)
|
||||
except exception.HostBinaryNotFound as exc:
|
||||
except (exception.HostBinaryNotFound,
|
||||
exception.HostMappingNotFound) as exc:
|
||||
raise webob.exc.HTTPNotFound(explanation=exc.format_message())
|
||||
|
||||
def _perform_action(self, req, id, body, actions):
|
||||
|
@ -193,6 +195,9 @@ class ServiceController(wsgi.Controller):
|
|||
except exception.ServiceNotFound:
|
||||
explanation = _("Service %s not found.") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=explanation)
|
||||
except exception.ServiceNotUnique:
|
||||
explanation = _("Service id %s refers to multiple services.") % id
|
||||
raise webob.exc.HTTPBadRequest(explanation=explanation)
|
||||
|
||||
@extensions.expected_errors(())
|
||||
def index(self, req):
|
||||
|
|
|
@ -4281,6 +4281,22 @@ class API(base.Base):
|
|||
return host_statuses
|
||||
|
||||
|
||||
def target_host_cell(fn):
|
||||
"""Target a host-based function to a cell.
|
||||
|
||||
Expects to wrap a function of signature:
|
||||
|
||||
func(self, context, host, ...)
|
||||
"""
|
||||
|
||||
@functools.wraps(fn)
|
||||
def targeted(self, context, host, *args, **kwargs):
|
||||
mapping = objects.HostMapping.get_by_host(context, host)
|
||||
nova_context.set_target_cell(context, mapping.cell_mapping)
|
||||
return fn(self, context, host, *args, **kwargs)
|
||||
return targeted
|
||||
|
||||
|
||||
class HostAPI(base.Base):
|
||||
"""Sub-set of the Compute Manager API for managing host operations."""
|
||||
|
||||
|
@ -4299,6 +4315,7 @@ class HostAPI(base.Base):
|
|||
return service['host']
|
||||
|
||||
@wrap_exception()
|
||||
@target_host_cell
|
||||
def set_host_enabled(self, context, host_name, enabled):
|
||||
"""Sets the specified host's ability to accept new instances."""
|
||||
host_name = self._assert_host_exists(context, host_name)
|
||||
|
@ -4313,6 +4330,7 @@ class HostAPI(base.Base):
|
|||
payload)
|
||||
return result
|
||||
|
||||
@target_host_cell
|
||||
def get_host_uptime(self, context, host_name):
|
||||
"""Returns the result of calling "uptime" on the target host."""
|
||||
host_name = self._assert_host_exists(context, host_name,
|
||||
|
@ -4320,6 +4338,7 @@ class HostAPI(base.Base):
|
|||
return self.rpcapi.get_host_uptime(context, host=host_name)
|
||||
|
||||
@wrap_exception()
|
||||
@target_host_cell
|
||||
def host_power_action(self, context, host_name, action):
|
||||
"""Reboots, shuts down or powers up the host."""
|
||||
host_name = self._assert_host_exists(context, host_name)
|
||||
|
@ -4335,6 +4354,7 @@ class HostAPI(base.Base):
|
|||
return result
|
||||
|
||||
@wrap_exception()
|
||||
@target_host_cell
|
||||
def set_host_maintenance(self, context, host_name, mode):
|
||||
"""Start/Stop host maintenance window. On start, it triggers
|
||||
guest VMs evacuation.
|
||||
|
@ -4391,10 +4411,51 @@ class HostAPI(base.Base):
|
|||
ret_services.append(service)
|
||||
return ret_services
|
||||
|
||||
def _find_service(self, context, service_id):
|
||||
"""Find a service by id by searching all cells.
|
||||
|
||||
If one matching service is found, return it. If none or multiple
|
||||
are found, raise an exception.
|
||||
|
||||
:param context: A context.RequestContext
|
||||
:param service_id: The DB ID of the service to find
|
||||
:returns: An objects.Service
|
||||
:raises: ServiceNotUnique if multiple matches are found
|
||||
:raises: ServiceNotFound if no matches are found
|
||||
"""
|
||||
|
||||
load_cells()
|
||||
# NOTE(danms): Unfortunately this API exposes database identifiers
|
||||
# which means we really can't do something efficient here
|
||||
service = None
|
||||
found_in_cell = None
|
||||
for cell in CELLS:
|
||||
# NOTE(danms): Services can be in cell0, so don't skip it here
|
||||
try:
|
||||
with nova_context.target_cell(context, cell):
|
||||
cell_service = objects.Service.get_by_id(context,
|
||||
service_id)
|
||||
except exception.ServiceNotFound:
|
||||
# NOTE(danms): Keep looking in other cells
|
||||
continue
|
||||
if service and cell_service:
|
||||
raise exception.ServiceNotUnique()
|
||||
service = cell_service
|
||||
found_in_cell = cell
|
||||
|
||||
if service:
|
||||
# NOTE(danms): Set the cell on the context so it remains
|
||||
# when we return to our caller
|
||||
nova_context.set_target_cell(context, found_in_cell)
|
||||
return service
|
||||
else:
|
||||
raise exception.ServiceNotFound(service_id=service_id)
|
||||
|
||||
def service_get_by_id(self, context, service_id):
|
||||
"""Get service entry for the given service id."""
|
||||
return objects.Service.get_by_id(context, service_id)
|
||||
return self._find_service(context, service_id)
|
||||
|
||||
@target_host_cell
|
||||
def service_get_by_compute_host(self, context, host_name):
|
||||
"""Get service entry for the given compute hostname."""
|
||||
return objects.Service.get_by_compute_host(context, host_name)
|
||||
|
@ -4406,6 +4467,7 @@ class HostAPI(base.Base):
|
|||
service.save()
|
||||
return service
|
||||
|
||||
@target_host_cell
|
||||
def service_update(self, context, host_name, binary, params_to_update):
|
||||
"""Enable / Disable a service.
|
||||
|
||||
|
@ -4417,12 +4479,14 @@ class HostAPI(base.Base):
|
|||
|
||||
def _service_delete(self, context, service_id):
|
||||
"""Performs the actual Service deletion operation."""
|
||||
objects.Service.get_by_id(context, service_id).destroy()
|
||||
service = self._find_service(context, service_id)
|
||||
service.destroy()
|
||||
|
||||
def service_delete(self, context, service_id):
|
||||
"""Deletes the specified service."""
|
||||
self._service_delete(context, service_id)
|
||||
|
||||
@target_host_cell
|
||||
def instance_get_all_by_host(self, context, host_name):
|
||||
"""Return all instances on the given host."""
|
||||
return objects.InstanceList.get_by_host(context, host_name)
|
||||
|
@ -4440,15 +4504,65 @@ class HostAPI(base.Base):
|
|||
|
||||
def compute_node_get(self, context, compute_id):
|
||||
"""Return compute node entry for particular integer ID."""
|
||||
return objects.ComputeNode.get_by_id(context, int(compute_id))
|
||||
load_cells()
|
||||
|
||||
# NOTE(danms): Unfortunately this API exposes database identifiers
|
||||
# which means we really can't do something efficient here
|
||||
for cell in CELLS:
|
||||
if cell.uuid == objects.CellMapping.CELL0_UUID:
|
||||
continue
|
||||
with nova_context.target_cell(context, cell):
|
||||
try:
|
||||
return objects.ComputeNode.get_by_id(context,
|
||||
int(compute_id))
|
||||
except exception.ComputeHostNotFound:
|
||||
# NOTE(danms): Keep looking in other cells
|
||||
continue
|
||||
|
||||
raise exception.ComputeHostNotFound(host=compute_id)
|
||||
|
||||
def compute_node_get_all(self, context, limit=None, marker=None):
|
||||
return objects.ComputeNodeList.get_by_pagination(
|
||||
context, limit=limit, marker=marker)
|
||||
load_cells()
|
||||
|
||||
computes = []
|
||||
for cell in CELLS:
|
||||
if cell.uuid == objects.CellMapping.CELL0_UUID:
|
||||
continue
|
||||
with nova_context.target_cell(context, cell):
|
||||
try:
|
||||
cell_computes = objects.ComputeNodeList.get_by_pagination(
|
||||
context, limit=limit, marker=marker)
|
||||
except exception.MarkerNotFound:
|
||||
# NOTE(danms): Keep looking through cells
|
||||
continue
|
||||
computes.extend(cell_computes)
|
||||
# NOTE(danms): We must have found the marker, so continue on
|
||||
# without one
|
||||
marker = None
|
||||
if limit:
|
||||
limit -= len(cell_computes)
|
||||
if limit <= 0:
|
||||
break
|
||||
|
||||
if marker is not None and len(computes) == 0:
|
||||
# NOTE(danms): If we did not find the marker in any cell,
|
||||
# mimic the db_api behavior here.
|
||||
raise exception.MarkerNotFound(marker=marker)
|
||||
|
||||
return objects.ComputeNodeList(objects=computes)
|
||||
|
||||
def compute_node_search_by_hypervisor(self, context, hypervisor_match):
|
||||
return objects.ComputeNodeList.get_by_hypervisor(context,
|
||||
hypervisor_match)
|
||||
load_cells()
|
||||
|
||||
computes = []
|
||||
for cell in CELLS:
|
||||
if cell.uuid == objects.CellMapping.CELL0_UUID:
|
||||
continue
|
||||
with nova_context.target_cell(context, cell):
|
||||
cell_computes = objects.ComputeNodeList.get_by_hypervisor(
|
||||
context, hypervisor_match)
|
||||
computes.extend(cell_computes)
|
||||
return objects.ComputeNodeList(objects=computes)
|
||||
|
||||
def compute_node_statistics(self, context):
|
||||
return self.db.compute_node_statistics(context)
|
||||
|
|
|
@ -431,6 +431,10 @@ class ServiceUnavailable(Invalid):
|
|||
msg_fmt = _("Service is unavailable at this time.")
|
||||
|
||||
|
||||
class ServiceNotUnique(Invalid):
|
||||
msg_fmt = _("More than one possible service found.")
|
||||
|
||||
|
||||
class ComputeResourcesUnavailable(ServiceUnavailable):
|
||||
msg_fmt = _("Insufficient compute resources: %(reason)s.")
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
from oslo_utils import fixture as utils_fixture
|
||||
|
||||
from nova.tests import fixtures
|
||||
from nova.tests.functional.notification_sample_tests \
|
||||
import notification_sample_base
|
||||
from nova.tests.unit.api.openstack.compute import test_services
|
||||
|
@ -29,6 +30,7 @@ class TestServiceUpdateNotificationSample(
|
|||
self.stub_out("nova.db.service_update",
|
||||
test_services.fake_service_update)
|
||||
self.useFixture(utils_fixture.TimeFixture(test_services.fake_utcnow()))
|
||||
self.useFixture(fixtures.SingleCellSimple())
|
||||
|
||||
def test_service_enable(self):
|
||||
body = {'host': 'host1',
|
||||
|
|
|
@ -47,6 +47,8 @@ def fake_compute_api_get(self, context, instance_id, **kwargs):
|
|||
def fake_service_get_by_compute_host(self, context, host):
|
||||
if host == 'bad-host':
|
||||
raise exception.ComputeHostNotFound(host=host)
|
||||
elif host == 'unmapped-host':
|
||||
raise exception.HostMappingNotFound(name=host)
|
||||
else:
|
||||
return {
|
||||
'host_name': host,
|
||||
|
@ -154,6 +156,12 @@ class EvacuateTestV21(test.NoDBTestCase):
|
|||
'onSharedStorage': 'False',
|
||||
'adminPass': 'MyNewPass'})
|
||||
|
||||
def test_evacuate_instance_with_unmapped_target(self):
|
||||
self._check_evacuate_failure(webob.exc.HTTPNotFound,
|
||||
{'host': 'unmapped-host',
|
||||
'onSharedStorage': 'False',
|
||||
'adminPass': 'MyNewPass'})
|
||||
|
||||
def test_evacuate_instance_with_target(self):
|
||||
admin_pass = 'MyNewPass'
|
||||
res = self._get_evacuate_response({'host': 'my-host',
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
import webob.exc
|
||||
|
||||
|
@ -23,6 +24,7 @@ from nova import context as context_maker
|
|||
from nova import db
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests import fixtures
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit import fake_hosts
|
||||
from nova.tests import uuidsentinel
|
||||
|
@ -158,6 +160,7 @@ class HostTestCaseV21(test.TestCase):
|
|||
self.controller = self.Controller()
|
||||
self.hosts_api = self.controller.api
|
||||
self.req = fakes.HTTPRequest.blank('', use_admin_context=True)
|
||||
self.useFixture(fixtures.SingleCellSimple())
|
||||
|
||||
self._setup_stubs()
|
||||
|
||||
|
@ -369,6 +372,14 @@ class HostTestCaseV21(test.TestCase):
|
|||
db.instance_destroy(ctxt, i_ref1['uuid'])
|
||||
db.instance_destroy(ctxt, i_ref2['uuid'])
|
||||
|
||||
def test_show_late_host_mapping_gone(self):
|
||||
s_ref = self._create_compute_service()
|
||||
with mock.patch.object(self.controller.api,
|
||||
'instance_get_all_by_host') as m:
|
||||
m.side_effect = exception.HostMappingNotFound
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show, self.req, s_ref['host'])
|
||||
|
||||
def test_list_hosts_with_zone(self):
|
||||
result = self.controller.index(FakeRequestWithNovaZone())
|
||||
self.assertIn('hosts', result)
|
||||
|
|
|
@ -447,6 +447,17 @@ class HypervisorsTestV21(test.NoDBTestCase):
|
|||
mock_get_uptime.assert_called_once_with(
|
||||
mock.ANY, self.TEST_HYPERS_OBJ[0].host)
|
||||
|
||||
def test_uptime_hypervisor_not_mapped(self):
|
||||
with mock.patch.object(self.controller.host_api, 'get_host_uptime',
|
||||
side_effect=exception.HostMappingNotFound(name='dummy')
|
||||
) as mock_get_uptime:
|
||||
req = self._get_request(True)
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.uptime, req,
|
||||
self.TEST_HYPERS_OBJ[0].id)
|
||||
mock_get_uptime.assert_called_once_with(
|
||||
mock.ANY, self.TEST_HYPERS_OBJ[0].host)
|
||||
|
||||
def test_search(self):
|
||||
req = self._get_request(True)
|
||||
result = self.controller.search(req, 'hyper')
|
||||
|
@ -488,6 +499,14 @@ class HypervisorsTestV21(test.NoDBTestCase):
|
|||
del server['name']
|
||||
self.assertEqual(dict(hypervisors=expected_dict), result)
|
||||
|
||||
def test_servers_not_mapped(self):
|
||||
req = self._get_request(True)
|
||||
with mock.patch.object(self.controller.host_api,
|
||||
'instance_get_all_by_host') as m:
|
||||
m.side_effect = exception.HostMappingNotFound
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.servers, req, 'hyper')
|
||||
|
||||
def test_servers_non_id(self):
|
||||
with mock.patch.object(self.controller.host_api,
|
||||
'compute_node_search_by_hypervisor',
|
||||
|
|
|
@ -33,6 +33,7 @@ from nova import exception
|
|||
from nova import objects
|
||||
from nova.servicegroup.drivers import db as db_driver
|
||||
from nova import test
|
||||
from nova.tests import fixtures
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit.objects import test_service
|
||||
|
||||
|
@ -130,7 +131,8 @@ class FakeRequestWithHostService(FakeRequest):
|
|||
|
||||
|
||||
def fake_service_get_all(services):
|
||||
def service_get_all(context, filters=None, set_zones=False):
|
||||
def service_get_all(context, filters=None, set_zones=False,
|
||||
all_cells=False):
|
||||
if set_zones or 'availability_zone' in filters:
|
||||
return availability_zones.set_availability_zones(context,
|
||||
services)
|
||||
|
@ -210,6 +212,7 @@ class ServicesTestV21(test.TestCase):
|
|||
fake_db_service_update(fake_services_list))
|
||||
|
||||
self.req = fakes.HTTPRequest.blank('')
|
||||
self.useFixture(fixtures.SingleCellSimple())
|
||||
|
||||
def _process_output(self, services, has_disabled=False, has_id=False):
|
||||
return services
|
||||
|
@ -487,6 +490,17 @@ class ServicesTestV21(test.TestCase):
|
|||
"enable",
|
||||
body=body)
|
||||
|
||||
def test_services_enable_with_unmapped_host(self):
|
||||
body = {'host': 'invalid', 'binary': 'nova-compute'}
|
||||
with mock.patch.object(self.controller.host_api,
|
||||
'service_update') as m:
|
||||
m.side_effect = exception.HostMappingNotFound
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.update,
|
||||
self.req,
|
||||
"enable",
|
||||
body=body)
|
||||
|
||||
def test_services_enable_with_invalid_binary(self):
|
||||
body = {'host': 'host1', 'binary': 'invalid'}
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
|
@ -573,12 +587,19 @@ class ServicesTestV21(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.delete, self.req, 1234)
|
||||
|
||||
def test_services_delete_bad_request(self):
|
||||
def test_services_delete_invalid_id(self):
|
||||
self.ext_mgr.extensions['os-extended-services-delete'] = True
|
||||
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.delete, self.req, 'abc')
|
||||
|
||||
def test_services_delete_duplicate_service(self):
|
||||
with mock.patch.object(self.controller, 'host_api') as host_api:
|
||||
host_api.service_delete.side_effect = exception.ServiceNotUnique()
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.delete, self.req, 1234)
|
||||
self.assertTrue(host_api.service_delete.called)
|
||||
|
||||
# This test is just to verify that the servicegroup API gets used when
|
||||
# calling the API
|
||||
@mock.patch.object(db_driver.DbDriver, 'is_up', side_effect=KeyError)
|
||||
|
|
|
@ -313,17 +313,47 @@ class ComputeHostAPITestCase(test.TestCase):
|
|||
|
||||
_do_test()
|
||||
|
||||
def test_service_delete(self):
|
||||
with test.nested(
|
||||
mock.patch.object(objects.Service, 'get_by_id',
|
||||
return_value=objects.Service()),
|
||||
mock.patch.object(objects.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()
|
||||
@mock.patch('nova.context.set_target_cell')
|
||||
@mock.patch('nova.compute.api.load_cells')
|
||||
@mock.patch('nova.objects.Service.get_by_id')
|
||||
def test_service_delete(self, get_by_id, load_cells, set_target):
|
||||
compute_api.CELLS = [
|
||||
objects.CellMapping(),
|
||||
objects.CellMapping(),
|
||||
objects.CellMapping(),
|
||||
]
|
||||
|
||||
service = mock.MagicMock()
|
||||
get_by_id.side_effect = [exception.ServiceNotFound(service_id=1),
|
||||
service,
|
||||
exception.ServiceNotFound(service_id=1)]
|
||||
self.host_api.service_delete(self.ctxt, 1)
|
||||
get_by_id.assert_has_calls([mock.call(self.ctxt, 1),
|
||||
mock.call(self.ctxt, 1),
|
||||
mock.call(self.ctxt, 1)])
|
||||
service.destroy.assert_called_once_with()
|
||||
set_target.assert_called_once_with(self.ctxt, compute_api.CELLS[1])
|
||||
|
||||
@mock.patch('nova.context.set_target_cell')
|
||||
@mock.patch('nova.compute.api.load_cells')
|
||||
@mock.patch('nova.objects.Service.get_by_id')
|
||||
def test_service_delete_ambiguous(self, get_by_id, load_cells, set_target):
|
||||
compute_api.CELLS = [
|
||||
objects.CellMapping(),
|
||||
objects.CellMapping(),
|
||||
objects.CellMapping(),
|
||||
]
|
||||
|
||||
service1 = mock.MagicMock()
|
||||
service2 = mock.MagicMock()
|
||||
get_by_id.side_effect = [exception.ServiceNotFound(service_id=1),
|
||||
service1,
|
||||
service2]
|
||||
self.assertRaises(exception.ServiceNotUnique,
|
||||
self.host_api.service_delete, self.ctxt, 1)
|
||||
self.assertFalse(service1.destroy.called)
|
||||
self.assertFalse(service2.destroy.called)
|
||||
self.assertFalse(set_target.called)
|
||||
|
||||
def test_service_delete_compute_in_aggregate(self):
|
||||
compute = self.host_api.db.service_create(self.ctxt,
|
||||
|
@ -353,6 +383,10 @@ class ComputeHostAPICellsTestCase(ComputeHostAPITestCase):
|
|||
def test_service_get_all_cells(self):
|
||||
pass
|
||||
|
||||
@testtools.skip('cellsv1 does not use this')
|
||||
def test_service_delete_ambiguous(self):
|
||||
pass
|
||||
|
||||
def test_service_get_all_no_zones(self):
|
||||
services = [
|
||||
cells_utils.ServiceProxy(
|
||||
|
|
Loading…
Reference in New Issue