diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9531b5751..a9c0838da 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -92,7 +92,6 @@ stages: [[ -f tools/test/database_config.json ]] && \ export DB_CONFIG_PATH=${PWD}/tools/test/database_config.json - # Run CVL tests pushd build/tests/cvl @@ -115,6 +114,8 @@ stages: ./transformer.test -test.v -logtostderr || STATUS=1 + ./testapp.test -test.v -logtostderr || STATUS=1 + popd exit ${STATUS} diff --git a/config/transformer/models_list b/config/transformer/models_list index c9f26c163..1789f52c9 100644 --- a/config/transformer/models_list +++ b/config/transformer/models_list @@ -1,3 +1,5 @@ #List yang models transformer need to load openconfig-acl.yang openconfig-acl-annot.yang +openconfig-sampling-sflow.yang +openconfig-sampling-sflow-annot.yang diff --git a/debian/sonic-mgmt-common.install b/debian/sonic-mgmt-common.install index 554e10fc9..3eb8cbae5 100644 --- a/debian/sonic-mgmt-common.install +++ b/debian/sonic-mgmt-common.install @@ -1,7 +1,7 @@ # Yang models build/yang/*.yang usr/models/yang build/yang/common/*.yang usr/models/yang -#build/yang/extensions/*.yang usr/models/yang +build/yang/extensions/*.yang usr/models/yang build/yang/sonic/*.yang usr/models/yang build/yang/sonic/common/*.yang usr/models/yang build/yang/annotations/*.yang usr/models/yang diff --git a/models/yang/annotations/openconfig-sampling-sflow-annot.yang b/models/yang/annotations/openconfig-sampling-sflow-annot.yang new file mode 100644 index 000000000..9d5abdf53 --- /dev/null +++ b/models/yang/annotations/openconfig-sampling-sflow-annot.yang @@ -0,0 +1,34 @@ +module openconfig-sampling-sflow-annot { + + yang-version "1"; + + namespace "http://openconfig.net/yang/annotation/oc-sampling-sflow-annot"; + prefix "oc-sampling-annot"; + + import openconfig-extensions { prefix oc-ext; } + import openconfig-sampling-sflow { prefix oc-sampling; } + import sonic-extensions { prefix sonic-ext; } + + deviation /oc-sampling:sampling/oc-sampling:sflow { + deviate add { + sonic-ext:subtree-transformer "sflow_xfmr"; + } + } + + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:collectors { + deviate add { + sonic-ext:subtree-transformer "sflow_collector_xfmr"; + sonic-ext:path-transformer "sflow_collector_path_xfmr"; + } + } + + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:interfaces { + deviate add { + sonic-ext:subtree-transformer "sflow_interface_xfmr"; + sonic-ext:path-transformer "sflow_interface_path_xfmr"; + } + } +} + diff --git a/models/yang/extensions/openconfig-sampling-sflow-deviation.yang b/models/yang/extensions/openconfig-sampling-sflow-deviation.yang new file mode 100644 index 000000000..002bd600d --- /dev/null +++ b/models/yang/extensions/openconfig-sampling-sflow-deviation.yang @@ -0,0 +1,54 @@ +module openconfig-sampling-sflow-deviation { + + yang-version "1.1"; + + // namespace + namespace "http://openconfig.net/yang/sampling/sflow/deviation/extension"; + + prefix "oc-sampling-sflow-dev"; + + import openconfig-extensions { prefix "oc-ext"; } + import openconfig-sampling-sflow { prefix oc-sampling; } + + organization "SONiC"; + contact + "SONiC"; + description + "This is a deviation yang for openconfig sampling model."; + + oc-ext:openconfig-version "0.1.0"; + + revision 2020-06-23 { + description + "Initial version."; + reference "0.1.0"; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:config/oc-sampling:source-address { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:config/oc-sampling:sample-size { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:config/oc-sampling:sampling-rate { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:interfaces/oc-sampling:interface/oc-sampling:state/oc-sampling:packets-sampled { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:state/oc-sampling:source-address { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:state/oc-sampling:sample-size { + deviate not-supported; + } + + deviation /oc-sampling:sampling/oc-sampling:sflow/oc-sampling:collectors/oc-sampling:collector/oc-sampling:state/oc-sampling:packets-sent { + deviate not-supported; + } +} diff --git a/models/yang/openconfig-sampling-sflow.yang b/models/yang/openconfig-sampling-sflow.yang new file mode 100644 index 000000000..8e08ba93f --- /dev/null +++ b/models/yang/openconfig-sampling-sflow.yang @@ -0,0 +1,344 @@ +module openconfig-sampling-sflow { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/sampling/sflow"; + + prefix "oc-sflow"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-yang-types { prefix oc-yang; } + //import openconfig-network-instance { prefix oc-netinst; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to data plane traffic sampling based on sFlow. + + RFC 3176 - InMon Corporation's sFlow: A Method for + Monitoring Traffic in Switched and Routed Networks"; + + oc-ext:openconfig-version "1.0.0"; + + revision "2021-06-23" { + description + "Additional attributes for sFlow global config. + Add network-instance as key for sflow collectors"; + reference "1.0.0"; + } + + revision "2020-06-26" { + description + "Initial revision"; + reference "0.1.0"; + } + + + grouping sflow-interfaces-config { + description + "Configuration data for sFlow data on interfaces."; + + leaf name { + type oc-if:base-interface-ref; + description + "Reference to the interface for sFlow configuration and + state."; + } + + leaf enabled { + type boolean; + description + "Enables or disables sFlow on the interface. If sFlow is + globally disabled, this leaf is ignored. If sFlow + is globally enabled, this leaf may be used to disable it + for a specific interface."; + } + + leaf sampling-rate { + type uint32; + description + "If sFlow is enabled on the interface, this leaf may be + used to override the global sampling rate for a specific + interface. The sampling rate semantics are the same as the + system-wide leaf."; + } + + } + + grouping sflow-interfaces-state { + description + "Operational state data for sFlow data on interfaces"; + + leaf packets-sampled { + type oc-yang:counter64; + description + "Total number of packets sampled from the interface."; + } + } + + grouping sflow-interfaces-top { + description + "Top-level grouping for sFlow data on an interface."; + + container interfaces { + description + "Enclosing container for list of sFlow interfaces."; + + list interface { + key "name"; + description + "List of interfaces with sFlow data."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to list key."; + } + + container config { + description + "Configuration data for sFlow data on interfaces."; + + uses sflow-interfaces-config; + } + + container state { + + config false; + + description + "Operational state data for sFlow data on interfaces."; + + uses sflow-interfaces-config; + uses sflow-interfaces-state; + } + } + } + } + + grouping sflow-collectors-config { + description + "Configuration data for sFlow collectors."; + + leaf address { + type oc-inet:ip-address; + description + "IP address of the sFlow collector."; + } + + leaf port { + type oc-inet:port-number; + default 6343; + description + "UDP port number for the sFlow collector."; + } + + leaf network-instance { + //type oc-netinst:network-instance-ref; + type string; + default "default"; + description + "Reference to the network instance used to reach the + sFlow collector. If uspecified, the collector destination + is reachable in the default network instance."; + } + } + + grouping sflow-collectors-state { + description + "Operational state data for sFlow collectors."; + + leaf packets-sent { + type oc-yang:counter64; + description + "The total number of packets sampled and sent to the + collector."; + } + } + + grouping sflow-collectors-top { + description + "Top-level grouping for data related to sFlow collectors."; + + container collectors { + description + "Enclosing container for list of sFlow collectors."; + + list collector { + key "address port network-instance"; + description + "List of sFlow collectors to send sampling data. Packet + samples are sent to all collectors specified."; + + leaf address { + type leafref { + path "../config/address"; + } + description + "Reference to address list key."; + } + + leaf port { + type leafref { + path "../config/port"; + } + description + "Reference to port list key."; + } + + leaf network-instance { + type leafref { + path "../config/network-instance"; + } + description + "Reference to network instance list key."; + } + + container config { + description + "Configuration data for sFlow collectors."; + + uses sflow-collectors-config; + } + + container state { + + config false; + + description + "Operational state data for sFlow collectors."; + + uses sflow-collectors-config; + uses sflow-collectors-state; + } + } + } + } + + grouping sflow-global-config { + description + "Configuration data for global sflow"; + + leaf enabled { + type boolean; + default false; + description + "Enables or disables sFlow sampling for the device."; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Sets the source IP address for sFlow datagrams sent to + sFlow collectors."; + } + + leaf sampling-rate { + type uint32; + description + "Sets the global packet sampling rate. The rate is + is expressed as an integer N, where the intended sampling + rate is 1/N packets. An implementation may implement the + sampling rate as a statistical average, rather than a strict + periodic sampling. + + The allowable sampling rate range is generally a + property of the system, e.g., determined by the + capability of the hardware."; + } + + leaf sample-size { + type uint16; + units bytes; + default 128; + description + "Sets the maximum number of bytes to be copied from a + sampled packet."; + reference + "RFC 3176 - InMon Corporation's sFlow: A Method for + Monitoring Traffic in Switched and Routed Networks"; + } + + leaf polling-interval { + type uint16 { + range "0|5..300" { + error-message "Polling interval out of range"; + } + } + description "sFlow polling interval"; + } + + leaf agent { + type oc-if:base-interface-ref; + description "sFlow agent interface"; + } + + } + + grouping sflow-global-state { + description + "Operational state data for global sFlow."; + } + + grouping sflow-global-top { + description + "Top-level grouping for global sFlow"; + + container sflow { + description + "Top-level container for sFlow data."; + + container config { + description + "Configuration data for global sFlow."; + + uses sflow-global-config; + } + + container state { + + config false; + + description + "Operational state data for global sFlow."; + + uses sflow-global-config; + uses sflow-global-state; + } + + uses sflow-collectors-top; + uses sflow-interfaces-top; + } + } + + grouping sampling-top { + description + "Top-level grouping for traffic sampling data."; + + container sampling { + description + "Top-level container for data related to traffic sampling + protocols."; + + uses sflow-global-top; + } + } + + // data definition statements + + uses sampling-top; + +} diff --git a/models/yang/sonic/import.mk b/models/yang/sonic/import.mk index b889061ca..e52ff765f 100644 --- a/models/yang/sonic/import.mk +++ b/models/yang/sonic/import.mk @@ -5,4 +5,4 @@ # or glob patterns of basenames (like sonic-telemetry*.yang) can be specified. # Other sonic yangs referred by these will also be copied. # -#SONICYANG_IMPORTS += sonic-sflow.yang +SONICYANG_IMPORTS += sonic-sflow.yang \ No newline at end of file diff --git a/translib/Makefile b/translib/Makefile index fce161392..76b6ead56 100644 --- a/translib/Makefile +++ b/translib/Makefile @@ -24,6 +24,7 @@ TRANSFORMER_SRCS = $(filter ./transformer/%, $(shell find . -name '*.go' -not -n TRANSFORMER_TESTS := $(shell find . -name '*_test.go') TRANSFORMER_TEST_SRCS = $(filter ./transformer/%, $(TRANSFORMER_TESTS)) +TRANSFORMER_TEST_APP_BIN = $(TRANSLIB_TEST_DIR)/testapp.test YANG_DIR = ../build/yang YANG_FILES = $(shell find $(YANG_DIR) -name "*.yang" -not -path "*/annotations/*") YGOT_BINDS = ocbinds/ocbinds.go @@ -37,7 +38,7 @@ XFMR_TEST_MODELS = $(notdir $(wildcard transformer/test/*.yang)) DEFAULT_TARGETS = $(YGOT_BINDS) $(XFMR_MODELS_LIST) ifeq ($(NO_TEST_BINS),) -DEFAULT_TARGETS += $(TRANSLIB_TEST_BIN) $(TRANSL_DB_TEST_BIN) +DEFAULT_TARGETS += $(TRANSLIB_TEST_BIN) $(TRANSL_DB_TEST_BIN) $(TRANSFORMER_TEST_APP_BIN) ifdef INCLUDE_TEST_MODELS DEFAULT_TARGETS += $(TRANSFORMER_TEST_BIN) endif @@ -54,6 +55,9 @@ $(TRANSL_DB_TEST_BIN) : $(TRANSL_DB_ALL_SRCS) $(TRANSFORMER_TEST_BIN): $(TRANSFORMER_TEST_SRCS) $(TRANSFORMER_SRCS) $(TRANSLIB_MAIN_SRCS) $(YGOT_BINDS) $(GO) test -mod=vendor -tags xfmrtest -c -vet=off -cover -coverpkg=../translib/transformer ../translib/transformer -o $@ +$(TRANSFORMER_TEST_APP_BIN): $(TRANSFORMER_TEST_SRCS) $(TRANSFORMER_SRCS) $(TRANSLIB_MAIN_SRCS) $(YGOT_BINDS) + $(GO) test -mod=vendor -tags testapp -c -vet=off -cover -coverpkg=../translib/transformer ../translib/transformer -o $@ + $(YGOT_BINDS): $(YANG_FILES) $(RM) $@ $(GO) run \ diff --git a/translib/transformer/sflow_openconfig_test.go b/translib/transformer/sflow_openconfig_test.go new file mode 100644 index 000000000..ae000b313 --- /dev/null +++ b/translib/transformer/sflow_openconfig_test.go @@ -0,0 +1,280 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2023 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//go:build testapp +// +build testapp + +package transformer_test + +import ( + "errors" + "testing" + "time" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "github.com/Azure/sonic-mgmt-common/translib/db" +) + + + +func Test_node_on_openconfig_sflow(t *testing.T) { + var pre_req_map, expected_map, cleanuptbl map[string]interface{} + var url, url_body_json string + + t.Log("\n\n+++++++++++++ Performing Set on sflow node ++++++++++++") + url = "/openconfig-sampling-sflow:sampling/sflow/config" + url_body_json = "{ \"openconfig-sampling-sflow:enabled\": true, \"openconfig-sampling-sflow:polling-interval\": 100, \"openconfig-sampling-sflow:agent\": \"Ethernet0\"}" + expected_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet0" , "polling_interval":"100"}}} + cleanuptbl = map[string]interface{}{"SFLOW": map[string]interface{}{"global": ""}} + loadDB(db.ConfigDB, pre_req_map) + time.Sleep(1 * time.Second) + t.Run("Test set on sflow node", processSetRequest(url, url_body_json, "POST", false, nil)) + time.Sleep(1 * time.Second) + t.Run("Verify set on sflow node", verifyDbResult(rclient, "SFLOW|global", expected_map, false)) + time.Sleep(1 * time.Second) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Set on sflow node ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Delete on sflow node ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet4" , "polling_interval":"200"}}} + loadDB(db.ConfigDB, pre_req_map) + time.Sleep(1 * time.Second) + url = "/openconfig-sampling-sflow:sampling/sflow/config" + expected_err := errors.New("DELETE not supported on attribute") + t.Run("Test delete on sflow node", processDeleteRequest(url, true, expected_err )) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"SFLOW": map[string]interface{}{"global": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Delete on sflow node ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Get on Sflow node ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet8" , "polling_interval":"300"}}} + loadDB(db.ConfigDB, pre_req_map) + expected_get_json := "{\"openconfig-sampling-sflow:state\":{\"agent\":\"Ethernet8\",\"enabled\":true,\"polling-interval\":300}}" + url = "/openconfig-sampling-sflow:sampling/sflow/state" + t.Run("Test get on sflow node", processGetRequest(url, expected_get_json, false)) + time.Sleep(1 * time.Second) + unloadDB(db.ConfigDB, cleanuptbl) + t.Log("\n\n+++++++++++++ Done Performing Get on Sflow node ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Put/Replace on sflow leaf node ++++++++++++") + url = "/openconfig-sampling-sflow:sampling/sflow/config" + url_body_json = "{ \"openconfig-sampling-sflow:enabled\": true, \"openconfig-sampling-sflow:polling-interval\": 100, \"openconfig-sampling-sflow:agent\": \"Ethernet0\"}" + pre_req_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet0" , "polling_interval":"100"}}} + expected_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet0" , "polling_interval":"300"}}} + cleanuptbl = map[string]interface{}{"SFLOW": map[string]interface{}{"global": ""}} + loadDB(db.ConfigDB, pre_req_map) + time.Sleep(1 * time.Second) + url = "/openconfig-sampling-sflow:sampling/sflow/config/polling-interval" + url_body_json = "{ \"openconfig-sampling-sflow:polling-interval\": 300}" + t.Run("Update polling-interval on sflow node", processSetRequest(url, url_body_json, "PUT", false, nil)) + time.Sleep(1 * time.Second) + t.Run("Verify polling-interval on sflow node", verifyDbResult(rclient, "SFLOW|global", expected_map, false)) + time.Sleep(1 * time.Second) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Put/Replace on sflow leaf node ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Patch on sflow leaf node ++++++++++++") + url = "/openconfig-sampling-sflow:sampling/sflow/config" + url_body_json = "{ \"openconfig-sampling-sflow:enabled\": true, \"openconfig-sampling-sflow:polling-interval\": 100, \"openconfig-sampling-sflow:agent\": \"Ethernet0\"}" + pre_req_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet0" , "polling_interval":"100"}}} + expected_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet4" , "polling_interval":"100"}}} + cleanuptbl = map[string]interface{}{"SFLOW": map[string]interface{}{"global": ""}} + loadDB(db.ConfigDB, pre_req_map) + time.Sleep(1 * time.Second) + url = "/openconfig-sampling-sflow:sampling/sflow/config/agent" + url_body_json = "{ \"openconfig-sampling-sflow:agent\": \"Ethernet4\"}" + t.Run("Update Agent on sflow node", processSetRequest(url, url_body_json, "PATCH", false, nil)) + time.Sleep(1 * time.Second) + t.Run("Verify Agent on sflow node", verifyDbResult(rclient, "SFLOW|global", expected_map, false)) + time.Sleep(1 * time.Second) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Patch on sflow leaf node ++++++++++++") +} + +func Test_node_openconfig_sflow_collector(t *testing.T) { + var pre_req_map, expected_map, cleanuptbl map[string]interface{} + var url, url_body_json string + + t.Log("\n\n+++++++++++++ Performing Set on Collector ++++++++++++") + url = "/openconfig-sampling-sflow:sampling/sflow/collectors" + url_body_json = "{ \"openconfig-sampling-sflow:collector\": [ { \"address\": \"1.1.1.1\", \"port\": 6343, \"network-instance\": \"default\", \"config\": { \"address\": \"1.1.1.1\", \"port\": 6343, \"network-instance\": \"default\" } } ]}" + expected_map = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"1.1.1.1_6343_default": map[string]interface{}{ + "collector_ip": "1.1.1.1", + "collector_port": "6343", + "collector_vrf": "default"}}} + cleanuptbl = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"1.1.1.1_6343_default": ""}} + t.Run("Test set on collector node for sflow", processSetRequest(url, url_body_json, "POST", false, nil)) + time.Sleep(1 * time.Second) + t.Run("Verify set on collector node for sflow", verifyDbResult(rclient, "SFLOW_COLLECTOR|1.1.1.1_6343_default", expected_map, false)) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Set on Collector ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Delete on Collector ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"2.2.2.2_4444_default": map[string]interface{}{ + "collector_ip": "2.2.2.2", + "collector_port": "4444", + "collector_vrf": "default"}}} + cleanuptbl = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"2.2.2.2_4444_default": ""}} + loadDB(db.ConfigDB, pre_req_map) + time.Sleep(1 * time.Second) + url = "/openconfig-sampling-sflow:sampling/sflow/collectors/collector[address=2.2.2.2][port=4444][network-instance=default]" + t.Run("Test delete on collector for sflow", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + delete_expected := make(map[string]interface{}) + t.Run("Verify delete on collector node for sflow", verifyDbResult(rclient, "SFLOW_COLLECTOR|2.2.2.2_4444_default", delete_expected, false)) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(1 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Delete on Collector ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Get on Collector ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"3.3.3.3_6666_default": map[string]interface{}{ + "collector_ip": "3.3.3.3", + "collector_port": "6666", + "collector_vrf": "default"}}} + loadDB(db.ConfigDB, pre_req_map) + expected_get_json := "{ \"openconfig-sampling-sflow:collectors\":{\"collector\":[{\"address\":\"3.3.3.3\",\"config\":{\"address\":\"3.3.3.3\",\"network-instance\":\"default\",\"port\":6666},\"network-instance\":\"default\",\"port\":6666,\"state\":{\"address\":\"3.3.3.3\",\"network-instance\":\"default\",\"port\":6666}}]}}" + url = "/openconfig-sampling-sflow:sampling/sflow/collectors" + t.Run("Test get on collector node for sflow", processGetRequest(url, expected_get_json, false)) + time.Sleep(1 * time.Second) + cleanuptbl = map[string]interface{}{"SFLOW_COLLECTOR": map[string]interface{}{"3.3.3.3_6666_default": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + t.Log("\n\n+++++++++++++ Done Performing Get on Collector ++++++++++++") +} + + +func Test_node_openconfig_sflow_interface(t *testing.T) { + var pre_req_map, non_pre_req_map, expected_map, cleanuptbl , cleanuptbl_sflow map[string]interface{} + var url, url_body_json string + + //Sflow needs to be enabled to configure for an sflow interface + t.Log("\n\n+++++++++++++ Performing Set on Sflow Interface ++++++++++++") + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces" + url_body_json = "{ \"openconfig-sampling-sflow:interface\": [ { \"name\": \"Ethernet0\", \"config\": { \"name\": \"Ethernet0\", \"enabled\": true, \"sampling-rate\": 10000 } } ]}" + pre_req_map = map[string]interface{}{"SFLOW": map[string]interface{}{"global": map[string]interface{}{"admin_state": "up", "agent_id":"Ethernet0" , "polling_interval":"100"}}} + loadDB(db.ConfigDB, pre_req_map) + expected_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "10000"}}} + cleanuptbl = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": ""}} + t.Run("Test set on sflow interface", processSetRequest(url, url_body_json, "POST", false, nil)) + time.Sleep(2 * time.Second) + t.Run("Verify set on sflow interface", verifyDbResult(rclient, "SFLOW_SESSION|Ethernet0", expected_map, false)) + unloadDB(db.ConfigDB, cleanuptbl) + time.Sleep(2 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Set on Sflow interface ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Delete on Sflow interface ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet4": map[string]interface{}{ + "admin_state": "down", + "sample_rate": "20000"}}} + non_pre_req_map = map[string]interface{}{"SFLOW_SESSION_TABLE": map[string]interface{}{"Ethernet4": map[string]interface{}{ + "admin_state": "down", + "sample_rate": "20000"}}} + cleanuptbl = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet4": ""}} + loadDB(db.ConfigDB, pre_req_map) + loadDB(db.ApplDB, non_pre_req_map) + time.Sleep(2 * time.Second) + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface[name=Ethernet4]" + t.Run("Test delete on sflow interface", processDeleteRequest(url, false)) + time.Sleep(2 * time.Second) + delete_expected := make(map[string]interface{}) + t.Run("Verify delete on sflow interface", verifyDbResult(rclient, "SFLOW_SESSION|Ethernet4", delete_expected, false)) + unloadDB(db.ConfigDB, cleanuptbl) + unloadDB(db.ApplDB, non_pre_req_map) + time.Sleep(2 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Delete on Sflow Interface ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Get on Sflow Interface ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet8": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "30000"}}} + non_pre_req_map = map[string]interface{}{"SFLOW_SESSION_TABLE": map[string]interface{}{"Ethernet8": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "30000"}}} + loadDB(db.ConfigDB, pre_req_map) + loadDB(db.ApplDB, non_pre_req_map) + expected_get_json := "{\"openconfig-sampling-sflow:state\":{\"enabled\":true,\"name\":\"Ethernet8\",\"sampling-rate\":30000}}" + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface[name=Ethernet8]/state" + t.Run("Test get on sflow interface", processGetRequest(url, expected_get_json, false)) + time.Sleep(2 * time.Second) + cleanuptbl = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet8": ""}} + unloadDB(db.ConfigDB, cleanuptbl) + unloadDB(db.ApplDB, non_pre_req_map) + t.Log("\n\n+++++++++++++ Done Performing Get on SFlow Interface ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Put/Replace on Sflow Interface ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "10000"}}} + non_pre_req_map = map[string]interface{}{"SFLOW_SESSION_TABLE": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "10000"}}} + expected_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "down", + "sample_rate": "10000"}}} + cleanuptbl = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": ""}} + loadDB(db.ConfigDB, pre_req_map) + loadDB(db.ApplDB, non_pre_req_map) + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface[name=Ethernet0]/config/enabled" + url_body_json = "{ \"openconfig-sampling-sflow:enabled\": false}" + t.Run("Update admin-state for sflow interface", processSetRequest(url, url_body_json, "PUT", false, nil)) + time.Sleep(2 * time.Second) + t.Run("Verify admin-state for sflow interface", verifyDbResult(rclient, "SFLOW_SESSION|Ethernet0", expected_map, false)) + unloadDB(db.ConfigDB, cleanuptbl) + unloadDB(db.ApplDB, non_pre_req_map) + time.Sleep(2 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Put/Replace on Sflow interface ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Patch on Sflow Interface ++++++++++++") + pre_req_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "10000"}}} + non_pre_req_map = map[string]interface{}{"SFLOW_SESSION_TABLE": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "10000"}}} + expected_map = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": map[string]interface{}{ + "admin_state": "up", + "sample_rate": "20000"}}} + cleanuptbl = map[string]interface{}{"SFLOW_SESSION": map[string]interface{}{"Ethernet0": ""}} + loadDB(db.ConfigDB, pre_req_map) + loadDB(db.ApplDB, non_pre_req_map) + cleanuptbl_sflow = map[string]interface{}{"SFLOW": map[string]interface{}{"global": ""}} + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface[name=Ethernet0]/config/sampling-rate" + url_body_json = "{ \"openconfig-sampling-sflow:sampling-rate\": 20000}" + t.Run("Update sampling-rate on sflow interface", processSetRequest(url, url_body_json, "PATCH", false, nil)) + time.Sleep(2 * time.Second) + t.Run("Verify sampling-rate on sflow interface", verifyDbResult(rclient, "SFLOW_SESSION|Ethernet0", expected_map, false)) + unloadDB(db.ConfigDB, cleanuptbl) + unloadDB(db.ConfigDB, cleanuptbl_sflow) + unloadDB(db.ApplDB, non_pre_req_map) + time.Sleep(2 * time.Second) + t.Log("\n\n+++++++++++++ Done Performing Patch on Sflow interface ++++++++++++") + + t.Log("\n\n+++++++++++++ Performing Delete on Interfaces when sflow is disabled ++++++++++++") + //Sflow global interfaces deletion is not supported + url = "/openconfig-sampling-sflow:sampling/sflow/interfaces" + err_str := "DELETE not supported" + expected_err := tlerr.NotSupportedError{Format: err_str} + t.Run("Test delete on sflow interfaces", processDeleteRequest(url, true, expected_err )) + t.Log("\n\n+++++++++++++ Done Delete on Interfaces when sflow is disabled ++++++++++++") +} diff --git a/translib/transformer/sflow_sonic_test.go b/translib/transformer/sflow_sonic_test.go new file mode 100644 index 000000000..79081daa3 --- /dev/null +++ b/translib/transformer/sflow_sonic_test.go @@ -0,0 +1,151 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2023 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//go:build testapp +// +build testapp + +package transformer_test + +import ( + "testing" + "time" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + + +func Test_node_sonic_sflow(t *testing.T) { + var url, url_body_json string + + //Add sflow node + url = "/sonic-sflow:sonic-sflow/SFLOW" + url_body_json = "{ \"sonic-sflow:global\": { \"admin_state\": \"down\", \"polling_interval\": 0, \"agent_id\": \"Ethernet0\" }}" + t.Run("Add sFlow collector", processSetRequest(url, url_body_json, "POST", false,nil)) + time.Sleep(1 * time.Second) + + //Set admin state + url = "/sonic-sflow:sonic-sflow/SFLOW/global/admin_state" + url_body_json = "{ \"sonic-sflow:admin_state\": \"up\"}" + t.Run("Enable sFlow", processSetRequest(url, url_body_json, "PATCH", false)) + time.Sleep(1 * time.Second) + + //Set polling interval + url = "/sonic-sflow:sonic-sflow/SFLOW/global/polling_interval" + url_body_json = "{ \"sonic-sflow:polling_interval\": 100}" + t.Run("Set sFlow polling interval", processSetRequest(url, url_body_json, "PUT", false)) + time.Sleep(1 * time.Second) + + //Set AgentID + url = "/sonic-sflow:sonic-sflow/SFLOW/global/agent_id" + url_body_json = "{ \"sonic-sflow:agent_id\": \"Ethernet4\"}" + t.Run("Set sFlow agent ID", processSetRequest(url, url_body_json, "PATCH", false)) + time.Sleep(1 * time.Second) + + // Verify global configurations + url = "/sonic-sflow:sonic-sflow/SFLOW/global" + url_body_json = "{\"sonic-sflow:global\":{\"admin_state\":\"up\",\"agent_id\":\"Ethernet4\",\"polling_interval\":100}}" + t.Run("Verify sFlow global configurations", processGetRequest(url, url_body_json, false)) + + //Delete sflow global configurations + url = "/sonic-sflow:sonic-sflow/SFLOW" + t.Run("Test delete on sflow node", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + //Verify deleted sflow global configuration + url = "/sonic-sflow:sonic-sflow/SFLOW" + url_body_json = "{}" + t.Run("Verify delete on sflow node", processGetRequest(url, url_body_json, false)) +} + +func Test_node_sonic_sflow_collector(t *testing.T) { + var url, url_body_json string + + //Add sFlow collector + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR" + url_body_json = "{ \"sonic-sflow:SFLOW_COLLECTOR_LIST\": [ { \"name\": \"1.1.1.1_6343_default\", \"collector_ip\": \"1.1.1.1\", \"collector_port\": 6343, \"collector_vrf\": \"default\" } ]}" + t.Run("Add sFlow collector", processSetRequest(url, url_body_json, "POST", false,nil)) + time.Sleep(1 * time.Second) + + // Verify sFlow collector configurations + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR/SFLOW_COLLECTOR_LIST[name=1.1.1.1_6343_default]" + t.Run("Verify sFlow collector", processGetRequest(url, url_body_json, false)) + + // Set collector ip + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR/SFLOW_COLLECTOR_LIST[name=1.1.1.1_6343_default]/collector_ip" + url_body_json = "{ \"sonic-sflow:collector_ip\": \"1.1.1.2\"}" + t.Run("Set sFlow collector ip", processSetRequest(url, url_body_json, "PATCH", false)) + time.Sleep(1 * time.Second) + + // Set collector port + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR/SFLOW_COLLECTOR_LIST[name=1.1.1.1_6343_default]/collector_port" + url_body_json = "{ \"sonic-sflow:collector_port\": 1234}" + t.Run("Set sFlow collector port", processSetRequest(url, url_body_json, "PATCH", false)) + time.Sleep(1 * time.Second) + + //Delete collector port + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR/SFLOW_COLLECTOR_LIST" + t.Run("Test delete on Collector", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + //Verify collector port + url = "/sonic-sflow:sonic-sflow/SFLOW_COLLECTOR/SFLOW_COLLECTOR_LIST" + url_body_json = "{}" + t.Run("Verify delete on sFlow collector", processGetRequest(url, url_body_json, false)) +} + +func Test_node_sonic_sflow_interface(t *testing.T) { + var url, url_body_json string + + //Sflow needs to be enabled to configure for the interface + url = "/sonic-sflow:sonic-sflow/SFLOW" + url_body_json = "{ \"sonic-sflow:global\": { \"admin_state\": \"up\", \"polling_interval\": 0, \"agent_id\": \"Ethernet0\" }}" + t.Run("Enable sFlow", processSetRequest(url, url_body_json, "POST", false,nil)) + time.Sleep(1 * time.Second) + + //Configure for the sflow interface + url = "/sonic-sflow:sonic-sflow/SFLOW_SESSION" + url_body_json = "{ \"sonic-sflow:SFLOW_SESSION_LIST\": [ { \"port\": \"Ethernet0\", \"admin_state\": \"up\", \"sample_rate\": 10000 } ]}" + t.Run("Configure sFlow interface", processSetRequest(url, url_body_json, "POST", false,nil)) + time.Sleep(1 * time.Second) + + //Changing the sampling-rate + url = "/sonic-sflow:sonic-sflow/SFLOW_SESSION/SFLOW_SESSION_LIST[port=Ethernet0]/sample_rate" + url_body_json = "{ \"sonic-sflow:sample_rate\": 20000}" + t.Run("Configuring the sampling_rate in sflow interface", processSetRequest(url, url_body_json, "PATCH", false)) + time.Sleep(1 * time.Second) + + //Deleting the configured interface + url = "/sonic-sflow:sonic-sflow/SFLOW_SESSION/SFLOW_SESSION_LIST[port=Ethernet0]" + t.Run("Delete on configured sflow interface", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + //Verify the deleted sflow interface + url_body_json = "{}" + err_str := "Resource not found" + expected_err := tlerr.NotFoundError{Format: err_str} + t.Run("Verify delete on sFlow Interface", processGetRequest(url, url_body_json, true, expected_err)) + + //Delete sflow global configurations + url = "/sonic-sflow:sonic-sflow/SFLOW" + t.Run("Test delete on sflow node", processDeleteRequest(url, false)) + time.Sleep(1 * time.Second) + + //Verify deleted sflow global configuration + url = "/sonic-sflow:sonic-sflow/SFLOW" + url_body_json = "{}" + t.Run("Verify delete on sFlow collector", processGetRequest(url, url_body_json, false)) +} diff --git a/translib/transformer/transformer_test.go b/translib/transformer/transformer_test.go index 26942ccdf..7504380b5 100644 --- a/translib/transformer/transformer_test.go +++ b/translib/transformer/transformer_test.go @@ -138,6 +138,12 @@ func prepareDb() bool { } rclient = rclientDBNum[db.ConfigDB] + rclientDBNum[db.ApplDB] = getDbClient(int(db.ApplDB)) + if rclientDBNum[db.ApplDB] == nil { + fmt.Printf("error in getDbClient(int(db.ApplDB)") + return false + } + return true } diff --git a/translib/transformer/xfmr_sflow.go b/translib/transformer/xfmr_sflow.go new file mode 100644 index 000000000..f035dc7bb --- /dev/null +++ b/translib/transformer/xfmr_sflow.go @@ -0,0 +1,748 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "errors" + "strconv" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" +) + +const ( + /* sFlow tables */ + SFLOW_GLOBAL_TBL = "SFLOW" + SFLOW_COL_TBL = "SFLOW_COLLECTOR" + SFLOW_SESS_TBL = "SFLOW_SESSION_TABLE" /* Session table in ApplDb */ + SFLOW_INTF_TBL = "SFLOW_SESSION" /* Session table in ConfigDb */ + + /* sFlow keys */ + SFLOW_GLOBAL_KEY = "global" + SFLOW_ADMIN_KEY = "admin_state" + SFLOW_POLLING_INT_KEY = "polling_interval" + SFLOW_SAMPL_RATE_KEY = "sample_rate" + SFLOW_AGENT_KEY = "agent_id" + SFLOW_INTF_NAME_KEY = "name" + SFLOW_COL_IP_KEY = "collector_ip" + SFLOW_COL_PORT_KEY = "collector_port" + SFLOW_COL_VRF_KEY = "collector_vrf" + + /* sFlow default values */ + DEFAULT_POLLING_INT = 20 + DEFAULT_AGENT = "default" + DEFAULT_VRF_NAME = "default" + DEFAULT_COL_PORT = "6343" + + /* sFlow URIs */ + SAMPLING = "/openconfig-sampling-sflow:sampling" + SAMPLING_SFLOW = "/openconfig-sampling-sflow:sampling/sflow" + SAMPLING_SFLOW_CONFIG = "/openconfig-sampling-sflow:sampling/sflow/config" + SAMPLING_SFLOW_CONFIG_ENABLED = "/openconfig-sampling-sflow:sampling/sflow/config/enabled" + SAMPLING_SFLOW_CONFIG_POLLING_INT = "/openconfig-sampling-sflow:sampling/sflow/config/polling-interval" + SAMPLING_SFLOW_CONFIG_AGENT = "/openconfig-sampling-sflow:sampling/sflow/config/agent" + SAMPLING_SFLOW_STATE = "/openconfig-sampling-sflow:sampling/sflow/state" + SAMPLING_SFLOW_STATE_ENABLED = "/openconfig-sampling-sflow:sampling/sflow/state/enabled" + SAMPLING_SFLOW_STATE_POLLING_INT = "/openconfig-sampling-sflow:sampling/sflow/state/polling-interval" + SAMPLING_SFLOW_STATE_AGENT = "/openconfig-sampling-sflow:sampling/sflow/state/agent" + SAMPLING_SFLOW_COLS = "/openconfig-sampling-sflow:sampling/sflow/collectors" + SAMPLING_SFLOW_COLS_COL = "/openconfig-sampling-sflow:sampling/sflow/collectors/collector" + SAMPLING_SFLOW_COLS_COL_CONFIG = "/openconfig-sampling-sflow:sampling/sflow/collectors/collector/config" + SAMPLING_SFLOW_COLS_COL_STATE = "/openconfig-sampling-sflow:sampling/sflow/collectors/collector/state" + SAMPLING_SFLOW_INTFS = "/openconfig-sampling-sflow:sampling/sflow/interfaces" + SAMPLING_SFLOW_INTFS_INTF = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface" + SAMPLING_SFLOW_INTFS_INTF_CONFIG = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface/config" + SAMPLING_SFLOW_INTFS_INTF_CONFIG_SAMPL_RATE = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface/config/sampling-rate" + SAMPLING_SFLOW_INTFS_INTF_CONFIG_ENABLED = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface/config/enabled" + SAMPLING_SFLOW_INTFS_INTF_STATE = "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface/state" + + /* IPv4/v6 localhost address */ + IPV4_LOCALHOST = "127.0.0.1" + IPV6_LOCALHOST = "::1" +) + +type Sflow struct { + Enabled string + Polling_Interval string + Agent string +} + +type SflowCol struct { + Ip string + Port string + Vrf string +} + +type SflowIntf struct { + Enabled string + Sampling_Rate string +} + +func init() { + XlateFuncBind("DbToYang_sflow_xfmr", DbToYang_sflow_xfmr) + XlateFuncBind("YangToDb_sflow_xfmr", YangToDb_sflow_xfmr) + XlateFuncBind("Subscribe_sflow_xfmr", Subscribe_sflow_xfmr) + XlateFuncBind("DbToYang_sflow_collector_xfmr", DbToYang_sflow_collector_xfmr) + XlateFuncBind("YangToDb_sflow_collector_xfmr", YangToDb_sflow_collector_xfmr) + XlateFuncBind("Subscribe_sflow_collector_xfmr", Subscribe_sflow_collector_xfmr) + XlateFuncBind("DbToYangPath_sflow_collector_path_xfmr", DbToYangPath_sflow_collector_path_xfmr) + XlateFuncBind("DbToYang_sflow_interface_xfmr", DbToYang_sflow_interface_xfmr) + XlateFuncBind("YangToDb_sflow_interface_xfmr", YangToDb_sflow_interface_xfmr) + XlateFuncBind("Subscribe_sflow_interface_xfmr", Subscribe_sflow_interface_xfmr) + XlateFuncBind("DbToYangPath_sflow_interface_path_xfmr", DbToYangPath_sflow_interface_path_xfmr) +} + +var DbToYangPath_sflow_collector_path_xfmr PathXfmrDbToYangFunc = func(params XfmrDbToYgPathParams) error { + log.V(3).Info("DbToYangPath_sflow_collector fmr: tbl:", params.tblName) + sflowCollRoot := "/openconfig-sampling-sflow:sampling/sflow/collectors/collector" + + if params.tblName != SFLOW_COL_TBL { + oper_err := errors.New("wrong config DB table sent") + log.Errorf("Sflow collector Path-xfmr: table name %s not in sflow view", params.tblName) + return oper_err + } else { + if len(params.tblKeyComp) > 0 { + key_parts := strings.Split(params.tblKeyComp[0], "_") + if len(key_parts) != 3 { + oper_err := errors.New("Invalid key " + params.tblKeyComp[0]) + log.Errorf("sflow_collector_path_xfmr: Invalid Key %s", params.tblKeyComp[0]) + return oper_err + } + params.ygPathKeys[sflowCollRoot+"/address"] = key_parts[0] + params.ygPathKeys[sflowCollRoot+"/port"] = key_parts[1] + params.ygPathKeys[sflowCollRoot+"/network-instance"] = key_parts[2] + } else { + oper_err := errors.New("Missing DB key.") + log.Errorf("Missing DB Key") + return oper_err + } + } + log.V(3).Info("DbToYangPath sflow_collector ygPathKeys: ", params.ygPathKeys) + + return nil +} + +var DbToYangPath_sflow_interface_path_xfmr PathXfmrDbToYangFunc = func(params XfmrDbToYgPathParams) error { + log.V(3).Info("DbToYangPath_sflow_interface fmr: tbl:", params.tblName) + sflowCollRoot := "/openconfig-sampling-sflow:sampling/sflow/interfaces/interface" + + if params.tblName != SFLOW_SESS_TBL { + oper_err := errors.New("wrong config DB table sent") + log.Errorf("Sflow interface Path-xfmr: table name %s not in sflow view", params.tblName) + return oper_err + } else { + if len(params.tblKeyComp) > 0 { + params.ygPathKeys[sflowCollRoot+"/name"] = params.tblKeyComp[0] + } else { + oper_err := errors.New("Missing DB key.") + log.Errorf("Missing DB Key") + return oper_err + } + } + log.V(3).Info("DbToYangPath sflow_interface ygPathKeys: ", params.ygPathKeys) + + return nil +} + +func getSflowRootObject(s *ygot.GoStruct) *ocbinds.OpenconfigSamplingSflow_Sampling { + deviceObj := (*s).(*ocbinds.Device) + return deviceObj.Sampling +} + +var Subscribe_sflow_xfmr SubTreeXfmrSubscribe = func(inParams XfmrSubscInParams) (XfmrSubscOutParams, error) { + var err error + var result XfmrSubscOutParams + if inParams.subscProc != TRANSLATE_SUBSCRIBE { + result.isVirtualTbl = true + log.V(3).Info("Subscribe_sflow_xfmr :- result. isVirtualTbl: ", result.isVirtualTbl) + return result, nil + } + result.dbDataMap = make(RedisDbSubscribeMap) + + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.uri) + + log.V(3).Infof("Subscribe_sflow_xfmr: targetUri %v ", targetUriPath) + + if targetUriPath == SAMPLING_SFLOW || targetUriPath == SAMPLING_SFLOW_CONFIG || + targetUriPath == SAMPLING_SFLOW_STATE { + result.dbDataMap = RedisDbSubscribeMap{db.ConfigDB: {SFLOW_GLOBAL_TBL: { + SFLOW_GLOBAL_KEY: {"admin_state": "enabled", "polling_interval": "polling-interval", "agent_id": "agent"}}}} + result.onChange = OnchangeEnable + result.nOpts = ¬ificationOpts{} + result.nOpts.pType = OnChange + result.isVirtualTbl = false + } + return result, err +} + +var DbToYang_sflow_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + pathInfo := NewPathInfo(inParams.uri) + log.V(3).Infof("Received GET for sFlow path: %s, vars: %v", pathInfo.Path, pathInfo.Vars) + + log.V(3).Info("inParams.Uri:", inParams.requestUri) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.requestUri) + return getSflow(getSflowRootObject(inParams.ygRoot), targetUriPath, inParams.uri, inParams.dbs[:]) +} + +var YangToDb_sflow_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + var err error + res_map := make(map[string]map[string]db.Value) + + log.V(3).Info("sFlow SubTreeXfmr: ", inParams.uri) + global_map := make(map[string]db.Value) + sflowObj := getSflowRootObject(inParams.ygRoot) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.requestUri) + + global_map[SFLOW_GLOBAL_KEY] = db.Value{Field: make(map[string]string)} + + if inParams.oper == DELETE { + switch targetUriPath { + case SAMPLING_SFLOW_CONFIG_AGENT: + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_AGENT_KEY] = "" + case SAMPLING_SFLOW_CONFIG_POLLING_INT: + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_POLLING_INT_KEY] = "" + default: + return res_map, errors.New("DELETE not supported on attribute") + } + + res_map[SFLOW_GLOBAL_TBL] = global_map + return res_map, err + } + + if sflowObj.Sflow.Config.Enabled != nil { + if *(sflowObj.Sflow.Config.Enabled) { + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_ADMIN_KEY] = "up" + } else { + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_ADMIN_KEY] = "down" + } + } + + if sflowObj.Sflow.Config.PollingInterval != nil { + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_POLLING_INT_KEY] = + strconv.FormatUint(uint64(*(sflowObj.Sflow.Config.PollingInterval)), 10) + } + + if sflowObj.Sflow.Config.Agent != nil { + global_map[SFLOW_GLOBAL_KEY].Field[SFLOW_AGENT_KEY] = *(sflowObj.Sflow.Config.Agent) + } + + res_map[SFLOW_GLOBAL_TBL] = global_map + return res_map, err +} + +var DbToYang_sflow_collector_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + pathInfo := NewPathInfo(inParams.uri) + log.V(3).Infof("Received GET for sFlow Collector path: %s, vars: %v", pathInfo.Path, pathInfo.Vars) + log.V(3).Info("inParams.Uri:", inParams.requestUri) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.requestUri) + return getSflowCol(getSflowRootObject(inParams.ygRoot), targetUriPath, inParams.uri, inParams.dbs[db.ConfigDB]) +} + +func makeColKey(uri string) string { + ip := NewPathInfo(uri).Var("address") + port := NewPathInfo(uri).Var("port") + vrf := NewPathInfo(uri).Var("network-instance") + name := "" + if ip != "" { + name = ip + "_" + port + "_" + vrf + } + return name +} + +var YangToDb_sflow_collector_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + var err error + res_map := make(map[string]map[string]db.Value) + + log.V(3).Info("sFlow Collector YangToDBSubTreeXfmr: ", inParams.uri) + col_map := make(map[string]db.Value) + sflowObj := getSflowRootObject(inParams.ygRoot) + + + key := makeColKey(inParams.uri) + if inParams.oper == DELETE { + if key != "" { + col_map[key] = db.Value{Field: make(map[string]string)} + } + res_map[SFLOW_COL_TBL] = col_map + return res_map, err + } + + if key != "" { + ip := NewPathInfo(inParams.uri).Var("address") + port := NewPathInfo(inParams.uri).Var("port") + vrf := NewPathInfo(inParams.uri).Var("network-instance") + col_map[key] = db.Value{Field: make(map[string]string)} + col_map[key].Field[SFLOW_COL_IP_KEY] = ip + col_map[key].Field[SFLOW_COL_PORT_KEY] = port + col_map[key].Field[SFLOW_COL_VRF_KEY] = vrf + } else { + for col := range sflowObj.Sflow.Collectors.Collector { + port := strconv.FormatUint(uint64(col.Port), 10) + key = col.Address + "_" + port + "_" + col.NetworkInstance + col_map[key] = db.Value{Field: make(map[string]string)} + col_map[key].Field[SFLOW_COL_IP_KEY] = col.Address + col_map[key].Field[SFLOW_COL_PORT_KEY] = port + col_map[key].Field[SFLOW_COL_VRF_KEY] = col.NetworkInstance + } + } + + res_map[SFLOW_COL_TBL] = col_map + return res_map, err +} + +var Subscribe_sflow_collector_xfmr SubTreeXfmrSubscribe = func(inParams XfmrSubscInParams) (XfmrSubscOutParams, error) { + + var err error + var result XfmrSubscOutParams + result.dbDataMap = make(RedisDbSubscribeMap) + + pathInfo := NewPathInfo(inParams.uri) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.uri) + + log.V(3).Infof("Subscribe_sflow_collector_xfmr: pathInfo %v targetUri %v ", pathInfo, targetUriPath) + + ip := pathInfo.Var("address") + if ip == "" { + ip = "*" + } + + port := pathInfo.Var("port") + if port == "" { + port = "*" + } + vrf := pathInfo.Var("network-instance") + if vrf == "" { + vrf = "*" + } + var name string + if ip == "*" && port == "*" && vrf == "*" { + name = "*" + } else { + name = ip + "_" + port + "_" + vrf + } + log.V(3).Infof("Subscribe_sflow_collector_xfmr: key %s", name) + result.dbDataMap = RedisDbSubscribeMap{db.ConfigDB: {SFLOW_COL_TBL: {name: { + "collector_ip": "address", "collector_port": "port", "collector_vrf": "network-instance"}}}} + + return result, err +} + +var DbToYang_sflow_interface_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + pathInfo := NewPathInfo(inParams.uri) + log.V(3).Infof("Received GET for sFlow Interface path: %s, vars: %v", pathInfo.Path, pathInfo.Vars) + log.V(3).Info("inParams.Uri:", inParams.requestUri) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.requestUri) + return getSflowIntf(getSflowRootObject(inParams.ygRoot), targetUriPath, inParams.uri, inParams.dbs[db.ApplDB]) +} + +var Subscribe_sflow_interface_xfmr SubTreeXfmrSubscribe = func(inParams XfmrSubscInParams) (XfmrSubscOutParams, error) { + var err error + var result XfmrSubscOutParams + result.dbDataMap = make(RedisDbSubscribeMap) + key := NewPathInfo(inParams.uri).Var("name") + + if key == "" { + key = "*" + } + log.V(3).Infof("XfmrSubscribe_sflow_interface_xfmr key %s ", key) + result.dbDataMap = RedisDbSubscribeMap{db.ApplDB: {SFLOW_SESS_TBL: {key: { + "ifname": "name", "admin_state": "enabled", "sample_rate": "sampling-rate"}}}} + + return result, err +} + +var YangToDb_sflow_interface_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + var err error + res_map := make(map[string]map[string]db.Value) + + log.V(3).Info("sFlow Interface YangToDBSubTreeXfmr: ", inParams.uri) + intf_map := make(map[string]db.Value) + sflowObj := getSflowRootObject(inParams.ygRoot) + targetUriPath, _, _ := XfmrRemoveXPATHPredicates(inParams.requestUri) + log.V(3).Infof("Subscribe_sflow_xfmr: targetUri %v ", targetUriPath) + + if inParams.oper == DELETE { + if !strings.Contains(targetUriPath, SAMPLING_SFLOW_INTFS_INTF) { + return res_map, tlerr.NotSupportedError{Format: "DELETE not supported", Path: targetUriPath} + } + + name := NewPathInfo(inParams.uri).Var("name") + if name == "" { + return res_map, tlerr.InvalidArgs("Missing interface name") + } + + intf_map[name] = db.Value{Field: make(map[string]string)} + switch targetUriPath { + case SAMPLING_SFLOW_INTFS_INTF_CONFIG_SAMPL_RATE: + intf_map[name].Field[SFLOW_SAMPL_RATE_KEY] = "" + case SAMPLING_SFLOW_INTFS_INTF_CONFIG_ENABLED: + intf_map[name].Field[SFLOW_ADMIN_KEY] = "" + case SAMPLING_SFLOW_INTFS_INTF: + /* Delete all interface configurations */ + default: + return res_map, errors.New("DELETE not supported on attribute or container") + } + } else { + for _, intf := range sflowObj.Sflow.Interfaces.Interface { + + if intf.Name == nil { + return res_map, errors.New("sFlow Interface: No interface name") + } + + if intf.Config == nil { + log.V(3).Infof("sFlow Inteface: No configuration") + continue + } + + name := *(intf.Name) + intf_map[name] = db.Value{Field: make(map[string]string)} + + if intf.Config.Enabled != nil { + if *(intf.Config.Enabled) { + intf_map[name].Field[SFLOW_ADMIN_KEY] = "up" + } else { + intf_map[name].Field[SFLOW_ADMIN_KEY] = "down" + } + } + + if intf.Config.SamplingRate != nil { + intf_map[name].Field[SFLOW_SAMPL_RATE_KEY] = + strconv.FormatUint(uint64(*(intf.Config.SamplingRate)), 10) + } + } + } + + res_map[SFLOW_INTF_TBL] = intf_map + return res_map, err +} + +func getSflowInfoFromDb(d *db.DB) (Sflow, error) { + var sfInfo Sflow + var err error + + sflowEntry, err := d.GetEntry(&db.TableSpec{Name: SFLOW_GLOBAL_TBL}, db.Key{Comp: []string{SFLOW_GLOBAL_KEY}}) + if err != nil { + return sfInfo, err + } + + sfInfo.Enabled = sflowEntry.Get(SFLOW_ADMIN_KEY) + sfInfo.Polling_Interval = sflowEntry.Get(SFLOW_POLLING_INT_KEY) + sfInfo.Agent = sflowEntry.Get(SFLOW_AGENT_KEY) + + return sfInfo, err +} + +func fillSflowInfo(sflow *ocbinds.OpenconfigSamplingSflow_Sampling_Sflow, + targetUriPath string, d *db.DB) error { + var err error + enabled := false + sfInfo, err := getSflowInfoFromDb(d) + + if err != nil { + if !strings.Contains(err.Error(), "Entry does not exist") { + log.V(3).Info("Cant get entry: ", SFLOW_GLOBAL_TBL) + return err + } + err = nil + log.V(3).Info("sFlow not enabled") + } + + config := sflow.Config + state := sflow.State + + state.Enabled = &enabled + config.Enabled = &enabled + + if sfInfo.Enabled != "" { + enabled = sfInfo.Enabled == "up" + } + + if sfInfo.Polling_Interval != "" { + tmp, _ := strconv.ParseUint(sfInfo.Polling_Interval, 10, 16) + pollingInt := uint16(tmp) + state.PollingInterval = &pollingInt + config.PollingInterval = state.PollingInterval + } + + if sfInfo.Agent != "" { + state.Agent = &sfInfo.Agent + config.Agent = state.Agent + } + + return err +} + +func getSflow(sflow_tr *ocbinds.OpenconfigSamplingSflow_Sampling, targetUriPath string, + uri string, d []*db.DB) error { + log.V(3).Infof("Getting sFlow information") + var err error + + ygot.BuildEmptyTree(sflow_tr) + ygot.BuildEmptyTree(sflow_tr.Sflow) + ygot.BuildEmptyTree(sflow_tr.Sflow.Config) + ygot.BuildEmptyTree(sflow_tr.Sflow.State) + + switch targetUriPath { + case SAMPLING_SFLOW: + ygot.BuildEmptyTree(sflow_tr.Sflow.Collectors) + ygot.BuildEmptyTree(sflow_tr.Sflow.Interfaces) + err = fillSflowCollectorInfo(sflow_tr.Sflow.Collectors, "", targetUriPath, d[db.ConfigDB]) + if err != nil { + return err + } + err = fillSflowInterfaceInfo(sflow_tr.Sflow.Interfaces, "", targetUriPath, d[db.ApplDB]) + if err != nil { + return err + } + fallthrough + default: + err = fillSflowInfo(sflow_tr.Sflow, targetUriPath, d[db.ConfigDB]) + } + return err +} + +func getSflowColInfoFromDb(d *db.DB) (map[string]SflowCol, error) { + var sfInfo map[string]SflowCol + var col SflowCol + var err error + + sflowColTbl, err := d.GetTable(&db.TableSpec{Name: SFLOW_COL_TBL}) + + if err != nil { + return sfInfo, err + } + + keys, err := sflowColTbl.GetKeys() + if err != nil { + log.V(3).Info("No collectors configured") + return sfInfo, nil + } + + sfInfo = make(map[string]SflowCol) + for _, key := range keys { + name := key.Get(0) + colEntry, err := sflowColTbl.GetEntry(key) + if err != nil { + log.Errorf("Can't get entry with key: ", name) + return sfInfo, err + } + col.Ip = colEntry.Get(SFLOW_COL_IP_KEY) + col.Port = colEntry.Get(SFLOW_COL_PORT_KEY) + col.Vrf = colEntry.Get(SFLOW_COL_VRF_KEY) + sfInfo[name] = col + } + + return sfInfo, err +} + +func appendColToYang(sflowCols *ocbinds.OpenconfigSamplingSflow_Sampling_Sflow_Collectors, + ip string, port uint16, vrf string) error { + var err error + colKey := ocbinds.OpenconfigSamplingSflow_Sampling_Sflow_Collectors_Collector_Key{ip, port, vrf} + + sfc, found := sflowCols.Collector[colKey] + if !found { + sfc, err = sflowCols.NewCollector(ip, port, vrf) + if err != nil { + log.Errorf("Error creating Collector component") + return err + } + } + + ygot.BuildEmptyTree(sfc) + ygot.BuildEmptyTree(sfc.Config) + ygot.BuildEmptyTree(sfc.State) + sfc.Config.Address = &ip + sfc.Config.Port = &port + sfc.Config.NetworkInstance = &vrf + + sfc.State.Address = &ip + sfc.State.Port = &port + sfc.State.NetworkInstance = &vrf + + return err +} + +func fillSflowCollectorInfo(sflowCols *ocbinds.OpenconfigSamplingSflow_Sampling_Sflow_Collectors, + name string, targetUriPath string, d *db.DB) error { + var err error + var port uint16 + + sfInfo, err := getSflowColInfoFromDb(d) + if err != nil { + return err + } + + if name == "" { + for _, v := range sfInfo { + if v.Ip == "" { + log.Errorf("No collector IP") + break + } + if v.Port == "" { + v.Port = DEFAULT_COL_PORT + } + if v.Vrf == "" { + v.Vrf = DEFAULT_VRF_NAME + } + tmp, _ := strconv.ParseUint(v.Port, 10, 16) + port = uint16(tmp) + err = appendColToYang(sflowCols, v.Ip, port, v.Vrf) + } + return err + } + + if v, ok := sfInfo[name]; ok { + if v.Ip == "" { + log.Errorf("No collector IP") + return err + } + if v.Port == "" { + v.Port = DEFAULT_COL_PORT + } + if v.Vrf == "" { + v.Vrf = DEFAULT_VRF_NAME + } + tmp, _ := strconv.ParseUint(v.Port, 10, 16) + port = uint16(tmp) + err = appendColToYang(sflowCols, v.Ip, port, v.Vrf) + return err + } + + return errors.New("Collector entry not found") +} + +func getSflowCol(sflow_tr *ocbinds.OpenconfigSamplingSflow_Sampling, targetUriPath string, + uri string, d *db.DB) error { + log.V(3).Infof("Getting sFlow collector information") + ygot.BuildEmptyTree(sflow_tr.Sflow) + ygot.BuildEmptyTree(sflow_tr.Sflow.Collectors) + key := makeColKey(uri) + return fillSflowCollectorInfo(sflow_tr.Sflow.Collectors, key, targetUriPath, d) +} + +func getSflowIntfInfoFromDb(d *db.DB) (map[string]SflowIntf, error) { + var sfInfo map[string]SflowIntf + var intf SflowIntf + var err error + + sflowIntfTbl, err := d.GetTable(&db.TableSpec{Name: SFLOW_SESS_TBL}) + + if err != nil { + return sfInfo, err + } + + keys, err := sflowIntfTbl.GetKeys() + if err != nil { + log.V(3).Info("No interface configured, sFlow not enabled") + return sfInfo, nil + } + + sfInfo = make(map[string]SflowIntf) + for _, key := range keys { + name := key.Get(0) + intfEntry, err := sflowIntfTbl.GetEntry(key) + if err != nil { + log.Errorf("Can't get entry with key: ", name) + return sfInfo, err + } + intf.Enabled = intfEntry.Get(SFLOW_ADMIN_KEY) + intf.Sampling_Rate = intfEntry.Get(SFLOW_SAMPL_RATE_KEY) + sfInfo[name] = intf + } + + return sfInfo, err +} + +func fillSflowInterfaceInfo(sflowIntfs *ocbinds.OpenconfigSamplingSflow_Sampling_Sflow_Interfaces, + name string, targetUriPath string, d *db.DB) error { + var err error + var enabled bool + var samplingRate uint32 + + sfInfo, err := getSflowIntfInfoFromDb(d) + if err != nil { + return err + } + + if name == "" { + for name, v := range sfInfo { + tmp, _ := strconv.ParseUint(v.Sampling_Rate, 10, 32) + samplingRate = uint32(tmp) + enabled = v.Enabled == "up" + err = appendIntfToYang(sflowIntfs, name, enabled, samplingRate) + if err != nil { + break + } + } + return err + } + + if v, ok := sfInfo[name]; ok { + tmp, _ := strconv.ParseUint(v.Sampling_Rate, 10, 32) + samplingRate = uint32(tmp) + enabled = v.Enabled == "up" + err = appendIntfToYang(sflowIntfs, name, enabled, samplingRate) + return err + } + + return errors.New("sFlow Interface entry not found") +} + +func appendIntfToYang(sflowIntf *ocbinds.OpenconfigSamplingSflow_Sampling_Sflow_Interfaces, + name string, enabled bool, samplingRate uint32) error { + var err error + sfc, found := sflowIntf.Interface[name] + if !found { + sfc, err = sflowIntf.NewInterface(name) + if err != nil { + log.Errorf("Error creating sFlow Interface") + return err + } + } + + ygot.BuildEmptyTree(sfc) + ygot.BuildEmptyTree(sfc.Config) + ygot.BuildEmptyTree(sfc.State) + + sfc.Config.Enabled = &enabled + sfc.Config.Name = &name + sfc.Config.SamplingRate = &samplingRate + + sfc.State.Enabled = &enabled + sfc.State.Name = &name + sfc.State.SamplingRate = &samplingRate + + return err +} + +func getSflowIntf(sflow_tr *ocbinds.OpenconfigSamplingSflow_Sampling, targetUriPath string, + uri string, d *db.DB) error { + log.V(3).Infof("Getting sFlow interface information") + + name := NewPathInfo(uri).Var("name") + ygot.BuildEmptyTree(sflow_tr.Sflow) + ygot.BuildEmptyTree(sflow_tr.Sflow.Interfaces) + err := fillSflowInterfaceInfo(sflow_tr.Sflow.Interfaces, name, targetUriPath, d) + return err +}