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 <john.kung@windriver.com>
This commit is contained in:
parent
d4549cf4b0
commit
168442b2e1
|
@ -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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="cgts-client"
|
||||
TIS_PATCH_VER=68
|
||||
TIS_PATCH_VER=69
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="sysinv"
|
||||
TIS_PATCH_VER=329
|
||||
TIS_PATCH_VER=330
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.')
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue