Ensure agent is ready before issuing runtime config

Update the conductor to ensure at least its own config agent
on the active controller is ready to handle the config.  Otherwise,
append runtime config to deferred config list until signalled ready.

Prior to this commit, runtime config can be missed on startup
on the active controller due to the agent not being ready
to handle the config request.

It does not defer runtime config application until all other hosts
are ready as the config target is still persisted to track required
config for host target in the event the rpc request is missed due
to unexpected event or agent not ready on other hosts.

Test Performed:
  Installation and deployment of AIO-SX
  Installation and deployment of multinode system
  Verify that deferred runtime configs are applied in order
  when agent becomes available.

Change-Id: I7388844d048453d302409eea36a939d81c9447ec
Closes-Bug: 1915343
Signed-off-by: John Kung <john.kung@windriver.com>
This commit is contained in:
John Kung 2021-02-15 15:05:24 -06:00
parent 274f9adce9
commit df6a338654
5 changed files with 412 additions and 72 deletions

View File

@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# #
# Copyright (c) 2013-2014 Wind River Systems, Inc. # Copyright (c) 2013-2021 Wind River Systems, Inc.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
@ -29,6 +29,7 @@ SYSINV_CONF_DIR="/etc/sysinv"
SYSINV_CONF_FILE="${SYSINV_CONF_DIR}/sysinv.conf" SYSINV_CONF_FILE="${SYSINV_CONF_DIR}/sysinv.conf"
SYSINV_CONF_DEFAULT_FILE="/opt/platform/sysinv/${SW_VERSION}/sysinv.conf.default" SYSINV_CONF_DEFAULT_FILE="/opt/platform/sysinv/${SW_VERSION}/sysinv.conf.default"
SYSINV_READY_FLAG=/var/run/.sysinv_ready SYSINV_READY_FLAG=/var/run/.sysinv_ready
SYSINV_REPORTED_FLAG=/var/run/sysinv/.sysinv_reported
DELAY_SEC=20 DELAY_SEC=20
@ -62,11 +63,11 @@ function mount_and_copy_config_file()
if [ ${RETVAL} -ne 0 ] ; then if [ ${RETVAL} -ne 0 ] ; then
logger "$0: Warn: nfs-mount controller:/opt/platform/sysinv/${SW_VERSION} /mnt/sysinv" logger "$0: Warn: nfs-mount controller:/opt/platform/sysinv/${SW_VERSION} /mnt/sysinv"
else else
mkdir -p $SYSINV_CONF_DIR mkdir -p $SYSINV_CONF_DIR
cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE} cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE}
RETVAL=$? RETVAL=$?
if [ $? -ne 0 ] ; then if [ $? -ne 0 ] ; then
logger "$0: Warn: cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE}" logger "$0: Warn: cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE}"
fi fi
timeout 5s umount /mnt/sysinv timeout 5s umount /mnt/sysinv
rmdir /mnt/sysinv rmdir /mnt/sysinv
@ -84,18 +85,20 @@ case "$1" in
exit 1 exit 1
fi fi
# if [ "$NODETYPE" = "compute" ] ; then echo -n "Setting up config for sysinv-agent: "
# if [ "$NODETYPE" = "compute" ] || [ "$NODETYPE" = "controller" ] ; then if [ -e ${SYSINV_READY_FLAG} ] ; then
echo -n "Setting up config for sysinv-agent: "
if [ -e ${SYSINV_READY_FLAG} ] ; then
# clear it on every restart, so agent can update it # clear it on every restart, so agent can update it
rm -f ${SYSINV_READY_FLAG} rm -f ${SYSINV_READY_FLAG}
fi fi
if [ -e ${SYSINV_REPORTED_FLAG} ] ; then
if [ -f ${SYSINV_CONF_FILE} ] ; then # clear it on every restart, so agent can update it
logger "$0: ${SYSINV_CONF_FILE} already exists" rm -f ${SYSINV_REPORTED_FLAG}
RETVAL=0 fi
else
if [ -f ${SYSINV_CONF_FILE} ] ; then
logger "$0: ${SYSINV_CONF_FILE} already exists"
RETVAL=0
else
# Avoid self-mount due to potential nfs issues # Avoid self-mount due to potential nfs issues
echo "Checking for controller-platform-nfs " echo "Checking for controller-platform-nfs "
@ -120,20 +123,20 @@ case "$1" in
echo "controller-platform-nfs is not available" echo "controller-platform-nfs is not available"
else else
# Only required if conf file does not already exist # Only required if conf file does not already exist
if [ -f ${SYSINV_CONF_DEFAULT_FILE} ] if [ -f ${SYSINV_CONF_DEFAULT_FILE} ]
then then
echo "Copying self sysinv.conf without mount" echo "Copying self sysinv.conf without mount"
mkdir -p $SYSINV_CONF_DIR mkdir -p $SYSINV_CONF_DIR
cp ${SYSINV_CONF_DEFAULT_FILE} ${SYSINV_CONF_FILE} cp ${SYSINV_CONF_DEFAULT_FILE} ${SYSINV_CONF_FILE}
RETVAL=$? RETVAL=$?
if [ $? -ne 0 ] ; then if [ $? -ne 0 ] ; then
logger "$0: Warn: cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE} failed. Try mount." logger "$0: Warn: cp /mnt/sysinv/sysinv.conf.default ${SYSINV_CONF_FILE} failed. Try mount."
else else
CONF_COPIED=1 CONF_COPIED=1
fi fi
fi fi
if [ ${CONF_COPIED} -eq 0 ] if [ ${CONF_COPIED} -eq 0 ]
then then
CONF_COPY_COUNT=0 CONF_COPY_COUNT=0
while [ $CONF_COPY_COUNT -lt 3 ]; do while [ $CONF_COPY_COUNT -lt 3 ]; do
if mount_and_copy_config_file ; if mount_and_copy_config_file ;
@ -144,52 +147,48 @@ case "$1" in
let CONF_COPY_COUNT=CONF_COPY_COUNT+1 let CONF_COPY_COUNT=CONF_COPY_COUNT+1
logger "$0: Warn: Mount and copy config file failed. Attempt: ${CONF_COPY_COUNT}" logger "$0: Warn: Mount and copy config file failed. Attempt: ${CONF_COPY_COUNT}"
done done
fi fi
fi fi
fi fi
echo -n "Installing virtio_net driver: " echo -n "Installing virtio_net driver: "
timeout 5s modprobe virtio_net timeout 5s modprobe virtio_net
RETVAL=$? RETVAL=$?
if [ ${RETVAL} -eq 0 ] ; then if [ ${RETVAL} -eq 0 ] ; then
echo "OK" echo "OK"
else else
echo "FAIL" echo "FAIL"
fi fi
if [ -e ${daemon_pidfile} ] ; then if [ -e ${daemon_pidfile} ] ; then
echo "Killing existing process before starting new" echo "Killing existing process before starting new"
pid=`cat ${daemon_pidfile}` pid=`cat ${daemon_pidfile}`
kill -TERM $pid kill -TERM $pid
rm -f ${daemon_pidfile} rm -f ${daemon_pidfile}
fi fi
echo -n "Starting sysinv-agent: " echo -n "Starting sysinv-agent: "
/bin/sh -c "${SYSINVAGENT}"' >> /dev/null 2>&1 & echo $!' > ${daemon_pidfile} /bin/sh -c "${SYSINVAGENT}"' >> /dev/null 2>&1 & echo $!' > ${daemon_pidfile}
RETVAL=$? RETVAL=$?
if [ $RETVAL -eq 0 ] ; then if [ $RETVAL -eq 0 ] ; then
echo "OK" echo "OK"
touch /var/lock/subsys/${DAEMON_NAME} touch /var/lock/subsys/${DAEMON_NAME}
else else
echo "FAIL" echo "FAIL"
fi fi
# fi
;; ;;
stop) stop)
# if [ "$NODETYPE" = "compute" ] ; then echo -n "Stopping sysinv-agent: "
# if [ "$NODETYPE" = "compute" ] || [ "$NODETYPE" = "controller" ] ; then if [ -e ${daemon_pidfile} ] ; then
echo -n "Stopping sysinv-agent: " pid=`cat ${daemon_pidfile}`
if [ -e ${daemon_pidfile} ] ; then kill -TERM $pid
pid=`cat ${daemon_pidfile}` rm -f ${daemon_pidfile}
kill -TERM $pid rm -f /var/lock/subsys/${DAEMON_NAME}
rm -f ${daemon_pidfile} echo "OK"
rm -f /var/lock/subsys/${DAEMON_NAME} else
echo "OK" echo "FAIL"
else fi
echo "FAIL"
fi
# fi
;; ;;
restart) restart)

View File

@ -94,8 +94,6 @@ CONF.register_opts(agent_opts, 'agent')
MAXSLEEP = 300 # 5 minutes MAXSLEEP = 300 # 5 minutes
SYSINV_READY_FLAG = os.path.join(tsc.VOLATILE_PATH, ".sysinv_ready") SYSINV_READY_FLAG = os.path.join(tsc.VOLATILE_PATH, ".sysinv_ready")
SYSINV_FIRST_REPORT_FLAG = os.path.join(tsc.VOLATILE_PATH,
".sysinv_agent_report_sent")
CONFIG_APPLIED_FILE = os.path.join(tsc.PLATFORM_CONF_PATH, ".config_applied") CONFIG_APPLIED_FILE = os.path.join(tsc.PLATFORM_CONF_PATH, ".config_applied")
CONFIG_APPLIED_DEFAULT = "install" CONFIG_APPLIED_DEFAULT = "install"
@ -209,6 +207,10 @@ class AgentManager(service.PeriodicService):
initial_reports_required = \ initial_reports_required = \
self.INVENTORY_REPORTS_REQUIRED - self._inventory_reported self.INVENTORY_REPORTS_REQUIRED - self._inventory_reported
initial_reports_required.discard(self.HOST_FILESYSTEMS) initial_reports_required.discard(self.HOST_FILESYSTEMS)
if self._inventory_reported:
utils.touch(constants.SYSINV_REPORTED)
if initial_reports_required: if initial_reports_required:
LOG.info("_report_to_conductor initial_reports_required=%s" % LOG.info("_report_to_conductor initial_reports_required=%s" %
initial_reports_required) initial_reports_required)
@ -218,7 +220,7 @@ class AgentManager(service.PeriodicService):
def _report_to_conductor_iplatform_avail(self): def _report_to_conductor_iplatform_avail(self):
# First report sent to conductor since boot # First report sent to conductor since boot
utils.touch(SYSINV_FIRST_REPORT_FLAG) utils.touch(constants.SYSINV_FIRST_REPORT_FLAG)
# Sysinv-agent ready; used also by the init script. # Sysinv-agent ready; used also by the init script.
utils.touch(SYSINV_READY_FLAG) utils.touch(SYSINV_READY_FLAG)
time.sleep(1) # give time for conductor to process time.sleep(1) # give time for conductor to process
@ -553,7 +555,7 @@ class AgentManager(service.PeriodicService):
# Is this the first time since boot we are reporting to conductor? # Is this the first time since boot we are reporting to conductor?
msg_dict.update({constants.SYSINV_AGENT_FIRST_REPORT: msg_dict.update({constants.SYSINV_AGENT_FIRST_REPORT:
not os.path.exists(SYSINV_FIRST_REPORT_FLAG)}) not os.path.exists(constants.SYSINV_FIRST_REPORT_FLAG)})
try: try:
rpcapi.iplatform_update_by_ihost(context, rpcapi.iplatform_update_by_ihost(context,

View File

@ -1328,6 +1328,11 @@ if tox_work_dir:
else: else:
SYSINV_VOLATILE_PATH = os.path.join(tsc.VOLATILE_PATH, "sysinv") SYSINV_VOLATILE_PATH = os.path.join(tsc.VOLATILE_PATH, "sysinv")
SYSINV_FIRST_REPORT_FLAG = os.path.join(SYSINV_VOLATILE_PATH,
".sysinv_agent_first_report_sent")
SYSINV_REPORTED = os.path.join(SYSINV_VOLATILE_PATH,
".sysinv_reported")
NETWORK_CONFIG_LOCK_FILE = os.path.join( NETWORK_CONFIG_LOCK_FILE = os.path.join(
tsc.VOLATILE_PATH, "apply_network_config.lock") tsc.VOLATILE_PATH, "apply_network_config.lock")

View File

@ -169,6 +169,10 @@ ACTIVE_CONFIG_REBOOT_REQUIRED = os.path.join(
# configuration UUID reboot required flag (bit) # configuration UUID reboot required flag (bit)
CONFIG_REBOOT_REQUIRED = (1 << 127) CONFIG_REBOOT_REQUIRED = (1 << 127)
# Types of runtime configuration applies
CONFIG_APPLY_RUNTIME_MANIFEST = 'config_apply_runtime_manifest'
CONFIG_UPDATE_FILE = 'config_update_file'
LOCK_NAME_UPDATE_CONFIG = 'update_config_' LOCK_NAME_UPDATE_CONFIG = 'update_config_'
LOCK_AUTO_APPLY = 'AutoApplyLock' LOCK_AUTO_APPLY = 'AutoApplyLock'
@ -212,6 +216,9 @@ class ConductorManager(service.PeriodicService):
# this will track the config w/ reboot request to apply # this will track the config w/ reboot request to apply
self._host_reboot_config_uuid = {} self._host_reboot_config_uuid = {}
# track deferred runtime config which need to be applied
self._host_deferred_runtime_config = []
def start(self): def start(self):
self._start() self._start()
# accept API calls and run periodic tasks after # accept API calls and run periodic tasks after
@ -5263,6 +5270,68 @@ class ConductorManager(service.PeriodicService):
'install_state_info': 'install_state_info':
host.install_state_info}) host.install_state_info})
def _ready_to_apply_runtime_config(
self, context, personalities=None, host_uuids=None):
"""Determine whether ready to apply runtime config"""
# Scope to the active controller since do not want to block runtime
# manifest apply due to other hosts here. The config target will
# still track for any missed config (on other hosts in case other
# hosts are unavailable).
if personalities is None:
personalities = []
if host_uuids is None:
host_uuids = []
check_required = False
if constants.CONTROLLER in personalities:
check_required = True
if constants.WORKER in personalities and cutils.is_aio_system(self.dbapi):
check_required = True
if host_uuids and self.host_uuid not in host_uuids:
check_required = False
if not check_required:
return True
if not os.path.exists(constants.SYSINV_REPORTED):
LOG.info("_ready_to_apply_runtime_config path does not exist: %s" %
constants.SYSINV_REPORTED)
return False
return True
def _audit_deferred_runtime_config(self, context):
"""Apply deferred config runtime manifests when ready"""
LOG.debug("_audit_deferred_runtime_config %s" %
self._host_deferred_runtime_config)
if not self._ready_to_apply_runtime_config(context):
return
if self._host_deferred_runtime_config:
# apply the deferred runtime manifests
for config in list(self._host_deferred_runtime_config):
config_type = config.get('config_type')
LOG.info("found _audit_deferred_runtime_config request apply %s" %
config)
if config_type == CONFIG_APPLY_RUNTIME_MANIFEST:
self._config_apply_runtime_manifest(
context,
config['config_uuid'],
config['config_dict'],
force=config.get('force', False))
elif config_type == CONFIG_UPDATE_FILE:
self._config_update_file(
context,
config['config_uuid'],
config['config_dict'])
else:
LOG.error("Removing unsupported deferred config_type %s" %
config_type)
self._host_deferred_runtime_config.remove(config)
@periodic_task.periodic_task(spacing=CONF.conductor.audit_interval) @periodic_task.periodic_task(spacing=CONF.conductor.audit_interval)
def _kubernetes_local_secrets_audit(self, context): def _kubernetes_local_secrets_audit(self, context):
# Audit kubernetes local registry secrets info # Audit kubernetes local registry secrets info
@ -5275,6 +5344,9 @@ class ConductorManager(service.PeriodicService):
# periodically, perform audit of inventory # periodically, perform audit of inventory
LOG.debug("Sysinv Conductor running periodic audit task.") LOG.debug("Sysinv Conductor running periodic audit task.")
# check whether there are deferred runtime manifests to apply
self._audit_deferred_runtime_config(context)
# check whether we may have just become active with target config # check whether we may have just become active with target config
self._controller_config_active_apply(context) self._controller_config_active_apply(context)
@ -9168,6 +9240,21 @@ class ConductorManager(service.PeriodicService):
: action_key: match key (for patch only) : action_key: match key (for patch only)
: } : }
""" """
if not self._ready_to_apply_runtime_config(
context,
config_dict.get('personalities'),
config_dict.get('host_uuids')):
# append to deferred for audit
self._host_deferred_runtime_config.append(
{'config_type': CONFIG_UPDATE_FILE,
'config_uuid': config_uuid,
'config_dict': config_dict,
})
LOG.info("defer update file to _host_deferred_runtime_config %s" %
self._host_deferred_runtime_config)
return
# Ensure hiera data is updated prior to active apply. # Ensure hiera data is updated prior to active apply.
self._config_update_puppet(config_uuid, config_dict) self._config_update_puppet(config_uuid, config_dict)
@ -9225,6 +9312,23 @@ class ConductorManager(service.PeriodicService):
else: else:
LOG.info("applying runtime manifest config_uuid=%s" % config_uuid) LOG.info("applying runtime manifest config_uuid=%s" % config_uuid)
# only apply runtime manifests to active controller if agent ready,
# otherwise will append to the list of outstanding runtime manifests
if not self._ready_to_apply_runtime_config(
context,
config_dict.get('personalities'),
config_dict.get('host_uuids')):
# append to deferred for audit
self._host_deferred_runtime_config.append(
{'config_type': CONFIG_APPLY_RUNTIME_MANIFEST,
'config_uuid': config_uuid,
'config_dict': config_dict,
'force': force,
})
LOG.info("defer apply runtime manifest %s" %
self._host_deferred_runtime_config)
return
# Update hiera data for all hosts prior to runtime apply if host_uuid # Update hiera data for all hosts prior to runtime apply if host_uuid
# is not set. If host_uuids is set only update hiera data for those hosts. # is not set. If host_uuids is set only update hiera data for those hosts.
self._config_update_puppet(config_uuid, self._config_update_puppet(config_uuid,

View File

@ -24,8 +24,10 @@
import mock import mock
import os.path import os.path
import tsconfig.tsconfig as tsc
import uuid import uuid
from sysinv.agent import rpcapi as agent_rpcapi
from sysinv.common import constants from sysinv.common import constants
from sysinv.common import device as dconstants from sysinv.common import device as dconstants
from sysinv.common import exception from sysinv.common import exception
@ -114,8 +116,19 @@ class ManagerTestCase(base.DbTestCase):
self.fail_config_apply_runtime_manifest = False self.fail_config_apply_runtime_manifest = False
def mock_config_apply_runtime_manifest(obj, context, config_uuid, # Mock ready to apply runtime config
config_dict, force=False): self._ready_to_apply_runtime_config = True
self.ready_to_apply_runtime_config_patcher = mock.patch.object(
manager.ConductorManager, '_ready_to_apply_runtime_config')
self.mock_ready_to_apply_runtime_config = \
self.ready_to_apply_runtime_config_patcher.start()
self.mock_ready_to_apply_runtime_config.return_value = \
self._ready_to_apply_runtime_config
self.addCleanup(self.ready_to_apply_runtime_config_patcher.stop)
# Mock agent config_apply_runtime_manifest
def mock_agent_config_apply_runtime_manifest(obj, context, config_uuid,
config_dict):
if not self.fail_config_apply_runtime_manifest: if not self.fail_config_apply_runtime_manifest:
# Pretend the config was applied # Pretend the config was applied
if 'host_uuids' in config_dict: if 'host_uuids' in config_dict:
@ -129,11 +142,40 @@ class ManagerTestCase(base.DbTestCase):
self.dbapi.ihost_update( self.dbapi.ihost_update(
host.uuid, {'config_applied': config_uuid}) host.uuid, {'config_applied': config_uuid})
self.mocked_config_apply_runtime_manifest = mock.patch.object( self.mocked_rpcapi_config_apply_runtime_manifest = mock.patch.object(
manager.ConductorManager, '_config_apply_runtime_manifest', agent_rpcapi.AgentAPI, 'config_apply_runtime_manifest',
mock_config_apply_runtime_manifest) mock_agent_config_apply_runtime_manifest)
self.mocked_config_apply_runtime_manifest.start() self.mocked_rpcapi_config_apply_runtime_manifest.start()
self.addCleanup(self.mocked_config_apply_runtime_manifest.stop) self.addCleanup(self.mocked_rpcapi_config_apply_runtime_manifest.stop)
self.fail_config_apply_runtime_manifest = False
# Mock agent iconfig_update_file
def mock_agent_iconfig_update_file(obj, context, iconfig_uuid, iconfig_dict):
if not self.fail_config_apply_runtime_manifest:
# Simulate the config was applied
if 'host_uuids' in iconfig_dict:
for host_uuid in iconfig_dict['host_uuids']:
self.dbapi.ihost_update(host_uuid,
{'config_applied': iconfig_uuid})
else:
for personality in iconfig_dict['personalities']:
hosts = self.dbapi.ihost_get_by_personality(personality)
for host in hosts:
self.dbapi.ihost_update(
host.uuid, {'config_applied': iconfig_uuid})
self.mocked_rpcapi_iconfig_update_file = mock.patch.object(
agent_rpcapi.AgentAPI, 'iconfig_update_file',
mock_agent_iconfig_update_file)
self.mocked_rpcapi_iconfig_update_file.start()
self.addCleanup(self.mocked_rpcapi_iconfig_update_file.stop)
self.mocked_is_initial_config_complete = mock.patch.object(
cutils, 'is_initial_config_complete')
self.mocked_is_initial_config_complete.start()
self.mocked_is_initial_config_complete.return_value = True
self.addCleanup(self.mocked_is_initial_config_complete.stop)
# Mock subprocess popen # Mock subprocess popen
self.fake_subprocess_popen = FakePopen() self.fake_subprocess_popen = FakePopen()
@ -1333,6 +1375,194 @@ class ManagerTestCase(base.DbTestCase):
self.service.iconfig_update_by_ihost(self.context, ihost['uuid'], imsg_dict) self.service.iconfig_update_by_ihost(self.context, ihost['uuid'], imsg_dict)
self.assertEqual(self.alarm_raised, True) self.assertEqual(self.alarm_raised, True)
def fake_rename(self, old, new):
self.executes.append(('mv', old, new))
@staticmethod
def scope_open(*args, **kwargs):
fake_contents = "lorem ipsum"
fake_file = mock.Mock()
fake_file.read.return_value = fake_contents
fake_context_manager = mock.MagicMock()
fake_context_manager.__enter__.return_value = fake_file
fake_context_manager.__exit__.return_value = None
if not args[0].startswith( # filename
os.path.join(tsc.CONFIG_PATH, 'resolv.conf')):
return open(*args, **kwargs)
else:
return fake_context_manager
def test_deferred_runtime_config_file(self):
# Create controller-0
config_uuid = str(uuid.uuid4())
chost = self._create_test_ihost(
personality=constants.CONTROLLER,
hostname='controller-0',
uuid=str(uuid.uuid4()),
config_status=None,
config_applied=config_uuid,
config_target=config_uuid,
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE,
)
# create test dns nameservers config
utils.create_test_dns(forisystemid=self.system.id,
nameservers='8.8.8.8,8.8.4.4')
cutils.gethostbyname = mock.Mock(return_value='192.168.204.2')
self.executes = []
self.stub_out('os.rename', self.fake_rename)
# These mock for builtin open are needed for py27 and py3 compatibility
mock_trace_caller = mock.MagicMock()
p = mock.patch(
'traceback.format_stack',
mock_trace_caller)
p.start()
p.return_value = ['one', 'two', 'three']
self.addCleanup(p.stop)
mock_open = mock.mock_open()
with mock.patch('six.moves.builtins.open', mock_open):
mock_open.side_effect = self.scope_open
self.mock_ready_to_apply_runtime_config.return_value = False
self.service.update_dns_config(self.context)
chost_updated = self.dbapi.ihost_get(chost.uuid)
# Verify that the config is updated and alarm is raised
self.assertNotEqual(chost_updated.config_applied,
chost_updated.config_target)
self.assertEqual(self.alarm_raised, True)
self.mock_ready_to_apply_runtime_config.return_value = True
self.service._audit_deferred_runtime_config(self.context)
# Simulate agent update
chost_updated = self.dbapi.ihost_get(chost.uuid)
self.service._update_host_config_applied(
self.context, chost_updated, chost_updated.config_applied)
# Verify the config is up to date.
self.assertEqual(chost_updated.config_target,
chost_updated.config_applied)
self.assertEqual(self.alarm_raised, False)
def test_deferred_runtime_config_manifest(self):
# Create controller-0
config_uuid = str(uuid.uuid4())
chost = self._create_test_ihost(
personality=constants.CONTROLLER,
hostname='controller-0',
uuid=str(uuid.uuid4()),
config_status=None,
config_applied=config_uuid,
config_target=config_uuid,
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE,
)
self.mock_ready_to_apply_runtime_config.return_value = False
self.service.update_user_config(self.context)
chost_updated = self.dbapi.ihost_get(chost.uuid)
# Verify that the config is updated and alarm is raised
self.assertNotEqual(chost_updated.config_applied,
chost_updated.config_target)
self.assertEqual(self.alarm_raised, True)
self.mock_ready_to_apply_runtime_config.return_value = True
self.service._audit_deferred_runtime_config(self.context)
# Simulate agent update
chost_updated = self.dbapi.ihost_get(chost.uuid)
self.service._update_host_config_applied(
self.context, chost_updated, chost_updated.config_applied)
# Verify the config is up to date.
self.assertEqual(chost_updated.config_target,
chost_updated.config_applied)
self.assertEqual(self.alarm_raised, False)
def test_deferred_multiple_runtime_config(self):
# Create controller-0
config_uuid = str(uuid.uuid4())
chost = self._create_test_ihost(
personality=constants.CONTROLLER,
hostname='controller-0',
uuid=str(uuid.uuid4()),
config_status=None,
config_applied=config_uuid,
config_target=config_uuid,
invprovision=constants.PROVISIONED,
administrative=constants.ADMIN_UNLOCKED,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_ONLINE,
)
# create test dns nameservers config
utils.create_test_dns(forisystemid=self.system.id,
nameservers='8.8.8.8,8.8.4.4')
cutils.gethostbyname = mock.Mock(return_value='192.168.204.2')
self.executes = []
self.stub_out('os.rename', self.fake_rename)
# These mock for builtin open are needed for py27 and py3 compatibility
mock_trace_caller = mock.MagicMock()
p = mock.patch(
'traceback.format_stack',
mock_trace_caller)
p.start()
p.return_value = ['one', 'two', 'three']
self.addCleanup(p.stop)
mock_open = mock.mock_open()
with mock.patch('six.moves.builtins.open', mock_open):
mock_open.side_effect = self.scope_open
# Attempt to apply a runtime config, which is deferred
self.mock_ready_to_apply_runtime_config.return_value = False
self.service.update_dns_config(self.context)
c1host_updated = self.dbapi.ihost_get(chost.uuid)
# Verify that the config is updated and alarm is raised
self.assertNotEqual(c1host_updated.config_applied,
c1host_updated.config_target)
self.assertEqual(self.alarm_raised, True)
# Attempt another runtime config, which is also deferred
self.service.update_user_config(self.context)
c2host_updated = self.dbapi.ihost_get(chost.uuid)
# Verify that the target is updated and alarm is still raised
self.assertNotEqual(c1host_updated.config_target,
c2host_updated.config_target)
self.assertEqual(c1host_updated.config_applied,
c1host_updated.config_applied)
self.assertNotEqual(c2host_updated.config_applied,
c2host_updated.config_target)
self.assertEqual(self.alarm_raised, True)
# Run the audit for deferred runtime config
self.mock_ready_to_apply_runtime_config.return_value = True
self.service._audit_deferred_runtime_config(self.context)
# Simulate agent update
chost_updated = self.dbapi.ihost_get(chost.uuid)
self.service._update_host_config_applied(
self.context, chost_updated, chost_updated.config_applied)
# Verify the config is up to date.
self.assertEqual(chost_updated.config_target,
chost_updated.config_applied)
self.assertEqual(self.alarm_raised, False)
def _raise_alarm(self, fault): def _raise_alarm(self, fault):
self.alarm_raised = True self.alarm_raised = True