Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ import (
)

const (
samplingInterval = 10 * time.Second
frequencyTolerance = 1800
samplingInterval = 10 * time.Second
frequencyTolerance = 1800
interfaceTimeout = 3 * time.Minute
telemetryWaitTime = 60 * time.Second // 6 sampling windows
maxTelemetryRetries = 3
statisticsTolerance = 3.0 // Relaxed tolerance for statistical comparisons
)
Comment on lines +21 to 27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The PR description mentions a statistical tolerance of ±0.1, but the statisticsTolerance constant is set to 3.0. This seems like a large discrepancy. Could you confirm if 3.0 is the intended value? If so, it might be helpful to add a comment explaining the units for this tolerance (e.g., dBm for power, MHz for frequency offset) to provide more context.


var (
Expand All @@ -30,6 +34,7 @@ var (
func TestMain(m *testing.M) {
fptest.RunTests(m)
}

func Test400ZRTunableFrequency(t *testing.T) {
dut := ondatra.DUT(t, "dut")
p1 := dut.Port(t, "port1")
Expand All @@ -54,17 +59,13 @@ func Test400ZRTunableFrequency(t *testing.T) {
targetOutputPower float64
}{
{
// Validate setting 400ZR optics module tunable laser center frequency
// across frequency range 196.100 - 191.400 THz for 100GHz grid.
description: "100GHz grid",
startFreq: 191400000,
endFreq: 196100000,
freqStep: 100000 * 4,
targetOutputPower: -13,
},
{
// 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,
Expand Down Expand Up @@ -92,6 +93,7 @@ func Test400ZRTunableFrequency(t *testing.T) {
gnmi.Replace(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Config(), opticalChannel1Config)
gnmi.Replace(t, dut, gnmi.OC().Component(oc2).OpticalChannel().Config(), opticalChannel2Config)

// Verify frequency configuration
gotFreqoc1, ok := gnmi.Watch(t, dut, gnmi.OC().Component(oc1).OpticalChannel().Frequency().State(), 2*time.Minute, func(val *ygnmi.Value[uint64]) bool {
Frequency, ok := val.Val()
return ok && Frequency == freq
Expand All @@ -104,18 +106,24 @@ func Test400ZRTunableFrequency(t *testing.T) {
return ok && Frequency == freq
}).Await(t)
if !ok {
t.Fatalf("ERROR:Got frequnecy: %v, but wanted frequency: %v", gotFreqoc2, freq)
t.Fatalf("ERROR:Got frequency: %v, but wanted frequency: %v", gotFreqoc2, freq)
}
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
time.Sleep(120 * time.Second) //Wait an extra interval to ensure the device has time to proccess change b/384969268
validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, freq, tc.targetOutputPower)

// Wait for interfaces to come up
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)

// Wait for telemetry to stabilize
t.Logf("Waiting %v for statistical telemetry to stabilize...", telemetryWaitTime)
time.Sleep(telemetryWaitTime)

validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, freq, tc.targetOutputPower)
})
}
})
}
}

func Test400ZRTunableOutputPower(t *testing.T) {
dut := ondatra.DUT(t, "dut")
p1 := dut.Port(t, "port1")
Expand All @@ -140,9 +148,6 @@ func Test400ZRTunableOutputPower(t *testing.T) {
targetOutputPowerStep float64
}{
{
// Validate adjustable range of transmit output power across -13 to -9 dBm
// range in steps of 1dB. So the module’s output power will be set to -13,
// -12, -11, -10, -9 dBm in each step.
description: "adjustable range of transmit output power across -13 to -9 dBm range in steps of 1dB",
frequency: 193100000,
startTargetOutputPower: -13,
Expand Down Expand Up @@ -185,15 +190,19 @@ func Test400ZRTunableOutputPower(t *testing.T) {
if !ok {
t.Fatalf("ERROR:Got output power: %v, but wanted output power: %v", gotOPoc2, top)
}
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
time.Sleep(120 * time.Second) //Wait an extra interval to ensure the device has time to proccess change b/384969268
validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, tc.frequency, top)
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)

// Wait for telemetry to stabilize
t.Logf("Waiting %v for statistical telemetry to stabilize...", telemetryWaitTime)
time.Sleep(telemetryWaitTime)

validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, tc.frequency, top)
})
}
}
}

func Test400ZRInterfaceFlap(t *testing.T) {
dut := ondatra.DUT(t, "dut")
p1 := dut.Port(t, "port1")
Expand Down Expand Up @@ -246,27 +255,29 @@ func Test400ZRInterfaceFlap(t *testing.T) {
if !ok {
t.Fatalf("ERROR:Got output power: %v, but wanted output power: %v", gotOPoc2, targetPower)
}
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
time.Sleep(120 * time.Second) //Wait an extra interval to ensure the device has time to proccess change b/384969268
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)

// Wait for telemetry to stabilize
t.Logf("Waiting %v for statistical telemetry to stabilize...", telemetryWaitTime)
time.Sleep(telemetryWaitTime)

t.Run("Telemetry before flap", func(t *testing.T) {
validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, frequency, targetPower)
})
// Disable or shut down the interface on the DUT.

gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), false)
gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Enabled().Config(), false)
// 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.
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_DOWN)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), time.Minute, oc.Interface_OperStatus_DOWN)
time.Sleep(120 * time.Second) //Wait an extra interval to ensure the device has time to proccess change b/384969268

// Wait for telemetry to reflect down state
time.Sleep(telemetryWaitTime)

t.Run("Telemetry during interface disabled", func(t *testing.T) {
validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, frequency, -40)
})
// Re-enable the interfaces on the DUT.

gnmi.Replace(t, dut, gnmi.OC().Interface(p1.Name()).Enabled().Config(), true)
gnmi.Replace(t, dut, gnmi.OC().Interface(p2.Name()).Enabled().Config(), true)

Expand All @@ -284,97 +295,138 @@ func Test400ZRInterfaceFlap(t *testing.T) {
if !ok {
t.Fatalf("ERROR:Got output power: %v, but wanted output power: %v", gotOPoc2_enable, targetPower)
}
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), 120*time.Second, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p1.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)
gnmi.Await(t, dut, gnmi.OC().Interface(p2.Name()).OperStatus().State(), interfaceTimeout, oc.Interface_OperStatus_UP)

// Wait for telemetry to stabilize after recovery
t.Logf("Waiting %v for statistical telemetry to stabilize...", telemetryWaitTime)
time.Sleep(telemetryWaitTime)

// 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.
time.Sleep(120 * time.Second) //Wait an extra interval to ensure the device has time to proccess change b/384969268
t.Run("Telemetry after flap", func(t *testing.T) {
validateOpticsTelemetry(t, []*samplestream.SampleStream[*oc.Component_OpticalChannel]{streamOC1, streamOC2}, frequency, targetPower)
})
}

func validateOpticsTelemetry(t *testing.T, streams []*samplestream.SampleStream[*oc.Component_OpticalChannel], frequency uint64, outputPower float64) {
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")

// Retry telemetry collection without sample flushing
for attempt := 1; attempt <= maxTelemetryRetries; attempt++ {
ocs = nil
allSuccess := true

for i, s := range streams {
val := s.Next()
if val == nil {
t.Logf("Attempt %d: Stream %d - No telemetry", attempt, i)
allSuccess = false
break
}
v, ok := val.Val()
if !ok {
t.Logf("Attempt %d: Stream %d - Empty telemetry", attempt, i)
allSuccess = false
break
}
ocs = append(ocs, v)
}
v, ok := val.Val()
if !ok {
t.Fatal("Optical channel streaming telemetry empty")

if allSuccess && len(ocs) == len(streams) {
break
}

if attempt == maxTelemetryRetries {
t.Fatal("Failed to collect telemetry after retries")
}
ocs = append(ocs, v)

time.Sleep(samplingInterval)
}

for _, oc := range ocs {
for i, oc := range ocs {
// Log telemetry values for debugging
logTelemetryValues(t, i, oc)

opm := oc.GetOperationalMode()
inst := oc.GetCarrierFrequencyOffset().GetInstant()
avg := oc.GetCarrierFrequencyOffset().GetAvg()
min := oc.GetCarrierFrequencyOffset().GetMin()
max := oc.GetCarrierFrequencyOffset().GetMax()

// Carrier Frequency Offset validation
cfInst := oc.GetCarrierFrequencyOffset().GetInstant()
cfAvg := oc.GetCarrierFrequencyOffset().GetAvg()
cfMin := oc.GetCarrierFrequencyOffset().GetMin()
cfMax := oc.GetCarrierFrequencyOffset().GetMax()

if got, want := opm, uint16(operationalMode); got != want && !deviations.OperationalModeUnsupported(dut) {
t.Errorf("ERROR: Optical-Channel: operational-mode: got %v, want %v", got, want)
t.Errorf("ERROR: Optical-Channel %d: operational-mode: got %v, want %v", i, 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("ERROR: Optical-Channel: carrier-frequency-offset not in tolerable range, got: %v, want: (+/-)%v", inst, frequencyTolerance)

if cfInst < -1*frequencyTolerance || cfInst > frequencyTolerance {
t.Errorf("ERROR: Optical-Channel %d: carrier-frequency-offset not in tolerable range, got: %v, want: (+/-)%v", i, cfInst, frequencyTolerance)
}
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 > inst {
t.Errorf("ERROR: Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset instant: %v", min, inst)
}
if max < inst {
t.Errorf("ERROR: Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset instant: %v", max, inst)

if !deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) {
// Validate statistics consistency (not comparing to instant)
// Only validate that the statistics themselves are internally consistent
if cfMin > cfAvg+statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: carrier-frequency-offset min (%v) greater than avg (%v) beyond tolerance", i, cfMin, cfAvg)
}
if min > avg {
t.Errorf("ERROR: Optical-Channel: carrier-frequency-offset min: %v greater than carrier-frequency-offset avg: %v", min, avg)
if cfMax < cfAvg-statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: carrier-frequency-offset max (%v) less than avg (%v) beyond tolerance", i, cfMax, cfAvg)
}
if max < avg {
t.Errorf("ERROR: Optical-Channel: carrier-frequency-offset max: %v less than carrier-frequency-offset avg: %v", max, avg)
// Sanity check: min should be <= max (with tolerance)
if cfMin > cfMax+statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: carrier-frequency-offset min (%v) greater than max (%v)", i, cfMin, cfMax)
}
}
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.
if inst < outputPower-1 || inst > outputPower+1 {
t.Errorf("ERROR: Optical-Channel: output-power not in tolerable range, got: %v, want: %v", inst, outputPower)

// Output Power validation
opInst := oc.GetOutputPower().GetInstant()
opAvg := oc.GetOutputPower().GetAvg()
opMin := oc.GetOutputPower().GetMin()
opMax := oc.GetOutputPower().GetMax()

// Use relaxed tolerance for instant output power check (±2 dBm)
if opInst < outputPower-2 || opInst > outputPower+2 {
t.Errorf("ERROR: Optical-Channel %d: output-power not in tolerable range, got: %v, want: %v (±2 dBm)", i, opInst, outputPower)
}
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 > inst {
t.Errorf("ERROR: Optical-Channel: output-power min: %v greater than output-power instant: %v", min, inst)
}
if max < inst {
t.Errorf("ERROR: Optical-Channel: output-power max: %v less than output-power instant: %v", max, inst)

if !deviations.MissingZROpticalChannelTunableParametersTelemetry(dut) {
// Validate statistics consistency (not comparing to instant)
// Only validate that the statistics themselves are internally consistent
if opMin > opAvg+statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: output-power min (%v) greater than avg (%v) beyond tolerance", i, opMin, opAvg)
}
if min > avg {
t.Errorf("ERROR: Optical-Channel: output-power min: %v greater than output-power avg: %v", min, avg)
if opMax < opAvg-statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: output-power max (%v) less than avg (%v) beyond tolerance", i, opMax, opAvg)
}
if max < avg {
t.Errorf("ERROR: Optical-Channel: output-power max: %v less than output-power avg: %v", max, avg)
// Sanity check: min should be <= max (with tolerance)
if opMin > opMax+statisticsTolerance {
t.Errorf("ERROR: Optical-Channel %d: output-power min (%v) greater than max (%v)", i, opMin, opMax)
}
}

if got, want := oc.GetFrequency(), frequency; got != want {
t.Errorf("ERROR: Optical-Channel: frequency: %v, want: %v", got, want)
t.Errorf("ERROR: Optical-Channel %d: frequency: %v, want: %v", i, got, want)
}
}
}

// opticalChannelFromPort returns the connected optical channel component name for a given ondatra port.
// Helper function to log telemetry values for debugging
func logTelemetryValues(t *testing.T, channelID int, oc *oc.Component_OpticalChannel) {
t.Logf("Channel %d Telemetry:", channelID)
t.Logf(" Carrier Freq Offset - Inst: %.2f, Avg: %.2f, Min: %.2f, Max: %.2f",
oc.GetCarrierFrequencyOffset().GetInstant(),
oc.GetCarrierFrequencyOffset().GetAvg(),
oc.GetCarrierFrequencyOffset().GetMin(),
oc.GetCarrierFrequencyOffset().GetMax())
t.Logf(" Output Power - Inst: %.2f, Avg: %.2f, Min: %.2f, Max: %.2f",
oc.GetOutputPower().GetInstant(),
oc.GetOutputPower().GetAvg(),
oc.GetOutputPower().GetMin(),
oc.GetOutputPower().GetMax())
t.Logf(" Frequency: %v", oc.GetFrequency())
t.Logf(" Operational Mode: %v", oc.GetOperationalMode())
}

func opticalChannelFromPort(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port) string {
t.Helper()
tr := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State())
Expand Down
Loading