Add ihost cpu_max_frequency
Adds a new host attribute to limit the host CPU frequency via system command. Test plan: PASS: Bootstrap a system. PASS: Configure and restart sysinv-agent and check configuration persistence. PASS: Configure a value higher than supported by the CPU. (cpupower doesn't allow) PASS: Configure a value below than supported by the CPU. (cpupower doesn't allow) PASS: Upgrade a system. PASS: Ensure cpupower utility is available on debian OS. (linux-cpupower package not present) https://review.opendev.org/c/starlingx/tools/+/839205 adds the package to the debian builds Story: 2009886 Task: 44882 Depends-On: https://review.opendev.org/c/starlingx/stx-puppet/+/835748 Signed-off-by: Iago Estrela <IagoFilipe.EstrelaBarros@windriver.com> Change-Id: If9ed56497791f5acd36a28fad5d3c2c7ba11db74
This commit is contained in:
parent
b8af3ac233
commit
6f47c14cbf
|
@ -35,8 +35,8 @@ def _print_ihost_show(ihost, columns=None, output_format=None):
|
|||
'boot_device', 'rootfs_device', 'install_output', 'console',
|
||||
'tboot', 'vim_progress_status', 'software_load',
|
||||
'install_state', 'install_state_info', 'inv_state',
|
||||
'clock_synchronization',
|
||||
'device_image_update', 'reboot_needed']
|
||||
'clock_synchronization', 'device_image_update',
|
||||
'reboot_needed', 'max_cpu_frequency', 'max_cpu_default']
|
||||
optional_fields = ['vsc_controllers', 'ttys_dcd']
|
||||
if ihost.subfunctions != ihost.personality:
|
||||
fields.append('subfunctions')
|
||||
|
@ -687,3 +687,23 @@ def do_host_device_image_update_abort(cc, args):
|
|||
raise exc.CommandError(
|
||||
'Device image update-abort failed: host %s' % args.hostnameorid)
|
||||
_print_ihost_show(host)
|
||||
|
||||
|
||||
@utils.arg('hostnameorid',
|
||||
metavar='<hostnameorid>',
|
||||
help="Name or ID of host")
|
||||
@utils.arg('max_cpu_frequency',
|
||||
metavar='<max_cpu_frequency>',
|
||||
help="Max CPU frequency MHz")
|
||||
def do_host_cpu_max_frequency_modify(cc, args):
|
||||
"""Modify host cpu max frequency."""
|
||||
|
||||
attributes = ['max_cpu_frequency=%s' % args.max_cpu_frequency]
|
||||
|
||||
patch = utils.args_array_to_patch("replace", attributes)
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
try:
|
||||
ihost = cc.ihost.update(ihost.id, patch)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('host not found: %s' % args.hostnameorid)
|
||||
_print_ihost_show(ihost)
|
||||
|
|
|
@ -288,6 +288,28 @@ class AgentManager(service.PeriodicService):
|
|||
else:
|
||||
LOG.debug("ttys_dcd is not configured")
|
||||
|
||||
def _max_cpu_frequency_configurable(self):
|
||||
fail_result = "System does not support"
|
||||
|
||||
output = utils.execute('/usr/bin/cpupower', 'info', run_as_root=True)
|
||||
|
||||
if isinstance(output, tuple):
|
||||
cpu_info = output[0] or ''
|
||||
if not cpu_info.startswith(fail_result):
|
||||
return constants.CONFIGURABLE
|
||||
return constants.NOT_CONFIGURABLE
|
||||
|
||||
def _max_cpu_frequency_default(self):
|
||||
output = utils.execute(
|
||||
"lscpu | grep 'CPU max MHz' | awk '{ print $4 }' | cut -d ',' -f 1",
|
||||
shell=True)
|
||||
|
||||
if isinstance(output, tuple):
|
||||
default_max = output[0]
|
||||
if default_max:
|
||||
LOG.info("Default CPU max frequency: {}".format(default_max))
|
||||
return int(default_max.split('.')[0])
|
||||
|
||||
@staticmethod
|
||||
def _get_active_device():
|
||||
# the list of currently configured console devices,
|
||||
|
@ -550,8 +572,14 @@ class AgentManager(service.PeriodicService):
|
|||
Action State to reinstalled, and remove the flag.
|
||||
"""
|
||||
if os.path.exists(FIRST_BOOT_FLAG):
|
||||
max_cpu_freq_dict = {
|
||||
constants.IHOST_MAX_CPU_CONFIG:
|
||||
self._max_cpu_frequency_configurable(),
|
||||
constants.IHOST_MAX_CPU_DEFAULT:
|
||||
self._max_cpu_frequency_default()}
|
||||
msg_dict.update({constants.HOST_ACTION_STATE:
|
||||
constants.HAS_REINSTALLED})
|
||||
constants.HAS_REINSTALLED,
|
||||
'max_cpu_dict': max_cpu_freq_dict})
|
||||
|
||||
# Is this the first time since boot we are reporting to conductor?
|
||||
msg_dict.update({constants.SYSINV_AGENT_FIRST_REPORT:
|
||||
|
|
|
@ -550,6 +550,12 @@ class Host(base.APIBase):
|
|||
install_state_info = wtypes.text
|
||||
"Represent install state extra information if there is any"
|
||||
|
||||
max_cpu_frequency = wtypes.text
|
||||
"Represent the CPU max frequency"
|
||||
|
||||
max_cpu_default = wtypes.text
|
||||
"Represent the default CPU max frequency"
|
||||
|
||||
iscsi_initiator_name = wtypes.text
|
||||
"The iscsi initiator name (only used for worker hosts)"
|
||||
|
||||
|
@ -587,7 +593,8 @@ class Host(base.APIBase):
|
|||
'install_state', 'install_state_info',
|
||||
'iscsi_initiator_name',
|
||||
'device_image_update', 'reboot_needed',
|
||||
'inv_state', 'clock_synchronization']
|
||||
'inv_state', 'clock_synchronization',
|
||||
'max_cpu_frequency', 'max_cpu_default']
|
||||
|
||||
fields = minimum_fields if not expand else None
|
||||
uhost = Host.from_rpc_object(rpc_ihost, fields)
|
||||
|
@ -2076,6 +2083,15 @@ class HostController(rest.RestController):
|
|||
'bm_username': None,
|
||||
'bm_password': None})
|
||||
|
||||
if 'max_cpu_frequency' in delta:
|
||||
self._check_max_cpu_frequency(hostupdate)
|
||||
max_cpu_frequency = hostupdate.ihost_patch.get('max_cpu_frequency')
|
||||
ihost_obj['max_cpu_frequency'] = max_cpu_frequency
|
||||
pecan.request.dbapi.ihost_update(
|
||||
ihost_obj['uuid'],
|
||||
{'max_cpu_frequency': max_cpu_frequency})
|
||||
hostupdate.configure_required = True
|
||||
|
||||
if hostupdate.ihost_val_prenotify:
|
||||
# update value in db prior to notifications
|
||||
LOG.info("update ihost_val_prenotify: %s" %
|
||||
|
@ -2128,6 +2144,9 @@ class HostController(rest.RestController):
|
|||
ihost_ret = pecan.request.rpcapi.configure_ihost(
|
||||
pecan.request.context, ihost_obj)
|
||||
|
||||
pecan.request.rpcapi.update_host_max_cpu_frequency(
|
||||
pecan.request.context, ihost_obj)
|
||||
|
||||
pecan.request.dbapi.ihost_update(
|
||||
ihost_obj['uuid'], {'capabilities': ihost_obj['capabilities']})
|
||||
|
||||
|
@ -2866,6 +2885,31 @@ class HostController(rest.RestController):
|
|||
"operation can proceed")
|
||||
% (personality, load.software_version))
|
||||
|
||||
def _check_max_cpu_frequency(self, host):
|
||||
max_cpu_frequency = host.ihost_patch.get('max_cpu_frequency')
|
||||
|
||||
if(constants.WORKER in host.ihost_orig[constants.SUBFUNCTIONS] and
|
||||
host.ihost_orig.get('capabilities').get(constants.IHOST_MAX_CPU_CONFIG) ==
|
||||
constants.CONFIGURABLE and max_cpu_frequency):
|
||||
|
||||
if max_cpu_frequency == constants.IHOST_MAX_CPU_DEFAULT:
|
||||
max_cpu_default = host.ihost_orig['max_cpu_default']
|
||||
host.ihost_patch['max_cpu_frequency'] = max_cpu_default
|
||||
return
|
||||
|
||||
if not max_cpu_frequency.lstrip('-+').isdigit():
|
||||
raise wsme.exc.ClientSideError(
|
||||
_("Max CPU frequency %s must be an integer")
|
||||
% (max_cpu_frequency))
|
||||
|
||||
if not int(max_cpu_frequency) >= 1:
|
||||
raise wsme.exc.ClientSideError(
|
||||
_("Max CPU frequency %s must be greater than 0")
|
||||
% (max_cpu_frequency))
|
||||
else:
|
||||
raise wsme.exc.ClientSideError(
|
||||
_("Host does not support CPU max frequency"))
|
||||
|
||||
def _check_host_load(self, hostname, load):
|
||||
host = pecan.request.dbapi.ihost_get_by_hostname(hostname)
|
||||
host_upgrade = objects.host_upgrade.get_by_host_id(
|
||||
|
|
|
@ -199,6 +199,8 @@ PATCH_DEFAULT_TIMEOUT_IN_SECS = 6
|
|||
|
||||
# ihost field attributes
|
||||
IHOST_STOR_FUNCTION = 'stor_function'
|
||||
IHOST_MAX_CPU_CONFIG = 'max_cpu_config'
|
||||
IHOST_MAX_CPU_DEFAULT = 'max_cpu_default'
|
||||
|
||||
# ihost config_status field values
|
||||
CONFIG_STATUS_OUT_OF_DATE = "Config out-of-date"
|
||||
|
@ -2052,3 +2054,7 @@ OS_RELEASE_FILE = '/etc/os-release'
|
|||
OS_CENTOS = 'centos'
|
||||
OS_DEBIAN = 'debian'
|
||||
SUPPORTED_OS_TYPES = [OS_CENTOS, OS_DEBIAN]
|
||||
|
||||
# Configuration support placeholders
|
||||
CONFIGURABLE = 'configurable'
|
||||
NOT_CONFIGURABLE = 'not-configurable'
|
||||
|
|
|
@ -5332,6 +5332,7 @@ class ConductorManager(service.PeriodicService):
|
|||
ihost_uuid.strip()
|
||||
LOG.info("Updating platform data for host: %s "
|
||||
"with: %s" % (ihost_uuid, imsg_dict))
|
||||
|
||||
try:
|
||||
ihost = self.dbapi.ihost_get(ihost_uuid)
|
||||
except exception.ServerNotFound:
|
||||
|
@ -5339,6 +5340,7 @@ class ConductorManager(service.PeriodicService):
|
|||
return
|
||||
|
||||
availability = imsg_dict.get('availability')
|
||||
max_cpu_dict = imsg_dict.get('max_cpu_dict')
|
||||
|
||||
val = {}
|
||||
|
||||
|
@ -5354,6 +5356,14 @@ class ConductorManager(service.PeriodicService):
|
|||
(ihost.hostname, iscsi_initiator_name))
|
||||
val['iscsi_initiator_name'] = iscsi_initiator_name
|
||||
|
||||
if max_cpu_dict:
|
||||
ihost.capabilities.update({
|
||||
constants.IHOST_MAX_CPU_CONFIG:
|
||||
max_cpu_dict.get(constants.IHOST_MAX_CPU_CONFIG)})
|
||||
ihost.max_cpu_default = max_cpu_dict.get('max_cpu_default')
|
||||
val.update({'capabilities': ihost.capabilities,
|
||||
constants.IHOST_MAX_CPU_DEFAULT: ihost.max_cpu_default})
|
||||
|
||||
if val:
|
||||
ihost = self.dbapi.ihost_update(ihost_uuid, val)
|
||||
|
||||
|
@ -13223,6 +13233,20 @@ class ConductorManager(service.PeriodicService):
|
|||
LOG.error(msg)
|
||||
raise exception.SysinvException(_(msg))
|
||||
|
||||
def update_host_max_cpu_frequency(self, context, host):
|
||||
personalities = [constants.WORKER]
|
||||
|
||||
config_uuid = self._config_update_hosts(context,
|
||||
personalities,
|
||||
[host['uuid']])
|
||||
config_dict = {
|
||||
"personalities": personalities,
|
||||
"classes": ['platform::compute::config::runtime']
|
||||
}
|
||||
self._config_apply_runtime_manifest(context,
|
||||
config_uuid,
|
||||
config_dict)
|
||||
|
||||
def update_admin_ep_certificate(self, context):
|
||||
"""
|
||||
Update admin endpoint certificate
|
||||
|
|
|
@ -2290,3 +2290,14 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
|
|||
certificate_list=certificate_list,
|
||||
issuers_list=issuers_list,
|
||||
secret_list=secret_list))
|
||||
|
||||
def update_host_max_cpu_frequency(self, context, host):
|
||||
"""Synchronously, execute runtime manifests to update host max_cpu_frequency.
|
||||
|
||||
:param context: request context.
|
||||
:param ihost: the host to update the max_cpu_frequency.
|
||||
|
||||
"""
|
||||
return self.call(context,
|
||||
self.make_msg('update_host_max_cpu_frequency',
|
||||
host=host))
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
# Copyright (c) 2022 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
|
||||
host_table = Table('i_host', meta, autoload=True)
|
||||
host_table.create_column(Column('max_cpu_frequency', String(64)))
|
||||
host_table.create_column(Column('max_cpu_default', String(64)))
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
raise NotImplementedError('SysInv database downgrade is unsupported.')
|
|
@ -239,6 +239,8 @@ class ihost(Base):
|
|||
|
||||
device_image_update = Column(String(64))
|
||||
reboot_needed = Column(Boolean, nullable=False, default=False)
|
||||
max_cpu_frequency = Column(String(64)) # in MHz
|
||||
max_cpu_default = Column(String(64)) # in MHz
|
||||
|
||||
forisystemid = Column(Integer,
|
||||
ForeignKey('i_system.id', ondelete='CASCADE'))
|
||||
|
|
|
@ -100,6 +100,8 @@ class Host(base.SysinvObject):
|
|||
'iscsi_initiator_name': utils.str_or_none,
|
||||
'device_image_update': utils.str_or_none,
|
||||
'reboot_needed': utils.bool_or_none,
|
||||
'max_cpu_frequency': utils.str_or_none,
|
||||
'max_cpu_default': utils.str_or_none
|
||||
}
|
||||
|
||||
_foreign_fields = {
|
||||
|
|
|
@ -634,6 +634,8 @@ class PlatformPuppet(base.BasePuppet):
|
|||
reserved_vswitch_cores,
|
||||
'platform::compute::params::reserved_platform_cores':
|
||||
reserved_platform_cores,
|
||||
'platform::compute::params::max_cpu_frequency':
|
||||
host.max_cpu_frequency,
|
||||
'platform::compute::grub::params::n_cpus': n_cpus,
|
||||
'platform::compute::grub::params::cpu_options': cpu_options,
|
||||
})
|
||||
|
|
|
@ -22,6 +22,7 @@ class FakeConductorAPI(object):
|
|||
|
||||
def __init__(self):
|
||||
self.create_host_filesystems = mock.MagicMock()
|
||||
self.update_host_max_cpu_frequency = mock.MagicMock()
|
||||
self.is_virtual_system_config_result = False
|
||||
|
||||
def is_virtual_system_config(self, ctxt):
|
||||
|
|
|
@ -46,6 +46,7 @@ class FakeConductorAPI(object):
|
|||
self.kube_upgrade_kubelet = mock.MagicMock()
|
||||
self.create_barbican_secret = mock.MagicMock()
|
||||
self.mtc_action_apps_semantic_checks = mock.MagicMock()
|
||||
self.update_host_max_cpu_frequency = mock.MagicMock()
|
||||
|
||||
def create_ihost(self, context, values):
|
||||
# Create the host in the DB as the code under test expects this
|
||||
|
@ -3369,3 +3370,67 @@ class RejectMaintananceActionByAppTestCase(TestHost):
|
|||
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertEqual(response.status_code, http_client.OK)
|
||||
|
||||
|
||||
class TestHostModifyCPUMaxFrequency(TestHost):
|
||||
def test_host_max_cpu_frequency_not_configurable(self):
|
||||
worker = self._create_worker(
|
||||
max_cpu_frequency=None,
|
||||
invprovision=constants.PROVISIONED,
|
||||
administrative=constants.ADMIN_UNLOCKED,
|
||||
operational=constants.OPERATIONAL_ENABLED,
|
||||
availability=constants.AVAILABILITY_ONLINE,
|
||||
capabilities={constants.IHOST_MAX_CPU_CONFIG:
|
||||
constants.NOT_CONFIGURABLE})
|
||||
|
||||
self.assertRaises(
|
||||
webtest.app.AppError,
|
||||
self._patch_host,
|
||||
worker.get('hostname'),
|
||||
[{'path': '/max_cpu_frequency',
|
||||
'value': '283487',
|
||||
'op': 'replace'}],
|
||||
'sysinv-test')
|
||||
|
||||
def test_host_max_cpu_frequency_configurable_bad_values(self):
|
||||
worker = self._create_worker(
|
||||
max_cpu_frequency=None,
|
||||
invprovision=constants.PROVISIONED,
|
||||
administrative=constants.ADMIN_UNLOCKED,
|
||||
operational=constants.OPERATIONAL_ENABLED,
|
||||
availability=constants.AVAILABILITY_ONLINE,
|
||||
capabilities={constants.IHOST_MAX_CPU_CONFIG:
|
||||
constants.CONFIGURABLE})
|
||||
|
||||
for bad_value in ['AAAAA', '1A1A1A1', '-1', '0']:
|
||||
self.assertRaises(
|
||||
webtest.app.AppError,
|
||||
self._patch_host,
|
||||
worker.get('hostname'),
|
||||
[{'path': '/max_cpu_frequency',
|
||||
'value': bad_value,
|
||||
'op': 'replace'}],
|
||||
'sysinv-test')
|
||||
|
||||
def test_host_max_cpu_frequency_default(self):
|
||||
max_cpu_default = 1000000
|
||||
|
||||
worker = self._create_worker(
|
||||
max_cpu_frequency=None,
|
||||
max_cpu_default=max_cpu_default,
|
||||
invprovision=constants.PROVISIONED,
|
||||
administrative=constants.ADMIN_UNLOCKED,
|
||||
operational=constants.OPERATIONAL_ENABLED,
|
||||
availability=constants.AVAILABILITY_ONLINE,
|
||||
capabilities={constants.IHOST_MAX_CPU_CONFIG:
|
||||
constants.CONFIGURABLE})
|
||||
|
||||
response = self._patch_host(
|
||||
worker.get('hostname'),
|
||||
[{'path': '/max_cpu_frequency',
|
||||
'value': 'max_cpu_default',
|
||||
'op': 'replace'}],
|
||||
'sysinv-test')
|
||||
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertEqual(response.status_code, http_client.OK)
|
||||
|
|
Loading…
Reference in New Issue