system host-label support stalld labels
update label unit tests modify system host-label cgtsclient add semantic checks to api/label.py for stalld - check valid label values for stalld labels - check cpu_list when enabling stalld service / modifying cpu_functions label update v1/label rest api Unit tests for stalld add configure_stalld() to rpcapi and conductor add stalld_config() to puppet Platform Plugin Document stalld label assignments update stalld semantic check to be case insensitive but case agnostic when saving to the label database Added semantic check for custom stalld label in sysinv api layer Added new label_get_all_like sql api to query all labels that match the specific pattern UT for stalld discontinous cpu ranges and multiple cpu threads Added unit tests for custom stalld parameters E.g system host-label-assign system host-label-assign \ <node> \ starlingx.io/stalld=enabled \ starlingx.io/stalld_cpu_functions=all \ starlingx.io/stalld.boost_period=100000 For parameters that do not have any values the user can provide any string including the empty string as the label value In order to get parameter: stalld --aggressive_mode system host-label-assign \ <node> starlingx.io/stalld.aggressive_mode='' system host-label-remove \ <node> starlingx.io/stalld.aggressive_mode Test plan: Verify documentation - restview <rst file> PASS - cgts-client Unit testing PASS - test_label.py Unit testing PASS - AIO-SX: iso install PASS - Semantic checking tests - worker nodes only AIO & worker - invalid label values - empty cpu list e.g isolated cpus not assigned but set for stalld PASS - enable/disable stalld service using host label verify status of stalld service PASS - change stalld cpu functions label [all | application-isolated| application] verify cpu list being modified by stalld changes PASS - host-lock/host-unlock controller AIO-SX PASS - switch to rt kernel verify stalld PASS - host reboot AIO-SX PASS - host-cpu-modify ( change core assigments) verify stalld labels get updated after host-unlock reboot PASS - AIO-DX iso install host-swact with stalld running verify running after swact PASS: - AIO-SX system with 32 cores and 2 threads per core - cpus 2-31 and 34-63 reserved for Application - stalld_cpu_functions label set for Application - cpus 2-31,34-63 are monitored by stalld PASS - AIO-SX: add custom stalld parameter and verify hieradata verify stalld startup parameters Story: 2011378 Task: 52174 Depends-On: https://review.opendev.org/c/starlingx/stx-puppet/+/949045 Change-Id: Ie718c5927b457da49c693d184a5c16cc73912de3 Signed-off-by: Kyale, Eliud <Eliud.Kyale@windriver.com>
This commit is contained in:
@ -6931,6 +6931,146 @@ Deletes a device label
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
--------------
|
||||
Host labels
|
||||
--------------
|
||||
|
||||
************************
|
||||
List all the host labels
|
||||
************************
|
||||
|
||||
.. rest_method:: GET /v1/ihosts/{ihost_uuid}/labels
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
|
||||
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
|
||||
itemNotFound (404)
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"labels ", "plain", "xsd:list", "The list of host labels."
|
||||
"uuid ", "plain", "csapi:UUID", "The universally unique identifier for this object."
|
||||
"label_key ", "plain", "xsd:string", "The key of the device label."
|
||||
"label_value ", "plain", "xsd:string", "The value of the device label."
|
||||
"host_uuid ", "plain", "csapi:UUID", "The universally unique identifier for the host object."
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"labels": [
|
||||
{
|
||||
"uuid": "71caa220-390f-4403-86a3-8061dba35d06",
|
||||
"label_key": "key1",
|
||||
"label_value": "value1",
|
||||
"host_uuid": "960c759f-fc00-42a1-b67e-a796bf709258"
|
||||
},
|
||||
{
|
||||
"uuid": "4512b32f-943a-48d0-9449-9119205302c2",
|
||||
"label_key": "key5",
|
||||
"label_value": "value5",
|
||||
"host_uuid": "960c759f-fc00-42a1-b67e-a796bf709258"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
******************************
|
||||
Assign host labels to a host
|
||||
******************************
|
||||
|
||||
.. rest_method:: POST /v1/labels/{ihost_uuid}?overwrite={overwrite_parameter}
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
|
||||
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
|
||||
itemNotFound (404)
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"ihost_uuid ", "plain", "csapi:UUID", "The universally unique identifier for the host object."
|
||||
"overwrite_parameter (Optional)", "plain", "xsd:boolean", "Overwrite label if it already exists."
|
||||
"host_labels", "URI", "xsd:list", "List of key-value paired of device labels."
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3"
|
||||
}
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"labels ", "plain", "xsd:list", "The list of host labels."
|
||||
"uuid ", "plain", "csapi:UUID", "The universally unique identifier for this object."
|
||||
"label_key ", "plain", "xsd:string", "The key of the device label."
|
||||
"label_value ", "plain", "xsd:string", "The value of the device label."
|
||||
"host_uuid ", "plain", "csapi:UUID", "The universally unique identifier for the host object."
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"labels": [
|
||||
{
|
||||
"uuid": "bfb37f67-d231-4bf2-836b-677c9cd04dd6",
|
||||
"label_key": "key1",
|
||||
"label_value": "value1",
|
||||
"host_uuid": "960c759f-fc00-42a1-b67e-a796bf709258"
|
||||
},
|
||||
{
|
||||
"uuid": "85acec16-a163-4ed3-9e24-005602979cd6",
|
||||
"label_key": "key2",
|
||||
"label_value": "value2",
|
||||
"host_uuid": "960c759f-fc00-42a1-b67e-a796bf709258"
|
||||
},
|
||||
{
|
||||
"uuid": "45fb9fff-5b32-4088-ad65-acc191fcd8b2",
|
||||
"label_key": "key3",
|
||||
"label_value": "value3",
|
||||
"host_uuid": "960c759f-fc00-42a1-b67e-a796bf709258"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
************************
|
||||
Delete a host label
|
||||
************************
|
||||
|
||||
.. rest_method:: DELETE /v1/labels/{host_label_uuid}
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
204
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"host_label_uuid", "URI", "csapi:UUID", "The unique identifier of the host label."
|
||||
|
||||
------------------
|
||||
Service Parameter
|
||||
------------------
|
||||
|
@ -0,0 +1,216 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import testtools
|
||||
import uuid
|
||||
|
||||
from cgtsclient.tests import utils
|
||||
import cgtsclient.v1.label
|
||||
|
||||
CONTROLLER_0 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'hostname': 'controller-0',
|
||||
'id': '0',
|
||||
}
|
||||
|
||||
LABEL_1 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': CONTROLLER_0['uuid'],
|
||||
'label_key': 'key1',
|
||||
'label_value': 'value1',
|
||||
}
|
||||
|
||||
LABEL_2 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': CONTROLLER_0['uuid'],
|
||||
'label_key': 'key2',
|
||||
'label_value': 'value2',
|
||||
}
|
||||
|
||||
LABEL_3 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': CONTROLLER_0['uuid'],
|
||||
'label_key': 'key3',
|
||||
'label_value': 'value3',
|
||||
}
|
||||
|
||||
LABEL_4 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': CONTROLLER_0['uuid'],
|
||||
'label_key': 'key4',
|
||||
'label_value': 'value4',
|
||||
}
|
||||
|
||||
LABELS = {
|
||||
'labels': [LABEL_1, LABEL_2]
|
||||
}
|
||||
|
||||
NEW_LABELS = {
|
||||
'labels': [LABEL_3, LABEL_4]
|
||||
}
|
||||
|
||||
OVERWRITE_PARAMETER = "overwrite=" + str(True)
|
||||
|
||||
fixtures_default = {
|
||||
'/v1/labels':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABELS,
|
||||
),
|
||||
},
|
||||
f'/v1/labels/{LABEL_1["uuid"]}':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABEL_1,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
)
|
||||
},
|
||||
f'/v1/labels/{LABEL_2["uuid"]}':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABEL_2,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
)
|
||||
},
|
||||
f'/v1/labels/{LABEL_3["uuid"]}':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABEL_3,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
)
|
||||
},
|
||||
f'/v1/labels/{LABEL_4["uuid"]}':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABEL_4,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
)
|
||||
},
|
||||
f'/v1/ihosts/{CONTROLLER_0["uuid"]}/labels':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
LABELS
|
||||
),
|
||||
},
|
||||
f'/v1/labels/{CONTROLLER_0["uuid"]}?{OVERWRITE_PARAMETER}':
|
||||
{
|
||||
'POST': (
|
||||
{},
|
||||
NEW_LABELS,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class KubernetesLabelManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(KubernetesLabelManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fixtures_default)
|
||||
self.mgr = \
|
||||
cgtsclient.v1.label.KubernetesLabelManager(self.api)
|
||||
|
||||
def test_host_label_list(self):
|
||||
host_uuid = CONTROLLER_0['uuid']
|
||||
labels = self.mgr.list(host_uuid)
|
||||
expect = [
|
||||
# (method, url, headers, body )
|
||||
(
|
||||
'GET',
|
||||
f'/v1/ihosts/{host_uuid}/labels',
|
||||
{},
|
||||
None
|
||||
)
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(len(labels), 2)
|
||||
|
||||
def test_host_label_get(self):
|
||||
label_id = LABEL_1['uuid']
|
||||
label = self.mgr.get(label_id)
|
||||
expect = [
|
||||
# (method, url, headers, body )
|
||||
(
|
||||
'GET',
|
||||
f'/v1/labels/{label_id}',
|
||||
{},
|
||||
None,
|
||||
)
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertTrue(
|
||||
isinstance(label,
|
||||
cgtsclient.v1.label.KubernetesLabel))
|
||||
|
||||
self.assertEqual(label.uuid, LABEL_1['uuid'])
|
||||
self.assertEqual(label.host_uuid, LABEL_1['host_uuid'])
|
||||
self.assertEqual(label.label_key, LABEL_1['label_key'])
|
||||
self.assertEqual(label.label_value, LABEL_1['label_value'])
|
||||
|
||||
def test_host_label_assign(self):
|
||||
keyvaluepairs = {
|
||||
LABEL_3['label_key']: LABEL_3['label_value'],
|
||||
LABEL_4['label_key']: LABEL_4['label_value'],
|
||||
}
|
||||
host_uuid = CONTROLLER_0['uuid']
|
||||
assigned_labels = self.mgr.assign(host_uuid,
|
||||
keyvaluepairs,
|
||||
[OVERWRITE_PARAMETER])
|
||||
expect = [
|
||||
# (method, url, headers, body )
|
||||
(
|
||||
'POST',
|
||||
f'/v1/labels/{host_uuid}?{OVERWRITE_PARAMETER}',
|
||||
{},
|
||||
keyvaluepairs,
|
||||
)
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
for label in assigned_labels:
|
||||
self.assertTrue(
|
||||
isinstance(label, cgtsclient.v1.label.KubernetesLabel)
|
||||
)
|
||||
|
||||
expected_labels = [
|
||||
cgtsclient.v1.label.KubernetesLabel(None, LABEL_3, True),
|
||||
cgtsclient.v1.label.KubernetesLabel(None, LABEL_4, True),
|
||||
]
|
||||
|
||||
self.assertEqual(expected_labels, assigned_labels)
|
||||
|
||||
def test_host_label_remove(self):
|
||||
label_uuid = LABEL_4['uuid']
|
||||
label = self.mgr.remove(label_uuid)
|
||||
expect = [
|
||||
# (method, url, headers, body )
|
||||
(
|
||||
'DELETE',
|
||||
f'/v1/labels/{label_uuid}',
|
||||
{},
|
||||
None,
|
||||
)
|
||||
]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertTrue(label is None)
|
@ -0,0 +1,171 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
import yaml
|
||||
|
||||
from cgtsclient import exc
|
||||
from cgtsclient.tests import test_shell
|
||||
from cgtsclient.v1.ihost import ihost
|
||||
from cgtsclient.v1.label import KubernetesLabel
|
||||
|
||||
from testtools import ExpectedException
|
||||
|
||||
|
||||
FAKE_HOST_CONTROLLER_0 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'hostname': 'controller-0',
|
||||
'id': '0',
|
||||
}
|
||||
|
||||
FAKE_HOST_LABEL_1 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': FAKE_HOST_CONTROLLER_0['uuid'],
|
||||
'hostname': FAKE_HOST_CONTROLLER_0['hostname'],
|
||||
'label_key': 'testkey1',
|
||||
'label_value': 'testvalue1',
|
||||
}
|
||||
|
||||
FAKE_HOST_LABEL_2 = {
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'host_uuid': FAKE_HOST_CONTROLLER_0['uuid'],
|
||||
'hostname': FAKE_HOST_CONTROLLER_0['hostname'],
|
||||
'label_key': 'testkey2',
|
||||
'label_value': 'testvalue2',
|
||||
}
|
||||
|
||||
|
||||
class KubernetesLabelTest(test_shell.ShellTest):
|
||||
|
||||
def setUp(self):
|
||||
super(KubernetesLabelTest, self).setUp()
|
||||
self.make_env()
|
||||
|
||||
def tearDown(self):
|
||||
super(KubernetesLabelTest, self).tearDown()
|
||||
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.list')
|
||||
@mock.patch('cgtsclient.v1.ihost._find_ihost')
|
||||
def test_host_label_list(self, mock_find_ihost, mock_label_list):
|
||||
mock_find_ihost.return_value = ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
mock_label_list.return_value = [
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_1, True),
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_2, True),
|
||||
]
|
||||
results_str = self.shell("host-label-list --format yaml controller-0")
|
||||
results_list = yaml.safe_load(results_str)
|
||||
self.assertTrue(isinstance(results_list, list),
|
||||
"host-label-list should return a list")
|
||||
|
||||
returned_keys = [
|
||||
'hostname',
|
||||
'label_key',
|
||||
'label_value',
|
||||
]
|
||||
lbl_1 = {
|
||||
k: v for k, v in FAKE_HOST_LABEL_1.items() if k in returned_keys
|
||||
}
|
||||
lbl_2 = {
|
||||
k: v for k, v in FAKE_HOST_LABEL_2.items() if k in returned_keys
|
||||
}
|
||||
expected_labels = [lbl_1, lbl_2]
|
||||
self.assertListEqual(expected_labels, results_list)
|
||||
|
||||
@mock.patch('cgtsclient.v1.ihost.ihostManager.list')
|
||||
def test_host_label_list_host_not_found(self, mock_ihost_list,):
|
||||
mock_ihost_list.return_value = [
|
||||
ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
]
|
||||
hostname = "controller-1"
|
||||
exception_str = f"host not found: {hostname}"
|
||||
with ExpectedException(exc.CommandError, exception_str):
|
||||
self.shell(f"host-label-list {hostname}")
|
||||
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.get')
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.assign')
|
||||
@mock.patch('cgtsclient.v1.ihost._find_ihost')
|
||||
def test_host_label_assign(self, mock_find_ihost, mock_label_assign, mock_label_get):
|
||||
mock_find_ihost.return_value = ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
mock_label_assign.return_value = [
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_1, True),
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_2, True),
|
||||
]
|
||||
mock_label_get.side_effect = [
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_1, True),
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_2, True),
|
||||
]
|
||||
hostname = "controller-0"
|
||||
parameters = \
|
||||
f" {FAKE_HOST_LABEL_1['label_key']}"\
|
||||
f"={FAKE_HOST_LABEL_1['label_value']}"\
|
||||
f" {FAKE_HOST_LABEL_2['label_key']}"\
|
||||
f"={FAKE_HOST_LABEL_2['label_value']}"
|
||||
|
||||
self.shell(f"host-label-assign {hostname} {parameters}")
|
||||
|
||||
@mock.patch('cgtsclient.v1.ihost.ihostManager.list')
|
||||
def test_host_label_assign_host_not_found(self, mock_ihost_list):
|
||||
mock_ihost_list.return_value = [
|
||||
ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
]
|
||||
hostname = "controller-1"
|
||||
exception_str = f'host not found: {hostname}'
|
||||
with ExpectedException(exc.CommandError, exception_str):
|
||||
self.shell(f"host-label-assign {hostname} newkey=newvalue")
|
||||
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.remove')
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.list')
|
||||
@mock.patch('cgtsclient.v1.ihost._find_ihost')
|
||||
def test_host_label_remove(self,
|
||||
mock_find_ihost,
|
||||
mock_label_list,
|
||||
mock_label_remove):
|
||||
mock_find_ihost.return_value = ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
mock_label_list.return_value = [
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_1, True),
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_2, True),
|
||||
]
|
||||
hostname = 'controller-0'
|
||||
key1 = FAKE_HOST_LABEL_1['label_key']
|
||||
key2 = FAKE_HOST_LABEL_2['label_key']
|
||||
self.shell(f"host-label-remove {hostname} {key1} {key2}")
|
||||
|
||||
label1_uuid = FAKE_HOST_LABEL_1['uuid']
|
||||
label2_uuid = FAKE_HOST_LABEL_2['uuid']
|
||||
mock_label_remove.assert_has_calls(
|
||||
[
|
||||
mock.call(label1_uuid),
|
||||
mock.call(label2_uuid),
|
||||
]
|
||||
)
|
||||
|
||||
@mock.patch('cgtsclient.v1.ihost.ihostManager.list')
|
||||
def test_host_label_remove_host_not_found(self, mock_ihost_list):
|
||||
mock_ihost_list.return_value = [
|
||||
ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
]
|
||||
hostname = "controller-1"
|
||||
exception_str = f'host not found: {hostname}'
|
||||
with ExpectedException(exc.CommandError, exception_str):
|
||||
self.shell(f"host-label-remove {hostname} key1")
|
||||
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.remove')
|
||||
@mock.patch('cgtsclient.v1.label.KubernetesLabelManager.list')
|
||||
@mock.patch('cgtsclient.v1.ihost._find_ihost')
|
||||
def test_host_label_remove_label_not_found(self,
|
||||
mock_find_ihost,
|
||||
mock_label_list,
|
||||
mock_label_remove):
|
||||
mock_find_ihost.return_value = ihost(None, FAKE_HOST_CONTROLLER_0, True)
|
||||
mock_label_list.return_value = [
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_1, True),
|
||||
KubernetesLabel(None, FAKE_HOST_LABEL_2, True),
|
||||
]
|
||||
hostname = "controller-0"
|
||||
self.shell(f"host-label-remove {hostname} unknownkey")
|
||||
|
||||
mock_label_remove.assert_not_called()
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -35,8 +35,28 @@ class KubernetesLabelManager(base.Manager):
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def _assign(self, url, body):
|
||||
_, body = self.api.json_request('POST', url, body=body)
|
||||
|
||||
if not body:
|
||||
return None
|
||||
|
||||
body = body.get('labels', body)
|
||||
if not isinstance(body, list):
|
||||
return self.resource_class(self, body) # noqa pylint: disable=not-callable
|
||||
|
||||
resources = []
|
||||
for item in body:
|
||||
resources.append(self.resource_class(self, item)) # noqa pylint: disable=not-callable
|
||||
|
||||
return resources
|
||||
|
||||
def assign(self, host_uuid, label, parameters=None):
|
||||
return self._create(options.build_url(self._path(host_uuid), q=None, params=parameters), label)
|
||||
url = options.build_url(
|
||||
self._path(host_uuid),
|
||||
q=None,
|
||||
params=parameters)
|
||||
return self._assign(url, label)
|
||||
|
||||
def remove(self, uuid):
|
||||
return self._delete(self._path(uuid))
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -23,15 +23,20 @@ def _print_label_show(obj):
|
||||
@utils.arg('hostnameorid',
|
||||
metavar='<hostname or id>',
|
||||
help="Name or ID of host [REQUIRED]")
|
||||
@utils.arg('--format',
|
||||
choices=['table', 'yaml', 'value'],
|
||||
help="specify the output format, defaults to table")
|
||||
def do_host_label_list(cc, args):
|
||||
"""List kubernetes labels assigned to a host."""
|
||||
"""List labels assigned to a host."""
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
host_label = cc.label.list(ihost.uuid)
|
||||
for i in host_label[:]:
|
||||
setattr(i, 'hostname', ihost.hostname)
|
||||
field_labels = ['hostname', 'label key', 'label value']
|
||||
fields = ['hostname', 'label_key', 'label_value']
|
||||
utils.print_list(host_label, fields, field_labels, sortby=1)
|
||||
utils.print_list(host_label, fields,
|
||||
field_labels, sortby=1,
|
||||
output_format=args.format)
|
||||
|
||||
|
||||
@utils.arg('hostnameorid',
|
||||
@ -52,13 +57,13 @@ def do_host_label_assign(cc, args):
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
parameters = ["overwrite=" + str(args.overwrite)]
|
||||
new_labels = cc.label.assign(ihost.uuid, attributes, parameters)
|
||||
for p in new_labels.labels:
|
||||
uuid = p['uuid']
|
||||
if uuid is not None:
|
||||
|
||||
for lbl in new_labels:
|
||||
if lbl.uuid is not None:
|
||||
try:
|
||||
label_obj = cc.label.get(uuid)
|
||||
label_obj = cc.label.get(lbl.uuid)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Host label not found: %s' % uuid)
|
||||
raise exc.CommandError(f'Host label not found: {lbl.uuid}')
|
||||
_print_label_show(label_obj)
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2018-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2018-2024,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -7,10 +7,12 @@ import json
|
||||
import os
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import re
|
||||
import wsme
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
from wsme import types as wtypes
|
||||
|
||||
from contextlib import suppress
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
from sysinv._i18n import _
|
||||
@ -152,16 +154,20 @@ class LabelController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
def _apply_manifest_after_label_operation(self, uuid, keys):
|
||||
def _apply_manifest_after_label_operation(self, host_uuid, keys):
|
||||
if (common.LABEL_DISABLE_NOHZ_FULL in keys or
|
||||
constants.KUBE_POWER_MANAGER_LABEL in keys):
|
||||
pecan.request.rpcapi.update_grub_config(
|
||||
pecan.request.context, uuid)
|
||||
pecan.request.context, host_uuid)
|
||||
|
||||
if constants.KUBE_POWER_MANAGER_LABEL in keys:
|
||||
pecan.request.rpcapi.configure_power_manager(
|
||||
pecan.request.context)
|
||||
|
||||
if _check_if_stalld_label_modified(keys):
|
||||
pecan.request.rpcapi.configure_stalld(
|
||||
pecan.request.context, host_uuid)
|
||||
|
||||
@wsme_pecan.wsexpose(LabelCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text)
|
||||
def get_all(self, uuid=None, marker=None, limit=None,
|
||||
@ -203,14 +209,14 @@ class LabelController(rest.RestController):
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(LabelCollection, types.uuid, types.boolean,
|
||||
body=types.apidict)
|
||||
def post(self, uuid, overwrite=False, body=None):
|
||||
def post(self, host_uuid, overwrite=False, body=None):
|
||||
"""Assign label(s) to a host.
|
||||
"""
|
||||
if self._from_ihosts:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
LOG.info("patch_data: %s" % body)
|
||||
host = objects.host.get_by_uuid(pecan.request.context, uuid)
|
||||
host = objects.host.get_by_uuid(pecan.request.context, host_uuid)
|
||||
|
||||
# Probably will never happen, but add an extra guard to be absolutely
|
||||
# sure no null values make it into the database.
|
||||
@ -226,13 +232,13 @@ class LabelController(rest.RestController):
|
||||
|
||||
_semantic_check_k8s_plugins_labels(host, body)
|
||||
|
||||
_semantic_check_stalld_labels(host, body)
|
||||
|
||||
existing_labels = {}
|
||||
for label_key in body.keys():
|
||||
label = None
|
||||
try:
|
||||
with suppress(exception.HostLabelNotFoundByKey):
|
||||
label = pecan.request.dbapi.label_query(host.id, label_key)
|
||||
except exception.HostLabelNotFoundByKey:
|
||||
pass
|
||||
if label:
|
||||
if overwrite:
|
||||
existing_labels.update({label_key: label.uuid})
|
||||
@ -268,7 +274,7 @@ class LabelController(rest.RestController):
|
||||
label_uuid = existing_labels.get(key)
|
||||
new_label = pecan.request.dbapi.label_update(label_uuid, {'label_value': value})
|
||||
else:
|
||||
new_label = pecan.request.dbapi.label_create(uuid, values)
|
||||
new_label = pecan.request.dbapi.label_create(host_uuid, values)
|
||||
new_records.append(new_label)
|
||||
except exception.HostLabelAlreadyExists:
|
||||
# We should not be here
|
||||
@ -277,11 +283,11 @@ class LabelController(rest.RestController):
|
||||
try:
|
||||
vim_api.vim_host_update(
|
||||
None,
|
||||
uuid,
|
||||
host_uuid,
|
||||
host.hostname,
|
||||
constants.VIM_DEFAULT_TIMEOUT_IN_SECS)
|
||||
self._apply_manifest_after_label_operation(
|
||||
uuid, body.keys())
|
||||
host_uuid, body.keys())
|
||||
except Exception as e:
|
||||
LOG.warn(_("No response vim_api host:%s e=%s" %
|
||||
(host.hostname, e)))
|
||||
@ -390,6 +396,154 @@ def evaluate_case_agnostic_policy_name(policy_name, policy_value):
|
||||
_case_agnostic_check(policy_value, constants.KUBE_CPU_MEMORY_MANAGER_VALUES, policy_name)
|
||||
|
||||
|
||||
def _check_if_stalld_label_modified(keys: list[str]) -> bool:
|
||||
# check for starlingx.io/stalld substring in any of the labels
|
||||
return any(constants.LABEL_STALLD in label_key for label_key in keys)
|
||||
|
||||
|
||||
def _is_stalld_enabled(host, body: dict) -> bool:
|
||||
|
||||
def _is_enabled_in_db(host) -> tuple[bool, bool]:
|
||||
# default value if missing from db
|
||||
found_in_db, stalld_enabled = False, constants.LABEL_VALUE_STALLD_DISABLED
|
||||
with suppress(exception.HostLabelNotFoundByKey):
|
||||
label_key = constants.LABEL_STALLD
|
||||
label = pecan.request.dbapi.label_query(host.id, label_key)
|
||||
stalld_enabled = label.label_value
|
||||
found_in_db = True
|
||||
return found_in_db, (stalld_enabled == constants.LABEL_VALUE_STALLD_ENABLED)
|
||||
|
||||
def _is_enabled_in_body(body: dict) -> tuple[bool, bool]:
|
||||
label_key = constants.LABEL_STALLD
|
||||
found = label_key in body.keys()
|
||||
stalld_enabled = body.get(label_key, constants.LABEL_VALUE_STALLD_DISABLED)
|
||||
return found, (stalld_enabled == constants.LABEL_VALUE_STALLD_ENABLED)
|
||||
|
||||
found_in_body, stalld_enabled_in_body = _is_enabled_in_body(body)
|
||||
found_in_db, stalld_enabled_in_db = _is_enabled_in_db(host)
|
||||
|
||||
if found_in_body:
|
||||
# user input highest priority
|
||||
return stalld_enabled_in_body
|
||||
elif found_in_db:
|
||||
# check database
|
||||
return stalld_enabled_in_db
|
||||
|
||||
# otherwise use the default value 'disabled'
|
||||
return False
|
||||
|
||||
|
||||
def _semantic_check_stalld_cpu_list_is_not_empty(host, body: dict) -> None:
|
||||
""" Check that stalld will not be started with an empty cpu list.
|
||||
|
||||
Args:
|
||||
host: host
|
||||
body (dict): dictionary of label key values being updated
|
||||
"""
|
||||
keys = body.keys()
|
||||
if not _check_if_stalld_label_modified(keys):
|
||||
return None
|
||||
|
||||
if not _is_stalld_enabled(host, body):
|
||||
return None
|
||||
|
||||
label_key = constants.LABEL_STALLD_CPU_FUNCTIONS
|
||||
supported_values = constants.VALID_STALLD_CPU_FUNCTION_VALUES
|
||||
|
||||
cpu_function = body.get(label_key, None)
|
||||
if not cpu_function:
|
||||
# default value if missing from db
|
||||
cpu_function = constants.LABEL_VALUE_CPU_DEFAULT
|
||||
with suppress(exception.HostLabelNotFoundByKey):
|
||||
label_key = constants.LABEL_STALLD_CPU_FUNCTIONS
|
||||
label = pecan.request.dbapi.label_query(host.id, label_key)
|
||||
cpu_function = label.label_value
|
||||
|
||||
if cpu_function.lower() == constants.LABEL_VALUE_CPU_ALL:
|
||||
# all cpus - assume there will be at least 1 cpu on the node
|
||||
return None
|
||||
|
||||
if cpu_function.lower() == constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED:
|
||||
functions = {
|
||||
constants.ISOLATED_FUNCTION
|
||||
}
|
||||
elif cpu_function.lower() == constants.LABEL_VALUE_CPU_APPLICATION:
|
||||
functions = {
|
||||
constants.APPLICATION_FUNCTION,
|
||||
constants.ISOLATED_FUNCTION
|
||||
}
|
||||
else:
|
||||
# should not get here
|
||||
msg = \
|
||||
f"{host.hostname} invalid value {label_key}={cpu_function}. "\
|
||||
f"Supported values are {supported_values}"
|
||||
LOG.error(msg)
|
||||
raise wsme.exc.ClientSideError(_(msg))
|
||||
|
||||
cpu_count = 0
|
||||
for cpu in pecan.request.dbapi.icpu_get_by_ihost(host.uuid):
|
||||
if cpu.allocated_function in functions or not functions:
|
||||
cpu_count += 1
|
||||
if cpu_count == 0:
|
||||
msg = \
|
||||
f"{host.hostname} stalld cpu list empty,"\
|
||||
f" update cpu core assignments for {functions}"
|
||||
raise wsme.exc.ClientSideError(_(msg))
|
||||
|
||||
|
||||
def _semantic_check_custom_stalld_label_format(host, body: dict) -> None:
|
||||
"""Check that custom stalld labels have the correct format
|
||||
starlingx.io/stalld.<paramater>=<value>
|
||||
"""
|
||||
keys = body.keys()
|
||||
stalld_labels = [label for label in keys if constants.LABEL_STALLD in label]
|
||||
for label_key in stalld_labels:
|
||||
# skip supported labels they are validated elsewhere
|
||||
if label_key in constants.SUPPORTED_STALLD_LABELS:
|
||||
continue
|
||||
if not re.match(constants.REGEX_STALLD_CUSTOM_LABEL, label_key):
|
||||
msg = \
|
||||
f"{host.hostname} '{label_key}' invalid format,"\
|
||||
f" format example 'starlingx.io/stalld.<paramater>'"
|
||||
raise wsme.exc.ClientSideError(_(msg))
|
||||
|
||||
|
||||
def _semantic_check_stalld_labels(host, body: dict) -> None:
|
||||
"""
|
||||
Perform semantic checks to ensure the stalld labels have supported values
|
||||
"""
|
||||
keys = body.keys()
|
||||
stalld_labels = [label for label in keys if constants.LABEL_STALLD in label]
|
||||
if not stalld_labels:
|
||||
return None
|
||||
|
||||
if constants.WORKER not in host.subfunctions:
|
||||
msg = f"{stalld_labels} can only be modified on worker nodes."
|
||||
raise wsme.exc.ClientSideError(_(msg))
|
||||
|
||||
# tuple format ( label_key, supported_values)
|
||||
label_tuples = [
|
||||
# starlingx.io/stalld=[enabled|disabled]
|
||||
(constants.LABEL_STALLD,
|
||||
constants.VALID_STALLD_VALUES),
|
||||
# starlingx.io/stalld_cpu_functions=[all|application|application_isolated]
|
||||
(constants.LABEL_STALLD_CPU_FUNCTIONS,
|
||||
constants.VALID_STALLD_CPU_FUNCTION_VALUES)]
|
||||
|
||||
for (label_key, supported_values) in label_tuples:
|
||||
label_value = body.get(label_key, None)
|
||||
if label_value:
|
||||
label_value = label_value.lower()
|
||||
if label_value and label_value not in supported_values:
|
||||
msg = \
|
||||
f"{host.hostname} invalid value {label_key}={label_value}. "\
|
||||
f"Supported values are {supported_values}"
|
||||
raise wsme.exc.ClientSideError(_(msg))
|
||||
|
||||
_semantic_check_stalld_cpu_list_is_not_empty(host, body)
|
||||
_semantic_check_custom_stalld_label_format(host, body)
|
||||
|
||||
|
||||
def _semantic_check_worker_labels(body):
|
||||
"""
|
||||
Perform semantic checks to ensure the worker labels are valid.
|
||||
|
@ -2809,3 +2809,40 @@ DEPLOYED = "deployed"
|
||||
DEPLOYING = "deploying"
|
||||
REMOVING = "removing"
|
||||
UNAVAILABLE = "unavailable"
|
||||
|
||||
# stalld labels
|
||||
LABEL_STALLD = 'starlingx.io/stalld'
|
||||
LABEL_VALUE_STALLD_ENABLED = 'enabled'
|
||||
LABEL_VALUE_STALLD_DISABLED = 'disabled'
|
||||
# Default is 'disabled'
|
||||
VALID_STALLD_VALUES = [
|
||||
LABEL_VALUE_STALLD_DISABLED,
|
||||
LABEL_VALUE_STALLD_ENABLED
|
||||
]
|
||||
|
||||
# stalld cpu functions values
|
||||
LABEL_STALLD_CPU_FUNCTIONS = 'starlingx.io/stalld_cpu_functions'
|
||||
LABEL_VALUE_CPU_ALL = 'all'
|
||||
LABEL_VALUE_CPU_APPLICATION = 'application'
|
||||
LABEL_VALUE_CPU_APPLICATION_ISOLATED = 'application-isolated'
|
||||
# Default is 'application'
|
||||
LABEL_VALUE_CPU_DEFAULT = LABEL_VALUE_CPU_APPLICATION
|
||||
VALID_STALLD_CPU_FUNCTION_VALUES = [
|
||||
LABEL_VALUE_CPU_APPLICATION,
|
||||
LABEL_VALUE_CPU_APPLICATION_ISOLATED,
|
||||
LABEL_VALUE_CPU_ALL
|
||||
]
|
||||
|
||||
SUPPORTED_STALLD_LABELS = [
|
||||
LABEL_STALLD,
|
||||
LABEL_STALLD_CPU_FUNCTIONS
|
||||
]
|
||||
|
||||
# Custom arguments follow starlingx.io/stalld.xxxx pattern
|
||||
# Examples
|
||||
# 'starlingx.io/stalld.boost_period'
|
||||
# 'starlingx.io/stalld.boost_runtime'
|
||||
# 'starlingx.io/stalld.boost_duration'
|
||||
# 'starlingx.io/stalld.starving_threshold'
|
||||
REGEX_STALLD_CUSTOM_LABEL = r"starlingx\.io\/stalld\.(\w+)"
|
||||
CUSTOM_STALLD_LABEL_STRING = "starlingx.io/stalld."
|
||||
|
@ -20024,6 +20024,40 @@ class ConductorManager(service.PeriodicService):
|
||||
"""
|
||||
return cutils.get_secrets_info()
|
||||
|
||||
def configure_stalld(self, context, host_uuid):
|
||||
""" Configure and restart the stalld daemon
|
||||
|
||||
:param context: admin context
|
||||
:param ihost_uuid: host uuid
|
||||
"""
|
||||
host_uuid = host_uuid.strip()
|
||||
try:
|
||||
host = self.dbapi.ihost_get(host_uuid)
|
||||
except exception.ServerNotFound:
|
||||
LOG.info(f'Host not found {host_uuid}')
|
||||
return None
|
||||
|
||||
hostname = host['hostname']
|
||||
LOG.info(f'Attempting to configure stalld for host={hostname}')
|
||||
|
||||
personalities = [host['personality']]
|
||||
host_uuids = [host['uuid']]
|
||||
config_uuid = self._config_update_hosts(
|
||||
context=context,
|
||||
personalities=personalities,
|
||||
host_uuids=host_uuids,
|
||||
reboot=False)
|
||||
config_dict = {
|
||||
"personalities": personalities,
|
||||
"host_uuids": host_uuids,
|
||||
"classes": [
|
||||
'platform::stalld::runtime'
|
||||
],
|
||||
}
|
||||
self._config_apply_runtime_manifest(context,
|
||||
config_uuid,
|
||||
config_dict)
|
||||
|
||||
|
||||
def device_image_state_sort_key(dev_img_state):
|
||||
if dev_img_state.bitstream_type == dconstants.BITSTREAM_TYPE_ROOT_KEY:
|
||||
|
@ -16,7 +16,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2025 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
"""
|
||||
@ -2309,3 +2309,13 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
|
||||
:param context: request context.
|
||||
"""
|
||||
return self.call(context, self.make_msg('get_all_k8s_certs'))
|
||||
|
||||
def configure_stalld(self, context, host_uuid):
|
||||
"""Synchronously, have the conductor reconfigure stalld
|
||||
for the specified host.
|
||||
|
||||
:param context: request context
|
||||
:param host_uuid: the uuid of the host
|
||||
"""
|
||||
return self.call(context, self.make_msg('configure_stalld',
|
||||
host_uuid=host_uuid))
|
||||
|
@ -8118,6 +8118,14 @@ class Connection(api.Connection):
|
||||
query = query.filter_by(host_id=hostid)
|
||||
return query.all()
|
||||
|
||||
@db_objects.objectify(objects.label)
|
||||
def label_get_all_like(self, pattern, hostid=None):
|
||||
query = model_query(models.Label, read_deleted="no")
|
||||
if hostid:
|
||||
query = query.filter_by(host_id=hostid)
|
||||
query = query.filter(models.Label.label_key.like(pattern))
|
||||
return query.all()
|
||||
|
||||
@db_objects.objectify(objects.label)
|
||||
def label_update(self, uuid, values):
|
||||
with _session_for_write() as session:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2017-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -9,6 +9,7 @@ import ruamel.yaml as yaml
|
||||
import re
|
||||
import numpy as np
|
||||
|
||||
from contextlib import suppress
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import base64
|
||||
from sysinv.common import constants
|
||||
@ -79,6 +80,7 @@ class PlatformPuppet(base.BasePuppet):
|
||||
config.update(self._get_host_lldp_config(host))
|
||||
config.update(self._get_ttys_dcd_config(host))
|
||||
config.update(self._get_host_tuned_devices(host))
|
||||
config.update(self._get_stalld_config(host))
|
||||
return config
|
||||
|
||||
def get_host_config_upgrade(self, host):
|
||||
@ -1156,3 +1158,102 @@ class PlatformPuppet(base.BasePuppet):
|
||||
"platform::tty::params::active_device":
|
||||
host.console.split(',')[0]
|
||||
}
|
||||
|
||||
def _get_stalld_config(self, host):
|
||||
LOG.debug(f"{host.hostname} _get_stalld_config()")
|
||||
|
||||
def _get_label_value(_label_key,
|
||||
_supported_values,
|
||||
_default_value):
|
||||
label_value = _default_value
|
||||
with suppress(exception.HostLabelNotFoundByKey):
|
||||
label = self.dbapi.label_query(host.id, _label_key)
|
||||
label_value = label.label_value.lower()
|
||||
# unsupported value in database
|
||||
if label_value not in _supported_values:
|
||||
LOG.error(f"Unexpected {_label_key}='{label.label_value}' "
|
||||
"using default values of "
|
||||
f"'{_default_value}' instead")
|
||||
label_value = _default_value
|
||||
return label_value
|
||||
|
||||
stalld_enabled = _get_label_value(
|
||||
constants.LABEL_STALLD,
|
||||
constants.VALID_STALLD_VALUES,
|
||||
constants.LABEL_VALUE_STALLD_DISABLED)
|
||||
|
||||
stalld_cpu_functions = _get_label_value(
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS,
|
||||
constants.VALID_STALLD_CPU_FUNCTION_VALUES,
|
||||
constants.LABEL_VALUE_CPU_DEFAULT)
|
||||
|
||||
cpus = []
|
||||
if stalld_cpu_functions == constants.LABEL_VALUE_CPU_ALL:
|
||||
cpus = self._get_host_cpu_list(host,
|
||||
threads=True)
|
||||
elif stalld_cpu_functions == constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED:
|
||||
cpus = self._get_host_cpu_list(host,
|
||||
constants.ISOLATED_FUNCTION,
|
||||
threads=True)
|
||||
else:
|
||||
# constants.LABEL_VALUE_CPU_APPLICATION = 'application'
|
||||
cpus_app = self._get_host_cpu_list(
|
||||
host,
|
||||
constants.APPLICATION_FUNCTION,
|
||||
threads=True)
|
||||
cpus_iso = self._get_host_cpu_list(
|
||||
host,
|
||||
constants.ISOLATED_FUNCTION,
|
||||
threads=True)
|
||||
cpus = cpus_app + cpus_iso
|
||||
|
||||
if not cpus:
|
||||
LOG.error(f"{host.hostname} - {stalld_cpu_functions} "
|
||||
"stalld cpu_list is empty")
|
||||
LOG.error("stalld auto-disabled update cpu core assignment")
|
||||
stalld_enabled = constants.LABEL_VALUE_STALLD_DISABLED
|
||||
|
||||
# generate cpu_list
|
||||
cpus = sorted(cpus, key=lambda c: c.cpu)
|
||||
cpu_set = set([c.cpu for c in cpus])
|
||||
stalld_cpu_list = utils.format_range_set(cpu_set)
|
||||
|
||||
config = {
|
||||
"platform::stalld::params::enable":
|
||||
stalld_enabled == constants.LABEL_VALUE_STALLD_ENABLED,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
}
|
||||
|
||||
# custom stalld labels
|
||||
config.update(self._get_custom_stalld_config(host))
|
||||
|
||||
LOG.debug(f"{host.hostname} updating stalld config\n{config}")
|
||||
return config
|
||||
|
||||
def _get_custom_stalld_config(self, host):
|
||||
config = {}
|
||||
|
||||
stalld_label_list = self.dbapi.label_get_all_like(
|
||||
pattern=f"{constants.CUSTOM_STALLD_LABEL_STRING}%",
|
||||
hostid=host.id
|
||||
)
|
||||
|
||||
for label in stalld_label_list:
|
||||
label_key = label.label_key
|
||||
label_value = label.label_value
|
||||
|
||||
# skip supported stalld label keys which are also case insensitive
|
||||
if label_key.lower() in constants.SUPPORTED_STALLD_LABELS:
|
||||
continue
|
||||
|
||||
custom_parameter = label_key.replace(
|
||||
constants.CUSTOM_STALLD_LABEL_STRING,
|
||||
''
|
||||
)
|
||||
config.update({
|
||||
f"platform::stalld::params::{custom_parameter}":
|
||||
f"'{label_value}'"
|
||||
})
|
||||
|
||||
return config
|
||||
|
@ -1,9 +1,11 @@
|
||||
# Copyright (c) 2019-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2019-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import itertools
|
||||
import mock
|
||||
import random
|
||||
from six.moves import http_client
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
@ -12,6 +14,7 @@ from sysinv.db import api as dbapi
|
||||
from sysinv.tests.api import base
|
||||
from sysinv.api.controllers.v1 import label as policylabel
|
||||
from sysinv.tests.db import utils as dbutils
|
||||
from sysinv.tests.db import base as dbbase
|
||||
import wsme
|
||||
|
||||
|
||||
@ -39,10 +42,9 @@ def mock_get_system_enabled_k8s_plugins_return_none():
|
||||
|
||||
|
||||
class LabelTestCase(base.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(LabelTestCase, self).setUp()
|
||||
self.dbapi = dbapi.get_instance()
|
||||
self.system = dbutils.create_test_isystem()
|
||||
|
||||
def _get_path(self, host=None, params=None):
|
||||
if host:
|
||||
@ -79,8 +81,11 @@ class LabelTestCase(base.FunctionalTest):
|
||||
|
||||
|
||||
class LabelAssignTestCase(LabelTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LabelAssignTestCase, self).setUp()
|
||||
self.dbapi = dbapi.get_instance()
|
||||
self.system = dbutils.create_test_isystem()
|
||||
self.controller = dbutils.create_test_ihost(
|
||||
id='1',
|
||||
uuid=None,
|
||||
@ -321,3 +326,411 @@ class LabelAssignTestCase(LabelTestCase):
|
||||
|
||||
response_data = self.get_host_labels(self.worker.uuid)
|
||||
self.validate_labels(test_plugin_label, response_data)
|
||||
|
||||
|
||||
class FakeConductorAPI(object):
|
||||
|
||||
def __init__(self):
|
||||
self.update_kubernetes_label = mock.MagicMock()
|
||||
self.update_grub_config = mock.MagicMock()
|
||||
self.configure_power_manager = mock.MagicMock()
|
||||
self.configure_stalld = mock.MagicMock()
|
||||
|
||||
|
||||
class StalldLabelTestCase(LabelTestCase, dbbase.BaseHostTestCase):
|
||||
|
||||
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
|
||||
|
||||
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 _create_standard_system(self):
|
||||
self.controller = self._create_host(constants.CONTROLLER)
|
||||
self.worker = self._create_host(constants.WORKER)
|
||||
self._create_test_host_cpus(self.worker,
|
||||
application=8)
|
||||
self.storage = self._create_host(constants.STORAGE)
|
||||
|
||||
def _create_aio_system(self):
|
||||
self.controller = self._create_host(constants.CONTROLLER,
|
||||
subfunction=constants.WORKER)
|
||||
self._create_test_host_cpus(self.controller,
|
||||
platform=2,
|
||||
application=6)
|
||||
|
||||
def setUp(self):
|
||||
super(StalldLabelTestCase, self).setUp()
|
||||
self._setup_context()
|
||||
|
||||
def test_stalld_enable_successful_on_aio_controller(self):
|
||||
self._create_aio_system()
|
||||
host_uuid = self.controller.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
response_data = self.get_host_labels(host_uuid)
|
||||
self.validate_labels(input_data, response_data)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_disable_successful_on_aio_controller(self):
|
||||
self._create_aio_system()
|
||||
host_uuid = self.controller.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_DISABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
response_data = self.get_host_labels(host_uuid)
|
||||
self.validate_labels(input_data, response_data)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_enable_successful_on_worker_node(self):
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
response_data = self.get_host_labels(host_uuid)
|
||||
self.validate_labels(input_data, response_data)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_disable_successful_on_worker_node(self):
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_DISABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
response_data = self.get_host_labels(host_uuid)
|
||||
self.validate_labels(input_data, response_data)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_enable_fails_on_standard_controller(self):
|
||||
self._create_standard_system()
|
||||
host_uuid = self.controller.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
||||
def test_stalld_enable_fails_on_storage_node(self):
|
||||
self._create_standard_system()
|
||||
host_uuid = self.storage.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
||||
def test_stalld_assign_application_cpus_successful(self):
|
||||
"""Labels assigned together
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED,
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_assign_all_cpus_successful(self):
|
||||
"""Labels assigned together
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED,
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_ALL
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_assign_application_isolated_cpus_fails(self):
|
||||
"""Fails because no cpus are assigned to application isolated function
|
||||
on the worker node.
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED,
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
||||
def _generate_case_insensite_permutations(self,
|
||||
label_values: list[str],
|
||||
sample_size=5) -> list:
|
||||
all_permutations = []
|
||||
for label_value in label_values:
|
||||
character_tuples = ((c.lower(), c.upper()) for c in label_value)
|
||||
label_permutations = [
|
||||
''.join(x) for x in itertools.product(*character_tuples)
|
||||
]
|
||||
# randomly sample 'n' permutation because the list could be very long
|
||||
sample_size = min(sample_size, len(label_permutations))
|
||||
all_permutations.extend(random.sample(label_permutations,
|
||||
k=sample_size))
|
||||
return all_permutations
|
||||
|
||||
def test_stalld_assign_case_insensitive(self):
|
||||
"""Labels assigned together
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
label_values = self._generate_case_insensite_permutations([
|
||||
'enabled',
|
||||
'disabled'
|
||||
])
|
||||
parameters = {'overwrite': True}
|
||||
for label_value in label_values:
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: label_value
|
||||
}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
def test_stalld_assign_cpu_function_case_insensitive(self):
|
||||
"""Labels assigned together
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
label_values = self._generate_case_insensite_permutations([
|
||||
'all',
|
||||
'Application',
|
||||
'Application-isolated'
|
||||
])
|
||||
parameters = {'overwrite': True}
|
||||
for label_value in label_values:
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: label_value
|
||||
}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
def test_stalld_enable_fails_if_assigned_app_iso_cpus_prior(self):
|
||||
"""Fails because no cpus are assigned to application isolated function
|
||||
on the worker node.
|
||||
While stalld is disabled we can assign the isolated cpu functions
|
||||
but if we try to enable stalld it will fail
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_DISABLED,
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
# after cpu functions are assigned attempt to enable stalld
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
||||
def test_stalld_assign_cpus_before_enable_successful(self):
|
||||
"""Labels assigned in sequence
|
||||
1. starlingx.io/stalld_cpu_functions=application
|
||||
2. starlingx.io/stalld_cpu_functions=all
|
||||
3. starlingx.io/stalld=enabled
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_ALL
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_assign_different_cpu_functions(self):
|
||||
"""Labels assigned in sequence
|
||||
1. starlingx.io/stalld=enabled
|
||||
2. starlingx.io/stalld_cpu_functions=all
|
||||
3. starlingx.io/stalld_cpu_functions=application
|
||||
4. starlingx.io/stalld_cpu_functions=application-isolated <- fails
|
||||
5. assign application-isolated function to 1 cpu of the worker node
|
||||
6. starlingx.io/stalld_cpu_functions=application-isolated <- success
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
input_data = {
|
||||
constants.LABEL_STALLD: constants.LABEL_VALUE_STALLD_ENABLED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_ALL
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
input_data = {
|
||||
constants.LABEL_STALLD_CPU_FUNCTIONS: constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
self.fake_conductor_api.configure_stalld.reset_mock()
|
||||
|
||||
# Change the last cpu to application-isolated
|
||||
last_cpu = self.dbapi.icpu_get_by_ihost(host_uuid)[-1]
|
||||
values = {"allocated_function": constants.ISOLATED_FUNCTION}
|
||||
self.dbapi.icpu_update(last_cpu.uuid, values)
|
||||
|
||||
# try again
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_assign_custom_label_to_worker_successful(self):
|
||||
"""Custom stalld label on worker node
|
||||
1. starlingx.io/stalld.custom_label=custom_value
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
custom_stalld_label = f"{constants.LABEL_STALLD}.customlabel"
|
||||
input_data = {
|
||||
custom_stalld_label: "custom_value"
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is called
|
||||
self.fake_conductor_api.configure_stalld.assert_called_once()
|
||||
|
||||
def test_stalld_assign_custom_label_to_storage_fails(self):
|
||||
"""Custom stalld label on worker node
|
||||
1. starlingx.io/stalld.custom_label=custom_value
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.storage.uuid
|
||||
custom_stalld_label = f"{constants.LABEL_STALLD}.customlabel"
|
||||
input_data = {
|
||||
custom_stalld_label: "custom_value"
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
||||
def test_stalld_assign_custom_label_invalid_format(self):
|
||||
"""Custom stalld label on worker node
|
||||
1. starlingx.io/stalld_custom_label=custom_value
|
||||
should be a '.' not an '_' character
|
||||
"""
|
||||
self._create_standard_system()
|
||||
host_uuid = self.worker.uuid
|
||||
custom_stalld_label = f"{constants.LABEL_STALLD}_customlabel"
|
||||
input_data = {
|
||||
custom_stalld_label: "custom_value"
|
||||
}
|
||||
parameters = {'overwrite': True}
|
||||
self.assign_labels_failure(host_uuid, input_data, parameters)
|
||||
|
||||
# Verify that the method configure_stalld() is not called
|
||||
self.fake_conductor_api.configure_stalld.assert_not_called()
|
||||
|
@ -5849,6 +5849,51 @@ class ManagerTestCase(base.DbTestCase):
|
||||
# update only 1 time
|
||||
mock_update_cached_app_bundles_set.assert_called_once()
|
||||
|
||||
@mock.patch('sysinv.conductor.manager.'
|
||||
'ConductorManager._config_apply_runtime_manifest')
|
||||
@mock.patch('sysinv.conductor.manager.'
|
||||
'ConductorManager._config_update_hosts')
|
||||
def test_configure_stalld(self,
|
||||
mock_config_update_hosts,
|
||||
mock_config_apply_runtime_manifest):
|
||||
self._create_test_ihosts()
|
||||
hostname = 'compute-0'
|
||||
host = self.service.get_ihost_by_hostname(self.context, hostname)
|
||||
host_uuid = host['uuid']
|
||||
personalities = [host['personality']]
|
||||
host_uuids = [host_uuid]
|
||||
config_dict = {
|
||||
"personalities": personalities,
|
||||
"host_uuids": host_uuids,
|
||||
"classes": [
|
||||
'platform::stalld::runtime'
|
||||
],
|
||||
}
|
||||
config_uuid = '1234'
|
||||
mock_config_update_hosts.return_value = config_uuid
|
||||
self.service.configure_stalld(context=self.context,
|
||||
host_uuid=host_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_configure_stalld_host_not_found(self,
|
||||
mock_config_update_hosts,
|
||||
mock_config_apply_runtime_manifest):
|
||||
host_uuid = str(uuid.uuid4())
|
||||
self.service.configure_stalld(context=self.context,
|
||||
host_uuid=host_uuid)
|
||||
|
||||
mock_config_update_hosts.assert_not_called()
|
||||
mock_config_apply_runtime_manifest.assert_not_called()
|
||||
|
||||
|
||||
class ManagerTestCaseInternal(base.BaseHostTestCase):
|
||||
def setUp(self):
|
||||
|
@ -116,3 +116,8 @@ class RPCAPITestCase(base.DbTestCase):
|
||||
'cast',
|
||||
ihost_uuid=self.fake_ihost['uuid'],
|
||||
kernel_running=constants.KERNEL_LOWLATENCY)
|
||||
|
||||
def test_configure_stalld(self):
|
||||
self._test_rpcapi('configure_stalld',
|
||||
'call',
|
||||
host_uuid=self.fake_ihost['uuid'])
|
||||
|
@ -1,10 +1,11 @@
|
||||
# Copyright (c) 2019-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2019-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import mock
|
||||
from sysinv.tests.db import base as dbbase
|
||||
from sysinv.tests.db import utils as dbutils
|
||||
from sysinv.tests.puppet import base
|
||||
from sysinv.common import constants
|
||||
|
||||
@ -193,3 +194,334 @@ class PlatformTestCaseKubernetesReservedMemory(base.PuppetTestCaseMixin,
|
||||
'platform::kubernetes::params::k8s_reserved_memory': expected_k8s_reserved_memory}
|
||||
)
|
||||
self.assertEqual(k8s_reserved_memory_config, config)
|
||||
|
||||
|
||||
class PlatformTestCaseStalldConfig(base.PuppetTestCaseMixin,
|
||||
dbbase.BaseHostTestCase):
|
||||
|
||||
def _create_test_host_cpus(self, host,
|
||||
platform=0, application=0, isolated=0,
|
||||
threads=1):
|
||||
counts = [platform, application, isolated]
|
||||
functions = [constants.PLATFORM_FUNCTION,
|
||||
constants.APPLICATION_FUNCTION,
|
||||
constants.ISOLATED_FUNCTION]
|
||||
|
||||
nodes = self.dbapi.inode_get_by_ihost(host.id)
|
||||
for node in nodes:
|
||||
cpu = 0
|
||||
for count, function in zip(counts, functions):
|
||||
for _ in range(0, count):
|
||||
for thread in range(0, threads):
|
||||
self.dbapi.icpu_create(host.id,
|
||||
dbutils.get_test_icpu(
|
||||
forinodeid=node.id,
|
||||
cpu=cpu, thread=thread,
|
||||
allocated_function=function))
|
||||
cpu = cpu + 1
|
||||
|
||||
def _configure_cpus(self, platform=0, application=0, isolated=0):
|
||||
self._create_test_host_cpus(self.host,
|
||||
platform=platform,
|
||||
application=application,
|
||||
isolated=isolated)
|
||||
self._create_test_host_addresses(self.host.hostname)
|
||||
|
||||
def _create_test_host_cpus_using_spec(self,
|
||||
host,
|
||||
cpu_assignment_spec: dict):
|
||||
node = self.dbapi.inode_get_by_ihost(host.id)[0]
|
||||
for function, _d in cpu_assignment_spec.items():
|
||||
for thread, cpu_ids in _d.items():
|
||||
for cpu_id in cpu_ids:
|
||||
icpu = dbutils.get_test_icpu(forinodeid=node.id,
|
||||
cpu=cpu_id,
|
||||
thread=thread,
|
||||
allocated_function=function)
|
||||
self.dbapi.icpu_create(host.id, icpu)
|
||||
|
||||
def _configure_cpus_using_assignment_spec(self, cpu_assignment_spec):
|
||||
self._create_test_host_cpus_using_spec(self.host, cpu_assignment_spec)
|
||||
self._create_test_host_addresses(self.host.hostname)
|
||||
|
||||
def _create_host_labels_in_db(self, labels):
|
||||
for label_str in labels:
|
||||
k, v = label_str.split('=')
|
||||
self.dbapi.label_create(self.host.id,
|
||||
{'host_id': self.host.id,
|
||||
'label_key': k,
|
||||
'label_value': v})
|
||||
|
||||
def setUp(self):
|
||||
super(PlatformTestCaseStalldConfig, self).setUp()
|
||||
self.host = self._create_test_host(constants.WORKER)
|
||||
|
||||
def test_get_stalld_config_defaults(self):
|
||||
""" stalld disabled with application default
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
stalld_cpu_list = "2-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
False,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_default(self):
|
||||
""" stalld enabled with cpu default=application
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "2-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_all(self):
|
||||
""" stalld enabled with all cpus
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=all
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_ALL}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "0-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_application(self):
|
||||
""" stalld enabled with application cpus
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=application
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_APPLICATION}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "2-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_isolated(self):
|
||||
""" stalld enabled with application-isolated cpus
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=application-isolated
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "8-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_case_insensitive(self):
|
||||
""" stalld enabled with application-isolated cpus
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}=EnablED",
|
||||
# starlingx.io/stalld_cpu_functions=application-isolated
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}=APPlicaTion-iSOLAted"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "8-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_isolated_not_configured(self):
|
||||
""" stalld enabled with application-isolated cpus
|
||||
but cpu list is empty so stalld will be disabled in config
|
||||
0-1 platform cpus
|
||||
2-9 application cpus
|
||||
n/a isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=8, isolated=0)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=application-isolated
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = ""
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
False,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_bad_data_in_db(self):
|
||||
""" stalld enabled with bad value
|
||||
stalld defaults to disabled
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=%#@#%#@
|
||||
f"{constants.LABEL_STALLD}=%#@#%#@",
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "2-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
False,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_cpus_bad_data_in_db(self):
|
||||
""" stalld enabled with bad cpu function value
|
||||
stalld cpu function defaults to application
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=%#@#%#@
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}=%#@#%#@"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "2-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_application_with_threads(self):
|
||||
""" stalld enabled with application cpus (with 2 threads)
|
||||
and discontinous cpu ids
|
||||
platform: 0-1 ... thread 0
|
||||
10-11 ... thread 1
|
||||
application: 2-7 ... thread 0
|
||||
12-17 ... thread 1
|
||||
isolated: 8-9 ... thread 0
|
||||
18-19 ... thread 1
|
||||
"""
|
||||
_THREAD_0, _THREAD_1 = 0, 1
|
||||
cpu_assignment_spec = {
|
||||
constants.PLATFORM_FUNCTION: {
|
||||
_THREAD_0: [0, 1],
|
||||
_THREAD_1: [10, 11]
|
||||
},
|
||||
constants.APPLICATION_FUNCTION: {
|
||||
_THREAD_0: [2, 3, 4, 5, 6, 7],
|
||||
_THREAD_1: [12, 13, 14, 15, 16, 17]
|
||||
},
|
||||
constants.ISOLATED_FUNCTION: {
|
||||
_THREAD_0: [8, 9],
|
||||
_THREAD_1: [18, 19]
|
||||
}
|
||||
}
|
||||
# Add application CPUs
|
||||
self._configure_cpus_using_assignment_spec(cpu_assignment_spec)
|
||||
labels = [
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_APPLICATION_ISOLATED}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "8-9,18-19"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable": True,
|
||||
"platform::stalld::params::cpu_list": f"'{stalld_cpu_list}'"
|
||||
})
|
||||
|
||||
def test_get_stalld_config_enabled_cpus_all_with_custom_parameters(self):
|
||||
""" stalld enabled with all cpus
|
||||
0-1 platform cpus
|
||||
2-7 application cpus
|
||||
8-9 isolated cpus
|
||||
"""
|
||||
self._configure_cpus(platform=2, application=6, isolated=2)
|
||||
custom_parameter1, value1 = "string_parameter", "string_value1"
|
||||
custom_parameter2, value2 = "numeric_parameter", 1792992
|
||||
|
||||
labels = [
|
||||
# starlingx.io/stalld=enabled
|
||||
f"{constants.LABEL_STALLD}={constants.LABEL_VALUE_STALLD_ENABLED}",
|
||||
# starlingx.io/stalld_cpu_functions=all
|
||||
f"{constants.LABEL_STALLD_CPU_FUNCTIONS}={constants.LABEL_VALUE_CPU_ALL}",
|
||||
f"{constants.CUSTOM_STALLD_LABEL_STRING}{custom_parameter1}={value1}",
|
||||
f"{constants.CUSTOM_STALLD_LABEL_STRING}{custom_parameter2}={value2}"
|
||||
]
|
||||
self._create_host_labels_in_db(labels)
|
||||
stalld_cpu_list = "0-9"
|
||||
self.operator.update_host_config(self.host)
|
||||
self.assertConfigParameters(self.mock_write_config, {
|
||||
"platform::stalld::params::enable":
|
||||
True,
|
||||
"platform::stalld::params::cpu_list":
|
||||
f"'{stalld_cpu_list}'",
|
||||
f"platform::stalld::params::{custom_parameter1}":
|
||||
f"'{value1}'",
|
||||
f"platform::stalld::params::{custom_parameter2}":
|
||||
f"'{value2}'"
|
||||
})
|
||||
|
Reference in New Issue
Block a user