Giter VIP home page Giter VIP logo

Comments (8)

ajbalogh avatar ajbalogh commented on August 22, 2024 1

Go Package Comparison

The following is a comparison between an open-traffic-generator packet forwarding test using the oapi-codegen generated otgclient package on the left and the proposed snappi package on the right.

otgclient package proposed snappi package
tx_port_name := "p1"
rx_port_name := "p2"
flow_name := "f1"
macAddr1 := "00:15:00:00:00:01"
macAddr2 := "00:16:00:00:00:01"
mtu := 1500

ipAddress1 := "20.20.20.1"
ipGateway1 := "20.20.20.2"
ipAddress2 := "30.30.30.1"
ipGateway2 := "30.30.30.2"
ipPrefix := 24

bgpLocalAddress1 := "20.20.20.1"
bgpDutAddress1 := "20.20.20.2"
bgpRouterId1 := "20.20.20.1"
bgpLocalAddress2 := "30.30.30.1"
bgpDutAddress2 := "30.30.30.2"
bgpRouterId2 := "30.30.30.1"
bgpActive := true
bgpAsType := "ebgp"
bgpAsNumber := 3001
nextHopAddress1 := "20.20.20.1"
routeAddress1 := "11.11.11.1"
nextHopAddress2 := "30.30.30.1"
routeAddress2 := "12.12.12.1"
routeCount := "2"
routeStep := "0.0.1.0"
routePrefix := 24

// Test config setup
duration_type := "fixed_packets"
rate_type := "pps"
packets := 10000
pps := 1000
src_mac := macAddr1
src_mac2 := macAddr2
dst_mac1 := "a2:71:d4:59:b3:b8"
dst_mac2 := "6e:d2:47:65:aa:69"
dst_ip := routeAddress2
src_ip := routeAddress1

eth_dst := otgclient.PatternFlowEthernetDst{Choice: "value", Value: &dst_mac1}
eth_src := otgclient.PatternFlowEthernetSrc{Choice: "value", Value: &src_mac}
pkt_ethernet := otgclient.FlowEthernet{Dst: &eth_dst, Src: &eth_src}
ip_dst := otgclient.PatternFlowIpv4Dst{Choice: "value", Value: &dst_ip}
ip_src := otgclient.PatternFlowIpv4Src{Choice: "value", Value: &src_ip}
pkt_ipv4 := otgclient.FlowIpv4{Dst: &ip_dst, Src: &ip_src}

eth_dst2 := otgclient.PatternFlowEthernetDst{Choice: "value", Value: &dst_mac2}
eth_src2 := otgclient.PatternFlowEthernetSrc{Choice: "value", Value: &src_mac2}
pkt2_ethernet := otgclient.FlowEthernet{Dst: &eth_dst2, Src: &eth_src2}
ip_dst2 := otgclient.PatternFlowIpv4Dst{Choice: "value", Value: &src_ip}
ip_src2 := otgclient.PatternFlowIpv4Src{Choice: "value", Value: &dst_ip}
pkt2_ipv4 := otgclient.FlowIpv4{Dst: &ip_dst2, Src: &ip_src2}

resp, err := client.SetConfigWithResponse(ctx, otgclient.SetConfigJSONRequestBody{
    Flows: &[]otgclient.Flow{{
        Duration: &otgclient.FlowDuration{
            Choice: duration_type,
            FixedPackets: &otgclient.FlowFixedPackets{
                Packets: &packets,
            },
        },
        Rate: &otgclient.FlowRate{
            Choice: rate_type,
            Pps:    &pps,
        },
        Name: "f1",
        Packet: &[]otgclient.FlowHeader{
            otgclient.FlowHeader{
                Choice:   "ethernet",
                Ethernet: &pkt_ethernet,
            },
            otgclient.FlowHeader{
                Choice: "ipv4",
                Ipv4:   &pkt_ipv4,
            },
        },
        TxRx: otgclient.FlowTxRx{
            Choice: "port",
            Port: &otgclient.FlowPort{
                TxName: tx_port_name,
                RxName: &rx_port_name,
            },
        },
    }, {
        Duration: &otgclient.FlowDuration{
            Choice: duration_type,
            FixedPackets: &otgclient.FlowFixedPackets{
                Packets: &packets,
            },
        },
        Rate: &otgclient.FlowRate{
            Choice: rate_type,
            Pps:    &pps,
        },
        Name: "f2",
        Packet: &[]otgclient.FlowHeader{
            otgclient.FlowHeader{
                Choice:   "ethernet",
                Ethernet: &pkt2_ethernet,
            },
            otgclient.FlowHeader{
                Choice: "ipv4",
                Ipv4:   &pkt2_ipv4,
            },
        },
        TxRx: otgclient.FlowTxRx{
            Choice: "port",
            Port: &otgclient.FlowPort{
                TxName: rx_port_name,
                RxName: &tx_port_name,
            },
        },
    }},
    Ports: &[]otgclient.Port{
        otgclient.Port{
            Location: &configMap[0].node.reference,
            Name:     tx_port_name,
        },
        otgclient.Port{
            Location: &configMap[1].node.reference,
            Name:     rx_port_name,
        },
    },
    Devices: &[]otgclient.Device{
        {
            Name:          "DeviceGroup1",
            ContainerName: tx_port_name,
            Ethernet: otgclient.DeviceEthernet{
                Name: "Ethernet1",
                Mac:  &macAddr1,
                Mtu:  &mtu,
                Ipv4: &otgclient.DeviceIpv4{
                    Name:    "IPv41",
                    Address: &ipAddress1,
                    Gateway: &ipGateway1,
                    Prefix:  &ipPrefix,
                    Bgpv4: &otgclient.DeviceBgpv4{
                        Name:         "BGP Peer 1",
                        LocalAddress: &bgpLocalAddress1,
                        DutAddress:   &bgpDutAddress1,
                        RouterId:     &bgpRouterId1,
                        Active:       &bgpActive,
                        AsType:       &bgpAsType,
                        AsNumber:     &bgpAsNumber,
                        Bgpv4Routes: &[]otgclient.DeviceBgpv4Route{
                            {
                                Name:           "RR 1",
                                NextHopAddress: &nextHopAddress1,
                                Addresses: &[]otgclient.DeviceBgpv4RouteAddress{
                                    {
                                        Address: &routeAddress1,
                                        Count:   &routeCount,
                                        Step:    &routeStep,
                                        Prefix:  &routePrefix,
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
        {
            Name:          "DeviceGroup2",
            ContainerName: rx_port_name,
            Ethernet: otgclient.DeviceEthernet{
                Name: "Ethernet1",
                Mac:  &macAddr2,
                Mtu:  &mtu,
                Ipv4: &otgclient.DeviceIpv4{
                    Name:    "IPv41",
                    Address: &ipAddress2,
                    Gateway: &ipGateway2,
                    Prefix:  &ipPrefix,
                    Bgpv4: &otgclient.DeviceBgpv4{
                        Name:         "BGP Peer 1",
                        LocalAddress: &bgpLocalAddress2,
                        DutAddress:   &bgpDutAddress2,
                        RouterId:     &bgpRouterId2,
                        Active:       &bgpActive,
                        AsType:       &bgpAsType,
                        AsNumber:     &bgpAsNumber,
                        Bgpv4Routes: &[]otgclient.DeviceBgpv4Route{
                            {
                                Name:           "RR 1",
                                NextHopAddress: &nextHopAddress2,
                                Addresses: &[]otgclient.DeviceBgpv4RouteAddress{
                                    {
                                        Address: &routeAddress2,
                                        Count:   &routeCount,
                                        Step:    &routeStep,
                                        Prefix:  &routePrefix,
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    },
},
)
// get a top level API object
api := snappi.NewApi()

// get an empty config
config := api.NewConfig()

// add and configure ports
tx_port := config.AddPort().
    SetName("p1").
    SetLocation("1.1.1.1;1;1")
rx_port := config.AddPort().
    SetName("p2").
    SetLocation("1.1.1.1;1;2")

// add and configure devices
dg1 := config.AddDevice().
    SetName("DeviceGroup1").
    SetContainerName(tx_port.Name)
eth1 := dg1.Ethernet().
    SetName("Ethernet1").
    SetMac("00:15:00:00:00:01").
    SetMtu(1500)
ip1 := eth1.Ipv4().
    SetName("IPv41").
    SetAddress("20.20.20.1").
    SetPrefix(24).
    SetGateway("20.20.20.2")
bgp1 := ip1.Bgpv4().
    SetName("BGP Peer 1").
    SetLocalAddress(ip1.Address).
    SetDutAddress(ip1.Gateway).
    SetRouterId(ip1.Address).
    SetAsType(BgpAsType.EBGP).
    SetAsNumber(3001)
rr1 := bgp1.AddBgpv4Routes().
    SetName("RR 1").
    SetNextHopAddress(ip1.Address)
rr1_addr := rr1.AddAddresses().
    SetAddress("11.11.11.1").
    SetCount(2).
    SetStep("0.0.1.0").
    SetPrefix(24)

dg2 := config.AddDevice().
    SetName("DeviceGroup2").
    SetContainerName(rx_port.Name)
eth2 := dg1.Ethernet().
    SetName("Ethernet2").
    SetMac("00:16:00:00:00:01").
    SetMtu(1500)
ip2 := eth2.Ipv4().
    SetName("IPv42").
    SetAddress("30.30.30.1").
    SetPrefix(24).
    SetGateway("30.30.30.2")
bgp2 := ip1.Bgpv4().
    SetName("BGP Peer 2").
    SetLocalAddress(ip2.Address).
    SetDutAddress(ip2.Gateway).
    SetRouterId(ip2.Address).
    SetAsType(BgpAsType.EBGP).
    SetAsNumber(3001)
rr2 := bgp1.AddBgpv4Routes().
    SetName("RR 2").
    SetNextHopAddress(ip2.Address)
rr2_addr := rr2.AddAddresses().
    SetAddress("12.12.12.1").
    SetCount(2).
    SetStep("0.0.1.0").
    SetPrefix(24)    

// add and configure a flow
f1 := config.AddFlow().
    SetName("f1")
f1.TxRx().Port().
    SetTxName(tx_port.Name()).
    SetRxName(rx_port.Name())
eth := f1.AddPacket().Ethernet()
eth.Src().SetValue("a2:71:d4:59:b3:b8")
eth.Dst().SetValue("6e:d2:47:65:aa:69")
ipv4 := f1.AddPacket().Ipv4()
ipv4.Src().SetValue(rr1_addr.Address)
ipv4.Dst().SetValue(rr2_addr.Address)
f1.Duration().FixedPackets().SetPackets(10000)
f1.Rate().SetPps(1000)

// set the configuration
result, err := api.SetConfig(config)
// err will contain any validation or SetConfig errors
if err != nil {
    log.Fatal(err)
}

// start transmit
tx_req = api.NewTransmitState()
tx_req.SetState(ts.START())
result, err := api.SetTransmitState(tx_req)
if err != nil {
    log.Fatal(err)
}

// get metrics
metrics_req := api.NewMetricsRequest().SetFlowNames(f1.name)
result, err := api.GetMetrics(metrics_req)
if err != nil {
    log.Fatal(err)
}

from openapiart.

ajbalogh avatar ajbalogh commented on August 22, 2024 1

Snappi Go SDK (proposal)

This is meant to solicit feedback and should be applied to the previous comparison comment.

  • Generated SDK from open-traffic-generator/models/artifacts/otg.proto
  • Custom code for top level API, transports
  • All structs, fields are private
    • Set<Fieldname> method to set fields
    • <Structname> <Fieldname> methods to get structs, fields
    • Add<Fieldname> method to append new objects to repeated fields, returns new object
  • Defaults
    • set when calling <Structname>, Add<Fieldname> methods
    • abstracts away choice
  • Validation
    • performed during any Set<Fieldname> method
    • bulk check of any failures during rpc call, serialization, Validate() method
  • Enums are specific to a struct <structname><fieldname>Enum
    type BgpAsTypeEnum string
    var BgpAsType = struct {
        IBGP BgpAsTypeEnum
        EBGP BgpAsTypeEnum
    }{
        UNKNOWN: "unknown",
        IBGP: "internal"
        EBGP: "external"
    }
    bgp2 := ip1.Bgpv4().
        SetName("BGP Peer 2").
        SetAsType(BgpAsType.EBGP)
  • Fluent interface limited to method chaining
    config.AddPort().
        SetName("Tx Port").
        SetLocation("1.1.1.1;1;1")
  • Serialization, deserialization
    • Json/Yaml deserialization on any object
    • allows for an object to be populated using json/yaml
  • Documentation
    • follow best practices suitable for go doc
  • Style
    • consistent formatting using go fmt
  • Intellisense, Auto-completion
  • Ubiquitous
    • available via go get github.com/open-traffic-generator/snappi/go/pkg

from openapiart.

marcushines avatar marcushines commented on August 22, 2024

+1 on the handling of pointer fields values
+1 on the lack of clear definition for enums

so on the first why are these fields "optional" there is often an argument to say the reason the "structure" is pointer based is to allow for "partial updates" as part of the api - you only update things which are "actually" set

Is this how your api consumes these structures?
For value "not present" what are your default values? is there a way to get the defaults from the API?

in proto they have moved to field masks for partial updates they are alittle clunky but overall allow for very clear declaration on values
https://pkg.go.dev/google.golang.org/protobuf/types/known/fieldmaskpb

so one of the first questions we should figure out what is the backing rpc format we care about presenting to the user
if it is rest then that is one thing but if it is grpc then maybe taking the open schema and converting to proto would be a good next step via something like https://github.com/google/gnostic

then once you have proto3 representations of the rpc's the modeling becomes more direct on what struct representations you want?

also you can build your own custom generators as well like what was done for ygot https://github.com/openconfig/ygot
though not sure that is a path you would want to go down

from openapiart.

ashutshkumr avatar ashutshkumr commented on August 22, 2024

@marcushines regarding fields being optional to facilitate partial updates, that's correct.
To determine the default values for missing fields, the generator shall provide language-specific utility.

e.g. we currently have an auto-generated python SDK (aka snappi) in place for which initializing an empty object will already fill in the missing fields (for native types).

# init an object of type Layer1 (with name 'ly')
# and append it to list of layer1 configs
ly = config.layer1.layer1(name='ly')[-1]

# access unset field
print(ly.speed) # output: speed_10_gbps

Checkout hello-snappi for more details. We intend to achieve something similar for Go SDK as well (of course it would be nice to have more inputs here).

What transport and payload to support is indeed a very important consideration and so far the consensus has been to generate an SDK which can at least support JSON/HTTP and protobuf3/gRPC. Why ?

  • constructing config (declaration) and communicating config (serialization and transport) are two separate activities
  • we get a more uniform workflow by keeping former independant of latter (snappi currently only supports JSON/HTTP but by design can be extended to support protobuf3/gRPC)

We currently do generate a valid otg.proto file for every release of Open Traffic Generator API. Although we use openapiart instead of gnostic because of following issues with latter:

  • it only supports a small subset of OpenAPI v3 schema
  • enums are not correctly scoped
  • we lose out on all the documentation (e.g. field meta: description and defaults)

We needed to apply patch for # 2. Check gnostic-generated proto for comparison.

ygot is interesting but needs more assesment in terms of UX.

from openapiart.

ankur-sheth avatar ankur-sheth commented on August 22, 2024

I like the example so far. It would be good if we can extend the example to show stats retrieval and also some error handling?

from openapiart.

ashutshkumr avatar ashutshkumr commented on August 22, 2024

Comments from @parthpower which we should consider as well (when posting pkg): open-traffic-generator/models#104

from openapiart.

ajbalogh avatar ajbalogh commented on August 22, 2024

I was thinking:

  • snappi ci/cd runs openapiart and pushes artifacts to snappi/go
  • end users go get github.com/open-traffic-generator/snappi/go

from openapiart.

ashutshkumr avatar ashutshkumr commented on August 22, 2024

Yes, this looks cleaner to me !

from openapiart.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.