Comments (8)
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: ð_dst, Src: ð_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: ð_dst2, Src: ð_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.
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, fieldsAdd<Fieldname>
method to append new objects to repeated fields, returns new object
- Defaults
- set when calling
<Structname>
,Add<Fieldname>
methods - abstracts away choice
- set when calling
- Validation
- performed during any
Set<Fieldname>
method - bulk check of any failures during rpc call, serialization, Validate() method
- performed during any
- 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
- available via
from openapiart.
+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.
@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.
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.
Comments from @parthpower which we should consider as well (when posting pkg): open-traffic-generator/models#104
from openapiart.
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.
Yes, this looks cleaner to me !
from openapiart.
Related Issues (20)
- Required properties cannot be set with 'None'.
- schema which has required choices are throwing required validation error at time of initialization
- array properties using same schema does not work in go SDK
- Validation throws an error stating invalid type str when an out-of-range int (64 in this case) is passed in ("property port_number shall be of type <class 'int'> at <class 'port_model.Port'> \n got bad of type <class 'str'> , expected min 1, expected max 32"). Error message must not mention type if type is not an issue
- Any 2xx response should satisfy the response requirements for openapi requests
- Resolve references to #/components/parameters/<parameter name>
- Allow defining multiple response codes with empty content in the openapi spec
- Dev workflow is currently very painful
- Support Telemetry in sanppi
- Telemetry Support for go-snappi
- valid formats for supported integer type is not checked
- Fix validate(assertion) methods of snappi and gosnappi for integers
- x-field-pattern now should generate uint32 values instead of int values as packet headers generally does not have negative values
- Current unit test structure has weird nomenclature and is missing multiple edge cases, Need to have a new models schema for openapaiart unit test cases which is readable, well structured and covers all edge cases
- Fix controller CI dp python test cases and do changes in controller for the data type refactor. HOT 1
- Add ability to Connect to secured server using SSL/TLS in python SDK
- Generated .proto seems to convert upper case enum values to lower case enum values
- Un-marhsall for choice properties has issue
- Generate Unit Tests for SDKs
- Schema validation errors are not helpful to pinpoint exact line which has the bad keyword/typo.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from openapiart.