diff --git a/configdb/configdb.go b/configdb/configdb.go index 7fe3761..a3a0793 100644 --- a/configdb/configdb.go +++ b/configdb/configdb.go @@ -35,6 +35,9 @@ type ConfigDB struct { PortChannels map[string]PortChannel `json:"PORTCHANNEL,omitempty"` PortChannelMembers map[string]struct{} `json:"PORTCHANNEL_MEMBER,omitempty"` SAG *SAG `json:"SAG,omitempty"` + SFLOW map[string]SFLOWGlobal `json:"SFLOW,omitempty"` + SFLOWCollector map[string]SFLOWCollector `json:"SFLOW_COLLECTOR,omitempty"` + SFLOWSession map[string]SFLOWSession `json:"SFLOW_SESSION,omitempty"` VLANs map[string]VLAN `json:"VLAN,omitempty"` VLANInterfaces map[string]VLANInterface `json:"VLAN_INTERFACE,omitempty"` VLANMembers map[string]VLANMember `json:"VLAN_MEMBER,omitempty"` @@ -80,6 +83,14 @@ func GenerateConfigDB(input *values.Values, platformFile string, environment *p. } features := getFeatures(input.Features) + if input.SFLOW != nil && input.SFLOW.Enabled { + if _, ok := features["sflow"]; !ok { + features["sflow"] = Feature{ + AutoRestart: FeatureModeEnabled, + State: FeatureModeEnabled, + } + } + } rules, tables := getACLRulesAndTables(input.SSHSourceranges) vxlanevpn, vxlanTunnel, vxlanTunnelMap := getVXLAN(input.VTEP, input.LoopbackAddress) @@ -88,6 +99,11 @@ func GenerateConfigDB(input *values.Values, platformFile string, environment *p. return nil, err } + sflow, sflowCollectors, sflowSessions, err := getSFLOW(input.SFLOW, version) + if err != nil { + return nil, err + } + vlanInterfaces, err := getVLANInterfaces(input.VLANs, version) if err != nil { return nil, err @@ -131,6 +147,9 @@ func GenerateConfigDB(input *values.Values, platformFile string, environment *p. PortChannels: getPortChannels(input.PortChannels), PortChannelMembers: getPortChannelMembers(input.PortChannels.List), SAG: sag, + SFLOW: sflow, + SFLOWCollector: sflowCollectors, + SFLOWSession: sflowSessions, VLANs: getVLANs(input.VLANs), VLANInterfaces: vlanInterfaces, VLANMembers: getVLANMembers(input.VLANs), @@ -546,6 +565,63 @@ func getSAG(sag *values.SAG, version *v.Version) (*SAG, error) { }, nil } +func getSFLOW(sflow *values.SFLOW, version *v.Version) (map[string]SFLOWGlobal, map[string]SFLOWCollector, map[string]SFLOWSession, error) { + if sflow == nil || !sflow.Enabled { + return nil, nil, nil, nil + } + + for _, c := range sflow.Collectors { + if c.Name == "" { + return nil, nil, nil, fmt.Errorf("sflow collector name must not be empty") + } + if c.IP == "" { + return nil, nil, nil, fmt.Errorf("sflow collector %q: ip must not be empty", c.Name) + } + if c.VRF != "" && version.Branch == string(v.Branch202111) { + return nil, nil, nil, fmt.Errorf("sflow collector %q: collector_vrf is not supported on the ec202111 branch", c.Name) + } + } + + pollingInterval := max(sflow.PollingInterval, minimumSFLOWPollingInterval) + + global := map[string]SFLOWGlobal{ + "global": { + AdminStatus: defaultAdminStatus, + PollingInterval: strconv.Itoa(pollingInterval), + }, + } + + collectors := make(map[string]SFLOWCollector, len(sflow.Collectors)) + for _, c := range sflow.Collectors { + port := defaultSFLOWCollectorPort + if c.Port != 0 { + port = c.Port + } + collectors[c.Name] = SFLOWCollector{ + CollectorIP: c.IP, + CollectorPort: strconv.Itoa(port), + CollectorVRF: c.VRF, + } + } + + sessions := make(map[string]SFLOWSession, len(sflow.Sessions)) + for _, s := range sflow.Sessions { + status := defaultAdminStatus + if s.Enabled != nil && !*s.Enabled { + status = AdminStatusDown + } + + session := SFLOWSession{ + AdminStatus: status, + } + if s.SampleRate != 0 { + session.SampleRate = strconv.Itoa(s.SampleRate) + } + sessions[s.Interface] = session + } + return global, collectors, sessions, nil +} + func getVLANs(vlans []values.VLAN) map[string]VLAN { configVLANs := make(map[string]VLAN) diff --git a/configdb/configdb_test.go b/configdb/configdb_test.go index fc4c9a8..c18d280 100644 --- a/configdb/configdb_test.go +++ b/configdb/configdb_test.go @@ -609,3 +609,163 @@ func Test_getSAG(t *testing.T) { }) } } + +func Test_getSFLOW(t *testing.T) { + bFalse := false + version202211 := &v.Version{Branch: string(v.Branch202211)} + version202111 := &v.Version{Branch: string(v.Branch202111)} + tests := []struct { + name string + sflow *values.SFLOW + version *v.Version + wantGlobal map[string]SFLOWGlobal + wantCollector map[string]SFLOWCollector + wantSessions map[string]SFLOWSession + wantErr bool + }{ + { + name: "nil input", + sflow: nil, + version: version202211, + }, + { + name: "disabled", + sflow: &values.SFLOW{Enabled: false}, + version: version202211, + }, + { + name: "collector with empty name", + sflow: &values.SFLOW{ + Enabled: true, + Collectors: []values.SFLOWCollector{{IP: "172.17.0.1" }}, + }, + version: version202211, + wantErr: true, + }, + { + name: "collector with empty ip", + sflow: &values.SFLOW{ + Enabled: true, + Collectors: []values.SFLOWCollector{{Name: "goflow2" }}, + }, + version: version202211, + wantErr: true, + }, + { + name: "enabled with default port", + sflow: &values.SFLOW{ + Enabled: true, + PollingInterval: 20, + Collectors: []values.SFLOWCollector{ + {Name: "goflow2", IP: "172.17.0.1", VRF: "default" }, + }, + }, + version: version202211, + wantGlobal: map[string]SFLOWGlobal{ + "global": {AdminStatus: AdminStatusUp, PollingInterval: "20"}, + }, + wantCollector: map[string]SFLOWCollector{ + "goflow2": {CollectorIP: "172.17.0.1", CollectorPort: "6343", CollectorVRF: "default"}, + }, + wantSessions: map[string]SFLOWSession{}, + }, + { + name: "enabled with multiple collectors and sessions", + sflow: &values.SFLOW{ + Enabled: true, + PollingInterval: 20, + Collectors: []values.SFLOWCollector{ + {Name: "primary", IP: "172.17.0.1", Port: 9999}, + {Name: "backup", IP: "172.17.0.1", Port: 6343}, + }, + Sessions: []values.SFLOWSession{ + {Interface: "Ethernet0", SampleRate: 1024}, + }, + }, + version: version202211, + wantGlobal: map[string]SFLOWGlobal{ + "global": {AdminStatus: AdminStatusUp, PollingInterval: "20"}, + }, + wantCollector: map[string]SFLOWCollector{ + "primary": {CollectorIP: "172.17.0.1", CollectorPort: "9999"}, + "backup": {CollectorIP: "172.17.0.1", CollectorPort: "6343"}, + }, + wantSessions: map[string]SFLOWSession{ + "Ethernet0": {AdminStatus: AdminStatusUp, SampleRate: "1024"}, + }, + }, + { + name: "low polling interval and disabled session", + sflow: &values.SFLOW{ + Enabled: true, + PollingInterval: 2, + Sessions: []values.SFLOWSession{ + {Interface: "Ethernet0", Enabled: &bFalse}, + {Interface: "Ethernet4"}, + }, + }, + version: version202211, + wantGlobal: map[string]SFLOWGlobal{ + "global": {AdminStatus: AdminStatusUp, PollingInterval: "5"}, + }, + wantCollector: map[string]SFLOWCollector{}, + wantSessions: map[string]SFLOWSession{ + "Ethernet0": {AdminStatus: AdminStatusDown}, + "Ethernet4": {AdminStatus: AdminStatusUp}, + }, + }, + { + name: "202111 rejects collector_vrf", + sflow: &values.SFLOW{ + Enabled: true, + PollingInterval: 20, + Collectors: []values.SFLOWCollector{ + {Name: "goflow2", IP: "172.17.0.1", VRF: "default"}, + }, + }, + version: version202111, + wantErr: true, + }, + { + name: "202111 without collector_vrf", + sflow: &values.SFLOW{ + Enabled: true, + PollingInterval: 20, + Collectors: []values.SFLOWCollector{ + {Name: "goflow2", IP: "172.17.0.1"}, + }, + Sessions: []values.SFLOWSession{ + {Interface: "Ethernet0", SampleRate: 1024}, + }, + }, + version: version202111, + wantGlobal: map[string]SFLOWGlobal{ + "global": {AdminStatus: AdminStatusUp, PollingInterval: "20"}, + }, + wantCollector: map[string]SFLOWCollector{ + "goflow2": {CollectorIP: "172.17.0.1", CollectorPort: "6343"}, + }, + wantSessions: map[string]SFLOWSession{ + "Ethernet0": {AdminStatus: AdminStatusUp, SampleRate: "1024"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotGlobal, gotCollectors, gotSessions, err := getSFLOW(tt.sflow, tt.version) + if (err != nil) != tt.wantErr { + t.Errorf("getSFLOW() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(tt.wantGlobal, gotGlobal); diff != "" { + t.Errorf("getSFLOW() global diff = %s", diff) + } + if diff := cmp.Diff(tt.wantCollector, gotCollectors); diff != "" { + t.Errorf("getSFLOW() collector diff = %s", diff) + } + if diff := cmp.Diff(tt.wantSessions, gotSessions); diff != "" { + t.Errorf("getSFLOW() session diff = %s", diff) + } + }) + } +} diff --git a/configdb/fields.go b/configdb/fields.go index bee40e8..7930c8e 100644 --- a/configdb/fields.go +++ b/configdb/fields.go @@ -199,6 +199,27 @@ type SAGGlobal struct { GatewayMAC string `json:"gateway_mac,omitempty"` } +type SFLOWGlobal struct { + AdminStatus AdminStatus `json:"admin_state,omitempty"` + PollingInterval string `json:"polling_interval,omitempty"` +} + +type SFLOWCollector struct { + CollectorIP string `json:"collector_ip"` + CollectorPort string `json:"collector_port"` + CollectorVRF string `json:"collector_vrf,omitempty"` +} + +type SFLOWSession struct { + AdminStatus AdminStatus `json:"admin_state"` + SampleRate string `json:"sample_rate,omitempty"` +} + +const ( + defaultSFLOWCollectorPort int = 6343 + minimumSFLOWPollingInterval int = 5 +) + type TaggingMode string const ( diff --git a/tests/6/expected.json b/tests/6/expected.json new file mode 100644 index 0000000..23bb87a --- /dev/null +++ b/tests/6/expected.json @@ -0,0 +1,62 @@ +{ + "DEVICE_METADATA": { + "localhost": { + "frr_mgmt_framework_config": "false", + "hostname": "leaf-sflow", + "hwsku": "Accton-AS7726-32X", + "mac": "aa:aa:aa:aa:aa:aa", + "platform": "x86_64-accton_as7726_32x-r0", + "type": "LeafRouter" + } + }, + "FEATURE": { + "sflow": { + "auto_restart": "enabled", + "state": "enabled" + } + }, + "MGMT_PORT": { + "eth0": { + "admin_status": "up", + "alias": "eth0", + "description": "Management Port" + } + }, + "MGMT_VRF_CONFIG": { + "vrf_global": { + "mgmtVrfEnabled": "false" + } + }, + "NTP": { + "global": { + "src_intf": "eth0" + } + }, + "SFLOW": { + "global": { + "admin_state": "up", + "polling_interval": "5" + } + }, + "SFLOW_COLLECTOR": { + "collector1": { + "collector_ip": "10.0.0.1", + "collector_port": "6343" + }, + "collector2": { + "collector_ip": "10.0.0.2", + "collector_port": "6343", + "collector_vrf": "mgmt" + } + }, + "SFLOW_SESSION": { + "Ethernet0": { + "admin_state": "down", + "sample_rate": "1024" + }, + "Ethernet4": { + "admin_state": "up", + "sample_rate": "2048" + } + } +} diff --git a/tests/6/sonic-config.yaml b/tests/6/sonic-config.yaml new file mode 100644 index 0000000..f9a8c63 --- /dev/null +++ b/tests/6/sonic-config.yaml @@ -0,0 +1,17 @@ +hostname: leaf-sflow +sflow: + enabled: true + polling_interval: 2 + collectors: + - name: collector1 + ip: 10.0.0.1 + port: 6343 + - name: collector2 + ip: 10.0.0.2 + vrf: mgmt + sessions: + - interface: Ethernet0 + enabled: false + sample_rate: 1024 + - interface: Ethernet4 + sample_rate: 2048 diff --git a/tests/6/sonic-environment b/tests/6/sonic-environment new file mode 100644 index 0000000..60e4fcd --- /dev/null +++ b/tests/6/sonic-environment @@ -0,0 +1,2 @@ + PLATFORM=x86_64-accton_as7726_32x-r0 + HWSKU=Accton-AS7726-32X diff --git a/tests/6/sonic_version.yml b/tests/6/sonic_version.yml new file mode 100644 index 0000000..be5cd00 --- /dev/null +++ b/tests/6/sonic_version.yml @@ -0,0 +1,2 @@ +--- +branch: 'ec202111' diff --git a/tests/test.sh b/tests/test.sh index 24f2005..dadf214 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,6 +1,6 @@ #!/bin/bash -for i in 1 2 3 4 5 +for i in 1 2 3 4 5 6 do test_dir=$(pwd)/tests/$i docker run --rm --mac-address aa:aa:aa:aa:aa:aa -v $test_dir:/etc/sonic -v $(pwd)/tests/device:/usr/share/sonic/device:ro -v $test_dir:/sonic sonic-configdb-utils:local generate -i /sonic/sonic-config.yaml -o /sonic/config_db.json diff --git a/values/values.go b/values/values.go index fd77fa1..84cb9a0 100644 --- a/values/values.go +++ b/values/values.go @@ -89,6 +89,26 @@ type SAG struct { MAC string `yaml:"mac"` } +type SFLOW struct { + Enabled bool `yaml:"enabled"` + PollingInterval int `yaml:"polling_interval"` + Collectors []SFLOWCollector `yaml:"collectors"` + Sessions []SFLOWSession `yaml:"sessions"` +} + +type SFLOWCollector struct { + Name string `yaml:"name"` + IP string `yaml:"ip"` + Port int `yaml:"port"` + VRF string `yaml:"vrf"` +} + +type SFLOWSession struct { + Interface string `yaml:"interface"` + Enabled *bool `yaml:"enabled"` + SampleRate int `yaml:"sample_rate"` +} + type Values struct { BGPPorts []string `yaml:"bgp_ports"` Breakouts map[string]string `yaml:"breakouts"` @@ -107,6 +127,7 @@ type Values struct { PortChannels PortChannels `yaml:"portchannels"` Ports *Ports `yaml:"ports"` SAG *SAG `yaml:"sag"` + SFLOW *SFLOW `yaml:"sflow"` SSHSourceranges []string `yaml:"ssh_sourceranges"` VLANs []VLAN `yaml:"vlans"` VLANSubinterfaces []VLANSubinterface `yaml:"vlan_subinterfaces"`