Skip to content

Commit

Permalink
Fix handling of unimplemented host actions
Browse files Browse the repository at this point in the history
Fixes bug #1060884

Host actions like enable/disable, maintenance mode and power management
are not implemented in most virt drivers.

In the case of set_host_enabled() in the libvirt driver, we have a no-op
implementation. In other cases we have an implementation which raises
NotImplementedError. Since the default implementation is to do this, we
should just remove the redundant methods from the drivers which don't
actually implement these actions. This ensures NotImplementedError is
consistently raised.

On the API side, rather than looking at the return value of these
methods to determine whether they're implemented, we should just catch
NotImplementedError and return a sensible error message.

In order to do this, we need to ensure the NotImplementedError is
properly deserialized from the RemoteError which will encapsualte
it. The fix for bug #1086798 ensures this happens.

Finally, add some tests to make sure we properly respond with 501
when the virt driver doesn't implement these actions.

Change-Id: I2d4fc51a4b16d5230e61bde75915142ea450557d
  • Loading branch information
markmc committed Dec 5, 2012
1 parent 2a33f8d commit 1dc6c80
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 40 deletions.
24 changes: 14 additions & 10 deletions nova/api/openstack/compute/contrib/hosts.py
Expand Up @@ -176,21 +176,24 @@ def _set_host_maintenance(self, req, host, mode=True):
context = req.environ['nova.context']
LOG.audit(_("Putting host %(host)s in maintenance "
"mode %(mode)s.") % locals())
result = self.api.set_host_maintenance(context, host, mode)
if result not in ("on_maintenance", "off_maintenance"):
raise webob.exc.HTTPBadRequest(explanation=result)
try:
result = self.api.set_host_maintenance(context, host, mode)
except NotImplementedError:
msg = _("Virt driver does not implement host maintenance mode.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "maintenance_mode": result}

def _set_enabled_status(self, req, host, enabled):
"""Sets the specified host's ability to accept new instances."""
context = req.environ['nova.context']
state = "enabled" if enabled else "disabled"
LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
result = self.api.set_host_enabled(context, host=host,
enabled=enabled)
if result not in ("enabled", "disabled"):
# An error message was returned
raise webob.exc.HTTPBadRequest(explanation=result)
try:
result = self.api.set_host_enabled(context, host=host,
enabled=enabled)
except NotImplementedError:
msg = _("Virt driver does not implement host disabled status.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "status": result}

def _host_power_action(self, req, host, action):
Expand All @@ -200,8 +203,9 @@ def _host_power_action(self, req, host, action):
try:
result = self.api.host_power_action(context, host=host,
action=action)
except NotImplementedError as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except NotImplementedError:
msg = _("Virt driver does not implement host power management.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "power_action": result}

@wsgi.serializers(xml=HostActionTemplate)
Expand Down
1 change: 1 addition & 0 deletions nova/openstack/common/rpc/__init__.py
Expand Up @@ -50,6 +50,7 @@
default=['nova.openstack.common.exception',
'nova.exception',
'cinder.exception',
'exceptions',
],
help='Modules of exceptions that are permitted to be recreated'
'upon receiving exception data from an rpc call.'),
Expand Down
36 changes: 36 additions & 0 deletions nova/tests/api/openstack/compute/contrib/test_hosts.py
Expand Up @@ -46,6 +46,8 @@ def stub_service_get_all(self, req):


def stub_set_host_enabled(context, host, enabled):
if host == "notimplemented":
raise NotImplementedError()
# We'll simulate success and failure by assuming
# that 'host_c1' always succeeds, and 'host_c2'
# always fails
Expand All @@ -55,6 +57,8 @@ def stub_set_host_enabled(context, host, enabled):


def stub_set_host_maintenance(context, host, mode):
if host == "notimplemented":
raise NotImplementedError()
# We'll simulate success and failure by assuming
# that 'host_c1' always succeeds, and 'host_c2'
# always fails
Expand All @@ -64,6 +68,8 @@ def stub_set_host_maintenance(context, host, mode):


def stub_host_power_action(context, host, action):
if host == "notimplemented":
raise NotImplementedError()
return action


Expand Down Expand Up @@ -153,6 +159,23 @@ def test_disable_maintenance(self):
self._test_host_update('host_c1', 'maintenance_mode',
'disable', 'off_maintenance')

def _test_host_update_notimpl(self, key, val):
def stub_service_get_all_notimpl(self, req):
return [{'host': 'notimplemented', 'topic': None,
'availability_zone': None}]
self.stubs.Set(db, 'service_get_all',
stub_service_get_all_notimpl)
body = {key: val}
self.assertRaises(webob.exc.HTTPNotImplemented,
self.controller.update,
self.req, 'notimplemented', body=body)

def test_disable_host_notimpl(self):
self._test_host_update_notimpl('status', 'disable')

def test_enable_maintenance_notimpl(self):
self._test_host_update_notimpl('maintenance_mode', 'enable')

def test_host_startup(self):
result = self.controller.startup(self.req, "host_c1")
self.assertEqual(result["power_action"], "startup")
Expand All @@ -165,6 +188,19 @@ def test_host_reboot(self):
result = self.controller.reboot(self.req, "host_c1")
self.assertEqual(result["power_action"], "reboot")

def _test_host_power_action_notimpl(self, method):
self.assertRaises(webob.exc.HTTPNotImplemented,
method, self.req, "notimplemented")

def test_host_startup_notimpl(self):
self._test_host_power_action_notimpl(self.controller.startup)

def test_host_shutdown_notimpl(self):
self._test_host_power_action_notimpl(self.controller.shutdown)

def test_host_reboot_notimpl(self):
self._test_host_power_action_notimpl(self.controller.reboot)

def test_bad_status_value(self):
bad_body = {"status": "bad"}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
Expand Down
4 changes: 0 additions & 4 deletions nova/virt/hyperv/driver.py
Expand Up @@ -131,10 +131,6 @@ def get_host_stats(self, refresh=False):
def host_power_action(self, host, action):
return self._hostops.host_power_action(host, action)

def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

def snapshot(self, context, instance, name):
self._snapshotops.snapshot(context, instance, name)

Expand Down
13 changes: 0 additions & 13 deletions nova/virt/libvirt/driver.py
Expand Up @@ -2819,19 +2819,6 @@ def get_host_stats(self, refresh=False):
If 'refresh' is True, run update the stats first."""
return self.host_state.get_host_stats(refresh=refresh)

def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
raise NotImplementedError()

def host_maintenance_mode(self, host, mode):
"""Start/Stop host maintenance window. On start, it triggers
guest VMs evacuation."""
raise NotImplementedError()

def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

def get_host_uptime(self, host):
"""Returns the result of calling "uptime"."""
#NOTE(dprince): host seems to be ignored for this call and in
Expand Down
13 changes: 0 additions & 13 deletions nova/virt/vmwareapi/driver.py
Expand Up @@ -199,19 +199,6 @@ def get_available_resource(self, nodename):
"""This method is supported only by libvirt."""
return

def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
raise NotImplementedError()

def host_maintenance_mode(self, host, mode):
"""Start/Stop host maintenance window. On start, it triggers
guest VMs evacuation."""
raise NotImplementedError()

def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
raise NotImplementedError()

def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks."""
self._vmops.plug_vifs(instance, network_info)
Expand Down

0 comments on commit 1dc6c80

Please sign in to comment.