cfgplugins package

October 9, 2025 ยท View on GitHub

Goal

The contributing guide specifies that cfgplugins should be used for DUT configuration generation.

The goal of the featureprofiles cfgplugins is to provide a library of functions that generate reusable configuration snippets. These in turn are used to compose configurations used in featureprofiles tests.

Implementing cfgplugins

The cfgplugins structure should align with the /feature folder, which in turn is roughly aligned with the OpenConfig data model tree. Top level feature folders should have a cfgplugins file using the same name.

Each function in cfgplugins should define a struct to hold the required attributes, which is then passed to the configuration generation function. The configuration parameters should be defined in a struct and passed by reference. (See example)

The code in the cfgplugin function should construct and return an ondatra gnmi.BatchReplace object. (See example)

Test code (_test.go files) should call as many cfgplugins as needed to compose the desired configuration. After assembling the configuration needed for a particular test or subtest, the test should call batch.Set() to replace the portion of the configuration on the DUT described by in the batch object. (See example.

The idea behind placing the .Set call in the test code is this is modifying the DUT and is what is being tested. In other words, we are not testing the configuration generation helper, but rather we are testing if the DUT will accept the configuration.

Function parameters

All exported/public functions from a cfgplugin should have a parameter signature like: (t *testing.T, dut *ondatra.DUTDevice, sb *gnmi.SetBatch, cfg MyConfigToUpdateStruct)

The purpose of the *gnmi.SetBatch is so the caller can pass in a Batch object containing their configuration and the plugin can add to it. This allows the caller to call many cfgplugins to accumulate configuration and then .Set that configuration all at once (or whenever the caller would like to Set the config to satisfy their workflow).

An example function and struct should look like:

// StaticARPConfig holds all per-port static ARP entries.
type StaticARPConfig struct {
 Entries []StaticARPEntry
}

// StaticARPWithMagicUniversalIP configures static ARP and static routes per-port.
func StaticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice, sb *gnmi.SetBatch, cfg StaticARPConfig) *gnmi.SetBatch {
   // implementaton goes here
}

An example usage of the cfgplugin might look like:

  b := &gnmi.SetBatch{}
  cfg := cfgplugins.SecondaryIPConfig{
   Entries: []cfgplugins.SecondaryIPEntry{
    {PortName: "port2", PortAttr: dutPort2IP, DumIP: otgPort2IP.IPv4, MagicMAC: magicMac},
    {PortName: "port3", PortAttr: dutPort3IP, DumIP: otgPort3IP.IPv4, MagicMAC: magicMac},
    {PortName: "port4", PortAttr: dutPort4IP, DumIP: otgPort4IP.IPv4, MagicMAC: magicMac},
   },
  }

  cfgplugins.StaticARPWithSecondaryIP(t, dut, b, cfg)
  b.Set(t, dut)

Deviations and cfgplugins

Deviations affecting configuration generation SHOULD be placed into the relevant cfgplugins functions. This way the deviation is used consistently across all tests, is easier to discover and maintain compared to the deviations being implemented in individual tests.

For example, the sflow plugin includes a minimum sampling rate. But a deviation exists for some platforms which do not support the required rate. The logic to implement the deviation is included in the sflow cfgplugin.