From 168442b2e17fd66797485b9e593533b1ca35ca85 Mon Sep 17 00:00:00 2001 From: John Kung Date: Thu, 25 Jul 2019 10:38:55 -0400 Subject: [PATCH] Create host state for determining initial inventory complete Add host inv_state attribute to allow determination of when the initial inventory collection has been completed. Update references which were using disks/pvs as proxy for inventory completion to reference the host inv_state attribute. Description of issue (from Bug 1837097): The system inventory agent needs to explicitly indicate that inventory collection has finished for each host. The current method for determining whether a host has been inventoried successfully is to wait for the disk/pv list to be non-empty. That worked well until recently when the host file system feature was merged. The system inventory agent now collects/creates host file systems after the disk list is populated so a provisioning system waiting on the disk list will move ahead to unlock the node prematurely before the host file systems have been created and reported to system inventory. This can lead to undefined behavior either on the system being provisioned or the provisioning system that is configuring the target system. If we do not fix this properly with an explicit/deterministic flag then we will trip over this issue each time someone adds a new inventory collection step to the end of the system inventory agent's initial process loop. Change-Id: Ifdb8871a892414ee4c433cf7a6ec7e79390c6420 Closes-bug: 1837097 Signed-off-by: John Kung --- .../controllerconfig/common/constants.py | 2 +- sysinv/cgts-client/centos/build_srpm.data | 2 +- .../cgts-client/cgtsclient/v1/iHost_shell.py | 2 +- sysinv/sysinv/centos/build_srpm.data | 2 +- sysinv/sysinv/sysinv/sysinv/agent/manager.py | 120 ++++++++++++++---- .../sysinv/sysinv/api/controllers/v1/host.py | 18 ++- .../sysinv/sysinv/sysinv/common/constants.py | 2 + sysinv/sysinv/sysinv/sysinv/common/utils.py | 8 +- .../sysinv/sysinv/sysinv/conductor/manager.py | 10 ++ .../sysinv/sysinv/sysinv/conductor/rpcapi.py | 12 ++ .../migrate_repo/versions/090_inv_state.py | 38 ++++++ .../sysinv/sysinv/db/sqlalchemy/models.py | 1 + sysinv/sysinv/sysinv/sysinv/objects/host.py | 1 + .../sysinv/sysinv/tests/api/test_host.py | 67 ++++++---- sysinv/sysinv/sysinv/sysinv/tests/db/utils.py | 1 + 15 files changed, 227 insertions(+), 59 deletions(-) create mode 100644 sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/090_inv_state.py diff --git a/controllerconfig/controllerconfig/controllerconfig/common/constants.py b/controllerconfig/controllerconfig/controllerconfig/common/constants.py index 97486b7d7e..e2864933aa 100644 --- a/controllerconfig/controllerconfig/controllerconfig/common/constants.py +++ b/controllerconfig/controllerconfig/controllerconfig/common/constants.py @@ -56,7 +56,7 @@ DEFAULT_VIRTUAL_BACKUP_STOR_SIZE = \ DEFAULT_EXTENSION_STOR_SIZE = \ sysinv_constants.DEFAULT_EXTENSION_STOR_SIZE -SYSTEM_CONFIG_TIMEOUT = 300 +SYSTEM_CONFIG_TIMEOUT = 420 SERVICE_ENABLE_TIMEOUT = 180 MINIMUM_ROOT_DISK_SIZE = 500 MAXIMUM_CGCS_LV_SIZE = 500 diff --git a/sysinv/cgts-client/centos/build_srpm.data b/sysinv/cgts-client/centos/build_srpm.data index bf1f9fbd69..6c18c584d7 100644 --- a/sysinv/cgts-client/centos/build_srpm.data +++ b/sysinv/cgts-client/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="cgts-client" -TIS_PATCH_VER=68 +TIS_PATCH_VER=69 diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py index 47f7400764..eb14e18165 100755 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iHost_shell.py @@ -35,7 +35,7 @@ def _print_ihost_show(ihost): 'location', 'uptime', 'reserved', 'created_at', 'updated_at', 'boot_device', 'rootfs_device', 'install_output', 'console', 'tboot', 'vim_progress_status', 'software_load', 'install_state', - 'install_state_info'] + 'install_state_info', 'inv_state'] optional_fields = ['vsc_controllers', 'ttys_dcd'] if ihost.subfunctions != ihost.personality: fields.append('subfunctions') diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index 3dd3dcdb5e..a3599bb3e7 100644 --- a/sysinv/sysinv/centos/build_srpm.data +++ b/sysinv/sysinv/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="sysinv" -TIS_PATCH_VER=329 +TIS_PATCH_VER=330 diff --git a/sysinv/sysinv/sysinv/sysinv/agent/manager.py b/sysinv/sysinv/sysinv/sysinv/agent/manager.py index 11409f1a39..3104595b89 100644 --- a/sysinv/sysinv/sysinv/sysinv/agent/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/agent/manager.py @@ -130,11 +130,34 @@ class AgentManager(service.PeriodicService): RPC_API_VERSION = '1.0' + NUMA = 'numa' + CPU = 'cpu' + PORT = 'port' + PCI_DEVICE = 'pci_device' + MEMORY = 'memory' + DISK = 'disk' + PV = 'pv' + LVG = 'lvg' + HOST_FILESYSTEMS = 'host_filesystems' + + # Note that this set must be extended when there are + # additional inventory required for the initial + # inventory complete (to be notified to conductor). + INVENTORY_REPORTS_REQUIRED = { + NUMA, + PORT, + PCI_DEVICE, + CPU, + MEMORY, + DISK, + PV, + LVG, + HOST_FILESYSTEMS} + def __init__(self, host, topic): serializer = objects_base.SysinvObjectSerializer() super(AgentManager, self).__init__(host, topic, serializer=serializer) - self._report_to_conductor = False self._report_to_conductor_iplatform_avail_flag = False self._ipci_operator = pci.PCIOperator() self._inode_operator = node.NodeOperator() @@ -161,6 +184,8 @@ class AgentManager(service.PeriodicService): self._tpmconfig_rpc_failure = False self._tpmconfig_host_first_apply = False self._first_grub_update = False + self._inventoried_initial = False + self._inventory_reported = set() def start(self): super(AgentManager, self).start() @@ -176,6 +201,22 @@ class AgentManager(service.PeriodicService): if tsc.system_mode == constants.SYSTEM_MODE_SIMPLEX: utils.touch(SYSINV_READY_FLAG) + def _report_to_conductor(self): + """ Initial inventory report to conductor required + + returns: True if initial inventory report_to_conductor is required + """ + + initial_reports_required = \ + self.INVENTORY_REPORTS_REQUIRED - self._inventory_reported + initial_reports_required.discard(self.HOST_FILESYSTEMS) + if initial_reports_required: + LOG.info("_report_to_conductor initial_reports_required=%s" % + initial_reports_required) + return True + else: + return False + def _report_to_conductor_iplatform_avail(self): # First report sent to conductor since boot utils.touch(SYSINV_FIRST_REPORT_FLAG) @@ -679,18 +720,16 @@ class AgentManager(service.PeriodicService): rpcapi.iport_update_by_ihost(context, host_uuid, port_list) + self._inventory_reported.add(self.PORT) except RemoteError as e: LOG.error("iport_update_by_ihost RemoteError exc_type=%s" % e.exc_type) - self._report_to_conductor = False - except exception.SysinvException: - LOG.exception("Sysinv Agent exception updating port.") - pass try: rpcapi.pci_device_update_by_host(context, host_uuid, pci_device_list) + self._inventory_reported.add(self.PCI_DEVICE) except exception.SysinvException: LOG.exception("Sysinv Agent exception updating pci_device.") pass @@ -743,7 +782,6 @@ class AgentManager(service.PeriodicService): ipersonality = ihost.get('personality') or "" if ihost and ipersonality: - self._report_to_conductor = True self._ihost_uuid = ihost['uuid'] self._ihost_personality = ihost['personality'] self._mgmt_ip = ihost['mgmt_ip'] @@ -773,7 +811,7 @@ class AgentManager(service.PeriodicService): time.sleep(30) slept += 30 - if not self._report_to_conductor: + if not self._report_to_conductor(): # let the audit take care of it instead LOG.info("Sysinv no matching ihost found... await Audit") return @@ -807,18 +845,10 @@ class AgentManager(service.PeriodicService): rpcapi.inumas_update_by_ihost(icontext, ihost['uuid'], inumas) + self._inventory_reported.add(self.NUMA) except RemoteError as e: LOG.error("inumas_update_by_ihost RemoteError exc_type=%s" % e.exc_type) - if e.exc_type == 'TimeoutError': - self._report_to_conductor = False - except Exception as e: - LOG.exception("Sysinv Agent exception updating inuma e=%s." % e) - self._report_to_conductor = True - pass - except exception.SysinvException: - LOG.exception("Sysinv Agent uncaught exception updating inuma.") - pass force_grub_update = self._force_grub_update() try: @@ -827,18 +857,10 @@ class AgentManager(service.PeriodicService): ihost['uuid'], icpus, force_grub_update) + self._inventory_reported.add(self.CPU) except RemoteError as e: LOG.error("icpus_update_by_ihost RemoteError exc_type=%s" % e.exc_type) - if e.exc_type == 'TimeoutError': - self._report_to_conductor = False - except Exception as e: - LOG.exception("Sysinv Agent exception updating icpus e=%s." % e) - self._report_to_conductor = True - pass - except exception.SysinvException: - LOG.exception("Sysinv Agent uncaught exception updating icpus conductor.") - pass imemory = self._inode_operator.inodes_get_imemory() if imemory: @@ -847,6 +869,7 @@ class AgentManager(service.PeriodicService): rpcapi.imemory_update_by_ihost(icontext, ihost['uuid'], imemory) + self._inventory_reported.add(self.MEMORY) except RemoteError as e: LOG.error("imemory_update_by_ihost RemoteError exc_type=%s" % e.exc_type) @@ -862,6 +885,7 @@ class AgentManager(service.PeriodicService): rpcapi.idisk_update_by_ihost(icontext, ihost['uuid'], idisk) + self._inventory_reported.add(self.DISK) except RemoteError as e: # TODO (oponcea): Valid for R4->R5, remove in R6. # safe to ignore during upgrades @@ -882,6 +906,7 @@ class AgentManager(service.PeriodicService): rpcapi.ipv_update_by_ihost(icontext, ihost['uuid'], ipv) + self._inventory_reported.add(self.PV) except exception.SysinvException: LOG.exception("Sysinv Agent exception updating ipv conductor.") pass @@ -891,6 +916,7 @@ class AgentManager(service.PeriodicService): rpcapi.ilvg_update_by_ihost(icontext, ihost['uuid'], ilvg) + self._inventory_reported.add(self.LVG) except exception.SysinvException: LOG.exception("Sysinv Agent exception updating ilvg conductor.") pass @@ -959,6 +985,36 @@ class AgentManager(service.PeriodicService): self._subfunctions_configured = True return True + def notify_initial_inventory_completed(self, context): + """Report the inventory completion event for this host to the + conductor when the conditions for inventory complete have + been met. + + :param context: an admin context + """ + def _conditions_for_inventory_complete_met(): + # NOTE: condition(s) for inventory complete must be + # reviewed for update when additional inventory is posted. + reports_required = \ + self.INVENTORY_REPORTS_REQUIRED - self._inventory_reported + if not reports_required: + return True + else: + LOG.info("_conditions_for_inventory_complete_met requires %s" % + reports_required) + return False + + if (_conditions_for_inventory_complete_met() and not + self._inventoried_initial): + LOG.info("Initial inventory completed host %s" % + self._ihost_uuid) + rpcapi = conductor_rpcapi.ConductorAPI( + topic=conductor_rpcapi.MANAGER_TOPIC) + + rpcapi.initial_inventory_completed(context, + self._ihost_uuid) + self._inventoried_initial = True + def _report_config_applied(self, context): """Report the latest configuration applied for this host to the conductor. @@ -1081,7 +1137,7 @@ class AgentManager(service.PeriodicService): if os.path.isfile(tsc.INITIAL_CONFIG_COMPLETE_FLAG): self._report_config_applied(icontext) - if self._report_to_conductor is False: + if self._report_to_conductor(): LOG.info("Sysinv Agent audit running inv_get_and_report.") self.ihost_inv_get_and_report(icontext) @@ -1110,6 +1166,7 @@ class AgentManager(service.PeriodicService): rpcapi.idisk_update_by_ihost(icontext, self._ihost_uuid, idisk) + self._inventory_reported.add(self.DISK) except RemoteError as e: # TODO (oponcea): Valid for R4->R5, remove in R6. # safe to ignore during upgrades @@ -1168,6 +1225,7 @@ class AgentManager(service.PeriodicService): rpcapi.imemory_update_by_ihost(icontext, self._ihost_uuid, imemory) + self._inventory_reported.add(self.MEMORY) if self._agent_throttle > 5: # throttle updates self._agent_throttle = 0 @@ -1207,6 +1265,7 @@ class AgentManager(service.PeriodicService): rpcapi.idisk_update_by_ihost(icontext, self._ihost_uuid, idisk) + self._inventory_reported.add(self.DISK) except RemoteError as e: # TODO (oponcea): Valid for R4->R5, remove in R6. # safe to ignore during upgrades @@ -1234,6 +1293,7 @@ class AgentManager(service.PeriodicService): rpcapi.ipv_update_by_ihost(icontext, self._ihost_uuid, ipv) + self._inventory_reported.add(self.PV) except exception.SysinvException: LOG.exception("Sysinv Agent exception updating ipv" "conductor.") @@ -1249,6 +1309,7 @@ class AgentManager(service.PeriodicService): rpcapi.ilvg_update_by_ihost(icontext, self._ihost_uuid, ilvg) + self._inventory_reported.add(self.LVG) except exception.SysinvException: LOG.exception("Sysinv Agent exception updating ilvg" "conductor.") @@ -1319,12 +1380,18 @@ class AgentManager(service.PeriodicService): filesystems) self._prev_fs = filesystems + self._inventory_reported.add(self.HOST_FILESYSTEMS) except Exception as e: LOG.exception( "Sysinv Agent exception creating the host filesystems." " %s" % e) self._prev_fs = None + # Notify conductor of inventory completion after necessary + # inventory reports have been sent to conductor. + # This is as defined by _conditions_for_inventory_complete_met(). + self.notify_initial_inventory_completed(icontext) + self._report_config_applied(icontext) if os.path.isfile(tsc.PLATFORM_CONF_FILE): @@ -1931,3 +1998,4 @@ class AgentManager(service.PeriodicService): self._ihost_uuid, memory, force_update=True) + self._inventory_reported.add(self.MEMORY) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py index 118011af39..423c83e13e 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py @@ -399,6 +399,9 @@ class Host(base.APIBase): ihost_action = wtypes.text 'Represent the current action task in progress' + inv_state = wtypes.text + 'Represent the inventory state' + vim_progress_status = wtypes.text 'Represent the vim progress status' @@ -559,7 +562,8 @@ class Host(base.APIBase): 'tboot', 'vsc_controllers', 'ttys_dcd', 'software_load', 'target_load', 'peers', 'peer_id', 'install_state', 'install_state_info', - 'iscsi_initiator_name'] + 'iscsi_initiator_name', + 'inv_state'] fields = minimum_fields if not expand else None uhost = Host.from_rpc_object(rpc_ihost, fields) @@ -2906,6 +2910,7 @@ class HostController(rest.RestController): 'invprovision', 'recordtype', 'ihost_action', 'action_state', + 'inv_state', 'iconfig_applied', 'iconfig_target'] @@ -4898,6 +4903,17 @@ class HostController(rest.RestController): LOG.warn("Allowing force-unlock of host %s " "undergoing reinstall." % hostupdate.displayid) + if not force_unlock: + # Ensure inventory has completed prior to allowing unlock + host = pecan.request.dbapi.ihost_get( + hostupdate.ihost_orig['uuid']) + if host.inv_state != constants.INV_STATE_INITIAL_INVENTORIED: + raise wsme.exc.ClientSideError( + _("Can not unlock host %s that has not yet been " + "inventoried. Please wait for host to complete " + "initial inventory prior to unlock." + % hostupdate.displayid)) + personality = hostupdate.ihost_patch.get('personality') if personality == constants.CONTROLLER: self.check_unlock_controller(hostupdate, force_unlock) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 6b3413e4a7..6735ebf054 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -192,6 +192,8 @@ HOST_ACTION_STATE = "action_state" HAS_REINSTALLING = "reinstalling" HAS_REINSTALLED = "reinstalled" +INV_STATE_INITIAL_INVENTORIED = "inventoried" + # Board Management Region Info REGION_PRIMARY = "Internal" REGION_SECONDARY = "External" diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index b2a963d87c..78b938ca1f 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -2024,15 +2024,11 @@ def is_default_huge_pages_required(host): def is_inventory_config_complete(dbapi, forihostid): """Check if the initial inventory has completed - - Due to lack of host state that signifies the completion of inventory, this - function retrieves the list of persistent volumes from the database. If - the count is not zero; ports, disks and PVs have been inventoried. """ try: - pvs = dbapi.ipv_get_by_ihost(forihostid) - return len(pvs) > 0 + host = dbapi.ihost_get(forihostid) + return host.inv_state == constants.INV_STATE_INITIAL_INVENTORIED except Exception: return False diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 3fbbc802d2..64f864af7f 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -4366,6 +4366,16 @@ class ConductorManager(service.PeriodicService): config_uuid = imsg_dict['config_applied'] self._update_host_config_applied(context, ihost, config_uuid) + def initial_inventory_completed(self, context, host_uuid): + host_uuid.strip() + try: + self.dbapi.ihost_update( + host_uuid, + {'inv_state': constants.INV_STATE_INITIAL_INVENTORIED}) + except exception.ServerNotFound: + LOG.error("initial_inventory_completed invalid host_uuid %s" % + host_uuid) + def subfunctions_update_by_ihost(self, context, ihost_uuid, subfunctions): """Update subfunctions for a host. diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index 9c54eba7f0..f404502ec1 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -1029,6 +1029,18 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): ihost_uuid=ihost_uuid, imsg_dict=imsg_dict)) + def initial_inventory_completed(self, context, host_uuid): + """Notify of initial inventory completion for a host. + + :param context: an admin context + :param host_uuid: host unique id + """ + + return self.call(context, + self.make_msg('initial_inventory_completed', + host_uuid=host_uuid, + )) + def iinterface_get_providernets(self, context, pn_names=None): diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/090_inv_state.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/090_inv_state.py new file mode 100644 index 0000000000..ebcfbc9dbb --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/090_inv_state.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sqlalchemy import Column, MetaData, Table +from sqlalchemy import String, Integer + +ENGINE = 'InnoDB' +CHARSET = 'utf8' + + +def upgrade(migrate_engine): + """ + This database upgrade creates a new host inv_state attribute for + storing the inventory state for a host. + """ + + meta = MetaData() + meta.bind = migrate_engine + + host = Table('i_host', + meta, + Column('id', Integer, primary_key=True, nullable=False), + mysql_engine=ENGINE, mysql_charset=CHARSET, autoload=True) + + # Add the inventory state attribute + host.create_column(Column('inv_state', String(255))) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + # Downgrade is unsupported in this release. + raise NotImplementedError('SysInv database downgrade is unsupported.') diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index b39c97116e..c5d319bd4c 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -211,6 +211,7 @@ class ihost(Base): action = Column(actionEnum, default="none") ihost_action = Column(String(255)) action_state = Column(String(255)) + inv_state = Column(String(255)) mtce_info = Column(String(255)) install_state = Column(String(255)) install_state_info = Column(String(255)) diff --git a/sysinv/sysinv/sysinv/sysinv/objects/host.py b/sysinv/sysinv/sysinv/sysinv/objects/host.py index 1946532a74..ee9d865178 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/host.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/host.py @@ -65,6 +65,7 @@ class Host(base.SysinvObject): 'availability': utils.str_or_none, 'ihost_action': utils.str_or_none, 'action_state': utils.str_or_none, + 'inv_state': utils.str_or_none, 'mtce_info': utils.str_or_none, 'vim_progress_status': utils.str_or_none, 'action': utils.str_or_none, diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py index 82158b716b..d0663f5bb5 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_host.py @@ -13,6 +13,7 @@ Tests for the API /ihosts/ methods. import mock import webtest.app +from six.moves import http_client from sysinv.common import constants from sysinv.openstack.common import uuidutils @@ -622,12 +623,14 @@ class TestListHosts(TestHost): class TestPatch(TestHost): - def _patch_host_action(self, hostname, action, user_agent): + def _patch_host_action( + self, hostname, action, user_agent, expect_errors=False): return self.patch_json('/ihosts/%s' % hostname, [{'path': '/action', 'value': action, 'op': 'replace'}], - headers={'User-Agent': user_agent}) + headers={'User-Agent': user_agent}, + expect_errors=expect_errors) def test_update_optimizable(self): # Create controller-0 @@ -645,7 +648,7 @@ class TestPatch(TestHost): 'op': 'replace'}], headers={'User-Agent': 'sysinv-test'}) self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the host was updated with the specified location result = self.get_json('/ihosts/%s' % ndict['hostname']) @@ -666,7 +669,7 @@ class TestPatch(TestHost): constants.UNLOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the unlock was sent to the VIM self.mock_vim_api_host_action.assert_called_with( @@ -698,7 +701,7 @@ class TestPatch(TestHost): constants.FORCE_UNLOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the unlock was sent to the VIM self.mock_vim_api_host_action.assert_called_with( @@ -715,6 +718,26 @@ class TestPatch(TestHost): result = self.get_json('/ihosts/%s' % c0_host['hostname']) self.assertEqual(constants.NONE_ACTION, result['action']) + def test_unlock_action_controller_inventory_not_complete(self): + self._configure_networks() + # Create controller-0 without inv_state initial inventory complete + c0_host = self._create_controller_0( + subfunctions=constants.CONTROLLER, + invprovision=constants.PROVISIONED, + administrative=constants.ADMIN_LOCKED, + operational=constants.OPERATIONAL_ENABLED, + availability=constants.AVAILABILITY_ONLINE, + inv_state=None) + + # Unlock host + response = self._patch_host_action(c0_host['hostname'], + constants.UNLOCK_ACTION, + 'sysinv-test', + expect_errors=True) + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(http_client.BAD_REQUEST, response.status_int) + self.assertTrue(response.json['error_message']) + def test_lock_action_controller(self): self._configure_networks() # Create controller-0 @@ -739,7 +762,7 @@ class TestPatch(TestHost): constants.LOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the SM lock pre check was done self.mock_sm_api_lock_pre_check.assert_called_with(c1_host['hostname'], @@ -782,7 +805,7 @@ class TestPatch(TestHost): constants.FORCE_LOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the SM lock pre check was not done self.mock_sm_api_lock_pre_check.assert_not_called() @@ -832,7 +855,7 @@ class TestPatch(TestHost): constants.UNLOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the unlock was sent to the VIM self.mock_vim_api_host_action.assert_called_with( @@ -880,7 +903,7 @@ class TestPatch(TestHost): constants.LOCK_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the SM lock pre check was not done self.mock_sm_api_lock_pre_check.assert_not_called() @@ -932,7 +955,7 @@ class TestPatch(TestHost): constants.SWACT_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the SM swact pre check was done self.mock_sm_api_swact_pre_check.assert_called_with(c1_host['hostname'], @@ -970,7 +993,7 @@ class TestPatch(TestHost): constants.FORCE_SWACT_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the SM swact pre check was not done self.mock_sm_api_swact_pre_check.assert_not_called() @@ -1007,7 +1030,7 @@ class TestPatch(TestHost): constants.RESET_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the reset was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1042,7 +1065,7 @@ class TestPatch(TestHost): constants.REBOOT_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the reboot was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1077,7 +1100,7 @@ class TestPatch(TestHost): constants.REINSTALL_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the host config was removed self.fake_conductor_api.remove_host_config.assert_called_with( @@ -1115,7 +1138,7 @@ class TestPatch(TestHost): constants.POWERON_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the poweron was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1150,7 +1173,7 @@ class TestPatch(TestHost): constants.POWEROFF_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the poweroff was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1186,7 +1209,7 @@ class TestPatch(TestHost): constants.VIM_SERVICES_ENABLED, 'vim') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the services enabled was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1229,7 +1252,7 @@ class TestPatch(TestHost): constants.VIM_SERVICES_DISABLED, 'vim') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the services disabled was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1273,7 +1296,7 @@ class TestPatch(TestHost): constants.VIM_SERVICES_DISABLE_FAILED, 'vim') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the services disable failed was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1316,7 +1339,7 @@ class TestPatch(TestHost): constants.VIM_SERVICES_DISABLE_EXTEND, 'vim') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the services disable extend was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1358,7 +1381,7 @@ class TestPatch(TestHost): constants.VIM_SERVICES_DELETE_FAILED, 'vim') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the services disable failed was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() @@ -1437,7 +1460,7 @@ class TestPatch(TestHost): constants.SUBFUNCTION_CONFIG_ACTION, 'sysinv-test') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, http_client.OK) # Verify that the configure was not sent to the VIM self.mock_vim_api_host_action.assert_not_called() diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py index c1865e9c98..3816a34265 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py @@ -160,6 +160,7 @@ def get_test_ihost(**kw): 'install_state': kw.get('install_state', None), 'install_state_info': kw.get('install_state_info', None), 'iscsi_initiator_name': kw.get('iscsi_initiator_name', None), + 'inv_state': kw.get('inv_state', 'inventoried'), } return inv