Add nova CLI extension for host maintenance mode status query/evaucation
Add a nova cli "nova host-maintenance" to query and enable/disable the host maintenance mode for a specified host. For detail usage, please refer to: "nova help host-maintenance". Change-Id: Ibc3f92521546a82672b1619f7b65416909d92dfc Closes-Bug: 1386026
This commit is contained in:
parent
70fc6cfada
commit
40e90593e3
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2013 IBM Corp.
|
# Copyright 2014 IBM Corp.
|
||||||
|
|
||||||
import six
|
import six
|
||||||
import urllib
|
import urllib
|
||||||
@ -13,6 +13,7 @@ from novaclient.v1_1.volume_types import VolumeType
|
|||||||
from powervc.common.client.extensions import base
|
from powervc.common.client.extensions import base
|
||||||
from powervc.common.gettextutils import _
|
from powervc.common.gettextutils import _
|
||||||
from powervc.common import utils
|
from powervc.common import utils
|
||||||
|
from webob import exc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -48,11 +49,13 @@ class PVCHypervisorManager(hypervisors.HypervisorManager):
|
|||||||
hypervisors = self.search(hostname)
|
hypervisors = self.search(hostname)
|
||||||
|
|
||||||
if not hypervisors[0] or not self.get(hypervisors[0]):
|
if not hypervisors[0] or not self.get(hypervisors[0]):
|
||||||
raise exceptions.NotFound(_("No hypervisor matching '%s' could be"
|
raise exc.HTTPNotFound(_("No hypervisor matching '%s' could be"
|
||||||
" found.")
|
" found.") % hostname)
|
||||||
% hostname)
|
|
||||||
|
|
||||||
|
try:
|
||||||
hypervisor = self.get(hypervisors[0])
|
hypervisor = self.get(hypervisors[0])
|
||||||
|
except Exception as ex:
|
||||||
|
raise exc.HTTPNotFound(explanation=six.text_type(ex))
|
||||||
|
|
||||||
# Either "ok" (maintenance off), "entering", "on" or "error"
|
# Either "ok" (maintenance off), "entering", "on" or "error"
|
||||||
# compatible with previous powervc version, if no such property
|
# compatible with previous powervc version, if no such property
|
||||||
@ -68,7 +71,8 @@ class PVCHypervisorManager(hypervisors.HypervisorManager):
|
|||||||
return {"maintenance_status": maintenance_status,
|
return {"maintenance_status": maintenance_status,
|
||||||
"maintenance_migration_action": maintenance_migration_action}
|
"maintenance_migration_action": maintenance_migration_action}
|
||||||
|
|
||||||
def update_host_maintenance_mode(self, hostname, enabled, migrate):
|
def update_host_maintenance_mode(self, hostname, enabled, migrate=None,
|
||||||
|
target_host=None):
|
||||||
"""Update host maintenance mode status.
|
"""Update host maintenance mode status.
|
||||||
:hostname: The hostname of the hypervisor
|
:hostname: The hostname of the hypervisor
|
||||||
:enabled: should be "enable" or "disable"
|
:enabled: should be "enable" or "disable"
|
||||||
@ -79,11 +83,22 @@ class PVCHypervisorManager(hypervisors.HypervisorManager):
|
|||||||
"""
|
"""
|
||||||
# Refer to PowerVC HLD host maintenance mode chapter
|
# Refer to PowerVC HLD host maintenance mode chapter
|
||||||
url = "/ego/prs/hypervisor_maintenance/%s" % hostname
|
url = "/ego/prs/hypervisor_maintenance/%s" % hostname
|
||||||
|
if not migrate:
|
||||||
|
body = {"status": enabled}
|
||||||
|
else:
|
||||||
|
if target_host:
|
||||||
|
body = {"status": enabled,
|
||||||
|
"migrate": migrate,
|
||||||
|
"target_host": target_host}
|
||||||
|
else:
|
||||||
body = {"status": enabled,
|
body = {"status": enabled,
|
||||||
"migrate": migrate}
|
"migrate": migrate}
|
||||||
|
|
||||||
# send set maintenance mode request by put http method
|
# send set maintenance mode request by put http method
|
||||||
|
try:
|
||||||
_resp, resp_body = self.api.client.put(url, body=body)
|
_resp, resp_body = self.api.client.put(url, body=body)
|
||||||
|
except Exception as ex:
|
||||||
|
raise exc.HTTPBadRequest(explanation=six.text_type(ex))
|
||||||
|
|
||||||
# check response content
|
# check response content
|
||||||
if "hypervisor_maintenance" not in resp_body:
|
if "hypervisor_maintenance" not in resp_body:
|
||||||
|
1
nova-powervc/hostmaintenanceclient/__init__.py
Normal file
1
nova-powervc/hostmaintenanceclient/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
15
nova-powervc/hostmaintenanceclient/setup.py
Normal file
15
nova-powervc/hostmaintenanceclient/setup.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="hostmaintenance-client",
|
||||||
|
version="0.1",
|
||||||
|
packages=find_packages(exclude=['*.tests', 'tests',
|
||||||
|
'tests.*', '*.tests.*']),
|
||||||
|
entry_points={
|
||||||
|
'novaclient.extension': [
|
||||||
|
'host_maintenance = v1_1.host_maintenance',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
1
nova-powervc/hostmaintenanceclient/v1_1/__init__.py
Normal file
1
nova-powervc/hostmaintenanceclient/v1_1/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
89
nova-powervc/hostmaintenanceclient/v1_1/host_maintenance.py
Normal file
89
nova-powervc/hostmaintenanceclient/v1_1/host_maintenance.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
|
||||||
|
from novaclient import base
|
||||||
|
from novaclient import exceptions
|
||||||
|
from powervc.common.gettextutils import _
|
||||||
|
from novaclient import utils
|
||||||
|
|
||||||
|
|
||||||
|
class HostMaintenanceResource(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Host maintenance: %s>" % self.hypervisor_hostname
|
||||||
|
|
||||||
|
|
||||||
|
class HostMaintenanceManager(base.Manager):
|
||||||
|
resource_class = HostMaintenanceResource
|
||||||
|
|
||||||
|
def update(self, host_name, status, migrate=None, target_host=None):
|
||||||
|
"""
|
||||||
|
Update status, migrate or target host when put a hypervisor
|
||||||
|
into maintenance mode.
|
||||||
|
"""
|
||||||
|
if "enable" == status:
|
||||||
|
body = {'status': status, 'migrate': migrate,
|
||||||
|
'target_host': target_host}
|
||||||
|
else:
|
||||||
|
body = {'status': status}
|
||||||
|
return self._update("/os-host-maintenance-mode/%s" % host_name,
|
||||||
|
body)
|
||||||
|
|
||||||
|
def get(self, host_name):
|
||||||
|
url = "/os-host-maintenance-mode/%s" % host_name
|
||||||
|
_resp, body = self.api.client.get(url)
|
||||||
|
# set host name for response body to form a object
|
||||||
|
if body:
|
||||||
|
body["hypervisor_hostname"] = host_name
|
||||||
|
source_obj = {}
|
||||||
|
source_obj['hypervisor_maintenance'] = body
|
||||||
|
source_obj['hypervisor_hostname'] = host_name
|
||||||
|
obj = self.resource_class(self, source_obj, loaded=True)
|
||||||
|
self._write_object_to_completion_cache(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('host',
|
||||||
|
metavar='<host>',
|
||||||
|
help='Name of a host.')
|
||||||
|
@utils.arg('--set-status', choices=["enable", "disable"],
|
||||||
|
metavar="<enable|disable>",
|
||||||
|
help='To enable or disable the host maintenance mode.')
|
||||||
|
@utils.arg('--migrate', choices=["active-only", "all", "none"],
|
||||||
|
metavar="<all|active-only|none>",
|
||||||
|
help='Which kinds of instances to migrate.')
|
||||||
|
@utils.arg('--target-host',
|
||||||
|
metavar='<target host>',
|
||||||
|
help='Which service host instances would be migrated to.')
|
||||||
|
def do_host_maintenance(cs, args):
|
||||||
|
"""Enable maintenance mode for a hypervisor."""
|
||||||
|
if not args.set_status:
|
||||||
|
if args.migrate or args.target_host:
|
||||||
|
raise exceptions.CommandError(_("Need to set --set-status "
|
||||||
|
"to 'enable' when --migrate "
|
||||||
|
"or --target-host specified."))
|
||||||
|
else:
|
||||||
|
return _show_maintenance_status(cs, args.host)
|
||||||
|
|
||||||
|
if "disable" == args.set_status.lower():
|
||||||
|
if args.migrate or args.target_host:
|
||||||
|
raise exceptions.CommandError(_("No need to specify migrate or "
|
||||||
|
"target-host when disabling the "
|
||||||
|
"host maintenance mode."))
|
||||||
|
|
||||||
|
hv = cs.host_maintenance.update(args.host,
|
||||||
|
args.set_status,
|
||||||
|
args.migrate,
|
||||||
|
args.target_host)
|
||||||
|
|
||||||
|
host = HostMaintenanceResource(HostMaintenanceManager,
|
||||||
|
hv.hypervisor_maintenance)
|
||||||
|
utils.print_list([host], ['hypervisor_hostname', 'status',
|
||||||
|
'migrate', 'target-host'])
|
||||||
|
|
||||||
|
|
||||||
|
def _show_maintenance_status(cs, host):
|
||||||
|
hv = cs.host_maintenance.get(host)
|
||||||
|
host = HostMaintenanceResource(HostMaintenanceManager,
|
||||||
|
hv.hypervisor_maintenance)
|
||||||
|
|
||||||
|
utils.print_list([host], ['hypervisor_hostname', 'maintenance_status',
|
||||||
|
'maintenance_migration_action'])
|
@ -53,15 +53,16 @@ class Controller(wsgi.Controller):
|
|||||||
"'enable' or 'disable'"))
|
"'enable' or 'disable'"))
|
||||||
|
|
||||||
migrate_candidate = ["none", "active-only", "all"]
|
migrate_candidate = ["none", "active-only", "all"]
|
||||||
migrate = body.get("migrate", "none")
|
migrate = body.get("migrate")
|
||||||
if migrate.lower() not in migrate_candidate:
|
if migrate and migrate.lower() not in migrate_candidate:
|
||||||
raise exc.HTTPBadRequest(_("Malformed request body, migrate wrong "
|
raise exc.HTTPBadRequest(_("Malformed request body, migrate wrong "
|
||||||
"in request body, should be 'none',"
|
"in request body, should be 'none',"
|
||||||
"active-only, all or empty"))
|
"active-only, all or empty"))
|
||||||
|
target_host = body.get("target_host")
|
||||||
# Set maintenance mode from powervc client
|
# Set maintenance mode from powervc client
|
||||||
maintenance_update_status = self.pvcclient.hypervisors.\
|
maintenance_update_status = self.pvcclient.hypervisors.\
|
||||||
update_host_maintenance_mode(host_name, maintenance_status,
|
update_host_maintenance_mode(host_name, maintenance_status,
|
||||||
migrate)
|
migrate, target_host)
|
||||||
return maintenance_update_status
|
return maintenance_update_status
|
||||||
|
|
||||||
|
|
||||||
|
1
nova-powervc/test/hostmaintenanceclient/__init__.py
Normal file
1
nova-powervc/test/hostmaintenanceclient/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
1
nova-powervc/test/hostmaintenanceclient/v1_1/__init__.py
Normal file
1
nova-powervc/test/hostmaintenanceclient/v1_1/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
@ -0,0 +1,142 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
|
||||||
|
from webob import exc
|
||||||
|
|
||||||
|
from mock import MagicMock
|
||||||
|
from novaclient import exceptions
|
||||||
|
from novaclient.tests.fixture_data import client
|
||||||
|
from novaclient.tests.fixture_data import hypervisors as data
|
||||||
|
from novaclient.tests import utils
|
||||||
|
from hostmaintenanceclient.v1_1 import host_maintenance
|
||||||
|
|
||||||
|
|
||||||
|
class HostMaintenanceTest(utils.FixturedTestCase):
|
||||||
|
client_fixture_class = client.V1
|
||||||
|
data_fixture_class = data.V1
|
||||||
|
|
||||||
|
def compare_to_expected(self, expected, hyper):
|
||||||
|
for key, value in expected.items():
|
||||||
|
self.assertEqual(getattr(hyper, key), value)
|
||||||
|
|
||||||
|
def test_host_maintenance_get(self):
|
||||||
|
restAPI = MagicMock()
|
||||||
|
args = MagicMock()
|
||||||
|
# generate cs parameter
|
||||||
|
get_return_value = {"maintenance_status": "on",
|
||||||
|
"maintenance_migration_action": "none"}
|
||||||
|
|
||||||
|
host_manager = host_maintenance.HostMaintenanceManager(restAPI)
|
||||||
|
host_manager.api.client.get = MagicMock(
|
||||||
|
return_value=(200, get_return_value))
|
||||||
|
self.cs.host_maintenance = host_manager
|
||||||
|
args.set_status = None
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = None
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
# generate args parameter
|
||||||
|
host_maintenance.do_host_maintenance(self.cs, args)
|
||||||
|
restAPI.client.get.assert_called_once_with(
|
||||||
|
'/os-host-maintenance-mode/789522X_067E30B')
|
||||||
|
|
||||||
|
def test_host_maintenance_get_not_found(self):
|
||||||
|
restAPI = MagicMock()
|
||||||
|
args = MagicMock()
|
||||||
|
# generate cs parameter
|
||||||
|
host_manager = host_maintenance.HostMaintenanceManager(restAPI)
|
||||||
|
host_manager.api.client.get = MagicMock(
|
||||||
|
side_effect=exc.HTTPNotFound('The specified host cannot be found'))
|
||||||
|
self.cs.host_maintenance = host_manager
|
||||||
|
args.set_status = None
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = None
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
# generate args parameter
|
||||||
|
self.assertRaises(exc.HTTPNotFound,
|
||||||
|
host_maintenance.do_host_maintenance,
|
||||||
|
self.cs, args)
|
||||||
|
restAPI.client.get.assert_called_once_with(
|
||||||
|
'/os-host-maintenance-mode/789522X_067E30B')
|
||||||
|
|
||||||
|
def test_host_maintenance_update_enable_success(self):
|
||||||
|
restAPI = MagicMock()
|
||||||
|
args = MagicMock()
|
||||||
|
# generate cs parameter
|
||||||
|
update_return_value = {"hypervisor_maintenance":
|
||||||
|
{"status": "enable",
|
||||||
|
"migrate": "none",
|
||||||
|
"target-host": "none",
|
||||||
|
"hypervisor_hostname": "789522X_067E30B"}
|
||||||
|
}
|
||||||
|
|
||||||
|
host_manager = host_maintenance.HostMaintenanceManager(restAPI)
|
||||||
|
host_manager.api.client.put = MagicMock(
|
||||||
|
return_value=(200, update_return_value))
|
||||||
|
self.cs.host_maintenance = host_manager
|
||||||
|
args.set_status = 'enable'
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = None
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
# generate args parameter
|
||||||
|
host_maintenance.do_host_maintenance(self.cs, args)
|
||||||
|
restAPI.client.put.assert_called_once_with(
|
||||||
|
'/os-host-maintenance-mode/789522X_067E30B',
|
||||||
|
body={'status': 'enable', 'migrate': None, 'target_host': None})
|
||||||
|
|
||||||
|
def test_host_maintenance_update_disable_success(self):
|
||||||
|
restAPI = MagicMock()
|
||||||
|
args = MagicMock()
|
||||||
|
# generate cs parameter
|
||||||
|
update_return_value = {"hypervisor_maintenance":
|
||||||
|
{"status": "disable",
|
||||||
|
"migrate": "none",
|
||||||
|
"target-host": "none",
|
||||||
|
"hypervisor_hostname": "789522X_067E30B"}
|
||||||
|
}
|
||||||
|
|
||||||
|
host_manager = host_maintenance.HostMaintenanceManager(restAPI)
|
||||||
|
host_manager.api.client.put = MagicMock(
|
||||||
|
return_value=(200, update_return_value))
|
||||||
|
self.cs.host_maintenance = host_manager
|
||||||
|
args.set_status = 'disable'
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = None
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
# generate args parameter
|
||||||
|
host_maintenance.do_host_maintenance(self.cs, args)
|
||||||
|
restAPI.client.put.assert_called_once_with(
|
||||||
|
'/os-host-maintenance-mode/789522X_067E30B',
|
||||||
|
body={'status': 'disable'})
|
||||||
|
|
||||||
|
def test_host_maintenance_get_parameter_error1(self):
|
||||||
|
args = MagicMock()
|
||||||
|
args.set_status = None
|
||||||
|
args.migrate = 'active-only'
|
||||||
|
args.target_host = None
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
host_maintenance.do_host_maintenance, self.cs, args)
|
||||||
|
|
||||||
|
def test_host_maintenance_get_parameter_error2(self):
|
||||||
|
args = MagicMock()
|
||||||
|
args.set_status = None
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = "789522X_067E31B"
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
host_maintenance.do_host_maintenance, self.cs, args)
|
||||||
|
|
||||||
|
def test_host_maintenance_get_parameter_error3(self):
|
||||||
|
args = MagicMock()
|
||||||
|
args.set_status = 'disable'
|
||||||
|
args.migrate = None
|
||||||
|
args.target_host = "789522X_067E31B"
|
||||||
|
args.host = "789522X_067E30B"
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
host_maintenance.do_host_maintenance, self.cs, args)
|
Loading…
Reference in New Issue
Block a user