From 528f3407c070fd0c9a1e7204a2951dc89e9ffb5f Mon Sep 17 00:00:00 2001 From: Marco <51787428+MarcoMruz@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:11:46 +0100 Subject: [PATCH] add new mutations and queries to support synce topology from topology discovery (#389) --- .../topology-discovery.graphql.ts | 236 +++++++++++++++++- .../topology-discovery-graphql.ts | 83 ++++++ src/helpers/topology.helpers.ts | 94 ++++++- src/schema/api.graphql | 22 ++ src/schema/nexus-typegen.ts | 64 ++++- src/schema/topology.ts | 48 +++- 6 files changed, 538 insertions(+), 9 deletions(-) diff --git a/src/__generated__/topology-discovery.graphql.ts b/src/__generated__/topology-discovery.graphql.ts index d171d5ee..e90c3cf9 100644 --- a/src/__generated__/topology-discovery.graphql.ts +++ b/src/__generated__/topology-discovery.graphql.ts @@ -509,18 +509,41 @@ export type PtpDeviceConnection = { /** Details specific to PTP (Precision Time Protocol). */ export type PtpDeviceDetails = { __typename?: 'PtpDeviceDetails'; + /** + * How accurate is the clock output to primary reference. This parameter is often automatically determined + * by the device based on the characteristics of its internal clock oscillator and how well it can track + * the reference time. + */ + clock_accuracy: Maybe; + /** Measure of clock traceability. */ + clock_class: Maybe; /** Unique identifier of the clock. */ clock_id: Scalars['String']; /** Type of clock (e.g., ordinary, master). */ clock_type: Scalars['String']; + /** + * Measure of clock precision. How much the clock-output varies when not synchronized to another source. + * The variance is determined by assessing how much the local clock deviates from the ideal time over a certain period, + * often expressed in parts per billion (ppb) or as the standard deviation of the clock's offset. + */ + clock_variance: Maybe; /** Domain of the PTP network. */ domain: Scalars['Int']; + /** Global priority of the clock (the first priority). */ + global_priority: Maybe; /** Unique identifier of the grandmaster clock. */ gm_clock_id: Scalars['String']; /** Unique identifier of the parent clock. */ parent_clock_id: Scalars['String']; /** PTP profile used (e.g., ITU-T G.8275.1). */ ptp_profile: Scalars['String']; + /** + * Indicates the current state of the time recovery process. Time recovery is the process of adjusting + * the local clock to synchronize with a more accurate reference clock. + */ + time_recovery_status: Maybe; + /** User defined value of the second priority. */ + user_priority: Maybe; }; /** Grouped PtpDevice object and associated cursor used by pagination. */ @@ -534,15 +557,33 @@ export type PtpDeviceEdge = { /** Filter for PtpDevice type based on device label and device name. */ export type PtpDeviceFilter = { + /** Regex: clock accuracy to primary reference. */ + clock_accuracy?: InputMaybe; + /** Measure of clock traceability. */ + clock_class?: InputMaybe; + /** Regex: Unique identifier of the clock. */ + clock_id?: InputMaybe; + /** Regex: Type of clock (e.g., ordinary, master). */ + clock_type?: InputMaybe; + /** Regex: measure of clock precision. */ + clock_variance?: InputMaybe; + /** Domain of the PTP network. */ + domain?: InputMaybe; /** Device label. */ label?: InputMaybe; /** Regex of device name. */ name?: InputMaybe; + /** PTP profile used (e.g., ITU-T G.8275.1). */ + ptp_profile?: InputMaybe; + /** Regex: indicates the current state of the time recovery process. */ + time_recovery_status?: InputMaybe; }; /** Port attached to the ptp device. */ export type PtpInterface = Node & { __typename?: 'PtpInterface'; + /** Interface details specific to PTP (Precision Time Protocol). */ + details: Maybe; /** Unique identifier of the object. */ id: Scalars['ID']; /** Identifier of the PtpHas document between interface and device. */ @@ -555,8 +596,6 @@ export type PtpInterface = Node & { ptpDevice: Maybe; /** Link to connected remote ptp device. */ ptpLink: Maybe; - /** State of the PTP process on the interface (e.g. 'master', 'slave', 'disabled', 'passive', 'unknown'). */ - ptpStatus: Maybe; /** Status of the interface from the view of the synced topology ('ok' or 'unknown'). */ status: NodeStatus; }; @@ -570,6 +609,20 @@ export type PtpInterfaceConnection = { pageInfo: PageInfo; }; +/** PTP interface details. */ +export type PtpInterfaceDetails = { + __typename?: 'PtpInterfaceDetails'; + /** Administrative/operational status of the interface (e.g. 'up/up', 'up/down'). */ + admin_oper_status: Scalars['String']; + /** State of the PTP process on the interface (e.g. 'master', 'slave', 'disabled', 'passive', 'unknown'). */ + ptp_status: Scalars['String']; + /** + * Unusable packet timing signal received by the slave, for example, where the packet delay variation is excessive, + * resulting in the slave being unable to meet the output clock performance requirements. + */ + ptsf_unusable: Scalars['String']; +}; + /** Grouped PtpInterface object and associated cursor used by pagination. */ export type PtpInterfaceEdge = { __typename?: 'PtpInterfaceEdge'; @@ -581,8 +634,14 @@ export type PtpInterfaceEdge = { /** Filter for PtpInterface type based on the current interface status and name of the device. */ export type PtpInterfaceFilter = { + /** Regex of administrative/operational status on the interface (e.g. 'up/up', 'up/down'). */ + admin_oper_status?: InputMaybe; /** Regex of interface name. */ name?: InputMaybe; + /** Regex of the PTP process status on the interface. */ + ptp_status?: InputMaybe; + /** Regex of unusable packet timing signal received by the slave. */ + ptsf_unusable?: InputMaybe; /** Status of the interface from the view of the synced topology. */ status?: InputMaybe; }; @@ -655,6 +714,8 @@ export type Query = { * If invalid device identifier is specified, error is returned. */ ptpPathToGmClock: PtpPath; + /** Read synce devices that match specified filter. */ + synceDevices: SynceDeviceConnection; /** * Computation of the diff between two databases per collections - created, deleted, and changed entries. * Only documents that belong to the specified topology are included in the diff. @@ -714,6 +775,13 @@ export type QueryPtpPathToGmClockArgs = { }; +export type QuerySynceDevicesArgs = { + cursor?: InputMaybe; + filters?: InputMaybe; + first?: InputMaybe; +}; + + export type QueryTopologyDiffArgs = { collection_type: TopologyDiffCollectionTypes; new_db: Scalars['String']; @@ -754,12 +822,156 @@ export type SyncResponse = { loaded_devices: Scalars['JSON']; }; +/** Representation of the device in the synce topology. */ +export type SynceDevice = Node & { + __typename?: 'SynceDevice'; + /** Coordinates of the device node on the graph. */ + coordinates: Coordinates; + /** Details of the device. */ + details: SynceDeviceDetails; + /** Unique identifier of the object. */ + id: Scalars['ID']; + /** List of strings that can be used for grouping of synced devices. */ + labels: Maybe>; + /** Human readable name of the device. */ + name: Scalars['String']; + /** Status of the device from the view of the synced topology. */ + status: NodeStatus; + /** List of ports that are present on the device. */ + synceInterfaces: SynceInterfaceConnection; +}; + + +/** Representation of the device in the synce topology. */ +export type SynceDeviceSynceInterfacesArgs = { + cursor?: InputMaybe; + filters?: InputMaybe; + first?: InputMaybe; +}; + +/** Grouped list of SynceDevice objects and pagination metadata. */ +export type SynceDeviceConnection = { + __typename?: 'SynceDeviceConnection'; + /** List of SynceDevice objects. */ + edges: Maybe>>; + /** Pagination metadata. */ + pageInfo: PageInfo; +}; + +/** Details specific to SyncE (Synchronous Ethernet). */ +export type SynceDeviceDetails = { + __typename?: 'SynceDeviceDetails'; + /** Identifier of the reference (for example, source interface) that is used to synchronize the clock. */ + selected_for_use: Maybe; +}; + +/** Grouped SynceDevice object and associated cursor used by pagination. */ +export type SynceDeviceEdge = { + __typename?: 'SynceDeviceEdge'; + /** Pagination cursor for this edge. */ + cursor: Scalars['String']; + /** The associated SynceDevice object. */ + node: Maybe; +}; + +/** Filter for SynceDevice type based on device label and device name. */ +export type SynceDeviceFilter = { + /** Device label. */ + label?: InputMaybe; + /** Regex of device name. */ + name?: InputMaybe; + /** Regex: identifier of the reference (for example, source interface) that is used to synchronize the clock. */ + selected_for_use?: InputMaybe; +}; + +/** Port attached to the SyncE device. */ +export type SynceInterface = Node & { + __typename?: 'SynceInterface'; + /** Interface details specific to SyncE operation. */ + details: Maybe; + /** Unique identifier of the object. */ + id: Scalars['ID']; + /** Identifier of the SynceHas document between interface and device. */ + idHas: Maybe; + /** Identifier of the link that connects this interface to the interface on the remote device */ + idLink: Maybe; + /** Human readable name of the network port. */ + name: Scalars['String']; + /** Status of the interface from the view of the synced topology ('ok' or 'unknown'). */ + status: NodeStatus; + /** Device that owns this interface. */ + synceDevice: Maybe; + /** Link to connected remote synce device. */ + synceLink: Maybe; +}; + +/** Grouped list of SynceInterface objects and pagination metadata. */ +export type SynceInterfaceConnection = { + __typename?: 'SynceInterfaceConnection'; + /** List of SynceInterface objects. */ + edges: Maybe>>; + /** Pagination metadata. */ + pageInfo: PageInfo; +}; + +/** Details specific to SyncE (Synchronous Ethernet). */ +export type SynceInterfaceDetails = { + __typename?: 'SynceInterfaceDetails'; + /** + * Information about why the interface is not qualified for SyncE synchronization + * (set to 'unknown' if the interface is qualified). + */ + not_qualified_due_to: Maybe; + /** + * Information about why the interface is not selected for SyncE synchronization + * (set to 'unknown' if the interface is selected). + */ + not_selected_due_to: Maybe; + /** Statement of whether the interface is qualified for SyncE synchronization. */ + qualified_for_use: Maybe; + /** Quality of the received SyncE signal (for example, 'DNU' or 'PRC'). */ + rx_quality_level: Maybe; + /** Configured SyncE on the port. */ + synce_enabled: Maybe; +}; + +/** Grouped SynceInterface object and associated cursor used by pagination. */ +export type SynceInterfaceEdge = { + __typename?: 'SynceInterfaceEdge'; + /** Pagination cursor for this edge. */ + cursor: Scalars['String']; + /** The associated SynceInterface object. */ + node: Maybe; +}; + +/** Filter for SynceInterface type based on the current interface status and name of the device. */ +export type SynceInterfaceFilter = { + /** Regex of interface name. */ + name?: InputMaybe; + /** Regex: Information about why the interface is not qualified for SyncE synchronization. */ + not_qualified_due_to?: InputMaybe; + /** Regex: Information about why the interface is not selected for SyncE synchronization. */ + not_selected_due_to?: InputMaybe; + /** Regex: Statement of whether the interface is qualified for SyncE synchronization. */ + qualified_for_use?: InputMaybe; + /** Regex: Quality of the received SyncE signal (for example, 'DNU' or 'PRC'). */ + rx_quality_level?: InputMaybe; + /** Status of the interface from the view of the synced topology. */ + status?: InputMaybe; + /** Configured SyncE on the port. */ + synce_enabled?: InputMaybe; +}; + /** Type of the topology from which the diff is created. */ export type TopologyDiffCollectionTypes = /** Network topology. */ | 'net' /** Physical topology. */ - | 'phy'; + | 'phy' + /** PTP topology. */ + | 'ptp' + /** SyncE topology. */ + | 'synce'; /** Response from the topologyDiff query that contains diff between two databases. */ export type TopologyResponse = { @@ -777,6 +989,7 @@ export type TopologyResponse = { /** Present topology types. */ export type TopologyType = + | 'EthTopology' | 'PhysicalTopology' | 'PtpTopology'; @@ -837,16 +1050,16 @@ export type UpdateCoordinatesMutationVariables = Exact<{ export type UpdateCoordinatesMutation = { __typename?: 'Mutation', updateCoordinates: { __typename?: 'CoordinatesResponse', updated: Array } }; -export type PtpDevicePartsFragment = { __typename?: 'PtpDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'PtpDeviceDetails', clock_type: string, domain: number, ptp_profile: string, clock_id: string, parent_clock_id: string, gm_clock_id: string }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', cursor: string, node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpStatus: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } }; +export type PtpDevicePartsFragment = { __typename?: 'PtpDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'PtpDeviceDetails', clock_type: string, domain: number, ptp_profile: string, clock_id: string, parent_clock_id: string, gm_clock_id: string }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', cursor: string, node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } }; export type PtpInterfaceDevicePartsFragment = { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } }; -export type PtpInterfacePartsFragment = { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpStatus: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null }; +export type PtpInterfacePartsFragment = { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null }; export type PtpTopologyQueryVariables = Exact<{ [key: string]: never; }>; -export type PtpTopologyQuery = { __typename?: 'Query', ptpDevices: { __typename?: 'PtpDeviceConnection', edges: Array<{ __typename?: 'PtpDeviceEdge', cursor: string, node: { __typename?: 'PtpDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'PtpDeviceDetails', clock_type: string, domain: number, ptp_profile: string, clock_id: string, parent_clock_id: string, gm_clock_id: string }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', cursor: string, node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpStatus: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } } | null } | null> | null } }; +export type PtpTopologyQuery = { __typename?: 'Query', ptpDevices: { __typename?: 'PtpDeviceConnection', edges: Array<{ __typename?: 'PtpDeviceEdge', cursor: string, node: { __typename?: 'PtpDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'PtpDeviceDetails', clock_type: string, domain: number, ptp_profile: string, clock_id: string, parent_clock_id: string, gm_clock_id: string }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', cursor: string, node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, status: NodeStatus, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, ptpDevice: { __typename?: 'PtpDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, ptpInterfaces: { __typename?: 'PtpInterfaceConnection', edges: Array<{ __typename?: 'PtpInterfaceEdge', node: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string, ptpLink: { __typename?: 'PtpInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } } | null } | null> | null } }; export type PtpPathToGrandMasterQueryVariables = Exact<{ deviceFrom: Scalars['ID']; @@ -854,3 +1067,14 @@ export type PtpPathToGrandMasterQueryVariables = Exact<{ export type PtpPathToGrandMasterQuery = { __typename?: 'Query', ptpPathToGmClock: { __typename?: 'PtpPath', nodes: Array | null } }; + +export type SynceDevicePartsFragment = { __typename?: 'SynceDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'SynceDeviceDetails', selected_for_use: string | null }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', cursor: string, node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, status: NodeStatus, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } }; + +export type SynceInterfaceDevicePartsFragment = { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } }; + +export type SynceInterfacePartsFragment = { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, status: NodeStatus, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null }; + +export type SynceTopologyQueryVariables = Exact<{ [key: string]: never; }>; + + +export type SynceTopologyQuery = { __typename?: 'Query', synceDevices: { __typename?: 'SynceDeviceConnection', edges: Array<{ __typename?: 'SynceDeviceEdge', cursor: string, node: { __typename?: 'SynceDevice', id: string, name: string, status: NodeStatus, labels: Array | null, coordinates: { __typename?: 'Coordinates', x: number, y: number }, details: { __typename?: 'SynceDeviceDetails', selected_for_use: string | null }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', cursor: string, node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, status: NodeStatus, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, synceDevice: { __typename?: 'SynceDevice', id: string, name: string, coordinates: { __typename?: 'Coordinates', x: number, y: number }, synceInterfaces: { __typename?: 'SynceInterfaceConnection', edges: Array<{ __typename?: 'SynceInterfaceEdge', node: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string, synceLink: { __typename?: 'SynceInterface', id: string, idLink: string | null, name: string } | null } | null } | null> | null } } | null } | null } | null } | null> | null } } | null } | null> | null } }; diff --git a/src/external-api/topology-discovery-graphql.ts b/src/external-api/topology-discovery-graphql.ts index eb2a5bcf..196f4df1 100644 --- a/src/external-api/topology-discovery-graphql.ts +++ b/src/external-api/topology-discovery-graphql.ts @@ -19,6 +19,7 @@ import { TopologyType, UpdateCoordinatesMutation, UpdateCoordinatesMutationVariables, + SynceTopologyQuery, } from '../__generated__/topology-discovery.graphql'; import { HasAndInterfacesOutput, @@ -276,6 +277,81 @@ const PTP_PATH = gql` } `; +const SYNCE_TOPOLOGY = gql` + fragment SynceDeviceParts on SynceDevice { + id + name + coordinates { + x + y + } + details { + selected_for_use + } + status + labels + synceInterfaces { + edges { + cursor + node { + ...SynceInterfaceParts + } + } + } + } + + fragment SynceInterfaceDeviceParts on SynceDevice { + id + name + coordinates { + x + y + } + synceInterfaces { + edges { + node { + id + idLink + name + synceLink { + id + idLink + name + } + } + } + } + } + + fragment SynceInterfaceParts on SynceInterface { + id + idLink + name + status + synceDevice { + ...SynceInterfaceDeviceParts + } + synceLink { + id + idLink + synceDevice { + ...SynceInterfaceDeviceParts + } + } + } + + query SynceTopology { + synceDevices { + edges { + cursor + node { + ...SynceDeviceParts + } + } + } + } +`; + function getTopologyDiscoveryApi() { if (!config.topologyEnabled) { return undefined; @@ -381,6 +457,12 @@ function getTopologyDiscoveryApi() { return response.ptpPathToGmClock.nodes; } + async function getSynceTopology(): Promise { + const response = await client.request(SYNCE_TOPOLOGY); + + return response; + } + return { getTopologyDevices, getNetTopologyDevices, @@ -393,6 +475,7 @@ function getTopologyDiscoveryApi() { getPtpTopology, getPtpPathToGrandMaster, updateCoordinates, + getSynceTopology, }; } diff --git a/src/helpers/topology.helpers.ts b/src/helpers/topology.helpers.ts index 239253d5..59ecb887 100644 --- a/src/helpers/topology.helpers.ts +++ b/src/helpers/topology.helpers.ts @@ -1,5 +1,10 @@ import { device as PrismaDevice } from '@prisma/client'; -import { NetTopologyQuery, PtpTopologyQuery, TopologyDevicesQuery } from '../__generated__/topology-discovery.graphql'; +import { + NetTopologyQuery, + PtpTopologyQuery, + SynceTopologyQuery, + TopologyDevicesQuery, +} from '../__generated__/topology-discovery.graphql'; import { ArangoDevice, ArangoEdge, @@ -28,6 +33,10 @@ type PtpDeviceDetails = { gmClockId: string; }; +type SynceDeviceDetails = { + selectedForUse: string | null; +}; + function getLabelsQuery(labelIds: string[]): Record | undefined { return labelIds.length ? { some: { labelId: { in: labelIds } } } : undefined; } @@ -410,3 +419,86 @@ export function makePtpTopologyEdges(ptpDevices?: PtpTopologyQuery) { .filter(omitNullValue) ?? [] ); } + +export function makeSynceDeviceDetails( + device: NonNullable[0]>['node']>, +): SynceDeviceDetails { + return { + selectedForUse: device.details.selected_for_use, + }; +} + +export function makeSynceTopologyNodes(synceDevices?: SynceTopologyQuery) { + return ( + synceDevices?.synceDevices.edges + ?.map((e) => { + const node = e?.node; + if (!node) { + return null; + } + return { + id: toGraphId('GraphNode', node.id), + nodeId: node.id, + name: node.name, + synceDeviceDetails: makeSynceDeviceDetails(node), + status: getStatus(node.status), + labels: node.labels?.map((l) => l) ?? [], + interfaces: + node.synceInterfaces.edges + ?.map((i) => { + const interfaceNode = i?.node; + if (!interfaceNode) { + return null; + } + return { + id: interfaceNode.id, + name: interfaceNode.name, + status: getStatus(interfaceNode.status), + }; + }) + .filter(omitNullValue) ?? [], + coordinates: node.coordinates ?? { x: 0, y: 0 }, + }; + }) + .filter(omitNullValue) ?? [] + ); +} + +export function makeSynceTopologyEdges(synceDevices?: SynceTopologyQuery) { + return ( + synceDevices?.synceDevices.edges + ?.flatMap((e) => { + const device = e?.node ?? null; + if (!device) { + return []; + } + + return device.synceInterfaces.edges + ?.map((i) => { + const deviceInterface = i?.node; + if ( + !deviceInterface || + !deviceInterface.synceLink || + !deviceInterface.synceLink.synceDevice || + !deviceInterface.synceDevice + ) { + return null; + } + + return { + id: `${deviceInterface.id}-${deviceInterface.synceLink.id}`, + source: { + interface: deviceInterface.id, + nodeId: deviceInterface.synceDevice.name, + }, + target: { + interface: deviceInterface.synceLink.id, + nodeId: deviceInterface.synceLink.synceDevice.name, + }, + }; + }) + .filter(omitNullValue); + }) + .filter(omitNullValue) ?? [] + ); +} diff --git a/src/schema/api.graphql b/src/schema/api.graphql index 249c5d39..4febb9e6 100644 --- a/src/schema/api.graphql +++ b/src/schema/api.graphql @@ -511,6 +511,7 @@ type Query { ptpPathToGrandMaster(deviceFrom: String!): [String!] ptpTopology: PtpTopology shortestPath(from: String!, to: String!): [NetRoutingPathNode!]! + synceTopology: SynceTopology topology(filter: FilterTopologyInput): Topology topologyCommonNodes(nodes: [String!]!): TopologyCommonNodes topologyVersionData(version: String!): TopologyVersionData! @@ -552,6 +553,26 @@ type SyncFromNetworkPayload { dataStore: DataStore } +type SynceDeviceDetails { + selectedForUse: String +} + +type SynceGraphNode { + coordinates: GraphNodeCoordinates! + id: ID! + interfaces: [GraphNodeInterface!]! + labels: [String!] + name: String! + nodeId: String! + status: GraphEdgeStatus! + synceDeviceDetails: SynceDeviceDetails! +} + +type SynceTopology { + edges: [GraphEdge!]! + nodes: [SynceGraphNode!]! +} + type Topology { edges: [GraphEdge!]! nodes: [GraphNode!]! @@ -562,6 +583,7 @@ type TopologyCommonNodes { } enum TopologyLayer { + EthTopology PhysicalTopology PtpTopology } diff --git a/src/schema/nexus-typegen.ts b/src/schema/nexus-typegen.ts index de9522d4..fcaaae79 100644 --- a/src/schema/nexus-typegen.ts +++ b/src/schema/nexus-typegen.ts @@ -169,7 +169,7 @@ export interface NexusGenEnums { GraphEdgeStatus: 'ok' | 'unknown'; SortDeviceBy: 'createdAt' | 'name' | 'serviceState'; SortDirection: 'ASC' | 'DESC'; - TopologyLayer: 'PhysicalTopology' | 'PtpTopology'; + TopologyLayer: 'EthTopology' | 'PhysicalTopology' | 'PtpTopology'; } export interface NexusGenScalars { @@ -481,6 +481,26 @@ export interface NexusGenObjects { // root type dataStore?: NexusGenRootTypes['DataStore'] | null; // DataStore }; + SynceDeviceDetails: { + // root type + selectedForUse?: string | null; // String + }; + SynceGraphNode: { + // root type + coordinates: NexusGenRootTypes['GraphNodeCoordinates']; // GraphNodeCoordinates! + id: string; // ID! + interfaces: NexusGenRootTypes['GraphNodeInterface'][]; // [GraphNodeInterface!]! + labels?: string[] | null; // [String!] + name: string; // String! + nodeId: string; // String! + status: NexusGenEnums['GraphEdgeStatus']; // GraphEdgeStatus! + synceDeviceDetails: NexusGenRootTypes['SynceDeviceDetails']; // SynceDeviceDetails! + }; + SynceTopology: { + // root type + edges: NexusGenRootTypes['GraphEdge'][]; // [GraphEdge!]! + nodes: NexusGenRootTypes['SynceGraphNode'][]; // [SynceGraphNode!]! + }; Topology: { // root type edges: NexusGenRootTypes['GraphEdge'][]; // [GraphEdge!]! @@ -939,6 +959,7 @@ export interface NexusGenFieldTypes { ptpPathToGrandMaster: string[] | null; // [String!] ptpTopology: NexusGenRootTypes['PtpTopology'] | null; // PtpTopology shortestPath: NexusGenRootTypes['NetRoutingPathNode'][]; // [NetRoutingPathNode!]! + synceTopology: NexusGenRootTypes['SynceTopology'] | null; // SynceTopology topology: NexusGenRootTypes['Topology'] | null; // Topology topologyCommonNodes: NexusGenRootTypes['TopologyCommonNodes'] | null; // TopologyCommonNodes topologyVersionData: NexusGenRootTypes['TopologyVersionData']; // TopologyVersionData! @@ -968,6 +989,26 @@ export interface NexusGenFieldTypes { // field return type dataStore: NexusGenRootTypes['DataStore'] | null; // DataStore }; + SynceDeviceDetails: { + // field return type + selectedForUse: string | null; // String + }; + SynceGraphNode: { + // field return type + coordinates: NexusGenRootTypes['GraphNodeCoordinates']; // GraphNodeCoordinates! + id: string; // ID! + interfaces: NexusGenRootTypes['GraphNodeInterface'][]; // [GraphNodeInterface!]! + labels: string[] | null; // [String!] + name: string; // String! + nodeId: string; // String! + status: NexusGenEnums['GraphEdgeStatus']; // GraphEdgeStatus! + synceDeviceDetails: NexusGenRootTypes['SynceDeviceDetails']; // SynceDeviceDetails! + }; + SynceTopology: { + // field return type + edges: NexusGenRootTypes['GraphEdge'][]; // [GraphEdge!]! + nodes: NexusGenRootTypes['SynceGraphNode'][]; // [SynceGraphNode!]! + }; Topology: { // field return type edges: NexusGenRootTypes['GraphEdge'][]; // [GraphEdge!]! @@ -1427,6 +1468,7 @@ export interface NexusGenFieldTypeNames { ptpPathToGrandMaster: 'String'; ptpTopology: 'PtpTopology'; shortestPath: 'NetRoutingPathNode'; + synceTopology: 'SynceTopology'; topology: 'Topology'; topologyCommonNodes: 'TopologyCommonNodes'; topologyVersionData: 'TopologyVersionData'; @@ -1456,6 +1498,26 @@ export interface NexusGenFieldTypeNames { // field return type name dataStore: 'DataStore'; }; + SynceDeviceDetails: { + // field return type name + selectedForUse: 'String'; + }; + SynceGraphNode: { + // field return type name + coordinates: 'GraphNodeCoordinates'; + id: 'ID'; + interfaces: 'GraphNodeInterface'; + labels: 'String'; + name: 'String'; + nodeId: 'String'; + status: 'GraphEdgeStatus'; + synceDeviceDetails: 'SynceDeviceDetails'; + }; + SynceTopology: { + // field return type name + edges: 'GraphEdge'; + nodes: 'SynceGraphNode'; + }; Topology: { // field return type name edges: 'GraphEdge'; diff --git a/src/schema/topology.ts b/src/schema/topology.ts index cce08275..387b441f 100644 --- a/src/schema/topology.ts +++ b/src/schema/topology.ts @@ -24,6 +24,8 @@ import { makeNodesMap, makePtpTopologyEdges, makePtpTopologyNodes, + makeSynceTopologyEdges, + makeSynceTopologyNodes, makeTopologyEdges, makeTopologyNodes, } from '../helpers/topology.helpers'; @@ -286,7 +288,7 @@ export const GraphNodeCoordinatesInput = inputObjectType({ export const TopologyLayer = enumType({ name: 'TopologyLayer', - members: ['PhysicalTopology', 'PtpTopology'], + members: ['PhysicalTopology', 'PtpTopology', 'EthTopology'], }); export const UpdateGraphNodeCooordinatesInput = inputObjectType({ @@ -497,3 +499,47 @@ export const PtpTopologyQuery = queryField('ptpTopology', { }; }, }); + +export const SynceDeviceDetails = objectType({ + name: 'SynceDeviceDetails', + definition: (t) => { + t.string('selectedForUse'); + }, +}); + +export const SynceGraphNode = objectType({ + name: 'SynceGraphNode', + definition: (t) => { + t.nonNull.id('id'); + t.nonNull.list.nonNull.field('interfaces', { type: nonNull(GraphNodeInterface) }); + t.nonNull.field('coordinates', { type: GraphNodeCoordinates }); + t.nonNull.string('nodeId'); + t.nonNull.string('name'); + t.nonNull.field('synceDeviceDetails', { type: SynceDeviceDetails }); + t.nonNull.field('status', { type: GraphInterfaceStatus }); + t.list.nonNull.string('labels'); + }, +}); + +export const SynceTopology = objectType({ + name: 'SynceTopology', + definition: (t) => { + t.nonNull.list.field('edges', { type: nonNull(GraphEdge) }); + t.nonNull.list.field('nodes', { type: nonNull(SynceGraphNode) }); + }, +}); + +export const SynceTopologyQuery = queryField('synceTopology', { + type: 'SynceTopology', + resolve: async (_, _args, { topologyDiscoveryGraphQLAPI }) => { + const synceTopologyResult = await topologyDiscoveryGraphQLAPI?.getSynceTopology(); + + const nodes = makeSynceTopologyNodes(synceTopologyResult); + const edges = makeSynceTopologyEdges(synceTopologyResult); + + return { + nodes, + edges, + }; + }, +});