diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md index d79558ddcb4..19b7daed9b7 100644 --- a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md +++ b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md @@ -1,4 +1,4 @@ -# TRANSCEIVER-11: Telemetry: 400ZR_PLUS Optics logical channels provisioning and related telemetry. +# TRANSCEIVER-11 (400ZR_PLUS): Telemetry: 400ZR_PLUS Optics logical channels provisioning and related telemetry. ## Summary diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/metadata.textproto b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/metadata.textproto new file mode 100644 index 00000000000..ce84a00ccfc --- /dev/null +++ b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/metadata.textproto @@ -0,0 +1,28 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "e5b469c9-f765-4f95-b3dc-af4c926309f5" +plan_id: "TRANSCEIVER-11 (400ZR_PLUS)" +description: "Telemetry: 400ZR_PLUS Optics logical channels provisioning and related telemetry." +testbed: TESTBED_DUT_400ZR_PLUS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_port_to_optical_channel_component_mapping: true + channel_assignment_rate_class_parameters_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + otn_channel_trib_unsupported: true + eth_channel_ingress_parameters_unsupported: true + eth_channel_assignment_cisco_numbering: true + otn_channel_assignment_cisco_numbering: true + } +} diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/zrp_logical_channels_test.go b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/zrp_logical_channels_test.go new file mode 100644 index 00000000000..d0253fa0c24 --- /dev/null +++ b/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/zrp_logical_channels_test.go @@ -0,0 +1,280 @@ +package zrp_logical_channels_test + +import ( + "flag" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/components" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/samplestream" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +const ( + targetOutputPower = -3 + frequency = 193100000 + samplingInterval = 10 * time.Second + timeout = 10 * time.Minute + otnIndex1 = uint32(4001) + otnIndex2 = uint32(4002) + ethernetIndex1 = uint32(40001) + ethernetIndex2 = uint32(40002) +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + } + operationalModeFlag = flag.Int("operational_mode", 5, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +type testcase struct { + desc string + got any + want any +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func Test400ZRLogicalChannels(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + + oc1 := components.OpticalChannelComponentFromPort(t, dut, p1) + oc2 := components.OpticalChannelComponentFromPort(t, dut, p2) + tr1 := gnmi.Get(t, dut, gnmi.OC().Interface(p1.Name()).Transceiver().State()) + tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(p2.Name()).Transceiver().State()) + + cfgplugins.ConfigOpticalChannel(t, dut, oc1, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOTNChannel(t, dut, oc1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigETHChannel(t, dut, p1.Name(), tr1, otnIndex1, ethernetIndex1) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOTNChannel(t, dut, oc2, otnIndex2, ethernetIndex2) + cfgplugins.ConfigETHChannel(t, dut, p2.Name(), tr2, otnIndex2, ethernetIndex2) + + ethChan1 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(ethernetIndex1).State(), samplingInterval) + defer ethChan1.Close() + ethChan2 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(ethernetIndex2).State(), samplingInterval) + defer ethChan2.Close() + otnChan1 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex1).State(), samplingInterval) + defer otnChan1.Close() + otnChan2 := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex2).State(), samplingInterval) + defer otnChan2.Close() + + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + + validateEthernetChannelTelemetry(t, dut, otnIndex1, ethernetIndex1, ethChan1) + validateEthernetChannelTelemetry(t, dut, otnIndex2, ethernetIndex2, ethChan2) + validateOTNChannelTelemetry(t, dut, otnIndex1, ethernetIndex1, oc1, otnChan1) + validateOTNChannelTelemetry(t, dut, otnIndex2, ethernetIndex2, oc2, otnChan2) +} + +func validateEthernetChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, otnChIdx, ethernetChIdx uint32, stream *samplestream.SampleStream[*oc.TerminalDevice_Channel]) { + val := stream.Next() // value received in the gnmi subscription within 10 seconds + if val == nil { + t.Fatalf("Ethernet Channel telemetry stream not received in last 10 seconds") + } + ec, ok := val.Val() + if !ok { + t.Fatalf("Ethernet Channel telemetry stream empty in last 10 seconds") + } + tcs := []testcase{ + { + desc: "Index", + got: ec.GetIndex(), + want: ethernetChIdx, + }, + { + desc: "Description", + got: ec.GetDescription(), + want: "ETH Logical Channel", + }, + { + desc: "Logical Channel Type", + got: ec.GetLogicalChannelType().String(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_ETHERNET.String(), + }, + { + desc: "Trib Protocol", + got: ec.GetTribProtocol().String(), + want: oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE.String(), + }, + } + var assignmentIndexTestcases []testcase + + index := 0 + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + index = 1 + } + + assignment := ec.GetAssignment(uint32(index)) + assignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: assignment.GetIndex(), + want: uint32(index), + }, + { + desc: "Assignment: Logical Channel", + got: assignment.GetLogicalChannel(), + want: otnChIdx, + }, + { + desc: "Assignment: Description", + got: assignment.GetDescription(), + want: "ETH to OTN", + }, + { + desc: "Assignment: Allocation", + got: assignment.GetAllocation(), + want: float64(400), + }, + { + desc: "Assignment: Type", + got: assignment.GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL.String(), + }, + } + tcs = append(tcs, assignmentIndexTestcases...) + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("Ethernet Logical Channel: %s, diff (-got +want):\n%s", tc.desc, diff) + } + }) + } +} + +func validateOTNChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, otnChIdx uint32, ethChIdx uint32, opticalChannel string, stream *samplestream.SampleStream[*oc.TerminalDevice_Channel]) { + val := stream.Next() // value received in the gnmi subscription within 10 seconds + if val == nil { + t.Fatalf("OTN Channel telemetry stream not received in last 10 seconds") + } + cc, ok := val.Val() + if !ok { + t.Fatalf("OTN Channel telemetry stream empty in last 10 seconds") + } + tcs := []testcase{ + { + desc: "Description", + got: cc.GetDescription(), + want: "OTN Logical Channel", + }, + { + desc: "Index", + got: cc.GetIndex(), + want: otnChIdx, + }, + { + desc: "Logical Channel Type", + got: cc.GetLogicalChannelType().String(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN.String(), + }, + } + var opticalChannelAssignmentIndexTestcases []testcase + + index := 0 + if deviations.OTNChannelAssignmentCiscoNumbering(dut) { + opticalChannel = strings.ReplaceAll(opticalChannel, "/", "_") // Ex: ciscoOpticalChannelFormat is OpticalChannel0_0_0_18 + index = 1 + } + + assignment := cc.GetAssignment(uint32(index)) + opticalChannelAssignmentIndexTestcases = []testcase{ + { + desc: "Assignment: Index", + got: assignment.GetIndex(), + want: uint32(index), + }, + { + desc: "Optical Channel Assignment: Optical Channel", + got: assignment.GetOpticalChannel(), + want: opticalChannel, + }, + { + desc: "Optical Channel Assignment: Description", + got: assignment.GetDescription(), + want: "OTN to Optical Channel", + }, + { + desc: "Optical Channel Assignment: Allocation", + got: assignment.GetAllocation(), + want: float64(400), + }, + { + desc: "Optical Channel Assignment: Type", + got: assignment.GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_OPTICAL_CHANNEL.String(), + }, + } + tcs = append(tcs, opticalChannelAssignmentIndexTestcases...) + + if !deviations.OTNChannelTribUnsupported(dut) { + logicalChannelAssignmentTestcases := []testcase{ + { + desc: "Ethernet Assignment: Index", + got: cc.GetAssignment(1).GetIndex(), + want: uint32(1), + }, + { + desc: "Ethernet Assignment: Logical Channel", + got: cc.GetAssignment(1).GetLogicalChannel(), + want: ethChIdx, + }, + { + desc: "Ethernet Assignment: Description", + got: cc.GetAssignment(1).GetDescription(), + want: "OTN to ETH", + }, + { + desc: "Ethernet Assignment: Allocation", + got: cc.GetAssignment(1).GetAllocation(), + want: float64(400), + }, + { + desc: "Ethernet Assignment: Type", + got: cc.GetAssignment(1).GetAssignmentType().String(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL.String(), + }, + } + tcs = append(tcs, logicalChannelAssignmentTestcases...) + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("OTN Logical Channel: %s, diff (-got +want):\n%s", tc.desc, diff) + } + }) + } +} diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md index 567a834ff8d..3ea934270db 100644 --- a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md +++ b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md @@ -1,4 +1,4 @@ -# TRANSCEIVER-5: Configuration: 400ZR_PLUS channel frequency, output TX launch power and operational mode setting. +# TRANSCEIVER-5 (400ZR_PLUS): Configuration: 400ZR_PLUS channel frequency, output TX launch power and operational mode setting. ## Summary diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/metadata.textproto b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/metadata.textproto new file mode 100644 index 00000000000..547fb7e066a --- /dev/null +++ b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "0ff18ddb-5333-4fc3-a2fb-5ac6d372a88f" +plan_id: "TRANSCEIVER-5 (400ZR_PLUS)" +description: "Configuration: 400ZR_PLUS channel frequency, output TX launch power and operational mode setting." +testbed: TESTBED_DUT_400ZR_PLUS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + missing_zr_optical_channel_tunable_parameters_telemetry: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + operational_mode_unsupported: true + } +} diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go new file mode 100644 index 00000000000..6c2ca0872f6 --- /dev/null +++ b/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go @@ -0,0 +1,305 @@ +package zrp_tunable_parameters_test + +import ( + "flag" + "fmt" + "math" + "reflect" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/components" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/samplestream" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +const ( + samplingInterval = 10 * time.Second + frequencyTolerance = 1800 + timeout = 10 * time.Minute +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + } + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + } + operationalModeFlag = flag.Int("operational_mode", 5, "vendor-specific operational-mode for the channel") + operationalMode uint16 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func Test400ZRPlusTunableFrequency(t *testing.T) { + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := components.OpticalChannelComponentFromPort(t, dut, p1) + oc2 := components.OpticalChannelComponentFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), samplingInterval) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), samplingInterval) + defer streamOC2.Close() + tests := []struct { + description string + startFreq uint64 + endFreq uint64 + freqStep uint64 + targetOutputPower float64 + }{ + { + // Validate setting 400ZR++ optics module tunable laser center frequency + // across frequency range 196.100 - 191.375 THz for 75GHz grid. + description: "75GHz grid", + startFreq: 191375000, + endFreq: 196100000, + freqStep: 75000 * 6, + targetOutputPower: -3, + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + for freq := tc.startFreq; freq <= tc.endFreq; freq += tc.freqStep { + t.Run(fmt.Sprintf("Freq: %v", freq), func(t *testing.T) { + if deviations.OperationalModeUnsupported(dut) { + operationalMode = 0 + } + cfgplugins.ConfigOpticalChannel(t, dut, oc1, freq, tc.targetOutputPower, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, freq, tc.targetOutputPower, operationalMode) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, freq, tc.targetOutputPower, oc.Interface_OperStatus_UP) + }) + } + }) + } + +} +func Test400ZRPlusTunableOutputPower(t *testing.T) { + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := components.OpticalChannelComponentFromPort(t, dut, p1) + oc2 := components.OpticalChannelComponentFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), samplingInterval) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), samplingInterval) + defer streamOC2.Close() + tests := []struct { + description string + frequency uint64 + startTargetOutputPower float64 + endTargetOutputPower float64 + targetOutputPowerStep float64 + }{ + { + // Validate adjustable range of transmit output power across -7 to 0 dBm + // range in steps of 1dB. So the module’s output power will be set to -7, + // -6,-5, -4, -3, -2, -1, 0 dBm in each step. + description: "adjustable range of transmit output power across -7 to 0 dBm range in steps of 1dB", + frequency: 193100000, + startTargetOutputPower: -7, + endTargetOutputPower: 0, + targetOutputPowerStep: 1, + }, + } + for _, tc := range tests { + for top := tc.startTargetOutputPower; top <= tc.endTargetOutputPower; top += tc.targetOutputPowerStep { + t.Run(fmt.Sprintf("Target Power: %v", top), func(t *testing.T) { + if deviations.OperationalModeUnsupported(dut) { + operationalMode = 0 + } + + cfgplugins.ConfigOpticalChannel(t, dut, oc1, tc.frequency, top, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, tc.frequency, top, operationalMode) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, tc.frequency, top, oc.Interface_OperStatus_UP) + }) + } + } +} + +func Test400ZRPlusInterfaceFlap(t *testing.T) { + if operationalModeFlag != nil { + operationalMode = uint16(*operationalModeFlag) + } else { + t.Fatalf("Please specify the vendor-specific operational-mode flag") + } + dut := ondatra.DUT(t, "dut") + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + fptest.ConfigureDefaultNetworkInstance(t, dut) + gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), dut)) + gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2.NewOCInterface(p2.Name(), dut)) + oc1 := components.OpticalChannelComponentFromPort(t, dut, p1) + oc2 := components.OpticalChannelComponentFromPort(t, dut, p2) + streamOC1 := samplestream.New(t, dut, gnmi.OC().Component(oc1).State(), samplingInterval) + defer streamOC1.Close() + streamOC2 := samplestream.New(t, dut, gnmi.OC().Component(oc2).State(), samplingInterval) + defer streamOC2.Close() + targetPower := float64(-3) + frequency := uint64(193100000) + + if deviations.OperationalModeUnsupported(dut) { + operationalMode = 0 + } + + cfgplugins.ConfigOpticalChannel(t, dut, oc1, frequency, targetPower, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetPower, operationalMode) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + t.Run("Telemetry before flap", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, targetPower, oc.Interface_OperStatus_UP) + }) + // Disable or shut down the interface on the DUT. + cfgplugins.ToggleInterface(t, dut, p1.Name(), false) + cfgplugins.ToggleInterface(t, dut, p2.Name(), false) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + + // Verify with interfaces in down state both optics are still streaming + // configured value for frequency. + // Verify for the TX output power with interface in down state a decimal64 + // value of -40 dB is streamed. + t.Run("Telemetry during interface disabled", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, -40, oc.Interface_OperStatus_DOWN) + }) + // Re-enable the interfaces on the DUT. + cfgplugins.ToggleInterface(t, dut, p1.Name(), true) + cfgplugins.ToggleInterface(t, dut, p2.Name(), true) + gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + // Verify the ZR optics tune back to the correct frequency and TX output + // power as per the configuration and related telemetry values are updated + // to the value in the normal range again. + t.Run("Telemetry after flap", func(t *testing.T) { + validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component]{streamOC1, streamOC2}, frequency, targetPower, oc.Interface_OperStatus_UP) + }) +} + +func validateOpticsTelemetry(t *testing.T, streams []*samplestream.SampleStream[*oc.Component], frequency uint64, outputPower float64, operStatus oc.E_Interface_OperStatus) { + dut := ondatra.DUT(t, "dut") + var ocs []*oc.Component_OpticalChannel + for _, s := range streams { + val := s.Next() + if val == nil { + t.Fatal("Optical channel streaming telemetry not received") + } + v, ok := val.Val() + if !ok { + t.Fatal("Optical channel streaming telemetry empty") + } + ocs = append(ocs, v.GetOpticalChannel()) + } + + for _, _oc := range ocs { + opm := _oc.GetOperationalMode() + inst := _oc.GetCarrierFrequencyOffset().GetInstant() + avg := _oc.GetCarrierFrequencyOffset().GetAvg() + min := _oc.GetCarrierFrequencyOffset().GetMin() + max := _oc.GetCarrierFrequencyOffset().GetMax() + if got, want := opm, uint16(operationalMode); got != want && !deviations.OperationalModeUnsupported(dut) { + t.Errorf("Optical-Channel: operational-mode: got %v, want %v", got, want) + } + // Laser frequency offset should not be more than +/- 1.8 GHz max from the + // configured centre frequency. + if inst < -1*frequencyTolerance || inst > frequencyTolerance { + t.Errorf("Optical-Channel: carrier-frequency-offset not in tolerable range, got: %v, want: (+/-)%v", inst, frequencyTolerance) + } + for _, ele := range []any{inst, min, max, avg} { + if reflect.TypeOf(ele).Kind() != reflect.Float64 { + t.Fatalf("Value %v is not type float64", ele) + } + } + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + // For reported data check for validity: min <= avg/instant <= max + if min > math.Round(inst) { + t.Errorf("Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset instant: %v", min, inst) + } + if max < math.Round(inst) { + t.Errorf("Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset instant: %v", max, inst) + } + if min > math.Round(avg) { + t.Errorf("Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset avg: %v", min, avg) + } + if max < math.Round(avg) { + t.Errorf("Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset avg: %v", max, avg) + } + } + inst = _oc.GetOutputPower().GetInstant() + avg = _oc.GetOutputPower().GetAvg() + min = _oc.GetOutputPower().GetMin() + max = _oc.GetOutputPower().GetMax() + // When set to a specific target output power, transmit power control + // absolute accuracy should be within +/- 1 dBm of the target configured + // output power. + switch operStatus { + case oc.Interface_OperStatus_UP: + if inst < outputPower-1 || inst > outputPower+1 { + t.Errorf("Optical-Channel: output-power not in tolerable range, got: %v, want: %v", inst, outputPower) + } + case oc.Interface_OperStatus_DOWN: + if inst != -40 { + t.Errorf("Optical-Channel: output-power not in tolerable range, got: %v, want: %v", inst, -40) + } + } + for _, ele := range []any{inst, min, max, avg} { + if reflect.TypeOf(ele).Kind() != reflect.Float64 { + t.Fatalf("Value %v is not type float64", ele) + } + } + if deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) { + t.Log("Skipping Min/Max/Avg Tunable Parameters Telemetry validation. Deviation MissingZROpticalChannelTunableParametersTelemetry enabled.") + } else { + // For reported data check for validity: min <= avg/instant <= max + if min > math.Round(inst) { + t.Errorf("Optical-Channel: output-power min: %v greater than output-power instant: %v", min, inst) + } + if max < math.Round(inst) { + t.Errorf("Optical-Channel: output-power max: %v less than output-power instant: %v", max, inst) + } + if min > math.Round(avg) { + t.Errorf("Optical-Channel: output-power min: %v greater than output-power avg: %v", min, avg) + } + if max < math.Round(avg) { + t.Errorf("Optical-Channel: output-power max: %v less than output-power avg: %v", max, avg) + } + } + if got, want := _oc.GetFrequency(), frequency; got != want { + t.Errorf("Optical-Channel: frequency: %v, want: %v", got, want) + } + } +}