low latency updates host-kernel-modify

command system host-kernel-modify <controller> [standard|lowlatency]

-------------
CODE CHANGES
-------------
* New cgts-client changes for new cli command
* New REST API changes in sysinv api and handler
  validation and update db before calling rpc to conductor
* New sysinv rpc to Conductor from REST API (update low latency)
  and trigger puppet runtime manifests
* New sysinv running kernel update from Agent
* New alarms logic in conductor which depend on alarm definitions
  in starlingx/fault repo 100.120 & 100.121
* Running kernel reports from agent using report-inventory mechanism
* running_kernel field added to ihost db

-------------
UNIT TESTING
-------------
- add UT for cgts-client host_kernel_modify, host_kernel_show
- add UT for rest api changes
- add UT for conductor changes

Task: 47946
Task: 48282
Story: 2010731

Test plan:
PASS - run UT on sysinv and cgts-client

PASS - AIO-SX: iso install and bootstrap.

PASS - AIO-SX: trigger low latency changes and unlock controller.

PASS - AIO-DX: trigger low latency changes and unlock controller.

PASS - AIO-DX: test kernel mismatched alarm raised/cleared 100.120

PASS - AIO-SX: test kernel mismatch alarm raised/clear 100.121
               restart agent after host-kernel-modify

Change-Id: Ie7ecfa4f56eb0ba17dddaae792256d49fdf446ae
Signed-off-by: Kyale, eliud <Eliud.Kyale@windriver.com>
This commit is contained in:
Kyale, eliud 2023-05-05 13:25:15 -04:00 committed by Kyale, Eliud
parent 9140826fdd
commit 45de178799
18 changed files with 1198 additions and 17 deletions

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2014 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
@ -54,6 +54,14 @@ UPDATED_IHOST = copy.deepcopy(IHOST)
NEW_LOC = 'newlocOttawa'
UPDATED_IHOST['location'] = NEW_LOC
KERNEL = {'ihost_uuid': IHOST['uuid'],
'hostname': IHOST['hostname'],
'kernel_provisioned': 'standard',
'kernel_running': 'standard'}
UPDATED_KERNEL = copy.deepcopy(KERNEL)
NEW_KERNEL = 'lowlatency'
UPDATED_KERNEL['kernel_provisioned'] = NEW_KERNEL
fixtures = {
'/v1/ihosts':
@ -89,6 +97,17 @@ fixtures = {
{"ports": [PORT]},
),
},
'/v1/ihosts/%s/kernel' % IHOST['uuid']:
{
'GET': (
{},
KERNEL,
),
'PATCH': (
{},
UPDATED_KERNEL,
),
},
}
@ -142,3 +161,25 @@ class HostManagerTest(testtools.TestCase):
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(ihost.location, NEW_LOC)
def test_host_kernel_modify(self):
patch = {'op': 'replace',
'value': NEW_KERNEL,
'path': '/kernel_provisioned'}
kernel = self.mgr.host_kernel_modify(hostid=IHOST['uuid'],
patch=patch)
expect = [
('PATCH', '/v1/ihosts/%s/kernel' % IHOST['uuid'], {}, patch),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(kernel.kernel_provisioned, NEW_KERNEL)
self.assertEqual(kernel.kernel_running, 'standard')
def test_host_kernel_show(self):
kernel = self.mgr.host_kernel_show(hostid=IHOST['uuid'])
expect = [
('GET', '/v1/ihosts/%s/kernel' % IHOST['uuid'], {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(kernel.kernel_provisioned, 'standard')
self.assertEqual(kernel.kernel_running, 'standard')

View File

@ -1,13 +1,15 @@
#
# Copyright (c) 2019 Wind River Systems, Inc.
# Copyright (c) 2019-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import mock
import yaml
from cgtsclient.tests import test_shell
from cgtsclient.v1.ihost import ihost
from cgtsclient.v1.ihost import ihost_kernel
from cgtsclient.v1.kube_host_upgrade import KubeHostUpgrade
FAKE_KUBE_HOST_UPGRADE = {
@ -61,6 +63,12 @@ FAKE_IHOST_3 = {
'personality': 'storage',
}
FAKE_KERNEL = {
'hostname': 'controller-0',
'kernel_provisioned': 'lowlatency',
'kernel_running': 'lowlatency',
}
class HostTest(test_shell.ShellTest):
@ -210,3 +218,20 @@ class HostTest(test_shell.ShellTest):
results)
self.assertIn(str(FAKE_KUBE_HOST_UPGRADE_2['status']),
results)
@mock.patch('cgtsclient.v1.ihost.ihostManager.host_kernel_show')
def test_host_kernel_show(self, mock_host_kernel_show):
"""Returns a single kernel """
self.make_env()
mock_host_kernel_show.return_value = ihost_kernel(None,
FAKE_KERNEL,
True)
results = self.shell("host-kernel-show --format=yaml "
f"{FAKE_KERNEL['hostname']}")
kernel = yaml.safe_load(results)
self.assertEqual(kernel['hostname'],
FAKE_KERNEL['hostname'])
self.assertEqual(kernel['kernel_provisioned'],
FAKE_KERNEL['kernel_provisioned'])
self.assertEqual(kernel['kernel_running'],
FAKE_KERNEL['kernel_running'])

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2021 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -711,3 +711,46 @@ def do_host_cpu_max_frequency_modify(cc, args):
except exc.HTTPNotFound:
raise exc.CommandError('host not found: %s' % args.hostnameorid)
_print_ihost_show(ihost)
@utils.arg('hostnameorid',
metavar='<hostnameorid>',
help="Name or ID of host")
@utils.arg('kernel',
metavar='<kernel>',
choices=['standard', 'lowlatency'],
help="Kernel image is either standard or lowlatency")
def do_host_kernel_modify(cc, args):
"""
Modify the kernel image to either standard or lowlatency.
"""
attributes = {'kernel_provisioned': args.kernel}
patch = utils.dict_to_patch(attributes)
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
try:
cc.ihost.host_kernel_modify(ihost.uuid, patch)
except exc.HTTPNotFound:
raise exc.CommandError('Host not found: %s' % args.hostnameorid)
def _print_kernel_show(kernel, output_format=None):
fields = ['hostname', 'kernel_provisioned', 'kernel_running']
data_list = [(f, getattr(kernel, f, '')) for f in fields]
data = dict(data_list)
utils.print_dict_with_format(data, wrap=72, output_format=output_format)
@utils.arg('hostnameorid', metavar='<hostname or id>',
help="Name or ID of host")
@utils.arg('--format',
choices=['table', 'yaml', 'value'],
help="specify the output format, defaults to table")
def do_host_kernel_show(cc, args):
"""Show kernel attributes."""
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
try:
kernel = cc.ihost.host_kernel_show(ihost.uuid)
except exc.HTTPNotFound:
raise exc.CommandError('Host not found: %s' % args.hostnameorid)
_print_kernel_show(kernel, args.format)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2021 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -27,6 +27,11 @@ class ihost(base.Resource):
return "<ihost %s>" % self._info
class ihost_kernel(base.Resource):
def __repr__(self):
return "<kernel %s>" % self._info
class ihostManager(base.Manager):
resource_class = ihost
@ -151,6 +156,16 @@ class ihostManager(base.Manager):
resp, body = self.api.json_request('POST', path)
return self.resource_class(self, body)
def host_kernel_modify(self, hostid, patch):
url = self._path(hostid) + "/kernel"
resp, body = self.api.json_request('PATCH', url, body=patch)
return ihost_kernel(self, body)
def host_kernel_show(self, hostid):
url = self._path(hostid) + "/kernel"
resp, body = self.api.json_request('GET', url)
return ihost_kernel(self, body)
def _find_ihost(cc, ihost_id):
if ihost_id.isdigit() or utils.is_uuid_like(ihost_id):

View File

@ -154,6 +154,7 @@ class AgentManager(service.PeriodicService):
LVG = 'lvg'
HOST_FILESYSTEMS = 'host_filesystems'
K8S_DEVICE_PLUGIN = 'k8s_device_plugin'
KERNEL = 'kernel'
# Note that this set must be extended when there are
# additional inventory required for the initial
@ -168,7 +169,8 @@ class AgentManager(service.PeriodicService):
PV,
LVG,
HOST_FILESYSTEMS,
K8S_DEVICE_PLUGIN}
K8S_DEVICE_PLUGIN,
KERNEL}
def __init__(self, host, topic):
self.host = host
@ -725,6 +727,21 @@ class AgentManager(service.PeriodicService):
LOG.exception("Sysinv Agent exception updating pci_device.")
pass
def _get_kernel_running(self):
"""Get the running kernel
Examples:
lowlatency - 5.10.0-6-rt-amd64
standard - 5.10.0-6-amd64
Returns:
str: running kernel either standard or lowlatency
"""
kernel_release = os.uname().release
if '-rt-' in kernel_release:
kernel_running = constants.KERNEL_LOWLATENCY
else:
kernel_running = constants.KERNEL_STANDARD
return kernel_running
def ihost_inv_get_and_report(self, icontext):
"""Collect data for an ihost.
@ -920,6 +937,19 @@ class AgentManager(service.PeriodicService):
LOG.exception("Sysinv Agent exception updating ilvg conductor.")
pass
kernel_running = self._get_kernel_running()
try:
rpcapi.report_kernel_running(icontext,
ihost['uuid'],
kernel_running)
self._inventory_reported.add(self.KERNEL)
except RemoteError as e:
LOG.error("report_kernel_running "
f"RemoteError exc_type={e.exc_type}")
except exception.SysinvException:
LOG.exception("Sysinv Agent exception updating kernel conductor.")
pass
if constants.WORKER in self.subfunctions_list_get():
platform_interfaces = []
# retrieve the mgmt interfaces and associated numa nodes

View File

@ -88,6 +88,7 @@ from sysinv.api.controllers.v1 import vim_api
from sysinv.api.controllers.v1 import patch_api
from sysinv.api.controllers.v1 import ptp_instance
from sysinv.api.controllers.v1 import ptp_interface
from sysinv.api.controllers.v1 import kernel
from sysinv.common import ceph
from sysinv.common import constants
from sysinv.common import device
@ -1145,6 +1146,9 @@ class HostController(rest.RestController):
ptp_interfaces = ptp_interface.PtpInterfaceController(parent="ihosts")
"Expose PTP interfaces as a sub-element of ihosts"
kernel = kernel.KernelController()
"Expose kernel as a sub-element of ihosts"
_custom_actions = {
'detail': ['GET'],
'bulk_add': ['POST'],

View File

@ -0,0 +1,257 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
import jsonpatch
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_log import log
from sysinv._i18n import _
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv.api.controllers.v1 import link
from sysinv.common import utils as cutils
from sysinv import objects
from sysinv.common import constants
from typing import Set
LOCK_NAME = 'KernelController'
LOG = log.getLogger(__name__)
class KernelPatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return ['ihost_uuid',
'hostname',
'kernel_provisioned',
'kernel_running']
class Kernel(base.APIBase):
"""
API representation of the kernel configuration of a ihost.
"""
ihost_uuid = types.uuid
"The UUID of the host of this kernel"
hostname = wtypes.text
"The name of the host of this kernel"
kernel_provisioned = wtypes.text
"The provisined kernel of the ihost"
kernel_running = wtypes.text
"The running kernel of the ihost"
links = [link.Link]
"A list containing a self link and associated kernel links"
def __init__(self, **kwargs):
self.fields = ['ihost_uuid',
'hostname',
'kernel_provisioned',
'kernel_running',
'links']
self.hostname = kwargs.get('hostname')
self.ihost_uuid = kwargs.get('uuid')
self.kernel_provisioned = kwargs.get('kernel_provisioned')
self.kernel_running = kwargs.get('kernel_running')
# if 'kernel_provisioned' key is missing use 'subfunctions' key instead
if self.kernel_provisioned is None:
if constants.LOWLATENCY in kwargs.get(constants.SUBFUNCTIONS):
self.kernel_provisioned = constants.KERNEL_LOWLATENCY
else:
self.kernel_provisioned = constants.KERNEL_STANDARD
@staticmethod
def _create_subfunctions_str(subfunctions_set: Set):
"""Generate the subfunctions string using the set
Preserves expected order
Args:
subfunctions_set (Set): _description_
"""
expected_order = [constants.CONTROLLER,
constants.WORKER,
constants.STORAGE,
constants.LOWLATENCY]
subfunctions_list = []
for i in expected_order:
if i in subfunctions_set:
subfunctions_list.append(i)
subfunctions_set.discard(i)
for i in subfunctions_set:
subfunctions_list.append(i)
return ','.join(subfunctions_list)
def _update_kernel(self, ihost, kernel: str):
""" Update the kernel value
Args:
ihost: rpc ihost object
kernel (str): kernel value
"""
LOG.info(
f"Updating kernel {self.hostname} "
f"[running={self.kernel_running} "
f"provisioned={self.kernel_provisioned}] "
f"to {kernel}]"
)
if self.kernel_provisioned == kernel and self.kernel_running == kernel:
return None
if kernel == constants.KERNEL_LOWLATENCY:
lowlatency = True
else:
lowlatency = False
subfunctions = ihost.get(constants.SUBFUNCTIONS) or ""
subfunctions_set = set(subfunctions.split(','))
if lowlatency is True:
subfunctions_set.add(constants.LOWLATENCY)
else:
subfunctions_set.discard(constants.LOWLATENCY)
updated_subfunctions = Kernel._create_subfunctions_str(subfunctions_set)
updates = \
{
constants.SUBFUNCTIONS: updated_subfunctions
}
ihost.save_changes(pecan.request.context, updates)
pecan.request.rpcapi.kernel_runtime_manifests(pecan.request.context,
self.ihost_uuid)
self.kernel_provisioned = kernel
@classmethod
def convert_with_links(cls, ihost):
ihost_dict = ihost.as_dict()
kernel = Kernel(**ihost_dict)
url_arg = f"{ihost.uuid}/kernel"
kernel.links = [link.Link.make_link('self',
pecan.request.host_url,
'ihosts', url_arg),
link.Link.make_link('bookmark',
pecan.request.host_url,
'ihosts', url_arg,
bookmark=True)
]
return kernel
class KernelController(rest.RestController):
@staticmethod
def _check_host(ihost):
if ihost.administrative != constants.ADMIN_LOCKED:
raise wsme.exc.ClientSideError(_('Host must be locked.'))
if constants.WORKER not in ihost.subfunctions:
raise wsme.exc.ClientSideError(_('Can only modify worker nodes.'))
@staticmethod
def _check_patch(patch):
KERNEL_PATH = '/kernel_provisioned'
if not isinstance(patch, list):
patch = [patch]
utils.validate_patch(patch)
supported_ops = ['replace']
supported_paths = [KERNEL_PATH]
supported_kernels = constants.SUPPORTED_KERNELS
for p in patch:
path = p["path"]
op = p["op"]
value = p["value"]
if path not in supported_paths:
error_msg = f"Path in not supported: {path}"
raise wsme.exc.ClientSideError(_(error_msg))
if op not in supported_ops:
error_msg = f"Operation in not supported: {op}"
raise wsme.exc.ClientSideError(_(error_msg))
if path == KERNEL_PATH and value not in supported_kernels:
error_msg = f"Supported kernels: {supported_kernels}"
raise wsme.exc.ClientSideError(_(error_msg))
# GET ihosts/<uuid>/kernel
@wsme_pecan.wsexpose(Kernel, types.uuid)
def get(self, ihost_uuid):
"""Query information of a specific host kernel
Args:
ihost_uuid (uuid): UUID of the host
Returns:
Kernel: Kernel API object
"""
ihost = objects.host.get_by_uuid(pecan.request.context, ihost_uuid)
kernel = Kernel.convert_with_links(ihost)
return kernel
# PATCH ihosts/<uuid>/kernel
@cutils.synchronized(LOCK_NAME)
@wsme.validate(types.uuid, [KernelPatchType])
@wsme_pecan.wsexpose(Kernel, types.uuid,
body=[KernelPatchType])
def patch(self, ihost_uuid, patch):
"""Modify a host kernel's configuration.
Example:
/v1/ihost/<uuid>/kernel
patch
[
{
"op" : "replace",
"path" : "/kernel",
"value" : "lowlatency"
}
]
Args:
ihost_uuid (uuid): UUID of the host
patch (json): kernel patch
"""
ihost = objects.host.get_by_uuid(pecan.request.context, ihost_uuid)
KernelController._check_host(ihost)
KernelController._check_patch(patch)
patch_obj = jsonpatch.JsonPatch(patch)
kernel_obj = Kernel.convert_with_links(ihost)
kernel_dict = kernel_obj.as_dict()
try:
patched_kernel_dict = jsonpatch.apply_patch(kernel_dict, patch_obj)
except jsonpatch.JsonPatchException as inst:
LOG.exception(inst)
error_msg = f"Update Kernel Error: {inst}"
raise wsme.exc.ClientSideError(_(error_msg))
kernel_value = patched_kernel_dict.get('kernel_provisioned')
kernel_obj._update_kernel(ihost, kernel_value)
return kernel_obj

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2015 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
import re
@ -205,7 +205,7 @@ class JsonPatchType(wtypes.Base):
@staticmethod
def mandatory_attrs():
"""Retruns a list of mandatory attributes.
"""Returns a list of mandatory attributes.
Mandatory attributes can't be removed from the document. This
method should be overwritten by derived class.

View File

@ -122,6 +122,12 @@ PERSONALITIES = [CONTROLLER, STORAGE, WORKER, EDGEWORKER]
SUBFUNCTIONS = 'subfunctions'
LOWLATENCY = 'lowlatency'
# Kernel types
KERNEL_LOWLATENCY = 'lowlatency'
KERNEL_STANDARD = 'standard'
SUPPORTED_KERNELS = [KERNEL_LOWLATENCY, KERNEL_STANDARD]
# CPU functions
PLATFORM_FUNCTION = "Platform"
VSWITCH_FUNCTION = "Vswitch"

View File

@ -128,7 +128,6 @@ from sysinv.helm.lifecycle_hook import LifecycleHookInfo
from sysinv.zmq_rpc.zmq_rpc import ZmqRpcServer
from sysinv.zmq_rpc.zmq_rpc import is_rpc_hybrid_mode_active
MANAGER_TOPIC = 'sysinv.conductor_manager'
LOG = log.getLogger(__name__)
@ -1232,6 +1231,181 @@ class ConductorManager(service.PeriodicService):
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
def kernel_runtime_manifests(self, context, ihost_uuid):
"""Execute kernel runtime manifests
Uses db lowlatency value from ihost.subfunctions
:param context: an admin context
:param ihost_uuid: uuid of host getting kernel config update
"""
try:
host = self.dbapi.ihost_get(ihost_uuid)
except exception.ServerNotFound:
LOG.error(f'Host not found {ihost_uuid}')
return None
personalities = [host['personality']]
host_uuids = [host['uuid']]
config_uuid = self._config_update_hosts(
context=context,
personalities=personalities,
host_uuids=host_uuids,
reboot=True) # TODO: check if reboot is required
config_dict = {
"personalities": personalities,
"host_uuids": host_uuids,
"classes": [
'platform::grub::kernel_image::runtime',
'platform::config::file::subfunctions::lowlatency::runtime'
]
}
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
def report_kernel_running(self, context, ihost_uuid, kernel_running: str):
"""Report from sysinv agent with the running kernel of that host
:param context: admin context
:param ihost_uuid: host uuid
:param kernel_running (str): the running kernel
"""
ihost_uuid = ihost_uuid.strip()
try:
host = self.dbapi.ihost_get(ihost_uuid)
except exception.ServerNotFound:
LOG.info(f'Report from uuid={ihost_uuid} '
f'kernel_running={kernel_running}')
LOG.error(f'Host not found {ihost_uuid}')
return None
hostname = host['hostname']
LOG.info(f'Report from {hostname} running kernel={kernel_running}')
# validate reported running kernel
if kernel_running not in constants.SUPPORTED_KERNELS:
error_msg = (f'{hostname} reported unexpected '
f'kernel_running {kernel_running}')
LOG.error(error_msg)
raise exception.SysinvException(_(error_msg))
# update db with kernel_running update and reload host object
host.save_changes(context, {'kernel_running': kernel_running})
host = self.dbapi.ihost_get(ihost_uuid)
LOG.info(f"DB updated {hostname} "
f"kernel_running={host['kernel_running']}")
# raise and clear running kernel mismatch alarms
self._update_controllers_kernel_mismatch_alarms()
self._update_kernel_provisioned_mismatch_alarm(host)
def _clear_kernel_mismatch_alarm(self, alarm_id: str, hostname: str):
"""Clear alarm that matches the alarm id and hostname
entity_id include host=<hostname>.kernel...
match the hostname
Args:
alarm_id (str): alarm id
hostname (str): hostname
"""
entity_instance_id_partial = f"host={hostname}"
alarms = self.fm_api.get_faults_by_id(alarm_id)
if alarms is None:
return None
for alarm in alarms:
if entity_instance_id_partial in alarm.entity_instance_id:
entity_instance_id = alarm.entity_instance_id
LOG.info(f"Clearing alarm {alarm_id} {entity_instance_id}")
self.fm_api.clear_fault(alarm_id, entity_instance_id)
def _update_controllers_kernel_mismatch_alarms(self):
""" Raise or clear the 100.120 alarm
Controllers running mismtached kernels.
compares the 2 controllers running kernels
"""
alarm_id = fm_constants.FM_ALARM_ID_CONTROLLERS_KERNEL_MISMATCH
pra = _(fm_constants.FM_PRA_CONTROLLERS_KERNEL_MISMATCH)
reason_text = _("Controllers running mismatched kernels.")
controller_kernels = set()
controllers = self.dbapi.ihost_get_by_personality(constants.CONTROLLER)
if len(controllers) != 2:
return None
for host in controllers:
hostname = host['hostname']
kernel_running = host['kernel_running']
# incomplete info, wait for reports from all controller agents
if not kernel_running:
LOG.info(f"{hostname} missing running kernel info")
return None
controller_kernels.add(kernel_running)
if len(controller_kernels) == 1:
# all running kernels match
for host in controllers:
hostname = host['hostname']
self._clear_kernel_mismatch_alarm(alarm_id, hostname)
return None
# detected mismatched running kernels
for host in controllers:
hostname = host['hostname']
kernel_running = host['kernel_running']
entity_instance_id = f"host={hostname}.kernel={kernel_running}"
fault = fm_api.Fault(
alarm_id=alarm_id,
alarm_state=fm_constants.FM_ALARM_STATE_SET,
entity_type_id=fm_constants.FM_ENTITY_TYPE_HOST,
entity_instance_id=entity_instance_id,
severity=fm_constants.FM_ALARM_SEVERITY_MINOR,
reason_text=reason_text,
alarm_type=fm_constants.FM_ALARM_TYPE_4,
probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_65,
proposed_repair_action=pra,
service_affecting=False)
LOG.info(f"Raising alarm {alarm_id} {entity_instance_id}")
self.fm_api.set_fault(fault)
def _update_kernel_provisioned_mismatch_alarm(self, host):
"""Raise or clear the 100.121 alarm
Host not running the provisioned kernel.
Compares the provisioned kernel vs the running kernel of the
specified host
Args:
host: the host the alarm is against
"""
alarm_id = fm_constants.FM_ALARM_ID_PROVISIONED_KERNEL_MISMATCH
pra = _(fm_constants.FM_PRA_PROVISIONED_KERNEL_MISMATCH)
reason_text = _("Host not running the provisioned kernel.")
if constants.LOWLATENCY in host[constants.SUBFUNCTIONS]:
kernel_provisioned = constants.KERNEL_LOWLATENCY
else:
kernel_provisioned = constants.KERNEL_STANDARD
hostname = host['hostname']
kernel_running = host['kernel_running']
entity_instance_id = f"host={hostname}.kernel={kernel_running}"
if kernel_running != kernel_provisioned:
fault = fm_api.Fault(
alarm_id=alarm_id,
alarm_state=fm_constants.FM_ALARM_STATE_SET,
entity_type_id=fm_constants.FM_ENTITY_TYPE_HOST,
entity_instance_id=entity_instance_id,
severity=fm_constants.FM_ALARM_SEVERITY_MAJOR,
reason_text=reason_text,
alarm_type=fm_constants.FM_ALARM_TYPE_4,
probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_65,
proposed_repair_action=pra,
service_affecting=False)
LOG.info(f"Raising alarm {alarm_id} {entity_instance_id}")
self.fm_api.set_fault(fault)
else:
self._clear_kernel_mismatch_alarm(alarm_id, hostname)
def _update_pxe_config(self, host, load=None):
"""Set up the PXE config file for this host so it can run
the installer.
@ -7093,7 +7267,6 @@ class ConductorManager(service.PeriodicService):
self.evaluate_apps_reapply(
context,
trigger={'type': constants.APP_EVALUATE_REAPPLY_TYPE_DETECTED_SWACT})
else:
LOG.info("Initial save active controller {}"
"".format(new_active))
@ -15778,7 +15951,7 @@ class ConductorManager(service.PeriodicService):
"""
LOG.info("Entering device_update_by_host %s %s" %
(host_uuid, fpga_device_dict_array))
host_uuid.strip()
host_uuid = host_uuid.strip()
try:
host = self.dbapi.ihost_get(host_uuid)
except exception.ServerNotFound:

View File

@ -2192,3 +2192,27 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
return self.call(context,
self.make_msg('update_host_max_cpu_mhz_configured',
host=host))
def kernel_runtime_manifests(self, context, ihost_uuid):
"""Synchronously, execute runtime manifests to update kernel
:param context: request context.
:param ihost_uuid: uuid of host getting kernel config update
"""
return self.call(context,
self.make_msg('kernel_runtime_manifests',
ihost_uuid=ihost_uuid))
def report_kernel_running(self, context, ihost_uuid, kernel_running: str):
"""Report the local running kernel to the Conductor
:param context: request context
:param ihost_uuid: uuid of the host sending the update
:param kernel_running (str): the running kernel
"""
return self.cast(context,
self.make_msg('report_kernel_running',
ihost_uuid=ihost_uuid,
kernel_running=kernel_running))

View File

@ -0,0 +1,22 @@
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import Column, MetaData, Table
from sqlalchemy import String
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
migrate_engine.connect()
i_host = Table('i_host', meta, autoload=True)
i_host.create_column(Column('kernel_running', String(64)))
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2022 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -204,6 +204,7 @@ class ihost(Base):
bm_username = Column(String(255))
personality = Column(invPersonalityEnum)
kernel_running = Column(String(64))
subfunctions = Column(String(255))
subfunction_oper = Column(operEnum, default="disabled")
subfunction_avail = Column(availEnum, default="not-installed")

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2022 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -36,7 +36,8 @@ class Host(base.SysinvObject):
dbapi = db_api.get_instance()
fields = {
fields = \
{
'id': int,
'forisystemid': utils.int_or_none,
'isystem_uuid': utils.str_or_none,
@ -47,6 +48,7 @@ class Host(base.SysinvObject):
# 'updated_at': utils.datetime_str_or_none,
'hostname': utils.str_or_none,
'personality': utils.str_or_none,
'kernel_running': utils.str_or_none,
'subfunctions': utils.str_or_none,
'subfunction_oper': utils.str_or_none,
'subfunction_avail': utils.str_or_none,
@ -105,7 +107,7 @@ class Host(base.SysinvObject):
'reboot_needed': utils.bool_or_none,
'max_cpu_mhz_configured': utils.str_or_none,
'max_cpu_mhz_allowed': utils.str_or_none
}
}
_foreign_fields = {
'isystem_uuid': 'system:uuid',

View File

@ -0,0 +1,302 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# -*- encoding: utf-8 -*-
#
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Tests for the API /ihosts/<uuid>/kernel methods.
"""
import mock
from six.moves import http_client
from sysinv.common import constants
from sysinv.tests.api import base
from sysinv.tests.db import base as dbbase
class FakeConductorAPI(object):
def __init__(self):
self.kernel_runtime_manifests = mock.MagicMock()
class FakeException(Exception):
pass
class TestKernel(base.FunctionalTest, dbbase.BaseHostTestCase):
# API_HEADERS are a generic header passed to most API calls
API_HEADERS = {'User-Agent': 'sysinv-test'}
def _setup_configuration(self):
pass
def _setup_context(self):
self.fake_conductor_api = FakeConductorAPI()
p = mock.patch('sysinv.conductor.rpcapiproxy.ConductorAPI')
self.mock_conductor_api = p.start()
self.mock_conductor_api.return_value = self.fake_conductor_api
self.addCleanup(p.stop)
def setUp(self):
super(TestKernel, self).setUp()
self._setup_context()
def _get_path(self, host_uuid):
return f'/ihosts/{host_uuid}/kernel'
def _create_host(self, personality, subfunction=None,
mgmt_mac=None, mgmt_ip=None,
admin=None,
invprovision=constants.PROVISIONED, **kw):
host = self._create_test_host(personality=personality,
subfunction=subfunction,
administrative=(admin or
constants.ADMIN_UNLOCKED),
invprovision=invprovision,
**kw)
return host
class TestPatchKernel(TestKernel):
def setUp(self):
super(TestPatchKernel, self).setUp()
def test_update_worker_kernel_noop(self):
""" Test updating the kernel from
standard -> standard of a locked worker node
"""
worker = self._create_host(constants.WORKER,
admin=constants.ADMIN_LOCKED)
hostid = worker['uuid']
hostname = worker['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_STANDARD,
expect_errors=False)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code)
self.assertEqual(constants.KERNEL_STANDARD,
response.json['kernel_provisioned'])
# Verify DB - lowlatency is not in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is not called
self.fake_conductor_api.kernel_runtime_manifests.assert_not_called()
def test_update_worker_kernel(self):
""" Test updating the kernel from
standard -> lowlatency of a locked worker node
"""
worker = self._create_host(constants.WORKER,
admin=constants.ADMIN_LOCKED)
hostid = worker['uuid']
hostname = worker['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_LOWLATENCY,
expect_errors=False)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code)
self.assertEqual(constants.KERNEL_LOWLATENCY,
response.json['kernel_provisioned'])
# Verify DB - lowlatency is in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is called once
self.fake_conductor_api.kernel_runtime_manifests.assert_called_once()
def test_update_lowlatency_worker_kernel(self):
""" Test updating the kernel from
lowlatency -> standard of a locked worker node
"""
worker = self._create_host(constants.WORKER,
subfunction=constants.LOWLATENCY,
kernel_running=constants.KERNEL_LOWLATENCY,
admin=constants.ADMIN_LOCKED)
hostid = worker['uuid']
hostname = worker['hostname']
# Verify DB - lowlatency is in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_STANDARD,
expect_errors=False)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code)
self.assertEqual(constants.KERNEL_STANDARD, response.json['kernel_provisioned'])
# Verify DB - lowlatency is not in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is called once
self.fake_conductor_api.kernel_runtime_manifests.assert_called_once()
def test_update_unlocked_worker_kernel(self):
""" Test updating the kernel from
standard -> lowlatency of a unlocked worker node
"""
worker = self._create_host(constants.WORKER,
admin=constants.ADMIN_UNLOCKED)
hostid = worker['uuid']
hostname = worker['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_LOWLATENCY,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
self.assertIn('Host must be locked',
response.json['error_message'])
# Verify DB - lowlatency is not in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is not called
self.fake_conductor_api.kernel_runtime_manifests.assert_not_called()
def test_update_storage_kernel(self):
""" Test updating the kernel from
standard -> lowlatency of a locked storage node
"""
storage = self._create_host(constants.STORAGE,
admin=constants.ADMIN_LOCKED)
hostid = storage['uuid']
hostname = storage['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_LOWLATENCY,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
self.assertIn('Can only modify worker nodes',
response.json['error_message'])
# Verify DB - lowlatency is not in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is not called
self.fake_conductor_api.kernel_runtime_manifests.assert_not_called()
def test_update_standard_controller_kernel(self):
""" Test updating the kernel from
standard -> lowlatency of a locked standard controller node
"""
controller = self._create_host(constants.CONTROLLER,
admin=constants.ADMIN_LOCKED)
hostid = controller['uuid']
hostname = controller['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_LOWLATENCY,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
self.assertIn('Can only modify worker nodes',
response.json['error_message'])
# Verify DB - lowlatency is not in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is not called
self.fake_conductor_api.kernel_runtime_manifests.assert_not_called()
def test_update_aio_controller_kernel(self):
""" Test updating the kernel from
standard -> lowlatency of a locked ALL-IN-ONE controller node
"""
controller = self._create_host(constants.CONTROLLER,
subfunction=constants.WORKER,
admin=constants.ADMIN_LOCKED)
hostid = controller['uuid']
hostname = controller['hostname']
# Verify DB - lowlatency is not in subfunctions before update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertNotIn(constants.LOWLATENCY, subfunctions)
url = '%s' % self._get_path(hostid)
response = self.patch_dict_json(url,
headers=self.API_HEADERS,
kernel_provisioned=constants.KERNEL_LOWLATENCY,
expect_errors=False)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code)
self.assertEqual(constants.KERNEL_LOWLATENCY,
response.json['kernel_provisioned'])
# Verify DB - lowlatency is in subfunctions after update
host = self._get_test_host_by_hostname(hostname)
subfunctions = host[constants.SUBFUNCTIONS]
self.assertIn(constants.LOWLATENCY, subfunctions)
# Verify that the method that updates kernel config is called once
self.fake_conductor_api.kernel_runtime_manifests.assert_called_once()

View File

@ -551,6 +551,7 @@ class ManagerTestCase(base.DbTestCase):
self.service._ceph_mon_create = mock.Mock()
self.service._sx_to_dx_post_migration_actions = mock.Mock()
self.alarm_raised = False
self.kernel_alarms = {}
def tearDown(self):
super(ManagerTestCase, self).tearDown()
@ -5088,6 +5089,226 @@ class ManagerTestCase(base.DbTestCase):
expected_ouput = ['intelgpu', 'intelqat', 'intelfpga']
self.assertEqual(actual_output, expected_ouput)
def _kernel_alarms_fix_keys(self, alarm_id, entity_id=None):
"""Create the nested dictionary keys if they are missing
Prevents KeyError exceptions
"""
if alarm_id not in self.kernel_alarms:
self.kernel_alarms[alarm_id] = {}
if entity_id and entity_id not in self.kernel_alarms[alarm_id]:
self.kernel_alarms[alarm_id][entity_id] = None
def _kernel_set_fault(self, fault):
self._kernel_alarms_fix_keys(fault.alarm_id)
self.kernel_alarms[fault.alarm_id][fault.entity_instance_id] = fault
def _kernel_clear_fault(self, alarm_id, entity_id):
self._kernel_alarms_fix_keys(alarm_id, entity_id)
self.kernel_alarms[alarm_id][entity_id] = None
def _kernel_get_faults_by_id(self, alarm_id):
faults = []
self._kernel_alarms_fix_keys(alarm_id)
for fault in self.kernel_alarms[alarm_id].values():
if fault is not None:
faults.append(fault)
if not faults:
faults = None
return faults
def _is_kernel_alarm_raised(self, alarm_id, hostname):
self._kernel_alarms_fix_keys(alarm_id)
entity_id = None
for key in self.kernel_alarms[alarm_id].keys():
entity_id_partial = f"host={hostname}.kernel="
if entity_id_partial in key:
entity_id = key
return self.kernel_alarms[alarm_id][entity_id] is not None
return False
@mock.patch('sysinv.conductor.manager.'
'ConductorManager._config_apply_runtime_manifest')
@mock.patch('sysinv.conductor.manager.'
'ConductorManager._config_update_hosts')
def test_kernel_runtime_manifests(self,
mock_config_update_hosts,
mock_config_apply_runtime_manifest):
self._create_test_ihosts()
ihost_hostname = 'controller-0'
ihost = self.service.get_ihost_by_hostname(self.context,
ihost_hostname)
ihost_uuid = ihost['uuid']
personalities = [ihost['personality']]
host_uuids = [ihost_uuid]
config_dict = {
"personalities": personalities,
"host_uuids": host_uuids,
"classes": [
'platform::grub::kernel_image::runtime',
'platform::config::file::subfunctions::lowlatency::runtime'
]
}
config_uuid = '1234'
mock_config_update_hosts.return_value = config_uuid
self.service.kernel_runtime_manifests(context=self.context,
ihost_uuid=ihost_uuid)
mock_config_update_hosts.assert_called_once()
mock_config_apply_runtime_manifest.assert_called_once_with(mock.ANY,
config_uuid,
config_dict)
@mock.patch('sysinv.conductor.manager.'
'ConductorManager._config_apply_runtime_manifest')
@mock.patch('sysinv.conductor.manager.'
'ConductorManager._config_update_hosts')
def test_kernel_runtime_manifests_no_host(self,
mock_config_update_hosts,
mock_apply_runtime_manifest):
ihost_uuid = str(uuid.uuid4())
self.service.kernel_runtime_manifests(context=self.context,
ihost_uuid=ihost_uuid)
mock_config_update_hosts.assert_not_called()
mock_apply_runtime_manifest.assert_not_called()
def test_host_kernel_mismatch_alarm(self):
"""Test raising and clearing 100.121 alarm id"""
alarm_id = fm_constants.FM_ALARM_ID_PROVISIONED_KERNEL_MISMATCH
self.service.fm_api.set_fault.side_effect = self._kernel_set_fault
self.service.fm_api.clear_fault.side_effect = self._kernel_clear_fault
self.service.fm_api.get_faults_by_id.side_effect = \
self._kernel_get_faults_by_id
# Create controller-0
ihost_hostname = 'controller-0'
config_uuid = str(uuid.uuid4())
self._create_test_ihost(
personality=constants.CONTROLLER,
hostname=ihost_hostname,
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,
mgmt_mac='00:11:22:33:44:55',
mgmt_ip='1.2.3.4')
ihost = self.service.get_ihost_by_hostname(self.context,
ihost_hostname)
# before - no alarm
self.assertFalse(self._is_kernel_alarm_raised(alarm_id, ihost_hostname))
# simulate a running kernel update from controller-0 agent
kernel_running = constants.KERNEL_LOWLATENCY
self.service.report_kernel_running(self.context,
ihost['uuid'],
kernel_running)
# after kernel=lowlatency update - alarm raised
self.assertTrue(self._is_kernel_alarm_raised(alarm_id, ihost_hostname))
# simulate a running kernel update from controller-0 agent
kernel_running = constants.KERNEL_STANDARD
self.service.report_kernel_running(self.context,
ihost['uuid'],
kernel_running)
# after kernel=standard update - alarm cleared
self.assertFalse(self._is_kernel_alarm_raised(alarm_id, ihost_hostname))
def test_controllers_kernel_mismatch_alarms(self):
"""Test raising and clearing 100.120 alarm id"""
alarm_id = fm_constants.FM_ALARM_ID_CONTROLLERS_KERNEL_MISMATCH
self.service.fm_api.set_fault.side_effect = self._kernel_set_fault
self.service.fm_api.clear_fault.side_effect = self._kernel_clear_fault
self.service.fm_api.get_faults_by_id.side_effect = \
self._kernel_get_faults_by_id
# Create controller-0
ihost_hostname0 = 'controller-0'
config_uuid = str(uuid.uuid4())
controller_0_uuid = str(uuid.uuid4())
self._create_test_ihost(
personality=constants.CONTROLLER,
hostname=ihost_hostname0,
uuid=controller_0_uuid,
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,
mgmt_mac='00:11:22:33:44:55',
mgmt_ip='1.2.3.4')
# Create controller-1
ihost_hostname1 = 'controller-1'
config_uuid = str(uuid.uuid4())
controller_1_uuid = str(uuid.uuid4())
self._create_test_ihost(
personality=constants.CONTROLLER,
hostname=ihost_hostname1,
uuid=controller_1_uuid,
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,
mgmt_mac='22:44:33:55:11:66',
mgmt_ip='1.2.3.5')
# before - no alarm
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname0))
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname1))
# simulate a running kernel update from controller-0 agent
kernel_running = constants.KERNEL_STANDARD
self.service.report_kernel_running(self.context,
controller_0_uuid,
kernel_running)
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname0))
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname1))
# simulate a running kernel update from controller-1 agent
kernel_running = constants.KERNEL_LOWLATENCY
self.service.report_kernel_running(self.context,
controller_1_uuid,
kernel_running)
# 2 alarms raised - for each controller
self.assertTrue(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname0))
self.assertTrue(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname1))
# simulate a running kernel update from controller-0 agent
kernel_running = constants.KERNEL_LOWLATENCY
self.service.report_kernel_running(self.context,
controller_0_uuid,
kernel_running)
# 2 alarms cleared - for each controller
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname0))
self.assertFalse(self._is_kernel_alarm_raised(alarm_id,
ihost_hostname1))
@mock.patch('sysinv.conductor.manager.verify_files', lambda x, y: True)
@mock.patch('sysinv.conductor.manager.cutils.ISO', mock.MagicMock())

View File

@ -33,6 +33,7 @@ from sysinv.db import api as dbapi
from sysinv.openstack.common import rpc
from sysinv.tests.db import base
from sysinv.tests.db import utils as dbutils
from sysinv.common import constants
CONF = cfg.CONF
@ -101,3 +102,14 @@ class RPCAPITestCase(base.DbTestCase):
def test_update_dnsmasq_config(self):
self._test_rpcapi('update_dnsmasq_config', 'call')
def test_kernel_runtime_manifests(self):
self._test_rpcapi('kernel_runtime_manifests',
'call',
ihost_uuid=self.fake_ihost['uuid'])
def test_report_kernel_running(self):
self._test_rpcapi('report_kernel_running',
'cast',
ihost_uuid=self.fake_ihost['uuid'],
kernel_running=constants.KERNEL_LOWLATENCY)

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2022 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
"""Sysinv test utilities."""
@ -111,11 +111,13 @@ def post_get_test_ihost(**kw):
del inv['action_state']
del inv['recordtype']
del inv['uuid']
del inv['kernel_running']
return inv
def get_test_ihost(**kw):
inv = {
inv = \
{
'id': kw.get('id', 123),
'forisystemid': kw.get('forisystemid', None),
'peer_id': kw.get('peer_id', None),
@ -139,6 +141,7 @@ def get_test_ihost(**kw):
'action': kw.get('action', "none"),
'task': kw.get('task', None),
'capabilities': kw.get('capabilities', {}),
'kernel_running': kw.get('kernel_running', constants.KERNEL_STANDARD),
'subfunctions': kw.get('subfunctions', "ihostsubfunctions"),
'subfunction_oper': kw.get('subfunction_oper', "disabled"),
'subfunction_avail': kw.get('subfunction_avail', "not-installed"),
@ -168,7 +171,7 @@ def get_test_ihost(**kw):
'clock_synchronization': kw.get('clock_synchronization', constants.NTP),
'max_cpu_mhz_configured': kw.get('max_cpu_mhz_configured', ''),
'max_cpu_mhz_allowed': kw.get('max_cpu_mhz_allowed', '')
}
}
return inv