Disallow host-unlock if a lock operation is in progress

If host-unlock is issued while a host-lock or host-lock force operation
is already in progress, restore the original host action and reject the
host-unlock in order to allow the lock operation to terminate normally.

Change-Id: Ib82da93aa4a55f5cd3f41ab904745bea7c4e3afb
Closes-Bug: 1847330
Signed-off-by: John Kung <john.kung@windriver.com>
This commit is contained in:
John Kung 2019-11-26 15:21:40 -05:00
parent 64bdd3e869
commit d70d45669d
3 changed files with 110 additions and 5 deletions

View File

@ -4350,17 +4350,30 @@ class HostController(rest.RestController):
if action == constants.UNLOCK_ACTION:
# Set ihost_action in DB as early as possible as we need
# it as a synchronization point for things like lvg/pv
# deletion which is not allowed when ihost is unlokced
# deletion which is not allowed when host is unlocked
# or in the process of unlocking.
rc = self.update_ihost_action(action, hostupdate)
if rc:
pecan.request.dbapi.ihost_update(hostupdate.ihost_orig['uuid'],
hostupdate.ihost_val_prenotify)
host_action_orig = \
hostupdate.ihost_orig.get('ihost_action', "")
try:
self.check_unlock(hostupdate, force_unlock)
except exception.HostLocking:
msg = _("Host unlock rejected due to in progress action %s"
% host_action_orig.strip('-'))
self.update_ihost_action(host_action_orig, hostupdate)
pecan.request.dbapi.ihost_update(
hostupdate.ihost_orig['uuid'],
hostupdate.ihost_val_prenotify)
# raise as a Client side exception
raise wsme.exc.ClientSideError(msg)
except Exception:
LOG.info("host unlock check didn't pass, "
"so set the ihost_action back to None and re-raise the exception")
"so set the ihost_action (%s) back to None "
"and re-raise the exception" %
host_action_orig)
self.update_ihost_action(None, hostupdate)
pecan.request.dbapi.ihost_update(hostupdate.ihost_orig['uuid'],
hostupdate.ihost_val_prenotify)
@ -5006,9 +5019,16 @@ class HostController(rest.RestController):
# Semantic Check: Avoid Unlock of Unlocked Host
if hostupdate.ihost_orig['administrative'] == constants.ADMIN_UNLOCKED:
raise wsme.exc.ClientSideError(
_("Avoiding 'unlock' action on already "
"'unlocked' host %s" % hostupdate.ihost_orig['hostname']))
host_action = hostupdate.ihost_orig['ihost_action'] or ""
if (host_action.startswith(constants.LOCK_ACTION) or
host_action.startswith(constants.FORCE_LOCK_ACTION)):
raise exception.HostLocking(
host=hostupdate.ihost_orig['hostname'],
action=host_action.strip('-'))
else:
raise wsme.exc.ClientSideError(
_("Avoiding 'unlock' action on already "
"'unlocked' host %s" % hostupdate.ihost_orig['hostname']))
# Semantic Check: Action Dependency: Power-Off / Unlock case
if (hostupdate.ihost_orig['availability'] ==

View File

@ -806,6 +806,11 @@ class RouteNotFoundByName(NotFound):
"could not be found.")
class HostLocking(SysinvException):
message = _("Unable to complete the action because "
"host %(host)s is undergoing action %(action)s.")
class HostLocked(SysinvException):
message = _("Unable to complete the action %(action)s because "
"Host %(host)s is in administrative state = unlocked.")

View File

@ -757,6 +757,86 @@ class TestPatch(TestHost):
result = self.get_json('/ihosts/%s' % w0_host['hostname'])
self.assertEqual(constants.NONE_ACTION, result['action'])
def test_unlock_action_worker_while_locking(self):
# Create controller-0
self._create_controller_0(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create worker-0
w0_host = self._create_worker(
mgmt_ip='192.168.204.5',
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_AVAILABLE,
ihost_action=constants.LOCK_ACTION)
self._create_test_host_platform_interface(w0_host)
self._create_test_host_cpus(
w0_host, platform=1, vswitch=2, application=12)
# Unlock worker host while lock action in progress
response = self._patch_host_action(w0_host['hostname'],
constants.UNLOCK_ACTION,
'sysinv-test',
expect_errors=True)
# Verify that the unlock was not sent to the VIM
self.mock_vim_api_host_action.assert_not_called()
# Verify that the host was not modified in maintenance
self.mock_mtce_api_host_modify.assert_not_called()
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertIn('Host unlock rejected due to in progress action %s' %
constants.LOCK_ACTION,
response.json['error_message'])
result = self.get_json('/ihosts/%s' % w0_host['hostname'])
self.assertEqual(constants.LOCK_ACTION, result['ihost_action'])
def test_unlock_action_worker_while_force_locking(self):
# Create controller-0
self._create_controller_0(
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE)
# Create worker-0
w0_host = self._create_worker(
mgmt_ip='192.168.204.5',
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_AVAILABLE,
ihost_action=constants.FORCE_LOCK_ACTION)
self._create_test_host_platform_interface(w0_host)
self._create_test_host_cpus(
w0_host, platform=1, vswitch=2, application=12)
# Unlock worker host while lock action in progress
response = self._patch_host_action(w0_host['hostname'],
constants.UNLOCK_ACTION,
'sysinv-test',
expect_errors=True)
# Verify that the unlock was not sent to the VIM
self.mock_vim_api_host_action.assert_not_called()
# Verify that the host was not modified in maintenance
self.mock_mtce_api_host_modify.assert_not_called()
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertIn('Host unlock rejected due to in progress action %s' %
constants.FORCE_LOCK_ACTION,
response.json['error_message'])
result = self.get_json('/ihosts/%s' % w0_host['hostname'])
self.assertEqual(constants.FORCE_LOCK_ACTION, result['ihost_action'])
def test_lock_action_worker(self):
# Create controller-0
self._create_controller_0(