Merge "DEV: Extend sysinv API to support PTP monitoring configuration"

This commit is contained in:
Zuul
2025-06-11 17:35:22 +00:00
committed by Gerrit Code Review
8 changed files with 188 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2023 Wind River Systems, Inc.
# Copyright (c) 2013-2023, 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -113,6 +113,13 @@ SB_SUPPORTED_NETWORKS = {
SB_TYPE_CEPH: [NETWORK_TYPE_MGMT, NETWORK_TYPE_CLUSTER_HOST]
}
# PTP definitions
PTP_INSTANCE_TYPE_MONITORING = "monitoring"
PTP_INSTANCE_TYPE_MONITORING_SUPPORTED_PARAMETERS = [
"devices", "satellite_count", "signal_quality_db",
"cmdline_opts"
]
EXPIRED = "--expired"
SOON_TO_EXPIRY = "--soon_to_expiry"
VALIDITY = "Validity"

View File

@@ -1,11 +1,12 @@
########################################################################
#
# Copyright (c) 2021-2023 Wind River Systems, Inc.
# Copyright (c) 2021-2023, 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
from cgtsclient.common import constants
from cgtsclient.common import utils
from cgtsclient import exc
from cgtsclient.v1 import ihost as ihost_utils
@@ -45,7 +46,10 @@ def do_ptp_instance_list(cc, args):
help="Name of PTP instance [REQUIRED]")
@utils.arg('service',
metavar='<service type>',
choices=['ptp4l', 'phc2sys', 'ts2phc', 'clock', 'synce4l'],
choices=[
'ptp4l', 'phc2sys', 'ts2phc', 'clock', 'synce4l',
constants.PTP_INSTANCE_TYPE_MONITORING
],
help="Service type [REQUIRED]")
def do_ptp_instance_add(cc, args):
"""Add a PTP instance."""
@@ -81,6 +85,20 @@ def _ptp_instance_parameter_op(cc, op, instance, parameters):
if len(parameters) == 0:
raise exc.CommandError('Missing PTP parameter')
ptp_instance = ptp_instance_utils._find_ptp_instance(cc, instance)
# check for supported parameters in case of monitoring type
if (ptp_instance.service == constants.PTP_INSTANCE_TYPE_MONITORING and op == "add"):
for param_keypair in parameters:
if param_keypair.find("=") < 0:
raise exc.CommandError(f"Bad PTP parameter keypair: {param_keypair}")
(param_name, param_value) = param_keypair.split("=", 1)
if param_name not in constants.PTP_INSTANCE_TYPE_MONITORING_SUPPORTED_PARAMETERS:
raise exc.CommandError(
f"Parameter {param_name} is not supported. Supported parameters:"
f"{constants.PTP_INSTANCE_TYPE_MONITORING_SUPPORTED_PARAMETERS}"
)
patch = []
for parameter in parameters:
patch.append({'op': op,

View File

@@ -1,11 +1,12 @@
########################################################################
#
# Copyright (c) 2021 Wind River Systems, Inc.
# Copyright (c) 2021, 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
from cgtsclient.common import constants
from cgtsclient.common import utils
from cgtsclient import exc
from cgtsclient.v1 import ihost as ihost_utils
@@ -60,6 +61,13 @@ def do_ptp_interface_add(cc, args):
# Check the PTP instance exists
ptp_instance = ptp_instance_utils._find_ptp_instance(
cc, args.ptpinstancenameorid)
# Do not allow for monitoring service type.
if ptp_instance.service == constants.PTP_INSTANCE_TYPE_MONITORING:
raise exc.CommandError(
"PTP instance of monitoring type does not support interface"
)
data.update({'ptp_instance_uuid': ptp_instance.uuid})
ptp_interface = cc.ptp_interface.create(**data)

View File

@@ -2036,6 +2036,15 @@ class HostController(rest.RestController):
constants.NETWORK_TYPE_MGMT, True)
return address.address
def _exists_monitoring_instance_on_host(self, host_uuid):
ptp_instances = pecan.request.dbapi.ptp_instances_get_list(host_uuid)
for instance in ptp_instances:
if instance["service"] == constants.PTP_INSTANCE_TYPE_MONITORING:
return True
return False
def _patch(self, uuid, patch):
log_start = cutils.timestamped("ihost_patch_start")
@@ -2048,12 +2057,22 @@ class HostController(rest.RestController):
ptp_instance_id = p.get('value')
try:
# Check PTP instance exists
pecan.request.dbapi.ptp_instance_get(ptp_instance_id)
ptp_instance = pecan.request.dbapi.ptp_instance_get(ptp_instance_id)
except exception.PtpInstanceNotFound:
raise wsme.exc.ClientSideError(_("No PTP instance object"))
values = {'host_id': ihost_obj.id,
'ptp_instance_id': ptp_instance_id}
if p.get('op') == constants.PTP_PATCH_OPERATION_ADD:
# Check constraint: single monitoring ptp instance on host
if ptp_instance[
"service"
] == constants.PTP_INSTANCE_TYPE_MONITORING and self._exists_monitoring_instance_on_host(
uuid
):
raise wsme.exc.ClientSideError(
_("Monitoring ptp instance already exists on host")
)
pecan.request.dbapi.ptp_instance_assign(values)
else:
pecan.request.dbapi.ptp_instance_remove(values)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2023 Wind River Systems, Inc.
# Copyright (c) 2021-2023, 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -74,7 +74,8 @@ class PtpInstance(base.APIBase):
constants.PTP_INSTANCE_TYPE_PHC2SYS,
constants.PTP_INSTANCE_TYPE_TS2PHC,
constants.PTP_INSTANCE_TYPE_CLOCK,
constants.PTP_INSTANCE_TYPE_SYNCE4L)
constants.PTP_INSTANCE_TYPE_SYNCE4L,
constants.PTP_INSTANCE_TYPE_MONITORING)
"Type of service of the PTP instance"
hostnames = types.MultiType([list])

View File

@@ -2403,6 +2403,10 @@ PTP_SYNCE_EEC_INVALID_VALUE = '0'
PTP_SYNCE_TX_HEARTBEAT_MSEC = '1000'
PTP_SYNCE_RX_HEARTBEAT_MSEC = '500'
# PTP instance monitoring default parameters
PTP_MONITORING_SATELLITE_COUNT = "5"
PTP_MONITORING_SIGNAL_QUALITY_DB_VALUE = "30"
# PTP pmc values
PTP_PMC_CLOCK_CLASS = '248'
PTP_PMC_CLOCK_ACCURACY = '0xfe'
@@ -2422,6 +2426,7 @@ PTP_INSTANCE_TYPE_PHC2SYS = 'phc2sys'
PTP_INSTANCE_TYPE_TS2PHC = 'ts2phc'
PTP_INSTANCE_TYPE_CLOCK = 'clock'
PTP_INSTANCE_TYPE_SYNCE4L = 'synce4l'
PTP_INSTANCE_TYPE_MONITORING = "monitoring"
# PTP instances created during migration
PTP_INSTANCE_LEGACY_PTP4L = 'ptp4l-legacy'

View File

@@ -323,8 +323,38 @@ class NetworkingPuppet(base.BasePuppet):
return config
def _set_ptp_instance_global_parameters(self, ptp_instances, ptp_parameters_instance):
def _set_ptp_instance_monitoring_global_parameters(
self, instance, ptp_parameters_instance
):
default_global_parameters = {
"satellite_count": constants.PTP_MONITORING_SATELLITE_COUNT,
"signal_quality_db": constants.PTP_MONITORING_SIGNAL_QUALITY_DB_VALUE,
}
default_cmdline_opts = ""
# Add default global parameters the instance
instance["global_parameters"] = default_global_parameters
instance["cmdline_opts"] = default_cmdline_opts
for global_param in ptp_parameters_instance:
# Add the supplied instance parameters to global_parameters
if instance["uuid"] in global_param["owners"]:
instance["global_parameters"][global_param["name"]] = global_param[
"value"
]
if "cmdline_opts" in instance["global_parameters"]:
cmdline = instance["global_parameters"].pop("cmdline_opts")
quotes = {"'", "\\'", '"', '\\"'}
for quote in quotes:
cmdline = cmdline.strip(quote)
instance["cmdline_opts"] = cmdline
allowed_instance_fields = ["global_parameters", "cmdline_opts"]
monitoring_config = {r: instance[r] for r in allowed_instance_fields}
return monitoring_config
def _set_ptp_instance_global_parameters(self, ptp_instances, ptp_parameters_instance):
default_global_parameters = {
# Default ptp4l parameters were determined during the original integration of PTP.
# These defaults maintain the same PTP behaviour as single instance implementation.
@@ -713,12 +743,20 @@ class NetworkingPuppet(base.BasePuppet):
nic_clock_config = {}
nic_clock_enabled = False
ptp_instance_configs = []
monitoring_instance_configs = []
for index, instance in enumerate(ptp_instances):
if ptp_instances[index]['service'] == constants.PTP_INSTANCE_TYPE_CLOCK:
clock_instance = ptp_instances[index]
nic_clocks[instance['name']] = clock_instance.as_dict()
nic_clocks[instance['name']]['interfaces'] = []
elif (
ptp_instances[index]["service"]
== constants.PTP_INSTANCE_TYPE_MONITORING
):
ptp_instances[index][instance["name"]] = instance.as_dict()
ptp_instances[index][instance["name"]]["interfaces"] = []
monitoring_instance_configs.append(ptp_instances[index])
else:
ptp_instances[index][instance['name']] = instance.as_dict()
ptp_instances[index][instance['name']]['interfaces'] = []
@@ -751,14 +789,34 @@ class NetworkingPuppet(base.BasePuppet):
else:
ptp_config = {}
return {'platform::ptpinstance::config': ptp_config,
# Generate the monitoring config
monitoring_enabled = False
monitoring_config = {}
len_monitoring_instance_configs = len(monitoring_instance_configs)
if ptpinstance_enabled and len_monitoring_instance_configs > 0:
# Only single monitoring instance per host is allowed.
if len_monitoring_instance_configs == 1:
monitoring_enabled = True
monitoring_config = self._set_ptp_instance_monitoring_global_parameters(
monitoring_instance_configs[0], ptp_parameters_instance
)
else:
LOG.warning(
f"PTP monitoring instances are {len_monitoring_instance_configs > 1} on host id {host.id}."
)
return {
'platform::ptpinstance::config': ptp_config,
'platform::ptpinstance::enabled': ptpinstance_enabled,
'platform::ptpinstance::monitoring::monitoring_config': monitoring_config,
'platform::ptpinstance::monitoring::monitoring_enabled': monitoring_enabled,
'platform::ptpinstance::nic_clock::nic_clock_config': nic_clock_config,
'platform::ptpinstance::nic_clock::nic_clock_enabled': nic_clock_enabled}
'platform::ptpinstance::nic_clock::nic_clock_enabled': nic_clock_enabled,
}
def _get_interface_config(self, networktype):
config = {}
network_interface = interface.find_interface_by_type(
self.context, networktype)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021-2022 Wind River Systems, Inc.
# Copyright (c) 2021-2022, 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -91,6 +91,11 @@ class TestCreatePtpInstance(BasePtpInstanceTestCase):
status_code=http_client.CONFLICT,
error_message=error_message)
def test_create_ptp_instance_monitoring_ok(self):
self._create_ptp_instance_success(
"fake-instance-monitoring", constants.PTP_INSTANCE_TYPE_MONITORING
)
class TestHostPtpInstance(BasePtpInstanceTestCase):
def setUp(self):
@@ -140,6 +145,57 @@ class TestHostPtpInstance(BasePtpInstanceTestCase):
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
def _assign_host_ptp_instance_monitoring_success(self):
ptp_instance = dbutils.create_test_ptp_instance(
name="test-instance", service=constants.PTP_INSTANCE_TYPE_MONITORING
)
ptp_instance_id = ptp_instance["id"]
response = self.patch_json(
self.get_host_url(self.controller.uuid),
[
{
"path": constants.PTP_INSTANCE_ARRAY_PATH,
"value": ptp_instance_id,
"op": constants.PTP_PATCH_OPERATION_ADD
}
],
headers=self.API_HEADERS,
)
self.assertEqual(response.content_type, "application/json")
self.assertEqual(response.status_code, http_client.OK)
return ptp_instance_id
def test_host_ptp_instance_monitoring_assign_ok(self):
self._assign_host_ptp_instance_monitoring_success()
def test_host_second_ptp_instance_monitoring_assign_failed(self):
self._assign_host_ptp_instance_monitoring_success()
ptp_instance = dbutils.create_test_ptp_instance(
name="test-instance2", service=constants.PTP_INSTANCE_TYPE_MONITORING
)
ptp_instance_id = ptp_instance["id"]
response = self.patch_json(
self.get_host_url(self.controller.uuid),
[
{
"path": constants.PTP_INSTANCE_ARRAY_PATH,
"value": ptp_instance_id,
"op": constants.PTP_PATCH_OPERATION_ADD
}
],
headers=self.API_HEADERS,
expect_errors=True,
)
self.assertEqual("application/json", response.content_type)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
self.assertIn(
"Monitoring ptp instance already exists on host",
response.json["error_message"]
)
class TestGetPtpInstance(BasePtpInstanceTestCase):
def setUp(self):
@@ -172,7 +228,8 @@ class TestListPtpInstance(BasePtpInstanceTestCase):
services = [constants.PTP_INSTANCE_TYPE_PTP4L,
constants.PTP_INSTANCE_TYPE_PHC2SYS,
constants.PTP_INSTANCE_TYPE_SYNCE4L,
constants.PTP_INSTANCE_TYPE_TS2PHC]
constants.PTP_INSTANCE_TYPE_TS2PHC,
constants.PTP_INSTANCE_TYPE_MONITORING]
for service in services:
name = '%s-%s' % (name_prefix, service)
instance = dbutils.create_test_ptp_instance(name=name,