From 290d3146ca99d64a4858c228538eaf2e4e00db84 Mon Sep 17 00:00:00 2001 From: Aidan Reilly <74046732+aireilly@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:32:44 +0100 Subject: [PATCH] Adding O-RAN API docs for PTP events Review updates further updates for Jack Lisa's comments --- _topic_maps/_topic_map.yml | 10 +- ...ift_PTP_bare-metal_OCP_nodes_0323_4.17.png | Bin 0 -> 80763 bytes ...f-about-ptp-and-clock-synchronization.adoc | 13 +- ...s-consumer-sidecar-and-http-transport.adoc | 37 ++ ...ing-ptp-event-producer-with-o-ran-api.adoc | 28 + ...tp-fast-event-notifications-framework.adoc | 42 +- ...figuring-the-ptp-fast-event-publisher.adoc | 26 +- ...-event-notifications-api-reference-v2.adoc | 484 ++++++++++++++++++ ...st-event-notifications-api-reference.adoc} | 276 +++++----- ...migrating-from-amqp-to-http-transport.adoc | 1 + .../cnf-monitoring-fast-events-metrics.adoc | 17 +- .../nw-ptp-operator-metrics-reference.adoc | 7 +- .../ptp-cloud-event-proxy-sidecar-api.adoc | 18 - .../ptp-events-consumer-application-v2.adoc | 108 ++++ modules/ptp-events-consumer-application.adoc | 12 +- ...-getting-the-current-ptp-clock-status.adoc | 46 -- ...ference-deployment-and-service-crs-v2.adoc | 108 ++++ ...-reference-deployment-and-service-crs.adoc | 2 +- ...tp-subscribing-consumer-app-to-events.adoc | 91 +--- ...s-consumer-app-is-receiving-events-v2.adoc | 82 +++ ...ents-consumer-app-is-receiving-events.adoc | 4 +- networking/ptp/about-ptp.adoc | 2 + networking/ptp/configuring-ptp.adoc | 6 +- ...loud-events-consumer-dev-reference-v2.adoc | 55 ++ ...p-cloud-events-consumer-dev-reference.adoc | 46 +- .../ptp/ptp-events-rest-api-reference-v2.adoc | 29 ++ .../ptp/ptp-events-rest-api-reference.adoc | 33 ++ networking/ptp/using-ptp-events.adoc | 64 --- snippets/ptp-event-config-api-v1.adoc | 15 + snippets/ptp-event-config-api-v2.adoc | 18 + .../ptp-events-rest-api-v1-deprecation.adoc | 6 + 31 files changed, 1261 insertions(+), 425 deletions(-) create mode 100644 images/319_OpenShift_PTP_bare-metal_OCP_nodes_0323_4.17.png create mode 100644 modules/cnf-about-ptp-events-consumer-sidecar-and-http-transport.adoc create mode 100644 modules/cnf-about-ptp-events-using-ptp-event-producer-with-o-ran-api.adoc create mode 100644 modules/cnf-fast-event-notifications-api-reference-v2.adoc rename modules/{cnf-fast-event-notifications-api-refererence.adoc => cnf-fast-event-notifications-api-reference.adoc} (67%) delete mode 100644 modules/ptp-cloud-event-proxy-sidecar-api.adoc create mode 100644 modules/ptp-events-consumer-application-v2.adoc delete mode 100644 modules/ptp-getting-the-current-ptp-clock-status.adoc create mode 100644 modules/ptp-reference-deployment-and-service-crs-v2.adoc create mode 100644 modules/ptp-verifying-events-consumer-app-is-receiving-events-v2.adoc create mode 100644 networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc create mode 100644 networking/ptp/ptp-events-rest-api-reference-v2.adoc create mode 100644 networking/ptp/ptp-events-rest-api-reference.adoc delete mode 100644 networking/ptp/using-ptp-events.adoc create mode 100644 snippets/ptp-event-config-api-v1.adoc create mode 100644 snippets/ptp-event-config-api-v2.adoc create mode 100644 snippets/ptp-events-rest-api-v1-deprecation.adoc diff --git a/_topic_maps/_topic_map.yml b/_topic_maps/_topic_map.yml index 06b80b7289..460d4c5725 100644 --- a/_topic_maps/_topic_map.yml +++ b/_topic_maps/_topic_map.yml @@ -1371,10 +1371,14 @@ Topics: File: about-ptp - Name: Configuring PTP hardware File: configuring-ptp - - Name: Using PTP events - File: using-ptp-events - - Name: Developing PTP events consumer applications + - Name: Developing PTP events consumer applications with the REST API v2 + File: ptp-cloud-events-consumer-dev-reference-v2 + - Name: PTP events REST API v2 reference + File: ptp-events-rest-api-reference-v2 + - Name: Developing PTP events consumer applications with the REST API v1 File: ptp-cloud-events-consumer-dev-reference + - Name: PTP events REST API v1 reference + File: ptp-events-rest-api-reference - Name: External DNS Operator Dir: external_dns_operator Topics: diff --git a/images/319_OpenShift_PTP_bare-metal_OCP_nodes_0323_4.17.png b/images/319_OpenShift_PTP_bare-metal_OCP_nodes_0323_4.17.png new file mode 100644 index 0000000000000000000000000000000000000000..4843d9c951b0fcffaad49fc30564cf666d32b554 GIT binary patch literal 80763 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 ----- -<1> Set `enableEventPublisher` to `true` to enable PTP fast event notifications. - +ifeval::["{ptp-events-rest-api}" == "v1"] +include::snippets/ptp-event-config-api-v1.adoc[] +endif::[] +ifeval::["{ptp-events-rest-api}" == "v2"] +include::snippets/ptp-event-config-api-v2.adoc[] +endif::[] + [NOTE] ==== diff --git a/modules/cnf-fast-event-notifications-api-reference-v2.adoc b/modules/cnf-fast-event-notifications-api-reference-v2.adoc new file mode 100644 index 0000000000..10663c471a --- /dev/null +++ b/modules/cnf-fast-event-notifications-api-reference-v2.adoc @@ -0,0 +1,484 @@ +// Module included in the following assemblies: +// +// * networking/ptp/ptp-events-rest-api-reference-v2.adoc + +:_mod-docs-content-type: PROCEDURE +[id="cnf-fast-event-notifications-v2-api-refererence_{context}"] += PTP events REST API v2 endpoints + +[id="api-ocloud-notifications-v2-subscriptions_{context}"] +== api/ocloudNotifications/v2/subscriptions + +[discrete] +=== HTTP method + +`GET api/ocloudNotifications/v2/subscriptions` + +[discrete] +=== Description + +Returns a list of subscriptions. If subscriptions exist, a `200 OK` status code is returned along with the list of subscriptions. + +.Example API response +[source,json] +---- +[ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/os-clock-sync-state", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "ccedbf08-3f96-4839-a0b6-2eb0401855ed", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/ccedbf08-3f96-4839-a0b6-2eb0401855ed" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/clock-class", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "a939a656-1b7d-4071-8cf1-f99af6e931f2", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/a939a656-1b7d-4071-8cf1-f99af6e931f2" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "ba4564a3-4d9e-46c5-b118-591d3105473c", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/ba4564a3-4d9e-46c5-b118-591d3105473c" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "ea0d772e-f00a-4889-98be-51635559b4fb", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/ea0d772e-f00a-4889-98be-51635559b4fb" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "762999bf-b4a0-4bad-abe8-66e646b65754", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/762999bf-b4a0-4bad-abe8-66e646b65754" + } +] +---- + +[discrete] +=== HTTP method + +`POST api/ocloudNotifications/v2/subscriptions` + +[discrete] +=== Description + +Creates a new subscription for the required event by passing the appropriate payload. +If a subscription is successfully created, or if it already exists, a `201 Created` status code is returned. +You can subscribe to the following PTP events: + +* `sync-state` events +* `lock-state` events +* `gnss-sync-status events` events +* `os-clock-sync-state` events +* `clock-class` events + +.Query parameters +[cols=2*, width="60%", options="header"] +|==== +|Parameter +|Type + +|subscription +|data +|==== + +.Example sync-state subscription payload +[source,json] +---- +{ +"EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", +"ResourceAddress": "/cluster/node/{node_name}/sync/sync-status/sync-state" +} +---- + +.Example PTP lock-state events subscription payload +[source,json] +---- +{ +"EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", +"ResourceAddress": "/cluster/node/{node_name}/sync/ptp-status/lock-state" +} +---- + +.Example PTP gnss-sync-status events subscription payload +[source,json] +---- +{ +"EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", +"ResourceAddress": "/cluster/node/{node_name}/sync/gnss-status/gnss-sync-status" +} +---- + +.Example PTP os-clock-sync-state events subscription payload +[source,json] +---- +{ +"EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", +"ResourceAddress": "/cluster/node/{node_name}/sync/sync-status/os-clock-sync-state" +} +---- + +.Example PTP clock-class events subscription payload +[source,json] +---- +{ +"EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", +"ResourceAddress": "/cluster/node/{node_name}/sync/ptp-status/clock-class" +} +---- + +.Example API response +[source,json] +---- +{ + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "620283f3-26cd-4a6d-b80a-bdc4b614a96a", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/620283f3-26cd-4a6d-b80a-bdc4b614a96a" +} +---- + +[discrete] +=== HTTP method + +`DELETE api/ocloudNotifications/v2/subscriptions` + +[discrete] +=== Description + +Deletes all subscriptions. + +.Example API response +[source,json] +---- +{ +"status": "deleted all subscriptions" +} +---- + +[id="api-ocloud-notifications-v2-subscriptions-subscription_id_{context}"] +== api/ocloudNotifications/v2/subscriptions/{subscription_id} + +[discrete] +=== HTTP method + +`GET api/ocloudNotifications/v2/subscriptions/{subscription_id}` + +[discrete] +=== Description + +Returns details for the subscription with ID `subscription_id`. + +.Global path parameters +[cols=2*, width="60%", options="header"] +|==== +|Parameter +|Type + +|`subscription_id` +|string +|==== + +.Example API response +[source,json] +---- +{ + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", + "EndpointUri": "http://consumer-events-subscription-service.cloud-events.svc.cluster.local:9043/event", + "SubscriptionId": "620283f3-26cd-4a6d-b80a-bdc4b614a96a", + "UriLocation": "http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions/620283f3-26cd-4a6d-b80a-bdc4b614a96a" +} +---- + +[discrete] +=== HTTP method + +`DELETE api/ocloudNotifications/v2/subscriptions/{subscription_id}` + +[discrete] +=== Description + +Deletes the subscription with ID `subscription_id`. + +.Global path parameters +[cols=2*, width="60%", options="header"] +|==== +|Parameter +|Type + +|`subscription_id` +|string +|==== + +.HTTP response codes +[cols=2*, width="60%", options="header"] +|==== +|HTTP response +|Description + +|204 No Content +|Success +|==== + +[id="api-ocloudnotifications-v2-health_{context}"] +== api/ocloudNotifications/v2/health + +[discrete] +=== HTTP method + +`GET api/ocloudNotifications/v2/health/` + +[discrete] +=== Description + +Returns the health status for the `ocloudNotifications` REST API. + +.HTTP response codes +[cols=2*, width="60%", options="header"] +|==== +|HTTP response +|Description + +|200 OK +|Success +|==== + +[id="api-ocloudnotifications-v2-publishers_{context}"] +== api/ocloudNotifications/v2/publishers + +[discrete] +=== HTTP method + +`GET api/ocloudNotifications/v2/publishers` + +[discrete] +=== Description + +Returns a list of publisher details for the cluster node. +The system generates notifications when the relevant equipment state changes. + +You can use equipment synchronization status subscriptions together to deliver a detailed view of the overall synchronization health of the system. + +.Example API response +[source,json] +---- +[ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "EndpointUri": "http://localhost:9043/api/ocloudNotifications/v2/dummy", + "SubscriptionId": "4ea72bfa-185c-4703-9694-cdd0434cd570", + "UriLocation": "http://localhost:9043/api/ocloudNotifications/v2/publishers/4ea72bfa-185c-4703-9694-cdd0434cd570" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/os-clock-sync-state", + "EndpointUri": "http://localhost:9043/api/ocloudNotifications/v2/dummy", + "SubscriptionId": "71fbb38e-a65d-41fc-823b-d76407901087", + "UriLocation": "http://localhost:9043/api/ocloudNotifications/v2/publishers/71fbb38e-a65d-41fc-823b-d76407901087" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/clock-class", + "EndpointUri": "http://localhost:9043/api/ocloudNotifications/v2/dummy", + "SubscriptionId": "7bc27cad-03f4-44a9-8060-a029566e7926", + "UriLocation": "http://localhost:9043/api/ocloudNotifications/v2/publishers/7bc27cad-03f4-44a9-8060-a029566e7926" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", + "EndpointUri": "http://localhost:9043/api/ocloudNotifications/v2/dummy", + "SubscriptionId": "6e7b6736-f359-46b9-991c-fbaed25eb554", + "UriLocation": "http://localhost:9043/api/ocloudNotifications/v2/publishers/6e7b6736-f359-46b9-991c-fbaed25eb554" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status", + "EndpointUri": "http://localhost:9043/api/ocloudNotifications/v2/dummy", + "SubscriptionId": "31bb0a45-7892-45d4-91dd-13035b13ed18", + "UriLocation": "http://localhost:9043/api/ocloudNotifications/v2/publishers/31bb0a45-7892-45d4-91dd-13035b13ed18" + } +] +---- + +.HTTP response codes +[cols=2*, width="60%", options="header"] +|==== +|HTTP response +|Description + +|200 OK +|Success +|==== + +[id="resource-address-current-state-v2_{context}"] +== api/ocloudNotifications/v2/{resource_address}/CurrentState + +[discrete] +=== HTTP method + +`GET api/ocloudNotifications/v2/cluster/node/{node_name}/sync/ptp-status/lock-state/CurrentState` + +`GET api/ocloudNotifications/v2/cluster/node/{node_name}/sync/sync-status/os-clock-sync-state/CurrentState` + +`GET api/ocloudNotifications/v2/cluster/node/{node_name}/sync/ptp-status/clock-class/CurrentState` + +`GET api/ocloudNotifications/v2/cluster/node/{node_name}/sync/sync-status/sync-state/CurrentState` + +`GET api/ocloudNotifications/v2/cluster/node/{node_name}/sync/gnss-status/gnss-sync-state/CurrentState` + +[discrete] +=== Description + +Returns the current state of the `os-clock-sync-state`, `clock-class`, `lock-state`, `gnss-sync-status`, or `sync-state` events for the cluster node. + +* `os-clock-sync-state` notifications describe the host operating system clock synchronization state. Can be in `LOCKED` or `FREERUN` state. +* `clock-class` notifications describe the current state of the PTP clock class. +* `lock-state` notifications describe the current status of the PTP equipment lock state. Can be in `LOCKED`, `HOLDOVER` or `FREERUN` state. +* `sync-state` notifications describe the current status of the least synchronized of the PTP clock `lock-state` and +`os-clock-sync-state` states. +* `gnss-sync-status` notifications describe the GNSS clock synchronization state. + +.Global path parameters +[cols=2*, width="60%", options="header"] +|==== +|Parameter +|Type + +|`resource_address` +|string +|==== + +.Example lock-state API response +[source,json] +---- +{ + "id": "c1ac3aa5-1195-4786-84f8-da0ea4462921", + "type": "event.sync.ptp-status.ptp-state-change", + "source": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", + "dataContentType": "application/json", + "time": "2023-01-10T02:41:57.094981478Z", + "data": { + "version": "1.0", + "values": [ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/ens5fx/master", + "dataType": "notification", + "valueType": "enumeration", + "value": "LOCKED" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/ens5fx/master", + "dataType": "metric", + "valueType": "decimal64.3", + "value": "29" + } + ] + } +} +---- + +.Example os-clock-sync-state API response +[source,json] +---- +{ + "specversion": "0.3", + "id": "4f51fe99-feaa-4e66-9112-66c5c9b9afcb", + "source": "/cluster/node/compute-1.example.com/sync/sync-status/os-clock-sync-state", + "type": "event.sync.sync-status.os-clock-sync-state-change", + "subject": "/cluster/node/compute-1.example.com/sync/sync-status/os-clock-sync-state", + "datacontenttype": "application/json", + "time": "2022-11-29T17:44:22.202Z", + "data": { + "version": "1.0", + "values": [ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/CLOCK_REALTIME", + "dataType": "notification", + "valueType": "enumeration", + "value": "LOCKED" + }, + { + "ResourceAddress": "/cluster/node/compute-1.example.com/CLOCK_REALTIME", + "dataType": "metric", + "valueType": "decimal64.3", + "value": "27" + } + ] + } +} +---- + +.Example clock-class API response +[source,json] +---- +{ + "id": "064c9e67-5ad4-4afb-98ff-189c6aa9c205", + "type": "event.sync.ptp-status.ptp-clock-class-change", + "source": "/cluster/node/compute-1.example.com/sync/ptp-status/clock-class", + "dataContentType": "application/json", + "time": "2023-01-10T02:41:56.785673989Z", + "data": { + "version": "1.0", + "values": [ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/ens5fx/master", + "dataType": "metric", + "valueType": "decimal64.3", + "value": "165" + } + ] + } +} +---- + +.Example sync-state API response +[source,json] +---- +{ + "specversion": "0.3", + "id": "8c9d6ecb-ae9f-4106-82c4-0a778a79838d", + "source": "/sync/sync-status/sync-state", + "type": "event.sync.sync-status.synchronization-state-change", + "subject": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "datacontenttype": "application/json", + "time": "2024-08-28T14:50:57.327585316Z", + "data": + { + "version": "1.0", + "values": [ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "data_type": "notification", + "value_type": "enumeration", + "value": "LOCKED" + }] + } +} +---- + +.Example gnss-sync-state API response +[source,json] +---- +{ + "id": "435e1f2a-6854-4555-8520-767325c087d7", + "type": "event.sync.gnss-status.gnss-state-change", + "source": "/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status", + "dataContentType": "application/json", + "time": "2023-09-27T19:35:33.42347206Z", + "data": { + "version": "1.0", + "values": [ + { + "resource": "/cluster/node/compute-1.example.com/ens2fx/master", + "dataType": "notification", + "valueType": "enumeration", + "value": "LOCKED" + }, + { + "resource": "/cluster/node/compute-1.example.com/ens2fx/master", + "dataType": "metric", + "valueType": "decimal64.3", + "value": "5" + } + ] + } +} +---- diff --git a/modules/cnf-fast-event-notifications-api-refererence.adoc b/modules/cnf-fast-event-notifications-api-reference.adoc similarity index 67% rename from modules/cnf-fast-event-notifications-api-refererence.adoc rename to modules/cnf-fast-event-notifications-api-reference.adoc index ed331a1ae3..cf1d3054cc 100644 --- a/modules/cnf-fast-event-notifications-api-refererence.adoc +++ b/modules/cnf-fast-event-notifications-api-reference.adoc @@ -1,12 +1,10 @@ // Module included in the following assemblies: // -// * networking/ptp/using-ptp-events.adoc +// * networking/ptp/ptp-events-rest-api-reference.adoc :_mod-docs-content-type: PROCEDURE [id="cnf-fast-event-notifications-api-refererence_{context}"] -= PTP events REST API reference - -Use the PTP event notifications REST API to subscribe a cluster application to the PTP events that are generated on the parent node. += PTP events REST API v1 endpoints [id="api-ocloud-notifications-v1-subscriptions_{context}"] == api/ocloudNotifications/v1/subscriptions @@ -42,7 +40,15 @@ Returns a list of subscriptions. If subscriptions exist, a `200 OK` status code [discrete] === Description -Creates a new subscription. If a subscription is successfully created, or if it already exists, a `201 Created` status code is returned. +Creates a new subscription for the required event by passing the appropriate payload. +If a subscription is successfully created, or if it already exists, a `201 Created` status code is returned. +You can subscribe to the following PTP events: + +* `lock-state` events +* `os-clock-sync-state` events +* `clock-class` events +* `gnss-sync-status` events +* `sync-state` events .Query parameters [cols=2*, width="60%", options="header"] @@ -54,7 +60,7 @@ Creates a new subscription. If a subscription is successfully created, or if it |data |==== -.Example payload +.Example PTP events subscription payload [source,json] ---- { @@ -63,6 +69,51 @@ Creates a new subscription. If a subscription is successfully created, or if it } ---- +.Example PTP lock-state events subscription payload +[source,json] +---- +{ +"endpointUri": "http://localhost:8989/event", +"resource": "/cluster/node/{node_name}/sync/ptp-status/lock-state" +} +---- + +.Example PTP os-clock-sync-state events subscription payload +[source,json] +---- +{ +"endpointUri": "http://localhost:8989/event", +"resource": "/cluster/node/{node_name}/sync/sync-status/os-clock-sync-state" +} +---- + +.Example PTP clock-class events subscription payload +[source,json] +---- +{ +"endpointUri": "http://localhost:8989/event", +"resource": "/cluster/node/{node_name}/sync/ptp-status/clock-class" +} +---- + +.Example PTP gnss-sync-status events subscription payload +[source,json] +---- +{ +"endpointUri": "http://localhost:8989/event", +"resource": "/cluster/node/{node_name}/sync/gnss-status/gnss-sync-status" +} +---- + +.Example sync-state subscription payload +[source,json] +---- +{ +"endpointUri": "http://localhost:8989/event", +"resource": "/cluster/node/{node_name}/sync/sync-status/sync-state" +} +---- + [discrete] === HTTP method @@ -165,6 +216,12 @@ OK [id="api-ocloudnotifications-v1-publishers_{context}"] == api/ocloudNotifications/v1/publishers +[IMPORTANT] +==== +The `api/ocloudNotifications/v1/publishers` endpoint is only available from the cloud-event-proxy container in the PTP Operator managed pod. +It is not available for consumer applications in the application pod. +==== + [discrete] === HTTP method @@ -173,14 +230,9 @@ OK [discrete] === Description -Returns an array of `os-clock-sync-state`, `ptp-clock-class-change`, `lock-state`, and `gnss-sync-status` details for the cluster node. +Returns a list of publisher details for the cluster node. The system generates notifications when the relevant equipment state changes. -* `os-clock-sync-state` notifications describe the host operating system clock synchronization state. Can be in `LOCKED` or `FREERUN` state. -* `ptp-clock-class-change` notifications describe the current state of the PTP clock class. -* `lock-state` notifications describe the current status of the PTP equipment lock state. Can be in `LOCKED`, `HOLDOVER` or `FREERUN` state. -* `gnss-sync-status` notifications describe the GPS synchronization state with regard to the external GNSS clock signal. Can be in `LOCKED` or `FREERUN` state. - You can use equipment synchronization status subscriptions together to deliver a detailed view of the overall synchronization health of the system. .Example API response @@ -197,7 +249,7 @@ You can use equipment synchronization status subscriptions together to deliver a "id": "28cd82df-8436-4f50-bbd9-7a9742828a71", "endpointUri": "http://localhost:9085/api/ocloudNotifications/v1/dummy", "uriLocation": "http://localhost:9085/api/ocloudNotifications/v1/publishers/28cd82df-8436-4f50-bbd9-7a9742828a71", - "resource": "/cluster/node/compute-1.example.com/sync/ptp-status/ptp-clock-class-change" + "resource": "/cluster/node/compute-1.example.com/sync/ptp-status/clock-class" }, { "id": "44aa480d-7347-48b0-a5b0-e0af01fa9677", @@ -214,143 +266,33 @@ You can use equipment synchronization status subscriptions together to deliver a ] ---- -You can find `os-clock-sync-state`, `ptp-clock-class-change`, `lock-state`, and `gnss-sync-status` events in the logs for the `cloud-event-proxy` container. For example: - -[source,terminal] ----- -$ oc logs -f linuxptp-daemon-cvgr6 -n openshift-ptp -c cloud-event-proxy ----- - -.Example os-clock-sync-state event -[source,json] ----- -{ - "id":"c8a784d1-5f4a-4c16-9a81-a3b4313affe5", - "type":"event.sync.sync-status.os-clock-sync-state-change", - "source":"/cluster/compute-1.example.com/ptp/CLOCK_REALTIME", - "dataContentType":"application/json", - "time":"2022-05-06T15:31:23.906277159Z", - "data":{ - "version":"v1", - "values":[ - { - "resource":"/sync/sync-status/os-clock-sync-state", - "dataType":"notification", - "valueType":"enumeration", - "value":"LOCKED" - }, - { - "resource":"/sync/sync-status/os-clock-sync-state", - "dataType":"metric", - "valueType":"decimal64.3", - "value":"-53" - } - ] - } -} ----- - -.Example ptp-clock-class-change event -[source,json] ----- -{ - "id":"69eddb52-1650-4e56-b325-86d44688d02b", - "type":"event.sync.ptp-status.ptp-clock-class-change", - "source":"/cluster/compute-1.example.com/ptp/ens2fx/master", - "dataContentType":"application/json", - "time":"2022-05-06T15:31:23.147100033Z", - "data":{ - "version":"v1", - "values":[ - { - "resource":"/sync/ptp-status/ptp-clock-class-change", - "dataType":"metric", - "valueType":"decimal64.3", - "value":"135" - } - ] - } -} ----- - -.Example lock-state event -[source,json] ----- -{ - "id":"305ec18b-1472-47b3-aadd-8f37933249a9", - "type":"event.sync.ptp-status.ptp-state-change", - "source":"/cluster/compute-1.example.com/ptp/ens2fx/master", - "dataContentType":"application/json", - "time":"2022-05-06T15:31:23.467684081Z", - "data":{ - "version":"v1", - "values":[ - { - "resource":"/sync/ptp-status/lock-state", - "dataType":"notification", - "valueType":"enumeration", - "value":"LOCKED" - }, - { - "resource":"/sync/ptp-status/lock-state", - "dataType":"metric", - "valueType":"decimal64.3", - "value":"62" - } - ] - } -} ----- - -.Example gnss-sync-status event -[source,json] ----- -{ - "id": "435e1f2a-6854-4555-8520-767325c087d7", - "type": "event.sync.gnss-status.gnss-state-change", - "source": "/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status", - "dataContentType": "application/json", - "time": "2023-09-27T19:35:33.42347206Z", - "data": { - "version": "v1", - "values": [ - { - "resource": "/cluster/node/compute-1.example.com/ens2fx/master", - "dataType": "notification", - "valueType": "enumeration", - "value": "LOCKED" - }, - { - "resource": "/cluster/node/compute-1.example.com/ens2fx/master", - "dataType": "metric", - "valueType": "decimal64.3", - "value": "5" - } - ] - } -} ----- - [id="resource-address-current-state_{context}"] == api/ocloudNotifications/v1/{resource_address}/CurrentState [discrete] === HTTP method -`GET api/ocloudNotifications/v1/cluster/node//sync/ptp-status/lock-state/CurrentState` +`GET api/ocloudNotifications/v1/cluster/node/{node_name}/sync/ptp-status/lock-state/CurrentState` -`GET api/ocloudNotifications/v1/cluster/node//sync/sync-status/os-clock-sync-state/CurrentState` +`GET api/ocloudNotifications/v1/cluster/node/{node_name}/sync/sync-status/os-clock-sync-state/CurrentState` -`GET api/ocloudNotifications/v1/cluster/node//sync/ptp-status/ptp-clock-class-change/CurrentState` +`GET api/ocloudNotifications/v1/cluster/node/{node_name}/sync/ptp-status/clock-class/CurrentState` + +`GET api/ocloudNotifications/v1/cluster/node/{node_name}/sync/sync-status/sync-state/CurrentState` + +`GET api/ocloudNotifications/v1/cluster/node/{node_name}/sync/gnss-status/gnss-sync-state/CurrentState` [discrete] === Description -Configure the `CurrentState` API endpoint to return the current state of the `os-clock-sync-state`, `ptp-clock-class-change`, `lock-state` events for the cluster node. +Returns the current state of the `os-clock-sync-state`, `clock-class`, `lock-state`, `gnss-sync-status`, or `sync-state` events for the cluster node. * `os-clock-sync-state` notifications describe the host operating system clock synchronization state. Can be in `LOCKED` or `FREERUN` state. -* `ptp-clock-class-change` notifications describe the current state of the PTP clock class. +* `clock-class` notifications describe the current state of the PTP clock class. * `lock-state` notifications describe the current status of the PTP equipment lock state. Can be in `LOCKED`, `HOLDOVER` or `FREERUN` state. +* `sync-state` notifications describe the current status of the least synchronized of the `ptp-status/lock-state` and +`sync-status/os-clock-sync-state` endpoints. +* `gnss-sync-status` notifications describe the GNSS clock synchronization state. .Global path parameters [cols=2*, width="60%", options="header"] @@ -372,7 +314,7 @@ Configure the `CurrentState` API endpoint to return the current state of the `os "dataContentType": "application/json", "time": "2023-01-10T02:41:57.094981478Z", "data": { - "version": "v1", + "version": "1.0", "values": [ { "resource": "/cluster/node/compute-1.example.com/ens5fx/master", @@ -403,7 +345,7 @@ Configure the `CurrentState` API endpoint to return the current state of the `os "datacontenttype": "application/json", "time": "2022-11-29T17:44:22.202Z", "data": { - "version": "v1", + "version": "1.0", "values": [ { "resource": "/cluster/node/compute-1.example.com/CLOCK_REALTIME", @@ -422,17 +364,17 @@ Configure the `CurrentState` API endpoint to return the current state of the `os } ---- -.Example ptp-clock-class-change API response +.Example clock-class API response [source,json] ---- { "id": "064c9e67-5ad4-4afb-98ff-189c6aa9c205", "type": "event.sync.ptp-status.ptp-clock-class-change", - "source": "/cluster/node/compute-1.example.com/sync/ptp-status/ptp-clock-class-change", + "source": "/cluster/node/compute-1.example.com/sync/ptp-status/clock-class", "dataContentType": "application/json", "time": "2023-01-10T02:41:56.785673989Z", "data": { - "version": "v1", + "version": "1.0", "values": [ { "resource": "/cluster/node/compute-1.example.com/ens5fx/master", @@ -444,3 +386,57 @@ Configure the `CurrentState` API endpoint to return the current state of the `os } } ---- + +.Example sync-state API response +[source,json] +---- +{ + "specversion": "0.3", + "id": "8c9d6ecb-ae9f-4106-82c4-0a778a79838d", + "source": "/sync/sync-status/sync-state", + "type": "event.sync.sync-status.synchronization-state-change", + "subject": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "datacontenttype": "application/json", + "time": "2024-08-28T14:50:57.327585316Z", + "data": + { + "version": "1.0", + "values": [ + { + "ResourceAddress": "/cluster/node/compute-1.example.com/sync/sync-status/sync-state", + "data_type": "notification", + "value_type": "enumeration", + "value": "LOCKED" + }] + } +} +---- + +.Example gnss-sync-status API response +[source,json] +---- +{ + "id": "435e1f2a-6854-4555-8520-767325c087d7", + "type": "event.sync.gnss-status.gnss-state-change", + "source": "/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status", + "dataContentType": "application/json", + "time": "2023-09-27T19:35:33.42347206Z", + "data": { + "version": "1.0", + "values": [ + { + "resource": "/cluster/node/compute-1.example.com/ens2fx/master", + "dataType": "notification", + "valueType": "enumeration", + "value": "LOCKED" + }, + { + "resource": "/cluster/node/compute-1.example.com/ens2fx/master", + "dataType": "metric", + "valueType": "decimal64.3", + "value": "5" + } + ] + } +} +---- diff --git a/modules/cnf-migrating-from-amqp-to-http-transport.adoc b/modules/cnf-migrating-from-amqp-to-http-transport.adoc index 20d4d45770..a72edb88ba 100644 --- a/modules/cnf-migrating-from-amqp-to-http-transport.adoc +++ b/modules/cnf-migrating-from-amqp-to-http-transport.adoc @@ -9,6 +9,7 @@ If you have previously deployed PTP or bare-metal events consumer applications, you need to update the applications to use HTTP message transport. + .Prerequisites * You have installed the OpenShift CLI (`oc`). diff --git a/modules/cnf-monitoring-fast-events-metrics.adoc b/modules/cnf-monitoring-fast-events-metrics.adoc index 71ab3d7355..fdb8db9cce 100644 --- a/modules/cnf-monitoring-fast-events-metrics.adoc +++ b/modules/cnf-monitoring-fast-events-metrics.adoc @@ -1,9 +1,10 @@ // Module included in the following assemblies: // -// * networking/ptp/using-ptp-events.adoc +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc +// * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: PROCEDURE -[id="cnf-monitoring-fast-events-metrics_{context}"] +[id="cnf-monitoring-fast-events-metrics-{ptp-events-rest-api}_{context}"] = Monitoring PTP fast event metrics You can monitor PTP fast events metrics from cluster nodes where the `linuxptp-daemon` is running. @@ -34,15 +35,25 @@ sh-4.4# curl http://localhost:9091/metrics ---- + .Example output +[source,terminal] ---- # HELP cne_api_events_published Metric to get number of events published by the rest api # TYPE cne_api_events_published gauge cne_api_events_published{address="/cluster/node/compute-1.example.com/sync/gnss-status/gnss-sync-status",status="success"} 1 cne_api_events_published{address="/cluster/node/compute-1.example.com/sync/ptp-status/lock-state",status="success"} 94 -cne_api_events_published{address="/cluster/node/compute-1.example.com/sync/ptp-status/ptp-clock-class-change",status="success"} 18 +cne_api_events_published{address="/cluster/node/compute-1.example.com/sync/ptp-status/class-change",status="success"} 18 cne_api_events_published{address="/cluster/node/compute-1.example.com/sync/sync-status/os-clock-sync-state",status="success"} 27 ---- +. Optional. +You can also find PTP events in the logs for the `cloud-event-proxy` container. +For example, run the following command: ++ +[source,terminal] +---- +$ oc logs -f linuxptp-daemon-cvgr6 -n openshift-ptp -c cloud-event-proxy +---- + . To view the PTP event in the {product-title} web console, copy the name of the PTP metric you want to query, for example, `openshift_ptp_offset_ns`. . In the {product-title} web console, click *Observe* -> *Metrics*. diff --git a/modules/nw-ptp-operator-metrics-reference.adoc b/modules/nw-ptp-operator-metrics-reference.adoc index b67e63d949..22c6eefa15 100644 --- a/modules/nw-ptp-operator-metrics-reference.adoc +++ b/modules/nw-ptp-operator-metrics-reference.adoc @@ -1,9 +1,10 @@ // Module included in the following assemblies: // -// * networking/ptp/using-ptp-events.adoc +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc +// * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: REFERENCE -[id="nw-ptp-operator-metrics-reference_{context}"] +[id="nw-ptp-operator-metrics-reference-{ptp-events-rest-api}_{context}"] = PTP fast event metrics reference The following table describes the PTP fast events metrics that are available from cluster nodes where the `linuxptp-daemon` service is running. @@ -112,5 +113,3 @@ Possible values are 0 (`NOFIX`), 1 (`DEAD RECKONING ONLY`), 2 (`2D-FIX`), 3 (`3D |`{from="gnss",iface="ens2fx",node="compute-1.example.com",process="gnss"} 3` |==== - - diff --git a/modules/ptp-cloud-event-proxy-sidecar-api.adoc b/modules/ptp-cloud-event-proxy-sidecar-api.adoc deleted file mode 100644 index 21cb5f9899..0000000000 --- a/modules/ptp-cloud-event-proxy-sidecar-api.adoc +++ /dev/null @@ -1,18 +0,0 @@ -// Module included in the following assemblies: -// -// * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc - -:_mod-docs-content-type: REFERENCE -[id="ptp-cloud-event-proxy-sidecar-api_{context}"] -= PTP events available from the cloud-event-proxy sidecar REST API - -PTP events consumer applications can poll the PTP events producer for the following PTP timing events. - -.PTP events available from the cloud-event-proxy sidecar -[options="header"] -|==== -|Resource URI|Description -|`/cluster/node//sync/ptp-status/lock-state`| Describes the current status of the PTP equipment lock state. Can be in `LOCKED`, `HOLDOVER`, or `FREERUN` state. -|`/cluster/node//sync/sync-status/os-clock-sync-state`| Describes the host operating system clock synchronization state. Can be in `LOCKED` or `FREERUN` state. -|`/cluster/node//sync/ptp-status/ptp-clock-class-change`| Describes the current state of the PTP clock class. -|==== diff --git a/modules/ptp-events-consumer-application-v2.adoc b/modules/ptp-events-consumer-application-v2.adoc new file mode 100644 index 0000000000..3288fa9ca1 --- /dev/null +++ b/modules/ptp-events-consumer-application-v2.adoc @@ -0,0 +1,108 @@ +// Module included in the following assemblies: +// +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc + +:_mod-docs-content-type: REFERENCE +[id="ptp-events-consumer-application-v2_{context}"] += PTP events REST API v2 consumer application reference + +PTP event consumer applications require the following features: + +. A web service running with a `POST` handler to receive the cloud native PTP events JSON payload +. A `createSubscription` function to subscribe to the PTP events producer +. A `getCurrentState` function to poll the current state of the PTP events producer + +The following example Go snippets illustrate these requirements: + +.Example PTP events consumer server function in Go +[source,go] +---- +func server() { + http.HandleFunc("/event", getEvent) + http.ListenAndServe(":9043", nil) +} + +func getEvent(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + log.Errorf("error reading event %v", err) + } + e := string(bodyBytes) + if e != "" { + processEvent(bodyBytes) + log.Infof("received event %s", string(bodyBytes)) + } else { + w.WriteHeader(http.StatusNoContent) + } +} +---- + +.Example PTP events createSubscription function in Go +[source,go,subs="attributes+"] +---- +import ( +"github.com/redhat-cne/sdk-go/pkg/pubsub" +"github.com/redhat-cne/sdk-go/pkg/types" +v1pubsub "github.com/redhat-cne/sdk-go/v1/pubsub" +) + +// Subscribe to PTP events using v2 REST API +s1,_:=createsubscription("/cluster/node//sync/sync-status/sync-state") +s2,_:=createsubscription("/cluster/node//sync/ptp-status/lock-state") +s3,_:=createsubscription("/cluster/node//sync/gnss-status/gnss-sync-status") +s4,_:=createsubscription("/cluster/node//sync/sync-status/os-clock-sync-state") +s5,_:=createsubscription("/cluster/node//sync/ptp-status/clock-class") + +// Create PTP event subscriptions POST +func createSubscription(resourceAddress string) (sub pubsub.PubSub, err error) { + var status int + apiPath := "/api/ocloudNotifications/v2/" + localAPIAddr := "localhost:8989" // vDU service API address + apiAddr := "ptp-event-publisher-service-.openshift-ptp.svc.cluster.local:9043" // <1> + apiVersion := "2.0" + + subURL := &types.URI{URL: url.URL{Scheme: "http", + Host: apiAddr + Path: fmt.Sprintf("%s%s", apiPath, "subscriptions")}} + endpointURL := &types.URI{URL: url.URL{Scheme: "http", + Host: localAPIAddr, + Path: "event"}} + + sub = v1pubsub.NewPubSub(endpointURL, resourceAddress, apiVersion) + var subB []byte + + if subB, err = json.Marshal(&sub); err == nil { + rc := restclient.New() + if status, subB = rc.PostWithReturn(subURL, subB); status != http.StatusCreated { + err = fmt.Errorf("error in subscription creation api at %s, returned status %d", subURL, status) + } else { + err = json.Unmarshal(subB, &sub) + } + } else { + err = fmt.Errorf("failed to marshal subscription for %s", resourceAddress) + } + return +} +---- +<1> Replace `` with the FQDN of the node that is generating the PTP events. For example, `compute-1.example.com`. + +.Example PTP events consumer getCurrentState function in Go +[source,go,subs="attributes+"] +---- +//Get PTP event state for the resource +func getCurrentState(resource string) { + //Create publisher + url := &types.URI{URL: url.URL{Scheme: "http", + Host: "ptp-event-publisher-service-.openshift-ptp.svc.cluster.local:9043", // <1> + Path: fmt.SPrintf("/api/ocloudNotifications/v2/%s/CurrentState",resource}} + rc := restclient.New() + status, event := rc.Get(url) + if status != http.StatusOK { + log.Errorf("CurrentState:error %d from url %s, %s", status, url.String(), event) + } else { + log.Debugf("Got CurrentState: %s ", event) + } +} +---- +<1> Replace `` with the FQDN of the node that is generating the PTP events. For example, `compute-1.example.com`. diff --git a/modules/ptp-events-consumer-application.adoc b/modules/ptp-events-consumer-application.adoc index e8886a4ba4..43c0a8a216 100644 --- a/modules/ptp-events-consumer-application.adoc +++ b/modules/ptp-events-consumer-application.adoc @@ -3,7 +3,7 @@ // * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: REFERENCE -[id="ptp-events-consumer-application_{context}"] +[id="ptp-events-consumer-application-{ptp-events-rest-api}_{context}"] = PTP events consumer application reference PTP event consumer applications require the following features: @@ -39,7 +39,7 @@ func getEvent(w http.ResponseWriter, req *http.Request) { ---- .Example PTP events createSubscription function in Go -[source,go] +[source,go,subs="attributes+"] ---- import ( "github.com/redhat-cne/sdk-go/pkg/pubsub" @@ -49,13 +49,13 @@ v1pubsub "github.com/redhat-cne/sdk-go/v1/pubsub" // Subscribe to PTP events using REST API s1,_:=createsubscription("/cluster/node//sync/sync-status/os-clock-sync-state") <1> -s2,_:=createsubscription("/cluster/node//sync/ptp-status/ptp-clock-class-change") +s2,_:=createsubscription("/cluster/node//sync/ptp-status/class-change") s3,_:=createsubscription("/cluster/node//sync/ptp-status/lock-state") // Create PTP event subscriptions POST func createSubscription(resourceAddress string) (sub pubsub.PubSub, err error) { var status int - apiPath:= "/api/ocloudNotifications/v1/" + apiPath:= "/api/ocloudNotifications/{ptp-events-rest-api}/" localAPIAddr:=localhost:8989 // vDU service API address apiAddr:= "localhost:8089" // event framework API address @@ -85,14 +85,14 @@ func createSubscription(resourceAddress string) (sub pubsub.PubSub, err error) { <1> Replace `` with the FQDN of the node that is generating the PTP events. For example, `compute-1.example.com`. .Example PTP events consumer getCurrentState function in Go -[source,go] +[source,go,subs="attributes+"] ---- //Get PTP event state for the resource func getCurrentState(resource string) { //Create publisher url := &types.URI{URL: url.URL{Scheme: "http", Host: localhost:8989, - Path: fmt.SPrintf("/api/ocloudNotifications/v1/%s/CurrentState",resource}} + Path: fmt.SPrintf("/api/ocloudNotifications/{ptp-events-rest-api}/%s/CurrentState",resource}} rc := restclient.New() status, event := rc.Get(url) if status != http.StatusOK { diff --git a/modules/ptp-getting-the-current-ptp-clock-status.adoc b/modules/ptp-getting-the-current-ptp-clock-status.adoc deleted file mode 100644 index dedcdbd928..0000000000 --- a/modules/ptp-getting-the-current-ptp-clock-status.adoc +++ /dev/null @@ -1,46 +0,0 @@ -// Module included in the following assemblies: -// -// * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc - -:_mod-docs-content-type: REFERENCE -[id="ptp-getting-the-current-ptp-clock-status_{context}"] -= Getting the current PTP clock status - -To get the current PTP status for the node, send a `GET` action to one of the following event REST APIs: - -* `+http://localhost:8081/api/ocloudNotifications/v1/cluster/node//sync/ptp-status/lock-state/CurrentState+` - -* `+http://localhost:8081/api/ocloudNotifications/v1/cluster/node//sync/sync-status/os-clock-sync-state/CurrentState+` - -* `+http://localhost:8081/api/ocloudNotifications/v1/cluster/node//sync/ptp-status/ptp-clock-class-change/CurrentState+` - -The response is a cloud native event JSON object. For example: - -.Example lock-state API response -[source,json] ----- -{ - "id": "c1ac3aa5-1195-4786-84f8-da0ea4462921", - "type": "event.sync.ptp-status.ptp-state-change", - "source": "/cluster/node/compute-1.example.com/sync/ptp-status/lock-state", - "dataContentType": "application/json", - "time": "2023-01-10T02:41:57.094981478Z", - "data": { - "version": "v1", - "values": [ - { - "resource": "/cluster/node/compute-1.example.com/ens5fx/master", - "dataType": "notification", - "valueType": "enumeration", - "value": "LOCKED" - }, - { - "resource": "/cluster/node/compute-1.example.com/ens5fx/master", - "dataType": "metric", - "valueType": "decimal64.3", - "value": "29" - } - ] - } -} ----- diff --git a/modules/ptp-reference-deployment-and-service-crs-v2.adoc b/modules/ptp-reference-deployment-and-service-crs-v2.adoc new file mode 100644 index 0000000000..7e582037bb --- /dev/null +++ b/modules/ptp-reference-deployment-and-service-crs-v2.adoc @@ -0,0 +1,108 @@ +// Module included in the following assemblies: +// +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc + +:_mod-docs-content-type: REFERENCE +[id="ptp-reference-deployment-and-service-crs-v2_{context}"] += Reference event consumer deployment and service CRs using PTP events REST API v2 + +Use the following example PTP event consumer custom resources (CRs) as a reference when deploying your PTP events consumer application for use with the PTP events REST API v2. + +.Reference cloud event consumer namespace +[source,yaml] +---- +apiVersion: v1 +kind: Namespace +metadata: + name: cloud-events + labels: + security.openshift.io/scc.podSecurityLabelSync: "false" + pod-security.kubernetes.io/audit: "privileged" + pod-security.kubernetes.io/enforce: "privileged" + pod-security.kubernetes.io/warn: "privileged" + name: cloud-events + openshift.io/cluster-monitoring: "true" + annotations: + workload.openshift.io/allowed: management +---- + +.Reference cloud event consumer deployment +[source,yaml] +---- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cloud-consumer-deployment + namespace: cloud-events + labels: + app: consumer +spec: + replicas: 1 + selector: + matchLabels: + app: consumer + template: + metadata: + annotations: + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + labels: + app: consumer + spec: + nodeSelector: + node-role.kubernetes.io/worker: "" + serviceAccountName: consumer-sa + containers: + - name: cloud-event-consumer + image: cloud-event-consumer + imagePullPolicy: Always + args: + - "--local-api-addr=consumer-events-subscription-service.cloud-events.svc.cluster.local:9043" + - "--api-path=/api/ocloudNotifications/v2/" + - "--api-addr=127.0.0.1:8089" + - "--api-version=2.0" + - "--http-event-publishers=ptp-event-publisher-service-NODE_NAME.openshift-ptp.svc.cluster.local:9043" + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: CONSUMER_TYPE + value: "PTP" + - name: ENABLE_STATUS_CHECK + value: "true" + volumes: + - name: pubsubstore + emptyDir: {} +---- + +.Reference cloud event consumer service account +[source,yaml] +---- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: consumer-sa + namespace: cloud-events +---- + +.Reference cloud event consumer service +[source,yaml] +---- +apiVersion: v1 +kind: Service +metadata: + annotations: + prometheus.io/scrape: "true" + name: consumer-events-subscription-service + namespace: cloud-events + labels: + app: consumer-service +spec: + ports: + - name: sub-port + port: 9043 + selector: + app: consumer + sessionAffinity: None + type: ClusterIP +---- diff --git a/modules/ptp-reference-deployment-and-service-crs.adoc b/modules/ptp-reference-deployment-and-service-crs.adoc index 1f4b7d14f8..c71e5b1576 100644 --- a/modules/ptp-reference-deployment-and-service-crs.adoc +++ b/modules/ptp-reference-deployment-and-service-crs.adoc @@ -3,7 +3,7 @@ // * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: REFERENCE -[id="ptp-reference-deployment-and-service-crs_{context}"] +[id="ptp-reference-deployment-and-service-crs-{ptp-events-rest-api}_{context}"] = Reference cloud-event-proxy deployment and service CRs Use the following example `cloud-event-proxy` deployment and subscriber service CRs as a reference when deploying your PTP events consumer application. diff --git a/modules/ptp-subscribing-consumer-app-to-events.adoc b/modules/ptp-subscribing-consumer-app-to-events.adoc index cc2dfce734..53651e434b 100644 --- a/modules/ptp-subscribing-consumer-app-to-events.adoc +++ b/modules/ptp-subscribing-consumer-app-to-events.adoc @@ -1,81 +1,32 @@ // Module included in the following assemblies: // +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc // * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: REFERENCE -[id="ptp-subscribing-consumer-app-to-events_{context}"] -= Subscribing the consumer application to PTP events +[id="ptp-subscribing-consumer-app-to-events-{ptp-events-rest-api}_{context}"] += Subscribing to PTP events with the REST API {ptp-events-rest-api} -Before the PTP events consumer application can poll for events, you need to subscribe the application to the event producer. +ifeval::["{ptp-events-rest-api}" == "v2"] +Deploy your `cloud-event-consumer` application container and subscribe the `cloud-event-consumer` application to PTP events posted by the `cloud-event-proxy` container in the pod managed by the PTP Operator. -[id="ptp-sub-lock-state-events_{context}"] -== Subscribing to PTP lock-state events +Subscribe consumer applications to PTP events by sending a `POST` request to `\http://ptp-event-publisher-service-NODE_NAME.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/subscriptions` passing the appropriate subscription request payload. -To create a subscription for PTP `lock-state` events, send a `POST` action to the cloud event API at `+http://localhost:8081/api/ocloudNotifications/v1/subscriptions+` with the following payload: +[NOTE] +==== +`9043` is the default port for the `cloud-event-proxy` container deployed in the PTP event producer pod. +You can configure a different port for your application as required. +==== +endif::[] -[source,json] ----- -{ -"endpointUri": "http://localhost:8989/event", -"resource": "/cluster/node//sync/ptp-status/lock-state", -} ----- +ifeval::["{ptp-events-rest-api}" == "v1"] +Deploy your `cloud-event-consumer` application container and `cloud-event-proxy` sidecar container in a separate application pod. -.Example response -[source,json] ----- -{ -"id": "e23473d9-ba18-4f78-946e-401a0caeff90", -"endpointUri": "http://localhost:8989/event", -"uriLocation": "http://localhost:8089/api/ocloudNotifications/v1/subscriptions/e23473d9-ba18-4f78-946e-401a0caeff90", -"resource": "/cluster/node//sync/ptp-status/lock-state", -} ----- +Subscribe the `cloud-event-consumer` application to PTP events posted by the `cloud-event-proxy` container at `\http://localhost:8089/api/ocloudNotifications/v1/` in the application pod. -[id="ptp-sub-os-clock-sync-state_{context}"] -== Subscribing to PTP os-clock-sync-state events - -To create a subscription for PTP `os-clock-sync-state` events, send a `POST` action to the cloud event API at `+http://localhost:8081/api/ocloudNotifications/v1/subscriptions+` with the following payload: - -[source,json] ----- -{ -"endpointUri": "http://localhost:8989/event", -"resource": "/cluster/node//sync/sync-status/os-clock-sync-state", -} ----- - -.Example response -[source,json] ----- -{ -"id": "e23473d9-ba18-4f78-946e-401a0caeff90", -"endpointUri": "http://localhost:8989/event", -"uriLocation": "http://localhost:8089/api/ocloudNotifications/v1/subscriptions/e23473d9-ba18-4f78-946e-401a0caeff90", -"resource": "/cluster/node//sync/sync-status/os-clock-sync-state", -} ----- - -[id="ptp-sub-ptp-clock-class-change_{context}"] -== Subscribing to PTP ptp-clock-class-change events - -To create a subscription for PTP `ptp-clock-class-change` events, send a `POST` action to the cloud event API at `+http://localhost:8081/api/ocloudNotifications/v1/subscriptions+` with the following payload: - -[source,json] ----- -{ -"endpointUri": "http://localhost:8989/event", -"resource": "/cluster/node//sync/ptp-status/ptp-clock-class-change", -} ----- - -.Example response -[source,json] ----- -{ -"id": "e23473d9-ba18-4f78-946e-401a0caeff90", -"endpointUri": "http://localhost:8989/event", -"uriLocation": "http://localhost:8089/api/ocloudNotifications/v1/subscriptions/e23473d9-ba18-4f78-946e-401a0caeff90", -"resource": "/cluster/node//sync/ptp-status/ptp-clock-class-change", -} ----- +[NOTE] +==== +`9089` is the default port for the `cloud-event-consumer` container deployed in the application pod. +You can configure a different port for your application as required. +==== +endif::[] diff --git a/modules/ptp-verifying-events-consumer-app-is-receiving-events-v2.adoc b/modules/ptp-verifying-events-consumer-app-is-receiving-events-v2.adoc new file mode 100644 index 0000000000..df2fed2218 --- /dev/null +++ b/modules/ptp-verifying-events-consumer-app-is-receiving-events-v2.adoc @@ -0,0 +1,82 @@ +// Module included in the following assemblies: +// +// * networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc +// * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc + +:_mod-docs-content-type: PROCEDURE +[id="ptp-verifying-events-consumer-app-is-receiving-events-v2_{context}"] += Verifying that the PTP events REST API v2 consumer application is receiving events + +Verify that the `cloud-event-consumer` container in the application pod is receiving Precision Time Protocol (PTP) events. + +.Prerequisites + +* You have installed the OpenShift CLI (`oc`). + +* You have logged in as a user with `cluster-admin` privileges. + +* You have installed and configured the PTP Operator. + +* You have deployed a cloud events application pod and PTP events consumer application. + +.Procedure + +. Check the logs for the deployed events consumer application. +For example, run the following command: ++ +[source,terminal] +---- +$ oc -n cloud-events logs -f deployment/cloud-consumer-deployment +---- ++ +.Example output +[source,terminal] +---- +time = "2024-09-02T13:49:01Z" +level = info msg = "transport host path is set to ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043" +time = "2024-09-02T13:49:01Z" +level = info msg = "apiVersion=2.0, updated apiAddr=ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043, apiPath=/api/ocloudNotifications/v2/" +time = "2024-09-02T13:49:01Z" +level = info msg = "Starting local API listening to :9043" +time = "2024-09-02T13:49:06Z" +level = info msg = "transport host path is set to ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043" +time = "2024-09-02T13:49:06Z" +level = info msg = "checking for rest service health" +time = "2024-09-02T13:49:06Z" +level = info msg = "health check http://ptp-event-publisher-service-compute-1.openshift-ptp.svc.cluster.local:9043/api/ocloudNotifications/v2/health" +time = "2024-09-02T13:49:07Z" +level = info msg = "rest service returned healthy status" +time = "2024-09-02T13:49:07Z" +level = info msg = "healthy publisher; subscribing to events" +time = "2024-09-02T13:49:07Z" +level = info msg = "received event {\"specversion\":\"1.0\",\"id\":\"ab423275-f65d-4760-97af-5b0b846605e4\",\"source\":\"/sync/ptp-status/clock-class\",\"type\":\"event.sync.ptp-status.ptp-clock-class-change\",\"time\":\"2024-09-02T13:49:07.226494483Z\",\"data\":{\"version\":\"1.0\",\"values\":[{\"ResourceAddress\":\"/cluster/node/compute-1.example.com/ptp-not-set\",\"data_type\":\"metric\",\"value_type\":\"decimal64.3\",\"value\":\"0\"}]}}" +---- + +. Optional. Test the REST API by using `oc` and port-forwarding port `9043` from the `linuxptp-daemon` deployment. +For example, run the following command: ++ +[source,terminal] +---- +$ oc port-forward -n openshift-ptp ds/linuxptp-daemon 9043:9043 +---- ++ +.Example output +[source,terminal] +---- +Forwarding from 127.0.0.1:9043 -> 9043 +Forwarding from [::1]:9043 -> 9043 +Handling connection for 9043 +---- ++ +Open a new shell prompt and test the REST API v2 endpoints: ++ +[source,terminal] +---- +$ curl -X GET http://localhost:9043/api/ocloudNotifications/v2/health +---- ++ +.Example output +[source,terminal] +---- +OK +---- diff --git a/modules/ptp-verifying-events-consumer-app-is-receiving-events.adoc b/modules/ptp-verifying-events-consumer-app-is-receiving-events.adoc index 8e339a6875..9fc6709de2 100644 --- a/modules/ptp-verifying-events-consumer-app-is-receiving-events.adoc +++ b/modules/ptp-verifying-events-consumer-app-is-receiving-events.adoc @@ -3,8 +3,8 @@ // * networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc :_mod-docs-content-type: PROCEDURE -[id="ptp-verifying-events-consumer-app-is-receiving-events_{context}"] -= Verifying that the PTP events consumer application is receiving events +[id="ptp-verifying-events-consumer-app-is-receiving-events-{ptp-events-rest-api}_{context}"] += Verifying that the PTP events REST API v1 consumer application is receiving events Verify that the `cloud-event-proxy` container in the application pod is receiving PTP events. diff --git a/networking/ptp/about-ptp.adoc b/networking/ptp/about-ptp.adoc index 4b0f3b2a4f..d5ba4c34e6 100644 --- a/networking/ptp/about-ptp.adoc +++ b/networking/ptp/about-ptp.adoc @@ -36,3 +36,5 @@ include::modules/ptp-dual-nics.adoc[leveloffset=+1] include::modules/ptp-linuxptp-introduction.adoc[leveloffset=+1] include::modules/ptp-overview-of-gnss-grandmaster-clock.adoc[leveloffset=+1] + +include::modules/cnf-about-ptp-and-clock-synchronization.adoc[leveloffset=+1] diff --git a/networking/ptp/configuring-ptp.adoc b/networking/ptp/configuring-ptp.adoc index 8ed99078a8..a7c4ee980e 100644 --- a/networking/ptp/configuring-ptp.adoc +++ b/networking/ptp/configuring-ptp.adoc @@ -25,7 +25,7 @@ include::modules/nw-ptp-configuring-linuxptp-services-as-grandmaster-clock-dual- [role="_additional-resources"] .Additional resources -* xref:../../networking/ptp/using-ptp-events.adoc#cnf-configuring-the-ptp-fast-event-publisher_using-ptp-hardware-fast-events-framework[Configuring the PTP fast event notifications publisher] +* xref:../../networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc#cnf-configuring-the-ptp-fast-event-publisher-v2_ptp-consumer[Configuring the PTP fast event notifications publisher] include::modules/nw-ptp-grandmaster-clock-configuration-reference.adoc[leveloffset=+2] @@ -44,7 +44,7 @@ include::modules/nw-ptp-configuring-linuxptp-services-as-boundary-clock.adoc[lev * xref:../../networking/ptp/configuring-ptp.adoc#cnf-configuring-fifo-priority-scheduling-for-ptp_configuring-ptp[Configuring FIFO priority scheduling for PTP hardware] -* xref:../../networking/ptp/using-ptp-events.adoc#cnf-configuring-the-ptp-fast-event-publisher_using-ptp-hardware-fast-events-framework[Configuring the PTP fast event notifications publisher] +* xref:../../networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc#cnf-configuring-the-ptp-fast-event-publisher-v2_ptp-consumer[Configuring the PTP fast event notifications publisher] include::modules/ptp-configuring-linuxptp-services-as-boundary-clock-dual-nic.adoc[leveloffset=+2] @@ -57,7 +57,7 @@ include::modules/nw-ptp-configuring-linuxptp-services-as-ordinary-clock.adoc[lev * xref:../../networking/ptp/configuring-ptp.adoc#cnf-configuring-fifo-priority-scheduling-for-ptp_configuring-ptp[Configuring FIFO priority scheduling for PTP hardware] -* xref:../../networking/ptp/using-ptp-events.adoc#cnf-configuring-the-ptp-fast-event-publisher_using-ptp-hardware-fast-events-framework[Configuring the PTP fast event notifications publisher] +* xref:../../networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc#cnf-configuring-the-ptp-fast-event-publisher-v2_ptp-consumer[Configuring the PTP fast event notifications publisher] include::modules/nw-columbiaville-ptp-config-refererence.adoc[leveloffset=+2] diff --git a/networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc b/networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc new file mode 100644 index 0000000000..e483b1084c --- /dev/null +++ b/networking/ptp/ptp-cloud-events-consumer-dev-reference-v2.adoc @@ -0,0 +1,55 @@ +:_mod-docs-content-type: ASSEMBLY +:ptp-events-rest-api: v2 +[id="ptp-cloud-events-consumer-dev-reference-{ptp-events-rest-api}"] += Developing PTP events consumer applications with the REST API {ptp-events-rest-api} +include::_attributes/common-attributes.adoc[] +:context: ptp-consumer + +toc::[] + +When developing consumer applications that make use of Precision Time Protocol (PTP) events on a bare-metal cluster node, you deploy your consumer application in a separate application pod. +The consumer application subscribes to PTP events by using the PTP events REST API {ptp-events-rest-api}. + +[NOTE] +==== +The following information provides general guidance for developing consumer applications that use PTP events. +A complete events consumer application example is outside the scope of this information. +==== + +[role="_additional-resources"] +.Additional resources + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#ptp-events-rest-api-reference-v2[PTP events REST API v2 reference] + +include::modules/cnf-about-ptp-fast-event-notifications-framework.adoc[leveloffset=+1] + +include::modules/cnf-about-ptp-events-using-ptp-event-producer-with-o-ran-api.adoc[leveloffset=+1] + +include::modules/cnf-configuring-the-ptp-fast-event-publisher.adoc[leveloffset=+1] + +[role="_additional-resources"] +.Additional resources + +* For a complete example CR that configures `linuxptp` services as an ordinary clock with PTP fast events, see xref:../../networking/ptp/configuring-ptp.adoc#configuring-linuxptp-services-as-ordinary-clock_configuring-ptp[Configuring linuxptp services as ordinary clock]. + +include::modules/ptp-events-consumer-application-v2.adoc[leveloffset=+1] + +include::modules/ptp-reference-deployment-and-service-crs-v2.adoc[leveloffset=+1] + +include::modules/ptp-subscribing-consumer-app-to-events.adoc[leveloffset=+1] + +[role="_additional-resources"] +.Additional resources + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#api-ocloud-notifications-v2-subscriptions_using-ptp-hardware-fast-events-framework-v2[api/ocloudNotifications/v2/subscriptions] + +include::modules/ptp-verifying-events-consumer-app-is-receiving-events-v2.adoc[leveloffset=+1] + +include::modules/cnf-monitoring-fast-events-metrics.adoc[leveloffset=+1] + +[role="_additional-resources"] +.Additional resources + +* xref:../../observability/monitoring/managing-metrics.adoc#managing-metrics[Managing metrics] + +include::modules/nw-ptp-operator-metrics-reference.adoc[leveloffset=+1] diff --git a/networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc b/networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc index c9ce69df03..06500d775b 100644 --- a/networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc +++ b/networking/ptp/ptp-cloud-events-consumer-dev-reference.adoc @@ -1,16 +1,14 @@ :_mod-docs-content-type: ASSEMBLY -[id="ptp-cloud-events-consumer-dev-reference"] -= Developing PTP events consumer applications +:ptp-events-rest-api: v1 +[id="ptp-cloud-events-consumer-dev-reference-{ptp-events-rest-api}"] += Developing PTP events consumer applications with the REST API {ptp-events-rest-api} include::_attributes/common-attributes.adoc[] :context: ptp-consumer toc::[] -When developing consumer applications that make use of Precision Time Protocol (PTP) events on a bare-metal cluster node, you need to deploy your consumer application and a `cloud-event-proxy` container in a separate application pod. -The `cloud-event-proxy` container receives the events from the PTP Operator pod and passes it to the consumer application. -The consumer application subscribes to the events posted in the `cloud-event-proxy` container by using a REST API. - -For more information about deploying PTP events applications, see xref:../../networking/ptp/using-ptp-events.adoc#cnf-about-ptp-fast-event-notifications-framework_using-ptp-hardware-fast-events-framework[About the PTP fast event notifications framework]. +When developing consumer applications that make use of Precision Time Protocol (PTP) events on a bare-metal cluster node, you deploy your consumer application in a separate application pod. +The consumer application subscribes to PTP events by using the PTP events REST API {ptp-events-rest-api}. [NOTE] ==== @@ -18,14 +16,42 @@ The following information provides general guidance for developing consumer appl A complete events consumer application example is outside the scope of this information. ==== +include::snippets/ptp-events-rest-api-v1-deprecation.adoc[] + +[role="_additional-resources"] +.Additional resources + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#ptp-events-rest-api-reference[PTP events REST API v1 reference] + +include::modules/cnf-about-ptp-fast-event-notifications-framework.adoc[leveloffset=+1] + +include::modules/cnf-about-ptp-events-consumer-sidecar-and-http-transport.adoc[leveloffset=+1] + +include::modules/cnf-configuring-the-ptp-fast-event-publisher.adoc[leveloffset=+1] + +[role="_additional-resources"] +.Additional resources + +* For a complete example CR that configures `linuxptp` services as an ordinary clock with PTP fast events, see xref:../../networking/ptp/configuring-ptp.adoc#configuring-linuxptp-services-as-ordinary-clock_configuring-ptp[Configuring linuxptp services as ordinary clock]. + include::modules/ptp-events-consumer-application.adoc[leveloffset=+1] include::modules/ptp-reference-deployment-and-service-crs.adoc[leveloffset=+1] -include::modules/ptp-cloud-event-proxy-sidecar-api.adoc[leveloffset=+1] - include::modules/ptp-subscribing-consumer-app-to-events.adoc[leveloffset=+1] -include::modules/ptp-getting-the-current-ptp-clock-status.adoc[leveloffset=+1] +[role="_additional-resources"] +.Additional resources + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#api-ocloud-notifications-v1-subscriptions_using-ptp-hardware-fast-events-framework-v1[api/ocloudNotifications/v1/subscriptions] include::modules/ptp-verifying-events-consumer-app-is-receiving-events.adoc[leveloffset=+1] + +include::modules/cnf-monitoring-fast-events-metrics.adoc[leveloffset=+1] + +[role="_additional-resources"] +.Additional resources + +* xref:../../observability/monitoring/managing-metrics.adoc#managing-metrics[Managing metrics] + +include::modules/nw-ptp-operator-metrics-reference.adoc[leveloffset=+1] diff --git a/networking/ptp/ptp-events-rest-api-reference-v2.adoc b/networking/ptp/ptp-events-rest-api-reference-v2.adoc new file mode 100644 index 0000000000..e2ba9e9b7a --- /dev/null +++ b/networking/ptp/ptp-events-rest-api-reference-v2.adoc @@ -0,0 +1,29 @@ +:_mod-docs-content-type: ASSEMBLY +[id="ptp-events-rest-api-reference-v2"] += PTP events REST API v2 reference +include::_attributes/common-attributes.adoc[] +:context: using-ptp-hardware-fast-events-framework-v2 + +toc::[] + +Use the following REST API v2 endpoints to subscribe the `cloud-event-consumer` application to Precision Time Protocol (PTP) events posted at `\http://localhost:9043/api/ocloudNotifications/v2` in the PTP events producer pod. + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#api-ocloud-notifications-v2-subscriptions_{context}[`api/ocloudNotifications/v2/subscriptions`] +** `POST`: Creates a new subscription +** `GET`: Retrieves a list of subscriptions +** `DELETE`: Deletes all subscriptions + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#api-ocloud-notifications-v2-subscriptions-subscription_id_{context}[`api/ocloudNotifications/v2/subscriptions/{subscription_id}`] +** `GET`: Returns details for the specified subscription ID +** `DELETE`: Deletes the subscription associated with the specified subscription ID + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#api-ocloudnotifications-v2-health_{context}[`api/ocloudNotifications/v2/health`] +** `GET`: Returns the health status of `ocloudNotifications` API + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#api-ocloudnotifications-v2-publishers_{context}[`api/ocloudNotifications/v2/publishers`] +** `GET`: Returns a list of PTP event publishers for the cluster node + +* xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#resource-address-current-state-v2_{context}[`api/ocloudnotifications/v2/{resource_address}/CurrentState`] +** `GET`: Returns the current state of the event type specified by the `{resouce_address}`. + +include::modules/cnf-fast-event-notifications-api-reference-v2.adoc[leveloffset=+1] diff --git a/networking/ptp/ptp-events-rest-api-reference.adoc b/networking/ptp/ptp-events-rest-api-reference.adoc new file mode 100644 index 0000000000..638e17c15a --- /dev/null +++ b/networking/ptp/ptp-events-rest-api-reference.adoc @@ -0,0 +1,33 @@ +:_mod-docs-content-type: ASSEMBLY +[id="ptp-events-rest-api-reference"] += PTP events REST API v1 reference +include::_attributes/common-attributes.adoc[] +:context: using-ptp-hardware-fast-events-framework-v1 + +toc::[] + +Use the following Precision Time Protocol (PTP) fast event REST API v1 endpoints to subscribe the `cloud-event-consumer` application to PTP events posted by the `cloud-event-proxy` container at [x-]`http://localhost:8089/api/ocloudNotifications/v1/` in the application pod. + +include::snippets/ptp-events-rest-api-v1-deprecation.adoc[] + +The following API endpoints are available: + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#api-ocloud-notifications-v1-subscriptions_{context}[`api/ocloudNotifications/v1/subscriptions`] +** `POST`: Creates a new subscription +** `GET`: Retrieves a list of subscriptions +** `DELETE`: Deletes all subscriptions + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#api-ocloud-notifications-v1-subscriptions-subscription_id_{context}[`api/ocloudNotifications/v1/subscriptions/{subscription_id}`] +** `GET`: Returns details for the specified subscription ID +** `DELETE`: Deletes the subscription associated with the specified subscription ID + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#api-ocloudnotifications-v1-health_{context}[`api/ocloudNotifications/v1/health`] +** `GET`: Returns the health status of `ocloudNotifications` API + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#api-ocloudnotifications-v1-publishers_{context}[`api/ocloudNotifications/v1/publishers`] +** `GET`: Returns a list of PTP event publishers for the cluster node + +* xref:../../networking/ptp/ptp-events-rest-api-reference.adoc#resource-address-current-state_{context}[`api/ocloudnotifications/v1/{resource_address}/CurrentState`] +** `GET`: Returns the current state of one the following event types: `sync-state`, `os-clock-sync-state`, `clock-class`, `lock-state`, or `gnss-sync-status` events + +include::modules/cnf-fast-event-notifications-api-reference.adoc[leveloffset=+1] diff --git a/networking/ptp/using-ptp-events.adoc b/networking/ptp/using-ptp-events.adoc deleted file mode 100644 index 841aa270a7..0000000000 --- a/networking/ptp/using-ptp-events.adoc +++ /dev/null @@ -1,64 +0,0 @@ -:_mod-docs-content-type: ASSEMBLY -[id="using-ptp-hardware-fast-events-framework"] -= Using the PTP hardware fast event notifications framework -include::_attributes/common-attributes.adoc[] -:context: using-ptp-hardware-fast-events-framework - -toc::[] - -Cloud native applications such as virtual RAN (vRAN) require access to notifications about hardware timing events that are critical to the functioning of the overall network. -Precision Time Protocol (PTP) clock synchronization errors can negatively affect the performance and reliability of your low-latency application, for example, a vRAN application running in a distributed unit (DU). - -include::modules/cnf-about-ptp-and-clock-synchronization.adoc[leveloffset=+1] - -include::modules/cnf-about-ptp-fast-event-notifications-framework.adoc[leveloffset=+1] - -include::modules/cnf-configuring-the-ptp-fast-event-publisher.adoc[leveloffset=+1] - -[role="_additional-resources"] -.Additional resources - -* For a complete example CR that configures `linuxptp` services as an ordinary clock with PTP fast events, see xref:../../networking/ptp/configuring-ptp.adoc#configuring-linuxptp-services-as-ordinary-clock_configuring-ptp[Configuring linuxptp services as ordinary clock]. - -[id="subscribing-du-applications-to-ptp-events-rest-api-reference_{context}"] -== Subscribing DU applications to PTP events with the REST API - -Subscribe applications to PTP events by using the resource address `/cluster/node//ptp`, where `` is the cluster node running the DU application. - -Deploy your `cloud-event-consumer` DU application container and `cloud-event-proxy` sidecar container in a separate DU application pod. The `cloud-event-consumer` DU application subscribes to the `cloud-event-proxy` container in the application pod. - -Use the following API endpoints to subscribe the `cloud-event-consumer` DU application to PTP events posted by the `cloud-event-proxy` container at [x-]`http://localhost:8089/api/ocloudNotifications/v1/` in the DU application pod: - -* xref:../../networking/ptp/using-ptp-events.adoc#api-ocloud-notifications-v1-subscriptions_{context}[`/api/ocloudNotifications/v1/subscriptions`] -- `POST`: Creates a new subscription -- `GET`: Retrieves a list of subscriptions -- `DELETE`: Deletes all subscriptions - -* xref:../../networking/ptp/using-ptp-events.adoc#api-ocloud-notifications-v1-subscriptions-subscription_id_{context}[`/api/ocloudNotifications/v1/subscriptions/{subscription_id}`] -- `GET`: Returns details for the specified subscription ID -- `DELETE`: Deletes the subscription associated with the specified subscription ID - -* xref:../../networking/ptp/using-ptp-events.adoc#api-ocloudnotifications-v1-health_{context}[`/api/ocloudNotifications/v1/health`] -- `GET`: Returns the health status of `ocloudNotifications` API - -* xref:../../networking/ptp/using-ptp-events.adoc#api-ocloudnotifications-v1-publishers_{context}[`api/ocloudNotifications/v1/publishers`] -- `GET`: Returns an array of `os-clock-sync-state`, `ptp-clock-class-change`, `lock-state`, and `gnss-sync-status` messages for the cluster node - -* xref:../../networking/ptp/using-ptp-events.adoc#resource-address-current-state_{context}[`/api/ocloudnotifications/v1/{resource_address}/CurrentState`] -- `GET`: Returns the current state of one the following event types: `os-clock-sync-state`, `ptp-clock-class-change`, `lock-state`, or `gnss-state-change` events - -[NOTE] -==== -`9089` is the default port for the `cloud-event-consumer` container deployed in the application pod. You can configure a different port for your DU application as required. -==== - -include::modules/cnf-fast-event-notifications-api-refererence.adoc[leveloffset=+2] - -include::modules/cnf-monitoring-fast-events-metrics.adoc[leveloffset=+1] - -[role="_additional-resources"] -.Additional resources - -* xref:../../observability/monitoring/managing-metrics.adoc#managing-metrics[Managing metrics] - -include::modules/nw-ptp-operator-metrics-reference.adoc[leveloffset=+1] diff --git a/snippets/ptp-event-config-api-v1.adoc b/snippets/ptp-event-config-api-v1.adoc new file mode 100644 index 0000000000..c7e6acdc24 --- /dev/null +++ b/snippets/ptp-event-config-api-v1.adoc @@ -0,0 +1,15 @@ +:_mod-docs-content-type: SNIPPET +[source,yaml] +---- +apiVersion: ptp.openshift.io/v1 +kind: PtpOperatorConfig +metadata: + name: default + namespace: openshift-ptp +spec: + daemonNodeSelector: + node-role.kubernetes.io/worker: "" + ptpEventConfig: + enableEventPublisher: true <1> +---- +<1> Enable PTP fast event notifications by setting `enableEventPublisher` to `true`. diff --git a/snippets/ptp-event-config-api-v2.adoc b/snippets/ptp-event-config-api-v2.adoc new file mode 100644 index 0000000000..b377033281 --- /dev/null +++ b/snippets/ptp-event-config-api-v2.adoc @@ -0,0 +1,18 @@ +:_mod-docs-content-type: SNIPPET +[source,yaml] +---- +apiVersion: ptp.openshift.io/v1 +kind: PtpOperatorConfig +metadata: + name: default + namespace: openshift-ptp +spec: + daemonNodeSelector: + node-role.kubernetes.io/worker: "" + ptpEventConfig: + apiVersion: 2.0 <1> + enableEventPublisher: true <2> +---- +<1> Enable the PTP events REST API v2 for the PTP event producer by setting the `ptpEventConfig.apiVersion` to 2.0. +The default value is 1.0. +<2> Enable PTP fast event notifications by setting `enableEventPublisher` to `true`. diff --git a/snippets/ptp-events-rest-api-v1-deprecation.adoc b/snippets/ptp-events-rest-api-v1-deprecation.adoc new file mode 100644 index 0000000000..0d25e662ff --- /dev/null +++ b/snippets/ptp-events-rest-api-v1-deprecation.adoc @@ -0,0 +1,6 @@ +:_mod-docs-content-type: SNIPPET +[IMPORTANT] +==== +The PTP events REST API v1 will be deprecated in a future release. +When developing applications that use PTP events, use the xref:../../networking/ptp/ptp-events-rest-api-reference-v2.adoc#ptp-events-rest-api-reference-v2[PTP events REST API v2]. +====