From 64db3be6097805a85e3693ad29aa2792011a8907 Mon Sep 17 00:00:00 2001 From: Arun Barboza <29963827+a-barboza@users.noreply.github.com> Date: Sat, 12 Oct 2019 16:44:23 -0700 Subject: [PATCH] Merge broadcom/broadcom_sonic --> project-arlo/brcm_poc --- Makefile | 12 +- goyang-modified-files/entry.go | 8 +- models/yang/Makefile | 18 +- .../openconfig-spanning-tree-types.yang | 243 +++ models/yang/common/sonic-extensions.yang | 5 + models/yang/openconfig-spanning-tree-ext.yang | 273 +++ models/yang/openconfig-spanning-tree.yang | 836 +++++++ models/yang/sonic/common/sonic-common.yang | 63 +- models/yang/testdata/api-tests.yang | 34 + src/cvl/cvl_test.go | 23 +- .../testdata/schema/sonic-spanning-tree.yang | 381 ++++ src/rest/server/context.go | 22 + src/rest/server/handler.go | 48 +- src/rest/server/handler_test.go | 105 +- src/translib/acl_app.go | 20 +- src/translib/api_tests_app.go | 162 ++ src/translib/app_interface.go | 38 +- src/translib/app_utils.go | 8 + src/translib/common_app.go | 12 + src/translib/intf_app.go | 12 + src/translib/lldp_app.go | 12 + src/translib/ocbinds/oc.go | 2 +- src/translib/pfm_app.go | 12 + src/translib/request_binder.go | 98 +- src/translib/request_binder_test.go | 180 +- src/translib/stp_app.go | 1936 +++++++++++++++++ src/translib/sys_app.go | 12 + src/translib/test/translibtest.go | 6 +- src/translib/translib.go | 614 +++--- tools/pyang/pyang_plugins/openapi.py | 169 +- ygot-modified-files/list.go | 10 +- ygot-modified-files/node.go | 13 +- ygot-modified-files/schema.go | 27 +- ygot-modified-files/string_type.go | 199 ++ ygot-modified-files/util_schema.go | 51 +- 35 files changed, 5185 insertions(+), 479 deletions(-) create mode 100755 models/yang/common/openconfig-spanning-tree-types.yang create mode 100755 models/yang/openconfig-spanning-tree-ext.yang create mode 100755 models/yang/openconfig-spanning-tree.yang create mode 100644 models/yang/testdata/api-tests.yang create mode 100755 src/cvl/testdata/schema/sonic-spanning-tree.yang create mode 100644 src/translib/api_tests_app.go create mode 100644 src/translib/stp_app.go create mode 100644 ygot-modified-files/string_type.go diff --git a/Makefile b/Makefile index f78cee25bb..cce497f328 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,9 @@ GO_DEPS_LIST = github.com/gorilla/mux \ github.com/pkg/profile \ gopkg.in/go-playground/validator.v9 \ golang.org/x/crypto/ssh \ - github.com/antchfx/jsonquery \ - github.com/antchfx/xmlquery + github.com/antchfx/jsonquery \ + github.com/antchfx/xmlquery \ + github.com/facette/natsort REST_BIN = $(BUILD_DIR)/rest_server/main @@ -72,7 +73,7 @@ $(GO_DEPS_LIST): cli: rest-server $(MAKE) -C src/CLI -cvl: go-deps +cvl: go-deps go-patch go-redis-patch $(MAKE) -C src/cvl $(MAKE) -C src/cvl/schema $(MAKE) -C src/cvl/testdata/schema @@ -108,12 +109,13 @@ cp $(TOPDIR)/ygot-modified-files/schema.go $(BUILD_GOPATH)/src/github.com/openco cp $(TOPDIR)/ygot-modified-files/unmarshal.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/unmarshal.go; \ cp $(TOPDIR)/ygot-modified-files/validate.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/validate.go; \ cp $(TOPDIR)/ygot-modified-files/reflect.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/../util/reflect.go; \ +cp $(TOPDIR)/ygot-modified-files/string_type.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/string_type.go; \ cp $(TOPDIR)/goyang-modified-files/README.md $(BUILD_GOPATH)/src/github.com/openconfig/goyang/README.md; \ cp $(TOPDIR)/goyang-modified-files/yang.go $(BUILD_GOPATH)/src/github.com/openconfig/goyang/yang.go; \ cp $(TOPDIR)/goyang-modified-files/annotate.go $(BUILD_GOPATH)/src/github.com/openconfig/goyang/annotate.go; \ cp $(TOPDIR)/goyang-modified-files/entry.go $(BUILD_GOPATH)/src/github.com/openconfig/goyang/pkg/yang/entry.go; \ -$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ygot; \ -$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/goyang +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/goyang; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ygot install: $(INSTALL) -D $(REST_BIN) $(DESTDIR)/usr/sbin/rest_server diff --git a/goyang-modified-files/entry.go b/goyang-modified-files/entry.go index 6b7870f924..f626dc970d 100644 --- a/goyang-modified-files/entry.go +++ b/goyang-modified-files/entry.go @@ -116,6 +116,10 @@ type Entry struct { // the augmenting entity per RFC6020 Section 7.15.2. The namespace // of the Entry should be accessed using the Namespace function. namespace *Value + + ChildSchemaCache map[reflect.StructTag]*Entry `json:"-"` + + IsSchemaValidated bool `json:"-"` } // An RPCEntry contains information related to an RPC Node. @@ -1399,8 +1403,8 @@ func (s sortedErrors) Less(i, j int) bool { } return nless(fi[x], fj[x]) } - for x := 1; x < 4; x++ { - switch compare(1) { + for x := 0; x < len(fi) && x < len(fj); x++ { + switch compare(x) { case -1: return true case 1: diff --git a/models/yang/Makefile b/models/yang/Makefile index 228fa75751..0a5b27e230 100644 --- a/models/yang/Makefile +++ b/models/yang/Makefile @@ -20,11 +20,13 @@ TOPDIR := ../.. BUILD_DIR := $(TOPDIR)/build -YANGAPI_DIR := $(TOPDIR)/build/yaml -YANGDIR := $(TOPDIR)/models/yang -YANGDIR_COMMON := $(TOPDIR)/models/yang/common -YANG_MOD_FILES := $(shell find $(YANGDIR) -maxdepth 1 -name '*.yang' | sort) -YANG_COMMON_FILES := $(shell find $(YANGDIR_COMMON) -name '*.yang' | sort) +YANGAPI_DIR := $(TOPDIR)/build/yaml +YANGDIR := $(TOPDIR)/models/yang +YANGDIR_COMMON := $(TOPDIR)/models/yang/common +YANGDIR_ANNOTATIONS := $(TOPDIR)/models/yang/annotations/openapi_annotations +YANG_MOD_FILES := $(shell find $(YANGDIR) -maxdepth 1 -name '*.yang' | sort) +YANG_ANNOTATIONS_FILES := $(shell find $(YANGDIR_ANNOTATIONS) -maxdepth 1 -name '*.yang' | sort) +YANG_COMMON_FILES := $(shell find $(YANGDIR_COMMON) -name '*.yang' | sort) TOOLS_DIR := $(TOPDIR)/tools PYANG_DIR := $(TOOLS_DIR)/pyang @@ -42,7 +44,7 @@ allyangs.tree: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) -f tree \ -o $(YANGDIR)/$@ \ -p $(YANGDIR_COMMON):$(YANGDIR) \ - $(YANG_MOD_FILES) + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) @echo "+++++ Generation of YANG tree for Yang modules completed +++++" allyangs_tree.html: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) @@ -50,7 +52,7 @@ allyangs_tree.html: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) -f jstree \ -o $(YANGDIR)/$@ \ -p $(YANGDIR_COMMON):$(YANGDIR) \ - $(YANG_MOD_FILES) + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) @echo "+++++ Generation of HTML tree for Yang modules completed +++++" #====================================================================== @@ -64,7 +66,7 @@ $(YANGAPI_DIR)/.done: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) --outdir $(YANGAPI_DIR) \ --plugindir $(PYANG_PLUGIN_DIR) \ -p $(YANGDIR_COMMON):$(YANGDIR) \ - $(YANG_MOD_FILES) + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) @echo "+++++ Generation of YAML files for Yang modules completed +++++" touch $@ diff --git a/models/yang/common/openconfig-spanning-tree-types.yang b/models/yang/common/openconfig-spanning-tree-types.yang new file mode 100755 index 0000000000..3a1c0bcbe4 --- /dev/null +++ b/models/yang/common/openconfig-spanning-tree-types.yang @@ -0,0 +1,243 @@ +module openconfig-spanning-tree-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/spanning-tree/types"; + + prefix "oc-stp-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines types related to the + spanning-tree protocol model."; + + oc-ext:openconfig-version "0.3.0"; + + revision "2014-03-14" { + description + "Remove the time-since-last-topology-change leaf and + replace it with a timestamp of last topology change."; + reference "0.3.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2017-07-14" { + description + "Migrated to OpenConfig types; fixed missing applied state + in rapid-pvst"; + reference "0.2.0"; + } + + revision "2016-10-03" { + description + "Initial public revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + identity STP_PROTOCOL { + description + "base identity for support spanning tree protocol"; + } + + identity RSTP { + base STP_PROTOCOL; + description + "Rapid Spanning Tree Protocol"; + reference "IEEE 802.1D 17"; + } + + identity MSTP { + base STP_PROTOCOL; + description + "Multiple Spanning Tree Protocol"; + reference "IEEE 802.1Q 13"; + } + + identity RAPID_PVST { + base STP_PROTOCOL; + description + "Rapid Per Vlan Spanning Tree Protocol"; + } + + identity STP_PORT_STATE { + description + "base identity for the different Spanning Tree Protocol port + states"; + reference + "IEEE 802.1D 7.4 Port States and the active topology"; + } + + identity DISABLED { + base STP_PORT_STATE; + description + "A port that is manually isolated from the network"; + } + + identity LISTENING { + base STP_PORT_STATE; + description + "Processing BPDUs and building active toplogy"; + } + + identity LEARNING { + base STP_PORT_STATE; + description + "Building bridging tables; no forwarding of data"; + } + + identity BLOCKING { + base STP_PORT_STATE; + description + "A port that would cause a loop if it were sending data, + so it is only receiving BPDUs, untill a topology change + removes the possibliity of a loop"; + } + + identity FORWARDING { + base STP_PORT_STATE; + description + "Sending and receiving data, normal operation"; + } + + identity STP_EDGE_PORT { + description + "base identity for the different edge port modes"; + reference + "IEEE 802.1D 17.13.1"; + } + + identity EDGE_ENABLE { + base STP_EDGE_PORT; + description + "Enable edge port for the bridge port"; + } + + identity EDGE_DISABLE { + base STP_EDGE_PORT; + description + "Disable edge port for the bridge port"; + } + + identity EDGE_AUTO { + base STP_EDGE_PORT; + description + "Enable edge port autodetction for the bridge port"; + } + + identity STP_PORT_ROLE { + description + "Base identity for the different Spanning Tree Protocol port + roles"; + reference + "IEEE 802.1D 17.7 Port Role assignments"; + } + + identity ROOT { + base STP_PORT_ROLE; + description + "The port that receives the best BPDU on a bridge is the + root port"; + } + + identity DESIGNATED { + base STP_PORT_ROLE; + description + "A port is designated if it can send the best BPDU on the + segment to which it is connected."; + } + + identity ALTERNATE { + base STP_PORT_ROLE; + description + "An alternate port receives more useful BPDUs from another + bridge and is a port blocked"; + } + + identity BACKUP { + base STP_PORT_ROLE; + description + "A backup port receives more useful BPDUs from the same + bridge it is on and is a port blocked"; + } + + // typedef statements + + typedef stp-bridge-priority-type { + type uint32 { + range 1..611440; + } + description + "The manageable component of the Bridge Identifier"; + reference "IEEE 802.1D 17.13.7 Bridge Identifier Priority"; + } + + typedef stp-port-priority-type { + type uint8 { + range 1..240; + } + description + "The manageable component of the Port Identifier, + also known as the Port Priority"; + reference + "IEEE 802.1D 17.13.10 Port Identifier Priority"; + } + + typedef stp-guard-type { + type enumeration { + enum ROOT { + description + "Enable root guard"; + } + enum LOOP { + description + "Enable loop guard"; + } + enum NONE { + description + "disable guard"; + } + } + description + "Type definition for the different STP guard for the switch port"; + reference "IEEE 802.1D 17.2"; + } + + typedef stp-link-type { + type enumeration { + enum P2P { + description + "Point-to-Point link"; + } + enum SHARED { + description + "Shared link"; + } + } + description + "Type definition for the different link types"; + reference "IEEE 802.1D 17.2"; + } +} diff --git a/models/yang/common/sonic-extensions.yang b/models/yang/common/sonic-extensions.yang index 263cf9274d..c1252ef601 100644 --- a/models/yang/common/sonic-extensions.yang +++ b/models/yang/common/sonic-extensions.yang @@ -50,6 +50,11 @@ module sonic-extensions { description "Db table field name."; } + extension openapi-opid { + argument "openapi-opid"; + description "Custom Operation ID for OpenAPI"; + } + extension field-transformer { argument "field-transformer-name"; description "Db table field transformer name.This can be applied to either transform yang value to some different format diff --git a/models/yang/openconfig-spanning-tree-ext.yang b/models/yang/openconfig-spanning-tree-ext.yang new file mode 100755 index 0000000000..3546a7c8fa --- /dev/null +++ b/models/yang/openconfig-spanning-tree-ext.yang @@ -0,0 +1,273 @@ +module openconfig-spanning-tree-ext { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/spanning-tree/extension"; + + prefix "oc-stp-ext"; + + import openconfig-spanning-tree { prefix oc-stp; } + import openconfig-spanning-tree-types { prefix oc-stp-types; } + + + identity PVST { + base oc-stp-types:STP_PROTOCOL; + description + "Per Vlan Spanning Tree Protocol"; + } + + grouping stp-global-ext-config { + leaf rootguard-timeout { + type uint16 { + range 5..600; + } + units "seconds"; + default 30; + } + uses oc-stp:stp-timer-config; + uses oc-stp:bridge-priority-config; + } + + grouping spanning-tree-enable-config { + leaf spanning-tree-enable { + type boolean; + description + "Enable/Disable spanning tree protocol on an interface or Vlan"; + } + } + + grouping stp-interface-common-ext-config { + leaf portfast { + type boolean; + description + "Enable/Disable portfast"; + } + + leaf uplink-fast { + type boolean; + description + "Enable/Disable uplink fast"; + } + + leaf bpdu-guard-port-shutdown { + type boolean; + description + "Port to be shutdown when it receives a BPDU"; + } + + leaf cost { + type uint32 { + range 1..200000000; + } + description + "The port's contribution, when it is the Root Port, + to the Root Path Cost for the Bridge"; + reference + "IEEE 802.1D 17.13.11 PortPathCost"; + } + + leaf port-priority { + type oc-stp-types:stp-port-priority-type; + description + "The manageable component of the Port Identifier, + also known as the Port Priority"; + reference + "IEEE 802.1D 17.13.10 Port Identifier Priority"; + } + + uses spanning-tree-enable-config; + } + + grouping stp-instance-state { + leaf stp-instance { + type uint16; + description + "Instance identifier of STP"; + } + } + + grouping vlan-interface-extra-state-field { + leaf root-guard-timer { + type uint16; + description + "Root guard current timer value"; + } + } + + grouping vlan-interface-extra-counters { + leaf tcn-sent { + type uint64; + description + "Tcn transmitted"; + } + + leaf tcn-received { + type uint64; + description + "Tcn received"; + } + } + + grouping stp-pvst-top { + description + "Top grouping for per vlan spanning tree configuration + and operation data"; + + list vlan { + key "vlan-id"; + description + "List of the vlans"; + + leaf vlan-id { + type leafref { + path "../config/vlan-id"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for each vlan"; + + uses oc-stp:stp-rapid-pvst-config; + uses oc-stp:stp-timer-config; + uses oc-stp:bridge-priority-config; + uses spanning-tree-enable-config; + } + + container state { + config false; + description + "Operational data for each vlan"; + + uses oc-stp:stp-rapid-pvst-config; + uses oc-stp:stp-timer-config; + uses oc-stp:bridge-priority-config; + uses oc-stp:stp-common-state; + uses spanning-tree-enable-config; + uses stp-instance-state; + } + + uses oc-stp:stp-interfaces-top; + } + } + + augment /oc-stp:stp { + container pvst { + description + "Per vlan Spanning-tree protocol configuration and + operational data"; + + uses stp-pvst-top; + } + } + + augment /oc-stp:stp/oc-stp:global/oc-stp:config { + uses stp-global-ext-config; + } + + augment /oc-stp:stp/oc-stp:global/oc-stp:state { + uses stp-global-ext-config; + } + + augment /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:config { + uses spanning-tree-enable-config; + } + + augment /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:state { + uses stp-instance-state; + } + + augment /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:interfaces/oc-stp:interface/oc-stp:state { + uses vlan-interface-extra-state-field; + } + + augment /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:interfaces/oc-stp:interface/oc-stp:state/oc-stp:counters { + uses vlan-interface-extra-counters; + } + + augment /oc-stp:stp/oc-stp-ext:pvst/oc-stp-ext:vlan/oc-stp-ext:interfaces/oc-stp-ext:interface/oc-stp-ext:state { + uses vlan-interface-extra-state-field; + } + + augment /oc-stp:stp/oc-stp-ext:pvst/oc-stp-ext:vlan/oc-stp-ext:interfaces/oc-stp-ext:interface/oc-stp-ext:state/oc-stp-ext:counters { + uses vlan-interface-extra-counters; + } + + augment /oc-stp:stp/oc-stp:interfaces/oc-stp:interface/oc-stp:config { + uses stp-interface-common-ext-config; + } + + augment /oc-stp:stp/oc-stp:interfaces/oc-stp:interface/oc-stp:state { + leaf bpdu-guard-shutdown { + type boolean; + description + "Port disabled due to bpdu-guard"; + } + uses stp-interface-common-ext-config; + } + + + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:bridge-assurance { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:bridge-assurance { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:etherchannel-misconfig-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:etherchannel-misconfig-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:bpduguard-timeout-recovery { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:bpduguard-timeout-recovery { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp-ext:hold-count { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:loop-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:loop-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:bpdu-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:bpdu-guard { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:config/oc-stp:bpdu-filter { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:global/oc-stp:state/oc-stp:bpdu-filter { + deviate not-supported; + } + + deviation /oc-stp:stp/oc-stp-ext:pvst/oc-stp-ext:vlan/oc-stp-ext:config/oc-stp-ext:hold-count { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp-ext:pvst/oc-stp-ext:vlan/oc-stp-ext:state/oc-stp-ext:hold-count { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:config/oc-stp:hold-count { + deviate not-supported; + } + deviation /oc-stp:stp/oc-stp:rapid-pvst/oc-stp:vlan/oc-stp:state/oc-stp:hold-count { + deviate not-supported; + } + + deviation /oc-stp:stp/oc-stp:rstp { + deviate not-supported; + } + + deviation /oc-stp:stp/oc-stp:mstp { + deviate not-supported; + } +} diff --git a/models/yang/openconfig-spanning-tree.yang b/models/yang/openconfig-spanning-tree.yang new file mode 100755 index 0000000000..9e72331046 --- /dev/null +++ b/models/yang/openconfig-spanning-tree.yang @@ -0,0 +1,836 @@ +module openconfig-spanning-tree { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/spanning-tree"; + + prefix "oc-stp"; + + import openconfig-spanning-tree-types { prefix oc-stp-types; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-types { prefix oc-types; } + import openconfig-vlan-types { prefix oc-vlan-types; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + for the spanning tree protocol."; + + oc-ext:openconfig-version "0.3.0"; + + revision "2014-03-14" { + description + "Remove the time-since-last-topology-change leaf and + replace it with a timestamp of last topology change."; + reference "0.3.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2017-07-14" { + description + "Migrated to OpenConfig types; fixed missing applied state + in rapid-pvst"; + reference "0.2.0"; + } + + revision "2016-10-03" { + description + "Initial public revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + + // grouping statements + + grouping stp-interfaces-state { + description + "Grouping of STP operational data for bridge port"; + + leaf port-num { + type uint16; + description + "The port number of the bridge port"; + reference "RFC4188 BRIDGE-MIB dot1dStpPort"; + } + + leaf role { + type identityref { + base oc-stp-types:STP_PORT_ROLE; + } + description + "The current role of the bridge port"; + reference + "IEEE8021-MSTP-MIB ieee8021MstpPortRole"; + } + + leaf port-state { + type identityref { + base oc-stp-types:STP_PORT_STATE; + } + description + "The current state of the bridge port"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortState"; + } + + leaf designated-root-priority { + type oc-stp-types:stp-bridge-priority-type; + description + "The bridge priority of the bridge recorded as the + root in the configuration BPDUs transmitted by the designated + bridge for the segment to which the port is attached"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedRoot"; + } + + leaf designated-root-address { + type oc-yang:mac-address; + description + "The bridge address of the bridge recorded as the + root in the configuration BPDUs transmitted by the designated + bridge for the segment to which the port is attached"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedRoot"; + } + + leaf designated-cost { + type uint32; + description + "The path cost of the Designated Port of the + segment connected to this port"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedCost"; + } + + leaf designated-bridge-priority { + type oc-stp-types:stp-bridge-priority-type; + description + "The bridge priority of the bridge that this port considers + to be the designated bridge for this port's segment."; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedBridge"; + } + + leaf designated-bridge-address { + type oc-yang:mac-address; + description + "The bridge address of the bridge that this port considers + to be the designated bridge for this port's segment."; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedBridge"; + } + + leaf designated-port-priority { + type oc-stp-types:stp-port-priority-type; + description + "The Port priority of the port on the Designated + Bridge for this port's segment, two octet string"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedPort"; + } + + leaf designated-port-num { + type uint16; + description + "The Port number of the port on the Designated + Bridge for this port's segment, two octet string"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortDesignatedPort"; + } + + leaf forward-transisitions { + type oc-yang:counter64; + description + "The number of times this port has transitioned + from the Learning state to the Forwarding state"; + reference "RFC4188 BRIDGE-MIB dot1dStpPortForwardTransitions"; + } + + container counters { + description + "The BPDU packet transmition statistics"; + + leaf bpdu-sent { + type oc-yang:counter64; + description + "The number of BPDU packet sent"; + } + + leaf bpdu-received { + type oc-yang:counter64; + description + "The number of BPDU packet received"; + } + } + } + + grouping stp-interfaces-config { + description + "Grouping of STP configuration for bridge port"; + + leaf name { + type oc-if:base-interface-ref; + description + "Reference to the STP ethernet interface"; + } + + leaf cost { + type uint32 { + range 1..200000000; + } + description + "The port's contribution, when it is the Root Port, + to the Root Path Cost for the Bridge"; + reference + "IEEE 802.1D 17.13.11 PortPathCost"; + } + + leaf port-priority { + type oc-stp-types:stp-port-priority-type; + description + "The manageable component of the Port Identifier, + also known as the Port Priority"; + reference + "IEEE 802.1D 17.13.10 Port Identifier Priority"; + } + } + + grouping stp-interfaces-top { + description + "Grouping of STP configuration and operation data for + bridge port"; + + container interfaces { + description + "Enclosing container for the list of interface references"; + + list interface { + key "name"; + description + "List of interfaces on which STP is enable"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for STP on each interface"; + + uses stp-interfaces-config; + } + + container state { + + config false; + + description + "Operational state data for STP on each interface"; + + uses stp-interfaces-config; + uses stp-interfaces-state; + } + } + } + } + + grouping bridge-priority-config { + description + "Grouping for bridge priority"; + + leaf bridge-priority { + type oc-stp-types:stp-bridge-priority-type; + description + "The manageable component of the Bridge Identifier"; + reference + "IEEE 802.1D 17.13.7 Bridge Identifier Priority"; + } + } + + grouping stp-common-state { + description + "Grouping for common STP operation data"; + + leaf bridge-address { + type oc-yang:mac-address; + description + "A unique 48-bit Universally Administered MAC Address + assigned to the bridge"; + reference + "IEEE 802.1D 7.12.5 Unique identification of a bridge"; + } + + leaf designated-root-priority { + type oc-stp-types:stp-bridge-priority-type; + description + "The bridge priority of the root of the spanning + tree, as determined by the Spanning Tree Protocol, + as executed by this node"; + reference + "RFC4188 BRIDGE-MIB dot1dStpDesignatedRoot"; + } + + leaf designated-root-address { + type oc-yang:mac-address; + description + "The bridge address of the root of the spanning + tree, as determined by the Spanning Tree Protocol, + as executed by this node"; + reference + "RFC4188 BRIDGE-MIB dot1dStpDesignatedRoot"; + } + + leaf root-port { + type uint16; + description + "The port number of the port which offers the lowest + cost path from this bridge to the root bridge"; + reference + "RFC4188 BRIDGE-MIB dot1dStpRootPort"; + } + + leaf root-cost { + type uint32; + description + "The cost of the path to the root as seen from this bridge"; + reference + "RFC4188 BRIDGE-MIB dot1dStpRootCost"; + } + + leaf hold-time { + type uint8; + description + "This time value determines the interval length + during which no more than two Configuration bridge + PDUs shall be transmitted by this node"; + reference + "RFC4188 BRIDGE-MIB dot1dStpHoldTime"; + } + + leaf topology-changes { + type oc-yang:counter64; + description + "The total number of topology changes detected by + this bridge since the management entity was last + reset or initialized"; + reference + "RFC4188 BRIDGE-MIB dot1dStpTopChanges"; + } + + leaf last-topology-change { + type oc-types:timeticks64; + description + "The time at which the last topology change was + detected by the bridge entity. The value is + expressed relative to the Unix Epoch (Jan 1, 1970 + 00:00:00 UTC)."; + } + } + + grouping stp-timer-config { + description + "Grouping for common STP parameters"; + + leaf hello-time { + type uint8 { + range 1..10; + } + units "seconds"; + description + "The interval between periodic transmissions of + configuration messages by designated ports"; + reference + "IEEE 802.1D 17.13.6 Bridge Hello Time"; + } + + leaf max-age { + type uint8 { + range 6..40; + } + units "seconds"; + description + "The maximum age of the information transmitted by the + bridge when it is the root bridge"; + reference + "IEEE 802.1D 17.13.8 Bridge Max Age"; + } + + leaf forwarding-delay { + type uint8 { + range 4..30; + } + units "seconds"; + description + "The delay used by STP bridges to transition root and + designated ports to forwarding"; + reference + "IEEE 802.1D 17.13.5 Bridge Forward Delay"; + } + + leaf hold-count { + type uint8 { + range 1..10; + } + default 6; + description + "the maximum number of BPDUs per second that the + switch can send from an interface"; + reference + "IEEE 802.1D 17.13.12 Transmit Hold Count"; + } + } + + grouping stp-rapid-pvst-config { + description + "Configuration parameters relating to rapid PVST"; + + leaf vlan-id { + type oc-vlan-types:vlan-id; + description + "Interface VLAN ID"; + } + } + + grouping stp-rapid-pvst-top { + description + "Top grouping for rapid per vlan spanning tree configuration + and operation data"; + + list vlan { + key "vlan-id"; + description + "List of the vlans"; + + leaf vlan-id { + type leafref { + path "../config/vlan-id"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for each vlan"; + + uses stp-rapid-pvst-config; + uses stp-timer-config; + uses bridge-priority-config; + } + + container state { + config false; + description + "Operational data for each vlan"; + + uses stp-rapid-pvst-config; + uses stp-timer-config; + uses bridge-priority-config; + uses stp-common-state; + } + + uses stp-interfaces-top; + } + } + + grouping mst-instance-config { + description + "Grouping for mstp instance configuration"; + + leaf mst-id { + type uint16 { + range "1..4094"; + } + description + "In an MSTP Bridge, an MSTID, i.e., a value used to identify + a spanning tree (or MST) instance."; + reference + "IEEE8021-TC-MIB IEEE8021MstIdentifier"; + } + + leaf-list vlan { + type union { + type oc-vlan-types:vlan-id; + type oc-vlan-types:vlan-range; + } + description + "list of vlans mapped to the MST instance"; + } + } + + grouping mst-instance-top { + description + "Top level grouping for mstp instances"; + + list mst-instance { + key "mst-id"; + description + "List of the mstp instances"; + + leaf mst-id { + type leafref { + path "../config/mst-id"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for MSTP instance"; + + uses mst-instance-config; + uses bridge-priority-config; + } + + container state { + config false; + + description + "Operational data for MSTP instance"; + + uses mst-instance-config; + uses bridge-priority-config; + uses stp-common-state; + } + + uses stp-interfaces-top; + } + } + + grouping mstp-config { + description + "Grouping for MSTP configuration data"; + + leaf name { + type string { + length "1..32"; + } + description + "The Configuration Name in the MST Configuration Identifier"; + reference + "IEEE 802.1Q 13.8 MST Configuration Identifier (MCID)"; + } + + leaf revision { + type uint32; + description + "The Revision Level in the MST Configuration Identifier"; + reference + "IEEE 802.1Q 13.8 MST Configuration Identifier"; + } + + leaf max-hop { + type uint8 { + range 1..255; + } + description + "The max hop determines the number of bridges in an MST + region that a BPDU can traverse before it is discarded"; + reference + "IEEE 802.1Q 13.26.4 BridgeTimes"; + } + + uses stp-timer-config; + } + + grouping mstp-state { + description + "Operational state data for MSTP"; + } + + grouping stp-mstp-top { + description + "Top grouping for MSTP configuration and operation data"; + + container config { + description + "Configuration data for MSTP"; + + uses mstp-config; + } + + container state { + config false; + + description + "Operational data for MSTP"; + + uses mstp-config; + uses mstp-state; + } + + container mst-instances { + description + "Configuration and operation data for MSTP instances"; + + uses mst-instance-top; + } + } + + grouping stp-rstp-top { + description + "Top grouping for RSTP configuration and operation data"; + + container config { + description + "Configuration data for RSTP"; + + uses stp-timer-config; + uses bridge-priority-config; + } + + container state { + config false; + + description + "Operational state data for RSTP"; + + uses stp-timer-config; + uses bridge-priority-config; + uses stp-common-state; + } + + uses stp-interfaces-top; + } + + grouping stp-interface-common-config { + description + "Configuration data for interface specific STP features"; + + leaf name { + type oc-if:base-interface-ref; + description + "Reference to the STP Ethernet interface"; + } + + leaf edge-port { + type identityref { + base oc-stp-types:STP_EDGE_PORT; + } + description + "Configure the edge port state"; + } + + leaf link-type { + type oc-stp-types:stp-link-type; + description + "specifies the interface's link type"; + } + + leaf guard { + type oc-stp-types:stp-guard-type; + description + "Enable root guard or loop guard"; + } + + uses stp-bpdu-config; + + } + + grouping stp-interface-common-state { + description + "Operational state data for STP on interfaces"; + } + + grouping stp-interface-common-top { + description + "Top-level grouping for interface specific STP features"; + + list interface { + key "name"; + description + "List of interfaces on which STP is enable"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for STP on each bridge port"; + + uses stp-interface-common-config; + } + + container state { + + config false; + + description + "Operational state data for STP on each bridge port"; + + uses stp-interface-common-config; + uses stp-interface-common-state; + } + } + } + + grouping stp-bpdu-config { + description + "Grouping for STP BPDU configuration"; + + leaf bpdu-guard { + type boolean; + description + "Enable edge port BPDU guard"; + } + + leaf bpdu-filter { + type boolean; + description + "Enable edge port BPDU filter"; + } + } + + grouping stp-global-config { + description + "Global spanning tree configuration"; + + leaf-list enabled-protocol { + type identityref { + base oc-stp-types:STP_PROTOCOL; + } + description + "List of the spanning tree protocols enabled on the + device"; + } + + leaf bridge-assurance { + type boolean; + description + "Enable bridge assurance to protect against unidirectional + link failure"; + } + + leaf etherchannel-misconfig-guard { + type boolean; + description + "EtherChannel guard detects a misconfigured EtherChannel + when interfaces on the switch are configured as an + EtherChannel while interfaces on the other device are not + or when not all the interfaces on the other device are in + the same EtherChannel."; + } + + leaf bpduguard-timeout-recovery { + type uint8; + units "seconds"; + description + "Amount of time, in seconds, the interface receiving BPDUs + is disabled. Once the timeout expires, the interface is + brought back into service."; + } + + leaf loop-guard { + type boolean; + description + "The loop guard default setting for the bridge"; + } + + uses stp-bpdu-config; + + } + + grouping stp-global-state { + description + "Global operational state for STP"; + } + + grouping stp-global-base { + description + "Grouping for global spanning tree data"; + + container config { + description + "Global spanning tree configuration"; + uses stp-global-config; + } + + container state { + config false; + + description + "Global spanning tree state"; + uses stp-global-config; + uses stp-global-state; + } + } + + grouping stp-top { + description + "Top-level grouping for spanning-tree model"; + + container stp { + description + "Top-level container for spanning tree configuration and + state data"; + + container global { + description + "Global configuration and state data"; + + uses stp-global-base; + } + + container rstp { + + description + "Rapid Spanning-tree protocol configuration and operation + data"; + + uses stp-rstp-top; + } + + container mstp { + description + "Multi Spanning-tree protocol configuration and operation + data"; + + uses stp-mstp-top; + } + + container rapid-pvst { + description + "Rapid per vlan Spanning-tree protocol configuration and + operational data"; + + uses stp-rapid-pvst-top; + } + + container interfaces { + description + "Enclosing container for the list of interface references"; + + uses stp-interface-common-top; + } + } + } + + // data definition statements + + uses stp-top; + +} diff --git a/models/yang/sonic/common/sonic-common.yang b/models/yang/sonic/common/sonic-common.yang index 77a81c5f84..43f9ecf958 100644 --- a/models/yang/sonic/common/sonic-common.yang +++ b/models/yang/sonic/common/sonic-common.yang @@ -3,6 +3,10 @@ module sonic-common { namespace "http://github.com/Azure/sonic-common"; prefix scommon; + import ietf-yang-types { + prefix yang; + } + organization "SONiC"; @@ -18,20 +22,61 @@ module sonic-common { } typedef tagging_mode { - type enumeration { - enum untagged; - enum tagged; - enum priority_tagged; - } + type enumeration { + enum untagged; + enum tagged; + enum priority_tagged; + } } typedef admin-status { - type enumeration { - enum up; - enum down; - } + type enumeration { + enum up; + enum down; + } + } + + extension custom-handler { + description + "Node should be handled by custom handler"; + argument "name"; + } + + extension db-name { + description + "DB name, e.g. APPL_DB, CONFIG_DB"; + argument "value"; + } + + extension key-delim { + description + "Key delimeter, e.g. - |, :"; + argument "value"; + } + + extension key-pattern { + description + "Key pattern, e.g. - ACL_RULE|{aclname}|{rulename}"; + argument "value"; + } + + extension map-list { + description + "If it is a map list"; + argument "value"; // + } + + extension map-leaf { + description + "Map leaf names"; + argument "value"; // } + extension pf-check { + description + "Platform specific validation"; + argument "handler"; // + } container operation { leaf operation { diff --git a/models/yang/testdata/api-tests.yang b/models/yang/testdata/api-tests.yang new file mode 100644 index 0000000000..8fd519d09b --- /dev/null +++ b/models/yang/testdata/api-tests.yang @@ -0,0 +1,34 @@ +module api-tests { + + yang-version "1"; + namespace "http://github.com/Azure/sonic-api-tests"; + prefix "api-tests"; + + description + "Test module for SONiC Management REST Server"; + + + container home { + leaf name { + type string; + } + } + + rpc my-echo { + input { + leaf message { + type string; + } + leaf error-type { + type string; + } + } + + output { + leaf message { + type string; + } + } + } + +} \ No newline at end of file diff --git a/src/cvl/cvl_test.go b/src/cvl/cvl_test.go index f08394ba34..8de207f1b0 100644 --- a/src/cvl/cvl_test.go +++ b/src/cvl/cvl_test.go @@ -221,7 +221,18 @@ func prepareDb() { fmt.Printf("read file %v err: %v", fileName, err) } - port_map := loadConfig("", PortsMapByte) + port_map = loadConfig("", PortsMapByte) + + portKeys, err:= rclient.Keys("PORT|*").Result() + //Load only the port config which are not there in Redis + if err == nil { + portMapKeys := port_map["PORT"].(map[string]interface{}) + for _, portKey := range portKeys { + //Delete the port key which is already there in Redis + delete(portMapKeys, portKey[len("PORTS|") - 1:]) + } + port_map["PORT"] = portMapKeys + } loadConfigDB(rclient, port_map) loadConfigDB(rclient, depDataMap) @@ -2115,7 +2126,7 @@ func TestValidateEditConfig_Create_DepData_From_Redis_Negative11(t *testing.T) { cvl.ValidationSessClose(cvSess) - if (err != cvl.CVL_SEMANTIC_DEPENDENT_DATA_MISSING) { + if err == cvl.CVL_SUCCESS { t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) } @@ -2273,8 +2284,7 @@ func TestValidateEditConfig_Create_ErrAppTag_In_Must_Negative(t *testing.T) { WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) - /* Compare expected error details and error tag. */ - if compareErrorDetails(cvlErrInfo, cvl.CVL_SEMANTIC_DEPENDENT_DATA_MISSING ,"vlan-invalid", "") != true { + if retCode == cvl.CVL_SUCCESS { t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) } @@ -2628,7 +2638,7 @@ func TestValidateEditConfig_DepData_Through_Cache(t *testing.T) { loadConfigDB(rclient, depDataMap) //Modify entry - depDataMap = map[string]interface{} { + modDepDataMap := map[string]interface{} { "PORT" : map[string]interface{} { "Ethernet3" : map[string]interface{} { "mtu": "9200", @@ -2636,7 +2646,7 @@ func TestValidateEditConfig_DepData_Through_Cache(t *testing.T) { }, } - loadConfigDB(rclient, depDataMap) + loadConfigDB(rclient, modDepDataMap) cfgDataAclRule := []cvl.CVLEditConfigData { cvl.CVLEditConfigData { @@ -2662,6 +2672,7 @@ func TestValidateEditConfig_DepData_Through_Cache(t *testing.T) { } unloadConfigDB(rclient, depDataMap) + unloadConfigDB(rclient, modDepDataMap) } /* Delete field for an existing key.*/ diff --git a/src/cvl/testdata/schema/sonic-spanning-tree.yang b/src/cvl/testdata/schema/sonic-spanning-tree.yang new file mode 100755 index 0000000000..2ff9f5e8c6 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-spanning-tree.yang @@ -0,0 +1,381 @@ +module sonic-spanning-tree { + namespace "http://github.com/Azure/sonic-spanning-tree"; + prefix sstp; + yang-version 1.1; + + import sonic-common { + prefix scommon; + } + + import sonic-port { + prefix prt; + } + + import sonic-portchannel { + prefix spc; + } + + import sonic-pf-limits { + prefix spf; + } + + organization + "BRCM"; + + contact + "BRCM"; + + description + "SONIC Spanning-tree"; + + revision 2019-09-21 { + description + "Initial revision."; + } + + typedef interface-type { + type union { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:ifname"; + } + type leafref { + path "/spc:sonic-portchannel/spc:PORTCHANNEL/spc:name"; + } + } + } + + grouping vlanModeAttr { + leaf forward_delay { + type uint8 { + range "4..30" { + error-message "Invalid Forwarding Delay value."; + } + } + units seconds; + default 15; + } + + leaf hello_time { + type uint8 { + range "1..10" { + error-message "Invalid Hello Time value."; + } + } + units seconds; + default 2; + } + + leaf max_age { + type uint8 { + range "6..40" { + error-message "Invalid Maximum Age Time value."; + } + } + units seconds; + default 20; + } + + leaf priority { + type uint16 { + range "0..61440" { + error-message "Invalid Bridge Priority value."; + } + } + units seconds; + default 20; + } + } + + grouping interfaceAttr { + leaf path_cost { + type uint64 { + range "1..200000000" { + error-message "Invalid Port Path Cost value."; + } + } + default 200; + } + + leaf priority { + type uint8 { + range "0..240" { + error-message "Invalid Port Priority value."; + } + } + default 128; + } + } + + container sonic-spanning-tree { + + /* + container STP { + list STP_LIST { + key "keyleaf"; + + leaf keyleaf { + type enumeration { + enum GLOBAL; + } + } + + leaf mode { + type enumeration { + enum pvst; + enum rpvst; + enum mstp; + enum rstp; + } + } + + leaf rootguard_timeout { + type uint16 { + range "5..600" { + error-message "Invalid Root-guard Timeout value."; + } + } + units seconds; + default 30; + } + + uses vlanModeAttr; + } + } + + container STP_VLAN { + list STP_VLAN_LIST { + key "name"; + must "./name = concat('Vlan', string(./vlanid))" { + error-app-tag vlan-invalid; + } + + leaf name { + type string { + pattern "Vlan(409[0-5]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])" { + error-message "Invalid Vlan name pattern"; + error-app-tag vlan-name-invalid; + } + } + } + + leaf vlanid { + mandatory true; + type uint16 { + range "1..4095" { + error-message "Vlan ID out of range"; + error-app-tag vlanid-invalid; + } + } + } + + leaf enabled { + type boolean; + } + + uses vlanModeAttr; + } + } + + container STP_VLAN_INTF { + list STP_VLAN_INTF_LIST { + key "vlan-name ifname"; + + leaf vlan-name { + type leafref { + path "../../../STP_VLAN/STP_VLAN_LIST/name"; + } + } + + leaf ifname { + type interface-type; + } + + uses interfaceAttr; + } + } + + container STP_INTF { + list STP_INTF_LIST { + key "ifname"; + + leaf ifname { + type interface-type; + } + + leaf enabled { + type boolean; + } + + leaf root_guard { + type boolean; + } + + leaf bpdu_guard { + type boolean; + } + + leaf bpdu_guard_do_disable { + type boolean; + } + + leaf uplink_fast { + type boolean; + } + + leaf portfast { + type boolean; + } + + uses interfaceAttr; + + // For RPVST+ + leaf edge_port { + //when ("../../../STP/STP_LIST/mode='rpvst'"); + type boolean; + } + + leaf pt2pt_mac { + //when ("../../../STP/STP_LIST/mode='rpvst'"); + type boolean; + } + } + } + */ + + list STP { + key "keyleaf"; + + leaf keyleaf { + type enumeration { + enum GLOBAL; + } + } + + leaf mode { + type enumeration { + enum pvst; + enum rpvst; + enum mstp; + enum rstp; + } + } + + leaf rootguard_timeout { + type uint16 { + range "5..600" { + error-message "Invalid Root-guard Timeout value."; + } + } + units seconds; + default 30; + } + + uses vlanModeAttr; + } + + list STP_VLAN { + key "name"; + /*must "./name = concat('Vlan', string(./vlanid))" { + error-app-tag vlan-invalid; + }*/ + + leaf name { + type string { + pattern "Vlan(409[0-5]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])" { + error-message "Invalid Vlan name pattern"; + error-app-tag vlan-name-invalid; + } + } + } + + leaf vlanid { + mandatory true; + type uint16 { + range "1..4095" { + error-message "Vlan ID out of range"; + error-app-tag vlanid-invalid; + } + } + } + + leaf enabled { + type boolean; + } + + uses vlanModeAttr; + } + + list STP_INTF { + key "ifname"; + + leaf ifname { + type string; + //type interface-type; + } + + leaf enabled { + type boolean; + } + + leaf root_guard { + type boolean; + } + + leaf bpdu_guard { + type boolean; + } + + leaf bpdu_filter { + type boolean; + } + + leaf bpdu_guard_do_disable { + type boolean; + } + + leaf uplink_fast { + type boolean; + } + + leaf portfast { + type boolean; + } + + uses interfaceAttr; + + // For RPVST+ + leaf edge_port { + //when ("../../STP/mode='rpvst'"); + type boolean; + } + + leaf pt2pt_mac { + //when ("../../STP/mode='rpvst'"); + type boolean; + } + } + + list STP_VLAN_INTF { + key "vlan-name ifname"; + + leaf vlan-name { + /*type leafref { + path "../../STP_VLAN/name"; + }*/ + type string { + pattern "Vlan(409[0-5]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])" { + error-message "Invalid Vlan name pattern"; + error-app-tag vlan-name-invalid; + } + } + } + + leaf ifname { + type string; + //type interface-type; + } + + uses interfaceAttr; + } + } +} diff --git a/src/rest/server/context.go b/src/rest/server/context.go index 883679e343..21674c7f31 100644 --- a/src/rest/server/context.go +++ b/src/rest/server/context.go @@ -45,6 +45,14 @@ type RequestContext struct { // the body. When set, the request handler can validate the // request payload by loading the body into this model object. Model interface{} + + // PMap is the mapping of URI parameter names to actual yang + // leaf names. Yang xpaths can have duplicate parameter names, + // which is not supported by swagger and mux libraries. We + // work around it by assigning different parameter names in + // swagger and map them back to yang names while converting + // REST paths to TransLib paths. + PMap NameMap } type contextkey int @@ -188,3 +196,17 @@ func (m MediaTypes) String() string { } return fmt.Sprintf("%v", types) } + +////////// + +// NameMap is a simple mapping of names (string to string) +type NameMap map[string]string + +// Get function returns the mapped name for a given name. +// Returns given name itself if no mapping exists. +func (m *NameMap) Get(name string) string { + if mappedName, ok := (*m)[name]; ok { + return mappedName + } + return name +} diff --git a/src/rest/server/handler.go b/src/rest/server/handler.go index 5596c310f9..e1af8fc5ae 100644 --- a/src/rest/server/handler.go +++ b/src/rest/server/handler.go @@ -54,7 +54,7 @@ func Process(w http.ResponseWriter, r *http.Request) { path = getPathForTranslib(r) glog.Infof("[%s] Translated path = %s", reqID, path) - status, data, err = invokeTranslib(reqID, r.Method, path, body) + status, data, err = invokeTranslib(r, path, body) if err != nil { glog.Errorf("[%s] Translib error %T - %v", reqID, err, err) status, data, rtype = prepareErrorResponse(err, r) @@ -175,10 +175,11 @@ func getPathForTranslib(r *http.Request) string { path = trimRestconfPrefix(path) path = strings.Replace(path, "={", "{", -1) path = strings.Replace(path, "},{", "}{", -1) + rc, _ := GetContext(r) for k, v := range vars { restStyle := fmt.Sprintf("{%v}", k) - gnmiStyle := fmt.Sprintf("[%v=%v]", k, v) + gnmiStyle := fmt.Sprintf("[%v=%v]", rc.PMap.Get(k), v) path = strings.Replace(path, restStyle, gnmiStyle, 1) } @@ -189,6 +190,10 @@ func getPathForTranslib(r *http.Request) string { func trimRestconfPrefix(path string) string { pattern := "/restconf/data/" k := strings.Index(path, pattern) + if k < 0 { + pattern = "/restconf/operations/" + k = strings.Index(path, pattern) + } if k >= 0 { path = path[k+len(pattern)-1:] } @@ -196,14 +201,29 @@ func trimRestconfPrefix(path string) string { return path } +// isOperationsRequest checks if a request is a RESTCONF operations +// request (rpc or action) +func isOperationsRequest(r *http.Request) bool { + k := strings.Index(r.URL.Path, "/restconf/operations/") + return k >= 0 + //FIXME URI pattern will not help identifying yang action APIs. + //Use swagger generated API name instead??? +} + +// getRequestID returns the request id encoded in the context +func getRequestID(r *http.Request) string { + rc, _ := GetContext(r) + return rc.ID +} + // invokeTranslib calls appropriate TransLib API for the given HTTP // method. Returns response status code and content. -func invokeTranslib(reqID, method, path string, payload []byte) (int, []byte, error) { +func invokeTranslib(r *http.Request, path string, payload []byte) (int, []byte, error) { var status = 400 var content []byte var err error - switch method { + switch r.Method { case "GET": req := translib.GetRequest{Path: path} resp, err1 := translib.Get(req) @@ -215,10 +235,20 @@ func invokeTranslib(reqID, method, path string, payload []byte) (int, []byte, er } case "POST": - //TODO return 200 for operations request - status = 201 - req := translib.SetRequest{Path: path, Payload: payload} - _, err = translib.Create(req) + if isOperationsRequest(r) { + req := translib.ActionRequest{Path: path, Payload: payload} + res, err1 := translib.Action(req) + if err1 == nil { + status = 200 + content = res.Payload + } else { + err = err1 + } + } else { + status = 201 + req := translib.SetRequest{Path: path, Payload: payload} + _, err = translib.Create(req) + } case "PUT": //TODO send 201 if PUT resulted in creation @@ -237,7 +267,7 @@ func invokeTranslib(reqID, method, path string, payload []byte) (int, []byte, er _, err = translib.Delete(req) default: - glog.Errorf("[%s] Unknown method '%v'", reqID, method) + glog.Errorf("[%s] Unknown method '%v'", getRequestID(r), r.Method) err = httpBadRequest("Invalid method") } diff --git a/src/rest/server/handler_test.go b/src/rest/server/handler_test.go index 16e1ca21f5..bed730290a 100644 --- a/src/rest/server/handler_test.go +++ b/src/rest/server/handler_test.go @@ -229,6 +229,31 @@ func TestPathConv(t *testing.T) { "*", "/test/id=NOTEMPLATE", "/test/id=NOTEMPLATE")) + + t.Run("no_empty_params", testPathConv2( + map[string]string{}, + "/test/id={name}", + "/test/id=X", + "/test/id[name=X]")) + + t.Run("no_one_param", testPathConv2( + map[string]string{"name1": "name"}, + "/test/id={name1}", + "/test/id=X", + "/test/id[name=X]")) + + t.Run("no_multi_params", testPathConv2( + map[string]string{"name1": "name", "name2": "name"}, + "/test/id={name1}/data/ref={name2}", + "/test/id=X/data/ref=Y", + "/test/id[name=X]/data/ref[name=Y]")) + + t.Run("no_extra_params", testPathConv2( + map[string]string{"name1": "name", "name2": "name"}, + "/test/id={name1}", + "/test/id=X", + "/test/id[name=X]")) + } // test handler to invoke getPathForTranslib and write the conveted @@ -243,6 +268,10 @@ var pathConvHandler = func(w http.ResponseWriter, r *http.Request) { } func testPathConv(template, path, expPath string) func(*testing.T) { + return testPathConv2(nil, template, path, expPath) +} + +func testPathConv2(m map[string]string, template, path, expPath string) func(*testing.T) { return func(t *testing.T) { router := mux.NewRouter() if template == "*" { @@ -252,8 +281,16 @@ func testPathConv(template, path, expPath string) func(*testing.T) { router.HandleFunc(template, pathConvHandler) } + r := httptest.NewRequest("GET", path, nil) w := httptest.NewRecorder() - router.ServeHTTP(w, httptest.NewRequest("GET", path, nil)) + + if m != nil { + rc, r1 := GetContext(r) + rc.PMap = m + r = r1 + } + + router.ServeHTTP(w, r) convPath := w.Body.String() if convPath != expPath { @@ -440,38 +477,76 @@ func testRespData(r *http.Request, rc *RequestContext, data []byte, expType stri func TestProcessGET(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "GET", "/test", "")) - verifyResponse(t, w, 500) + Process(w, prepareRequest(t, "GET", "/api-tests:sample", "")) + verifyResponse(t, w, 200) } -func TestProcessGET_ACL(t *testing.T) { +func TestProcessGET_error(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "GET", "/openconfig-acl:acl", "")) - verifyResponse(t, w, 200) + Process(w, prepareRequest(t, "GET", "/api-tests:sample/error/not-found", "")) + verifyResponse(t, w, 404) } func TestProcessPUT(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "PUT", "/test", "{}")) - verifyResponse(t, w, 500) + Process(w, prepareRequest(t, "PUT", "/api-tests:sample", "{}")) + verifyResponse(t, w, 204) +} + +func TestProcessPUT_error(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "PUT", "/api-tests:sample/error/not-supported", "{}")) + verifyResponse(t, w, 405) } func TestProcessPOST(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "POST", "/test", "{}")) - verifyResponse(t, w, 500) + Process(w, prepareRequest(t, "POST", "/api-tests:sample", "{}")) + verifyResponse(t, w, 201) +} + +func TestProcessPOST_error(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "POST", "/api-tests:sample/error/invalid-args", "{}")) + verifyResponse(t, w, 400) } func TestProcessPATCH(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "PATCH", "/test", "{}")) + Process(w, prepareRequest(t, "PATCH", "/api-tests:sample", "{}")) + verifyResponse(t, w, 204) +} + +func TestProcessPATCH_error(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "PATCH", "/api-tests:sample/error/unknown", "{}")) verifyResponse(t, w, 500) } func TestProcessDELETE(t *testing.T) { w := httptest.NewRecorder() - Process(w, prepareRequest(t, "DELETE", "/test", "{}")) - verifyResponse(t, w, 500) + Process(w, prepareRequest(t, "DELETE", "/api-tests:sample", "")) + verifyResponse(t, w, 204) +} + +func TestProcessDELETE_error(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "DELETE", "/api-tests:sample/error/not-found", "")) + verifyResponse(t, w, 404) +} + +func TestProcessRPC(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "POST", "/restconf/operations/api-tests:my-echo", + "{\"/api-tests:input\":{\"message\":\"Hii\"}}")) + verifyResponse(t, w, 200) +} + +func TestProcessRPC_error(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "POST", "/restconf/operations/api-tests:my-echo", + "{\"api-tests:input\":{\"error-type\":\"not-supported\"}}")) + verifyResponse(t, w, 405) } func TestProcessBadMethod(t *testing.T) { @@ -503,6 +578,10 @@ func TestProcessReadError(t *testing.T) { } func prepareRequest(t *testing.T, method, path, data string) *http.Request { + if !strings.Contains(path, "/restconf/") { + path = "/restconf/data" + path + } + r := httptest.NewRequest(method, path, strings.NewReader(data)) rc, r := GetContext(r) rc.ID = t.Name() diff --git a/src/translib/acl_app.go b/src/translib/acl_app.go index 0678cde7aa..2df65002fc 100644 --- a/src/translib/acl_app.go +++ b/src/translib/acl_app.go @@ -20,6 +20,7 @@ package translib import ( + "errors" "bytes" "fmt" "reflect" @@ -166,6 +167,11 @@ func (app *AclApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } +func (app *AclApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *AclApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { pathInfo := NewPathInfo(path) notifInfo := notificationInfo{dbno: db.ConfigDB} @@ -288,6 +294,13 @@ func (app *AclApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return GetResponse{Payload: payload}, err } +func (app *AclApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + func (app *AclApp) translateCRUCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { var err error var keys []db.WatchKeys @@ -1703,10 +1716,3 @@ func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_T return aclN + "_" + aclT } -/* Check if targetUriPath is child (subtree) of nodePath -The return value can be used to decide if subtrees needs -to visited to fill the data or not. -*/ -func isSubtreeRequest(targetUriPath string, nodePath string) bool { - return strings.HasPrefix(targetUriPath, nodePath) -} diff --git a/src/translib/api_tests_app.go b/src/translib/api_tests_app.go new file mode 100644 index 0000000000..17e3771e02 --- /dev/null +++ b/src/translib/api_tests_app.go @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Broadcom. All rights reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +/////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "encoding/json" + "reflect" + "strings" + "translib/db" + "translib/tlerr" + + "github.com/golang/glog" +) + +type apiTests struct { + path string + body []byte + + echoMsg string + echoErr string +} + +func init() { + err := register("/api-tests:", + &appInfo{ + appType: reflect.TypeOf(apiTests{}), + isNative: true, + tablesToWatch: nil}) + + if err != nil { + glog.Fatalf("Failed to register ApiTest app; %v", err) + } +} + +func (app *apiTests) initialize(inp appData) { + app.path = inp.path + app.body = inp.payload +} + +func (app *apiTests) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + return nil, app.translatePath() +} + +func (app *apiTests) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + return nil, app.translatePath() +} + +func (app *apiTests) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + return nil, app.translatePath() +} + +func (app *apiTests) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + return nil, app.translatePath() +} + +func (app *apiTests) translateGet(dbs [db.MaxDB]*db.DB) error { + return app.translatePath() +} + +func (app *apiTests) translateAction(dbs [db.MaxDB]*db.DB) error { + var req struct { + Input struct { + Message string `json:"message"` + ErrType string `json:"error-type"` + } `json:"api-tests:input"` + } + + err := json.Unmarshal(app.body, &req) + if err != nil { + glog.Errorf("Failed to parse rpc input; err=%v", err) + return tlerr.InvalidArgs("Invalid rpc input") + } + + app.echoMsg = req.Input.Message + app.echoErr = req.Input.ErrType + + return nil +} + +func (app *apiTests) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + return nil, nil, nil +} + +func (app *apiTests) processCreate(d *db.DB) (SetResponse, error) { + return app.processSet() +} + +func (app *apiTests) processUpdate(d *db.DB) (SetResponse, error) { + return app.processSet() +} + +func (app *apiTests) processReplace(d *db.DB) (SetResponse, error) { + return app.processSet() +} + +func (app *apiTests) processDelete(d *db.DB) (SetResponse, error) { + return app.processSet() +} + +func (app *apiTests) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var gr GetResponse + err := app.getError() + if err == nil { + gr.Payload, err = json.Marshal(&app.echoMsg) + } + return gr, err +} + +func (app *apiTests) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var ar ActionResponse + + err := app.getError() + if err == nil { + var respData struct { + Output struct { + Message string `json:"message"` + } `json:"api-tests:output"` + } + + respData.Output.Message = app.echoMsg + ar.Payload, err = json.Marshal(&respData) + } + + return ar, err +} + +func (app *apiTests) translatePath() error { + app.echoMsg = "Hello, world!" + k := strings.Index(app.path, "error/") + if k >= 0 { + app.echoErr = app.path[k+6:] + } + return nil +} + +func (app *apiTests) processSet() (SetResponse, error) { + var sr SetResponse + err := app.getError() + return sr, err +} + +func (app *apiTests) getError() error { + switch strings.ToLower(app.echoErr) { + case "invalid-args", "invalidargs": + return tlerr.InvalidArgs(app.echoMsg) + case "exists": + return tlerr.AlreadyExists(app.echoMsg) + case "not-found", "notfound": + return tlerr.NotFound(app.echoMsg) + case "not-supported", "notsupported", "unsupported": + return tlerr.NotSupported(app.echoMsg) + case "", "no", "none", "false": + return nil + default: + return tlerr.New(app.echoMsg) + } +} diff --git a/src/translib/app_interface.go b/src/translib/app_interface.go index ef4992e371..3e008822f0 100644 --- a/src/translib/app_interface.go +++ b/src/translib/app_interface.go @@ -18,7 +18,7 @@ //////////////////////////////////////////////////////////////////////////////// /* -Package translib defines the interface for all the app modules +Package translib defines the interface for all the app modules It exposes register function for all the app modules to register @@ -31,19 +31,19 @@ package translib import ( "errors" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" "reflect" "strings" "translib/db" - log "github.com/golang/glog" - "github.com/openconfig/ygot/ygot" ) //Structure containing app module information type appInfo struct { - appType reflect.Type - ygotRootType reflect.Type - isNative bool - tablesToWatch []*db.TableSpec + appType reflect.Type + ygotRootType reflect.Type + isNative bool + tablesToWatch []*db.TableSpec } //Structure containing the app data coming from translib infra @@ -68,18 +68,20 @@ type appInterface interface { translateReplace(d *db.DB) ([]db.WatchKeys, error) translateDelete(d *db.DB) ([]db.WatchKeys, error) translateGet(dbs [db.MaxDB]*db.DB) error + translateAction(dbs [db.MaxDB]*db.DB) error translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) processCreate(d *db.DB) (SetResponse, error) processUpdate(d *db.DB) (SetResponse, error) processReplace(d *db.DB) (SetResponse, error) processDelete(d *db.DB) (SetResponse, error) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) + processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) } //App modules will use this function to register with App interface during boot up func register(path string, info *appInfo) error { var err error - log.Info("Registering for path =", path) + log.Info("Registering for path =", path) if appMap == nil { appMap = make(map[string]*appInfo) @@ -152,16 +154,16 @@ func getModels() []ModelData { //Creates a new app from the appType and returns it as an appInterface func getAppInterface(appType reflect.Type) (appInterface, error) { - var err error - appInstance := reflect.New(appType) - app, ok := appInstance.Interface().(appInterface) - - if !ok { - err = errors.New("Invalid appType") - log.Fatal("Appmodule does not confirm to appInterface method conventions for appType=", appType) - } else { - log.Info("cast to appInterface worked", app) - } + var err error + appInstance := reflect.New(appType) + app, ok := appInstance.Interface().(appInterface) + + if !ok { + err = errors.New("Invalid appType") + log.Fatal("Appmodule does not confirm to appInterface method conventions for appType=", appType) + } else { + log.Info("cast to appInterface worked", app) + } return app, err } diff --git a/src/translib/app_utils.go b/src/translib/app_utils.go index 0fa8b66fd3..2be233953b 100644 --- a/src/translib/app_utils.go +++ b/src/translib/app_utils.go @@ -229,3 +229,11 @@ func asKey(parts ...string) db.Key { func createEmptyDbValue(fieldName string) db.Value { return db.Value{Field: map[string]string{fieldName: ""}} } + +/* Check if targetUriPath is child (subtree) of nodePath +The return value can be used to decide if subtrees needs +to visited to fill the data or not. +*/ +func isSubtreeRequest(targetUriPath string, nodePath string) bool { + return strings.HasPrefix(targetUriPath, nodePath) +} diff --git a/src/translib/common_app.go b/src/translib/common_app.go index 7571bb025c..ef0fb6a55a 100644 --- a/src/translib/common_app.go +++ b/src/translib/common_app.go @@ -118,6 +118,11 @@ func (app *CommonApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*no return nil, ¬ifInfo, err } +func (app *CommonApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *CommonApp) processCreate(d *db.DB) (SetResponse, error) { var err error var resp SetResponse @@ -205,6 +210,13 @@ func (app *CommonApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return GetResponse{Payload: resPayload}, err } +func (app *CommonApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + func (app *CommonApp) translateCRUDCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { var err error var keys []db.WatchKeys diff --git a/src/translib/intf_app.go b/src/translib/intf_app.go index e57ab932d8..d50cf1a7b5 100644 --- a/src/translib/intf_app.go +++ b/src/translib/intf_app.go @@ -245,6 +245,11 @@ func (app *IntfApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } +func (app *IntfApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *IntfApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { app.appDB = dbs[db.ApplDB] pathInfo := NewPathInfo(path) @@ -466,6 +471,13 @@ func (app *IntfApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return GetResponse{Payload: payload}, err } +func (app *IntfApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + /* Checking IP adderss is v4 */ func validIPv4(ipAddress string) bool { ipAddress = strings.Trim(ipAddress, " ") diff --git a/src/translib/lldp_app.go b/src/translib/lldp_app.go index 1b185279a6..f1af7b1c34 100644 --- a/src/translib/lldp_app.go +++ b/src/translib/lldp_app.go @@ -132,6 +132,11 @@ func (app *lldpApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } +func (app *lldpApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *lldpApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { pathInfo := NewPathInfo(path) notifInfo := notificationInfo{dbno: db.ApplDB} @@ -254,6 +259,13 @@ func (app *lldpApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return GetResponse{Payload:payload}, err } +func (app *lldpApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + /** Helper function to populate JSON response for GET request **/ func (app *lldpApp) getLldpNeighInfoFromInternalMap(ifName *string, ifInfo *ocbinds.OpenconfigLldp_Lldp_Interfaces_Interface) { diff --git a/src/translib/ocbinds/oc.go b/src/translib/ocbinds/oc.go index cb09edf1e3..8d0190080f 100644 --- a/src/translib/ocbinds/oc.go +++ b/src/translib/ocbinds/oc.go @@ -19,4 +19,4 @@ package ocbinds -//go:generate sh -c "/usr/local/go/bin/go run $BUILD_GOPATH/src/github.com/openconfig/ygot/generator/generator.go -generate_fakeroot -output_file ocbinds.go -package_name ocbinds -generate_fakeroot -fakeroot_name=device -compress_paths=false -exclude_modules ietf-interfaces -path . $(find ../../../models/yang ../../../src/cvl/schema -name '*.yang' | sort)" +//go:generate sh -c "$GO run $BUILD_GOPATH/src/github.com/openconfig/ygot/generator/generator.go -generate_fakeroot -output_file ocbinds.go -package_name ocbinds -generate_fakeroot -fakeroot_name=device -compress_paths=false -exclude_modules ietf-interfaces -path . $(find ../../../models/yang ../../../src/cvl/schema -name '*.yang' | sort)" diff --git a/src/translib/pfm_app.go b/src/translib/pfm_app.go index feb24d6f09..fa868cadee 100644 --- a/src/translib/pfm_app.go +++ b/src/translib/pfm_app.go @@ -116,6 +116,11 @@ func (app *PlatformApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } +func (app *PlatformApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *PlatformApp) processCreate(d *db.DB) (SetResponse, error) { var err error var resp SetResponse @@ -166,6 +171,13 @@ func (app *PlatformApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return GetResponse{Payload: payload}, err } +func (app *PlatformApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + /////////////////////////// func (app *PlatformApp) doGetSysEeprom() (GetResponse, error) { diff --git a/src/translib/request_binder.go b/src/translib/request_binder.go index 8dc39f105a..00ae987e19 100644 --- a/src/translib/request_binder.go +++ b/src/translib/request_binder.go @@ -15,6 +15,7 @@ import ( "github.com/openconfig/ygot/ygot" "github.com/openconfig/ygot/ytypes" + "github.com/openconfig/goyang/pkg/yang" "translib/ocbinds" "translib/tlerr" ) @@ -42,15 +43,17 @@ func initSchema() { } type requestBinder struct { - uri *string - payload *[]byte - opcode int - appRootNodeType *reflect.Type - pathTmp *gnmi.Path + uri *string + payload *[]byte + opcode int + appRootNodeType *reflect.Type + pathTmp *gnmi.Path + targetNodePath *gnmi.Path + targetNodeListInst bool } func getRequestBinder(uri *string, payload *[]byte, opcode int, appRootNodeType *reflect.Type) *requestBinder { - return &requestBinder{uri, payload, opcode, appRootNodeType, nil} + return &requestBinder{uri, payload, opcode, appRootNodeType, nil, nil, false} } func (binder *requestBinder) unMarshallPayload(workObj *interface{}) error { @@ -79,15 +82,9 @@ func (binder *requestBinder) unMarshallPayload(workObj *interface{}) error { func (binder *requestBinder) validateRequest(deviceObj *ocbinds.Device) error { if binder.pathTmp == nil || len(binder.pathTmp.Elem) == 0 { if binder.opcode == UPDATE || binder.opcode == REPLACE { - log.Info("validateRequest: path is base node") - devObjTmp, ok := (reflect.ValueOf(*deviceObj).Interface()).(ygot.ValidatedGoStruct) - if ok == true { - err := devObjTmp.Validate(&ytypes.LeafrefOptions{IgnoreMissingData: true}) - if err != nil { - return err - } - } else { - return errors.New("Invalid base Object in the binding: Not able to cast to type ValidatedGoStruct") + err := deviceObj.Validate(&ytypes.LeafrefOptions{IgnoreMissingData: true}) + if err != nil { + return err } return nil } else { @@ -150,6 +147,7 @@ func (binder *requestBinder) unMarshall() (*ygot.GoStruct, *interface{}, error) case UPDATE, REPLACE: var tmpTargetNode *interface{} + var ygEntry *yang.Entry if binder.pathTmp != nil { treeNodeList, err2 := ytypes.GetNode(ygSchema.RootSchema(), &deviceObj, binder.pathTmp) if err2 != nil { @@ -161,6 +159,7 @@ func (binder *requestBinder) unMarshall() (*ygot.GoStruct, *interface{}, error) } tmpTargetNode = &(treeNodeList[0].Data) + ygEntry = treeNodeList[0].Schema } else { tmpTargetNode = workObj } @@ -168,6 +167,43 @@ func (binder *requestBinder) unMarshall() (*ygot.GoStruct, *interface{}, error) err = binder.unMarshallPayload(tmpTargetNode) if err != nil { return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: err} + } else if ygEntry != nil { + var workObjIntf interface{} + if ygEntry.IsContainer() && binder.targetNodeListInst == false { + v := reflect.ValueOf(*tmpTargetNode).Elem() + for i := 0; i < v.NumField(); i++ { + ft := v.Type().Field(i) + tagVal, _ := ft.Tag.Lookup("path") + if len(binder.targetNodePath.Elem) > 0 && tagVal == binder.targetNodePath.Elem[0].Name { + fv := v.Field(i) + workObjIntf = fv.Interface() + break + } + } + } else if ygEntry.IsList() || binder.targetNodeListInst == true { + if treeNodeList, err2 := ytypes.GetNode(ygEntry, *tmpTargetNode, binder.targetNodePath); err2 != nil { + return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: err2} + } else { + if len(treeNodeList) == 0 { + return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: errors.New("Invalid URI")} + } + workObjIntf = treeNodeList[0].Data + } + } + + if workObjIntf != nil { + workObj = &workObjIntf + } else { + return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: errors.New("Target node not found.")} + } + } + + targetObj, ok := (*tmpTargetNode).(ygot.ValidatedGoStruct) + if ok == true { + err := targetObj.Validate(&ytypes.LeafrefOptions{IgnoreMissingData: true}) + if err != nil { + return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: err} + } } default: @@ -176,8 +212,10 @@ func (binder *requestBinder) unMarshall() (*ygot.GoStruct, *interface{}, error) } } - if err = binder.validateRequest(&deviceObj); err != nil { - return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: err} + if binder.opcode != UPDATE && binder.opcode != REPLACE { + if err = binder.validateRequest(&deviceObj); err != nil { + return nil, nil, tlerr.TranslibSyntaxValidationError{StatusCode: 400, ErrorStr: err} + } } return ygotRootObj, workObj, nil @@ -224,22 +262,24 @@ func (binder *requestBinder) unMarshallUri(deviceObj *ocbinds.Device) (*interfac switch binder.opcode { case UPDATE, REPLACE: - if ygEntry.IsList() == false || reflect.ValueOf(ygNode).Kind() == reflect.Map { - var pathList []*gnmi.PathElem = path.Elem + if ygEntry.IsList() && reflect.ValueOf(ygNode).Kind() != reflect.Map { + binder.targetNodeListInst = true + } + var pathList []*gnmi.PathElem = path.Elem - gpath := &gnmi.Path{} + gpath := &gnmi.Path{} - for i := 0; i < (len(pathList) - 1); i++ { - log.Info("pathList[i] ", pathList[i]) - gpath.Elem = append(gpath.Elem, pathList[i]) - } + for i := 0; i < (len(pathList) - 1); i++ { + log.Info("pathList[i] ", pathList[i]) + gpath.Elem = append(gpath.Elem, pathList[i]) + } - log.Info("modified path is: ", gpath) + binder.targetNodePath = &gnmi.Path{} + binder.targetNodePath.Elem = append(binder.targetNodePath.Elem, pathList[(len(pathList)-1)]) - binder.pathTmp = gpath - } else { - log.Info("ygot type of the node is Map") - } + log.Info("modified path is: ", gpath) + + binder.pathTmp = gpath } if (binder.opcode == GET || binder.opcode == DELETE) && (ygEntry.IsLeaf() == false && ygEntry.IsLeafList() == false) { diff --git a/src/translib/request_binder_test.go b/src/translib/request_binder_test.go index ab1c9f34a0..e2eb2230ab 100644 --- a/src/translib/request_binder_test.go +++ b/src/translib/request_binder_test.go @@ -2,8 +2,8 @@ package translib import ( "fmt" - "github.com/openconfig/ygot/ygot" "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" "reflect" "strings" "testing" @@ -60,15 +60,15 @@ func TestValidateRequest(t *testing.T) { appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), want: "no match found", }} - + for _, tt := range tests { deviceObj := ocbinds.Device{} deviceObj.Acl = &ocbinds.OpenconfigAcl_Acl{} deviceObj.Acl.AclSets = &ocbinds.OpenconfigAcl_Acl_AclSets{} - deviceObj.Acl.AclSets.NewAclSet ("SampleACL", ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4) - + deviceObj.Acl.AclSets.NewAclSet("SampleACL", ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4) + binder := getRequestBinder(&tt.uri, &tt.payload, tt.opcode, &tt.appRootType) - + path, err := binder.getUriPath() if err != nil { tmpPath := gnmi.Path{} @@ -76,9 +76,9 @@ func TestValidateRequest(t *testing.T) { } else { binder.pathTmp = path } - + err = binder.validateRequest(&deviceObj) - + if err != nil { // Negative test case if strings.Contains(err.Error(), tt.want) == false { @@ -193,6 +193,20 @@ func TestUnMarshallUri(t *testing.T) { payload: []byte{}, appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), want: "OpenconfigAcl_Acl_AclSets_AclSet", + }, { + tid: 14, + uri: "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL1][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]/transport/config/tcp-flags[tcp-flags=TCP_PSH]", + opcode: 4, + payload: []byte{}, + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "5", + // }, { + // tid: 15, + // uri: "/openconfig-bgp:bgp/neighbors/neighbor[neighbor-address=30.0.0.1]/", + // opcode: 2, + // payload: []byte{}, + // appRootType: reflect.TypeOf(ocbinds.OpenconfigBgp_Bgp{}), + // want: "5", }} for _, tt := range tests { @@ -204,15 +218,31 @@ func TestUnMarshallUri(t *testing.T) { if strings.Contains(err.Error(), tt.want) == false { t.Error("Error in unmarshalling the URI: didn't get the expected error, and the error string is", err) } + // } else if tt.tid == 15 { + // if neighborEntry, ok := (*workObj).(*ocbinds.OpenconfigBgp_Bgp_Neighbors_Neighbor); ok && *neighborEntry.NeighborAddress == "30.0.0.1" { + // fmt.Println("PASSED testcase 15 : neighborEntry.NeighborAddress => ", *neighborEntry.NeighborAddress) + // } else { + // t.Error("Error in unmarshalling the URI: OpenconfigBgp_Bgp_Neighbors_Neighbor - object casting failed") + // } + } else if tt.tid == 14 { + leafList, ok := (*workObj).([]ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS) + fmt.Println("leaf list target node value is :", leafList) + if ok == true { + if len(leafList) != 1 { + t.Error("Error in unmarshalling the URI with the target node as leaf-list - type is ", reflect.ValueOf(leafList).Type()) + } + } else { + t.Error("Error in unmarshalling the URI with the target node as leaf-list - faile for test case id : ", tt.tid) + } } else { _, ok := (*workObj).(ygot.GoStruct) if ok == false { -// objFieldName, err := getObjectFieldName(&tt.uri, &deviceObj, workObj) -// if err != nil { -// t.Error("Error in unmarshalling the URI: ", err) -// } else if objFieldName != tt.want { -// t.Error("Error in unmarshalling the URI: Invalid target node: ", objFieldName) -// } + // objFieldName, err := getObjectFieldName(&tt.uri, &deviceObj, workObj) + // if err != nil { + // t.Error("Error in unmarshalling the URI: ", err) + // } else if objFieldName != tt.want { + // t.Error("Error in unmarshalling the URI: Invalid target node: ", objFieldName) + // } } else if tt.tid == 4 { aclSet, ok := (*workObj).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet) if ok == true { @@ -265,26 +295,26 @@ func TestUnMarshallUri(t *testing.T) { func TestUnMarshallPayload(t *testing.T) { tests := []struct { - tid int - objIntf interface{} - uri string - opcode int - payload []byte - want string //target object name + tid int + objIntf interface{} + uri string + opcode int + payload []byte + want string //target object name }{{ - tid: 1, - objIntf: "TestObj", - uri: "/openconfig-acl:acl/acl-sets/", - opcode: 2, - payload: []byte{}, - want: "Error in casting the target object", + tid: 1, + objIntf: "TestObj", + uri: "/openconfig-acl:acl/acl-sets/", + opcode: 2, + payload: []byte{}, + want: "Error in casting the target object", }, { - tid: 2, - objIntf: ocbinds.OpenconfigAcl_Acl{}, - uri: "/openconfig-acl:acl/acl-sets/", - opcode: 3, - payload: []byte{}, - want: "Request payload is empty", + tid: 2, + objIntf: ocbinds.OpenconfigAcl_Acl{}, + uri: "/openconfig-acl:acl/acl-sets/", + opcode: 3, + payload: []byte{}, + want: "Request payload is empty", }} for _, tt := range tests { @@ -293,15 +323,17 @@ func TestUnMarshallPayload(t *testing.T) { var deviceObj ocbinds.Device = ocbinds.Device{} var workObj *interface{} var err error - workObj, err = reqBinder.unMarshallUri(&deviceObj); if err != nil { + workObj, err = reqBinder.unMarshallUri(&deviceObj) + if err != nil { t.Error(err) } - if (tt.tid == 1) { + if tt.tid == 1 { workObj = &tt.objIntf } - - err = reqBinder.unMarshallPayload(workObj); if err != nil { + + err = reqBinder.unMarshallPayload(workObj) + if err != nil { if strings.Contains(err.Error(), tt.want) == false { t.Error("Negative test case failed: ", err) } @@ -326,14 +358,15 @@ func TestGetUriPath(t *testing.T) { appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), want: "error formatting path", }} - + for _, tt := range tests { reqBinder := getRequestBinder(&tt.uri, &tt.payload, tt.opcode, &tt.appRootType) - _, err := reqBinder.getUriPath(); if err != nil { + _, err := reqBinder.getUriPath() + if err != nil { if strings.Contains(err.Error(), tt.want) == false { t.Error("Negative test case failed: ", err) } - } + } } } @@ -439,6 +472,34 @@ func TestUnMarshall(t *testing.T) { payload: []byte("{ \"acl-entries\": { \"acl-entry\": [ { \"sequence-id\": abc, \"config\": { \"sequence-id\": 1, \"description\": \"Description for MyACL1 Rule Seq 1\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.1/32\", \"destination-address\": \"21.1.1.1/32\", \"dscp\": 1, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 101, \"destination-port\": 201 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } } ] } } "), appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), want: "failed to create map value for insert", + // }, { + // tid: 14, // PATCH - test bgp neighbor bind with update + // uri: "/openconfig-bgp:bgp/neighbors/neighbor[neighbor-address=30.0.0.1]/ebgp-multihop/config/multihop-ttl", + // opcode: 4, + // payload: []byte("{\"openconfig-bgp:multihop-ttl\": 3}"), + // appRootType: reflect.TypeOf(ocbinds.OpenconfigBgp_Bgp{}), + // want: "15", + }, { + tid: 15, // PATCH - from the base node acl + uri: "/openconfig-acl:acl/", + opcode: 4, + payload: []byte("{ \"openconfig-acl:acl\": { \"acl-sets\": { \"acl-set\": [ { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL3\" } } ] } } }"), + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "XXX", + }, { + tid: 16, // PATCH - from the base node acl + uri: "/openconfig-acl:acl/acl-sets/acl-set", + opcode: 4, + payload: []byte("{ \"acl-set\": [ { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL3\" } } ] } "), + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "XXX", + }, { + tid: 17, // PATCH - from the base node acl + uri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=MyACL333][type=ACL_IPV4]/", + opcode: 4, + payload: []byte("{ \"acl-set\": [ { \"name\": \"MyACL333\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL333\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL333\" } } ] } "), + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "XXX", }} for _, tt := range tests { @@ -446,10 +507,37 @@ func TestUnMarshall(t *testing.T) { if err != nil { if strings.Contains(err.Error(), tt.want) == false { - t.Error("Error in unmarshalling the payload: didn't get the expected error, and the error string is", err) + t.Errorf("TestUnMarshall: Testcase id: %d Error in unmarshalling the payload: didn't get the expected error, and the error string is %s ", tt.tid, err) } } else { - if tt.tid == 4 { + if tt.tid == 17 { + fmt.Println("TestUnMarshall: Testcase 17 passed - workObj: ", *workObj) + if aclSet, ok := (*workObj).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet); ok { + fmt.Printf("TestUnMarshall: Testcase 17 passed - Acl Name: %s, and Acl Type: %d\n", *aclSet.Name, aclSet.Type) + } else { + t.Error("TestUnMarshall: Testcase 17 - Error in unmarshalling the payload: OpenconfigAcl_Acl_AclSets") + } + } else if tt.tid == 16 { + if reflect.ValueOf(*workObj).Kind().String() == "map" { + fmt.Println("TestUnMarshall: Testcase 16 passed - workobj: ", *workObj) + } else { + t.Error("TestUnMarshall: Testcase 16 - Error in unmarshalling the payload: OpenconfigAcl_AclSet") + } + } else if tt.tid == 15 { + aclObj, ok := (*workObj).(*ocbinds.OpenconfigAcl_Acl) + if ok == true { + fmt.Println("TestUnMarshall: Testcase 15 passed - ", aclObj) + } else { + t.Error("TestUnMarshall: Testcase 15 - Error in unmarshalling the payload: OpenconfigAcl_Acl") + } + } else if tt.tid == 14 { + ttlVal, ok := (*workObj).(*uint8) + if ok == false || *ttlVal != 3 { + t.Error("TestUnMarshall: Testcase 14 - Error in unmarshalling the payload: OpenconfigBgp_Bgp_Neighbors_Neighbor_EbgpMultihop_Config_multihop-ttl failed") + } else { + fmt.Println("TestUnMarshall: Testcase 14 passed - ptach method: => multihop-ttl value ==> ", *ttlVal) + } + } else if tt.tid == 4 { aclSet, ok := (*workObj).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet) if ok == true { if aclSet.AclEntries.AclEntry[1] != nil && *aclSet.AclEntries.AclEntry[1].SequenceId == 1 { @@ -480,12 +568,12 @@ func TestUnMarshall(t *testing.T) { } else { _, ok := (*workObj).(ygot.GoStruct) if ok == false { -// objFieldName, err := getObjectFieldName(&tt.uri, (*rootObj).(*ocbinds.Device), workObj) -// if err != nil { -// t.Error("Error in unmarshalling the URI: ", err) -// } else if objFieldName != tt.want { -// t.Error("Error in unmarshalling the payload: Invalid target node: ", objFieldName) -// } + // objFieldName, err := getObjectFieldName(&tt.uri, (*rootObj).(*ocbinds.Device), workObj) + // if err != nil { + // t.Error("Error in unmarshalling the URI: ", err) + // } else if objFieldName != tt.want { + // t.Error("Error in unmarshalling the payload: Invalid target node: ", objFieldName) + // } } else if reflect.TypeOf(*workObj).Elem().Name() != tt.want { t.Error("Error in unmarshalling the payload: Invalid target node: ", reflect.TypeOf(*workObj).Elem().Name()) } diff --git a/src/translib/stp_app.go b/src/translib/stp_app.go new file mode 100644 index 0000000000..e1a6976b78 --- /dev/null +++ b/src/translib/stp_app.go @@ -0,0 +1,1936 @@ +/////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Broadcom. All rights reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +/////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "reflect" + "strconv" + "strings" + "translib/db" + "translib/ocbinds" + "translib/tlerr" + + "github.com/facette/natsort" + log "github.com/golang/glog" + "github.com/openconfig/ygot/util" + "github.com/openconfig/ygot/ygot" +) + +const ( + GLOBAL_TABLE = "STP" + VLAN_TABLE = "STP_VLAN" + VLAN_INTF_TABLE = "STP_VLAN_INTF" + INTF_TABLE = "STP_INTF" + VLAN_OPER_TABLE = "_STP_VLAN_TABLE" + VLAN_INTF_OPER_TABLE = "_STP_VLAN_INTF_TABLE" + INTF_OPER_TABLE = "_STP_INTF_TABLE" + STP_MODE = "mode" + OC_STP_APP_MODULE_NAME = "/openconfig-spanning-tree:stp" + OC_STP_YANG_PATH_PREFIX = "/device/stp" + PVST_MAX_INSTANCES = 255 + + STP_DEFAULT_ROOT_GUARD_TIMEOUT = "30" + STP_DEFAULT_FORWARD_DELAY = "15" + STP_DEFAULT_HELLO_INTERVAL = "2" + STP_DEFAULT_MAX_AGE = "20" + STP_DEFAULT_BRIDGE_PRIORITY = "32768" +) + +type StpApp struct { + pathInfo *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + + globalTable *db.TableSpec + vlanTable *db.TableSpec + vlanIntfTable *db.TableSpec + interfaceTable *db.TableSpec + + vlanOperTable *db.TableSpec + vlanIntfOperTable *db.TableSpec + intfOperTable *db.TableSpec + + globalInfo db.Value + vlanTableMap map[string]db.Value + vlanIntfTableMap map[string]map[string]db.Value + intfTableMap map[string]db.Value + + vlanOperTableMap map[string]db.Value + vlanIntfOperTableMap map[string]map[string]db.Value + intfOperTableMap map[string]db.Value + + appDB *db.DB +} + +func init() { + err := register("/openconfig-spanning-tree:stp", + &appInfo{appType: reflect.TypeOf(StpApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigSpanningTree_Stp{}), + isNative: false, + tablesToWatch: []*db.TableSpec{&db.TableSpec{Name: GLOBAL_TABLE}, &db.TableSpec{Name: VLAN_TABLE}, &db.TableSpec{Name: VLAN_INTF_TABLE}, &db.TableSpec{Name: INTF_TABLE}}}) + + if err != nil { + log.Fatal("Register STP app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-spanning-tree", Org: "OpenConfig working group", Ver: "0.3.0"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *StpApp) initialize(data appData) { + log.Info("initialize:stp:path =", data.path) + app.pathInfo = NewPathInfo(data.path) + app.ygotRoot = data.ygotRoot + app.ygotTarget = data.ygotTarget + + app.globalTable = &db.TableSpec{Name: GLOBAL_TABLE} + app.vlanTable = &db.TableSpec{Name: VLAN_TABLE} + app.vlanIntfTable = &db.TableSpec{Name: VLAN_INTF_TABLE} + app.interfaceTable = &db.TableSpec{Name: INTF_TABLE} + + app.vlanOperTable = &db.TableSpec{Name: VLAN_OPER_TABLE} + app.vlanIntfOperTable = &db.TableSpec{Name: VLAN_INTF_OPER_TABLE} + app.intfOperTable = &db.TableSpec{Name: INTF_OPER_TABLE} + + app.globalInfo = db.Value{Field: map[string]string{}} + app.vlanTableMap = make(map[string]db.Value) + app.intfTableMap = make(map[string]db.Value) + app.vlanIntfTableMap = make(map[string]map[string]db.Value) + + app.vlanOperTableMap = make(map[string]db.Value) + app.vlanIntfOperTableMap = make(map[string]map[string]db.Value) + app.intfOperTableMap = make(map[string]db.Value) +} + +func (app *StpApp) getAppRootObject() *ocbinds.OpenconfigSpanningTree_Stp { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Stp +} + +func (app *StpApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:stp:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, CREATE) + return keys, err +} + +func (app *StpApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:stp:path =", app.pathInfo.Template) + + return keys, err +} + +func (app *StpApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:stp:path =", app.pathInfo.Template) + + return keys, err +} + +func (app *StpApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:stp:path =", app.pathInfo.Template) + + return keys, err +} + +func (app *StpApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:stp:path =", app.pathInfo.Template) + return err +} + +func (app *StpApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + +func (app *StpApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + var err error + return nil, nil, err +} + +func (app *StpApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, CREATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *StpApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + err = errors.New("Not Implemented") + return resp, err +} + +func (app *StpApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + err = errors.New("Not Implemented") + return resp, err +} + +func (app *StpApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, DELETE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *StpApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + + app.appDB = dbs[db.ApplDB] + + configDb := dbs[db.ConfigDB] + err = app.processCommon(configDb, GET) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + payload, err = generateGetResponsePayload(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device), app.ygotTarget) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + return GetResponse{Payload: payload}, err +} + +func (app *StpApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + +func (app *StpApp) translateCRUCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCRUCommon:STP:path =", app.pathInfo.Template) + + app.convertOCStpGlobalConfToInternal() + app.convertOCPvstToInternal() + app.convertOCRpvstConfToInternal() + app.convertOCStpInterfacesToInternal() + + return keys, err +} + +func (app *StpApp) processCommon(d *db.DB, opcode int) error { + var err error + var topmostPath bool = false + stp := app.getAppRootObject() + + log.Infof("processCommon--Path Received: %s", app.pathInfo.Template) + targetType := reflect.TypeOf(*app.ygotTarget) + if !util.IsValueScalar(reflect.ValueOf(*app.ygotTarget)) && util.IsValuePtr(reflect.ValueOf(*app.ygotTarget)) { + log.Infof("processCommon: Target object is a <%s> of Type: %s", targetType.Kind().String(), targetType.Elem().Name()) + if targetType.Elem().Name() == "OpenconfigSpanningTree_Stp" { + topmostPath = true + } + } + + targetUriPath, _ := getYangPathFromUri(app.pathInfo.Path) + log.Infof("processCommon -- isTopmostPath: %t and Uri: %s", topmostPath, targetUriPath) + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/global") { + switch opcode { + case CREATE: + err = app.setStpGlobalConfigInDB(d) + if err != nil { + return err + } + err = app.enableStpForInterfaces(d) + if err != nil { + return err + } + err = app.enableStpForVlans(d) + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == stp.Global || *app.ygotTarget == stp.Global.Config || targetUriPath == "/openconfig-spanning-tree:stp/global/config/enabled-protocol" { + if app.pathInfo.Template == "/openconfig-spanning-tree:stp/global/config/enabled-protocol{}" { + mode, _ := app.getStpModeFromConfigDB(d) + if mode != app.convertOCStpModeToInternal(stp.Global.Config.EnabledProtocol[0]) { + return tlerr.InvalidArgs("STP mode is configured as %s", mode) + } + } + err = app.disableStpMode(d) + } else { + err = app.handleStpGlobalFieldsDeletion(d) + } + case GET: + err = app.convertDBStpGlobalConfigToInternal(d) + if err != nil { + return err + } + ygot.BuildEmptyTree(stp) + app.convertInternalToOCStpGlobalConfig(stp.Global) + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/openconfig-spanning-tree-ext:pvst") { + mode, _ := app.getStpModeFromConfigDB(d) + if mode != "pvst" { + return tlerr.InvalidArgs("STP mode is configured as %s", mode) + } + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/openconfig-spanning-tree-ext:pvst/vlan{}") { + for vlanId, _ := range stp.Pvst.Vlan { + pvstVlan := stp.Pvst.Vlan[vlanId] + vlanName := "Vlan" + strconv.Itoa(int(vlanId)) + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/openconfig-spanning-tree-ext:pvst/vlan{}/interfaces/interface{}") { + // Subtree of one interface under a vlan + for intfId, _ := range pvstVlan.Interfaces.Interface { + pvstVlanIntf := pvstVlan.Interfaces.Interface[intfId] + switch opcode { + case CREATE: + if *app.ygotTarget == pvstVlanIntf { + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else { + err = app.setRpvstVlanInterfaceDataInDB(d, false) + } + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == pvstVlanIntf { + err = d.DeleteEntry(app.vlanIntfTable, asKey(vlanName, intfId)) + } else { + err = app.handleVlanInterfaceFieldsDeletion(d, vlanName, intfId) + } + case GET: + err = app.convertDBRpvstVlanInterfaceToInternal(d, vlanName, intfId, asKey(vlanName, intfId), true) + if err != nil { + return err + } + ygot.BuildEmptyTree(pvstVlanIntf) + app.convertInternalToOCPvstVlanInterface(vlanName, intfId, pvstVlan, pvstVlanIntf) + // populate operational data + app.convertOperInternalToOCVlanInterface(vlanName, intfId, pvstVlan, pvstVlanIntf) + } + } + } else { + isInterfacesSubtree := isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/openconfig-spanning-tree-ext:pvst/vlan{}/interfaces") + switch opcode { + case CREATE: + if *app.ygotTarget == pvstVlan { + log.Info("ygotTarget is pvstVlan") + err = app.setRpvstVlanDataInDB(d, true) + if err != nil { + return err + } + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else if isInterfacesSubtree { + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else { + err = d.SetEntry(app.vlanTable, asKey(vlanName), app.vlanTableMap[vlanName]) + } + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == pvstVlan { + err = d.DeleteKeys(app.vlanIntfTable, asKey(vlanName+TABLE_SEPARATOR+"*")) + if err != nil { + return err + } + err = d.DeleteEntry(app.vlanTable, asKey(vlanName)) + } else if isInterfacesSubtree { + err = d.DeleteKeys(app.vlanIntfTable, asKey(vlanName+TABLE_SEPARATOR+"*")) + } else { + err = app.handleVlanFieldsDeletion(d, vlanName) + } + case GET: + err = app.convertDBRpvstVlanConfigToInternal(d, asKey(vlanName)) + if err != nil { + return err + } + ygot.BuildEmptyTree(pvstVlan) + app.convertInternalToOCPvstVlan(vlanName, stp.Pvst, pvstVlan) + } + } + } + } else { + // Handle top PVST + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/rapid-pvst") { + mode, _ := app.getStpModeFromConfigDB(d) + if mode != "rpvst" { + return tlerr.InvalidArgs("STP mode is configured as %s", mode) + } + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/rapid-pvst/vlan{}") { + for vlanId, _ := range stp.RapidPvst.Vlan { + rpvstVlanConf := stp.RapidPvst.Vlan[vlanId] + vlanName := "Vlan" + strconv.Itoa(int(vlanId)) + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/rapid-pvst/vlan{}/interfaces/interface{}") { + // Subtree of one interface under a vlan + for intfId, _ := range rpvstVlanConf.Interfaces.Interface { + rpvstVlanIntfConf := rpvstVlanConf.Interfaces.Interface[intfId] + switch opcode { + case CREATE: + if *app.ygotTarget == rpvstVlanIntfConf { + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else { + err = app.setRpvstVlanInterfaceDataInDB(d, false) + } + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == rpvstVlanIntfConf { + err = d.DeleteEntry(app.vlanIntfTable, asKey(vlanName, intfId)) + } else { + err = app.handleVlanInterfaceFieldsDeletion(d, vlanName, intfId) + } + case GET: + err = app.convertDBRpvstVlanInterfaceToInternal(d, vlanName, intfId, asKey(vlanName, intfId), true) + if err != nil { + return err + } + ygot.BuildEmptyTree(rpvstVlanIntfConf) + app.convertInternalToOCRpvstVlanInterface(vlanName, intfId, rpvstVlanConf, rpvstVlanIntfConf) + // populate operational data + app.convertOperInternalToOCVlanInterface(vlanName, intfId, rpvstVlanConf, rpvstVlanIntfConf) + } + } + } else { + isInterfacesSubtree := isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/rapid-pvst/vlan{}/interfaces") + switch opcode { + case CREATE: + if *app.ygotTarget == rpvstVlanConf { + err = app.setRpvstVlanDataInDB(d, true) + if err != nil { + return err + } + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else if isInterfacesSubtree { + err = app.setRpvstVlanInterfaceDataInDB(d, true) + } else { + err = d.SetEntry(app.vlanTable, asKey(vlanName), app.vlanTableMap[vlanName]) + } + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == rpvstVlanConf { + err = d.DeleteKeys(app.vlanIntfTable, asKey(vlanName+TABLE_SEPARATOR+"*")) + if err != nil { + return err + } + err = d.DeleteEntry(app.vlanTable, asKey(vlanName)) + } else if isInterfacesSubtree { + err = d.DeleteKeys(app.vlanIntfTable, asKey(vlanName+TABLE_SEPARATOR+"*")) + } else { + err = app.handleVlanFieldsDeletion(d, vlanName) + } + case GET: + err = app.convertDBRpvstVlanConfigToInternal(d, asKey(vlanName)) + if err != nil { + return err + } + ygot.BuildEmptyTree(rpvstVlanConf) + app.convertInternalToOCRpvstVlanConfig(vlanName, stp.RapidPvst, rpvstVlanConf) + } + } + } + } else { + // Handle both rapid-pvst and rapid-pvst/vlan + err = app.processCommonRpvstVlanToplevelPath(d, stp, opcode) + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/mstp") { + mode, _ := app.getStpModeFromConfigDB(d) + if mode != "mstp" { + return tlerr.InvalidArgs("STP mode is configured as %s", mode) + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/interfaces") { + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-spanning-tree:stp/interfaces/interface{}") { + for intfId, _ := range stp.Interfaces.Interface { + intfData := stp.Interfaces.Interface[intfId] + switch opcode { + case CREATE: + if *app.ygotTarget == intfData { + err = app.setStpInterfacesDataInDB(d, true) + } else { + err = app.setStpInterfacesDataInDB(d, false) + } + case REPLACE: + case UPDATE: + case DELETE: + if *app.ygotTarget == intfData { + err = d.DeleteEntry(app.interfaceTable, asKey(intfId)) + } else { + err = app.handleInterfacesFieldsDeletion(d, intfId) + } + case GET: + err = app.convertDBStpInterfacesToInternal(d, asKey(intfId)) + if err != nil { + return err + } + ygot.BuildEmptyTree(intfData) + app.convertInternalToOCStpInterfaces(intfId, stp.Interfaces, intfData) + } + } + } else { + } + } else if topmostPath { + switch opcode { + case CREATE: + case DELETE: + err = app.disableStpMode(d) + case GET: + ygot.BuildEmptyTree(stp) + ////////////////////// + ygot.BuildEmptyTree(stp.Global) + err = app.convertDBStpGlobalConfigToInternal(d) + if err != nil { + return err + } + app.convertInternalToOCStpGlobalConfig(stp.Global) + + stpMode := (&app.globalInfo).Get(STP_MODE) + switch stpMode { + case "pvst": + ygot.BuildEmptyTree(stp.Pvst) + err = app.convertDBRpvstVlanConfigToInternal(d, db.Key{}) + if err != nil { + return err + } + app.convertInternalToOCPvstVlan("", stp.Pvst, nil) + case "rpvst": + ygot.BuildEmptyTree(stp.RapidPvst) + err = app.convertDBRpvstVlanConfigToInternal(d, db.Key{}) + if err != nil { + return err + } + app.convertInternalToOCRpvstVlanConfig("", stp.RapidPvst, nil) + case "mstp": + } + ////////////////////// + ygot.BuildEmptyTree(stp.Interfaces) + err = app.convertDBStpInterfacesToInternal(d, db.Key{}) + if err != nil { + return err + } + app.convertInternalToOCStpInterfaces("", stp.Interfaces, nil) + } + } + + return err +} + +func (app *StpApp) processCommonRpvstVlanToplevelPath(d *db.DB, stp *ocbinds.OpenconfigSpanningTree_Stp, opcode int) error { + var err error + + switch opcode { + case CREATE: + case REPLACE: + case UPDATE: + case DELETE: + case GET: + } + + return err +} + +///////////////// STP GLOBAL ////////////////////// +func (app *StpApp) setStpGlobalConfigInDB(d *db.DB) error { + var err error + + err = d.CreateEntry(app.globalTable, asKey("GLOBAL"), app.globalInfo) + + return err +} + +func (app *StpApp) convertOCStpGlobalConfToInternal() { + stp := app.getAppRootObject() + if stp != nil { + if stp.Global != nil && stp.Global.Config != nil { + if stp.Global.Config.BridgePriority != nil { + (&app.globalInfo).Set("priority", strconv.Itoa(int(*stp.Global.Config.BridgePriority))) + } else { + (&app.globalInfo).Set("priority", STP_DEFAULT_BRIDGE_PRIORITY) + } + if stp.Global.Config.ForwardingDelay != nil { + (&app.globalInfo).Set("forward_delay", strconv.Itoa(int(*stp.Global.Config.ForwardingDelay))) + } else { + (&app.globalInfo).Set("forward_delay", STP_DEFAULT_FORWARD_DELAY) + } + if stp.Global.Config.HelloTime != nil { + (&app.globalInfo).Set("hello_time", strconv.Itoa(int(*stp.Global.Config.HelloTime))) + } else { + (&app.globalInfo).Set("hello_time", STP_DEFAULT_HELLO_INTERVAL) + } + if stp.Global.Config.MaxAge != nil { + (&app.globalInfo).Set("max_age", strconv.Itoa(int(*stp.Global.Config.MaxAge))) + } else { + (&app.globalInfo).Set("max_age", STP_DEFAULT_MAX_AGE) + } + if stp.Global.Config.RootguardTimeout != nil { + (&app.globalInfo).Set("rootguard_timeout", strconv.Itoa(int(*stp.Global.Config.RootguardTimeout))) + } else { + (&app.globalInfo).Set("rootguard_timeout", STP_DEFAULT_ROOT_GUARD_TIMEOUT) + } + + mode := app.convertOCStpModeToInternal(stp.Global.Config.EnabledProtocol[0]) + if len(mode) > 0 { + (&app.globalInfo).Set(STP_MODE, mode) + } + + log.Infof("convertOCStpGlobalConfToInternal -- Internal Stp global config: %v", app.globalInfo) + } + } +} + +func (app *StpApp) convertDBStpGlobalConfigToInternal(d *db.DB) error { + var err error + + app.globalInfo, err = d.GetEntry(app.globalTable, asKey("GLOBAL")) + if err != nil { + return err + } + return err +} + +func (app *StpApp) convertInternalToOCStpGlobalConfig(stpGlobal *ocbinds.OpenconfigSpanningTree_Stp_Global) { + if stpGlobal != nil { + var priority uint32 + var forDelay, helloTime, maxAge uint8 + var rootGTimeout uint16 + if stpGlobal.Config != nil { + stpGlobal.Config.EnabledProtocol = app.convertInternalStpModeToOC((&app.globalInfo).Get(STP_MODE)) + + var num uint64 + num, _ = strconv.ParseUint((&app.globalInfo).Get("priority"), 10, 32) + priority = uint32(num) + stpGlobal.Config.BridgePriority = &priority + + num, _ = strconv.ParseUint((&app.globalInfo).Get("forward_delay"), 10, 8) + forDelay = uint8(num) + stpGlobal.Config.ForwardingDelay = &forDelay + + num, _ = strconv.ParseUint((&app.globalInfo).Get("hello_time"), 10, 8) + helloTime = uint8(num) + stpGlobal.Config.HelloTime = &helloTime + + num, _ = strconv.ParseUint((&app.globalInfo).Get("max_age"), 10, 8) + maxAge = uint8(num) + stpGlobal.Config.MaxAge = &maxAge + + num, _ = strconv.ParseUint((&app.globalInfo).Get("rootguard_timeout"), 10, 16) + rootGTimeout = uint16(num) + stpGlobal.Config.RootguardTimeout = &rootGTimeout + } + if stpGlobal.State != nil { + stpGlobal.State.EnabledProtocol = app.convertInternalStpModeToOC((&app.globalInfo).Get(STP_MODE)) + stpGlobal.State.BridgePriority = &priority + stpGlobal.State.ForwardingDelay = &forDelay + stpGlobal.State.HelloTime = &helloTime + stpGlobal.State.MaxAge = &maxAge + stpGlobal.State.RootguardTimeout = &rootGTimeout + } + } +} + +///////////////// RPVST ////////////////////// +func (app *StpApp) convertOCRpvstConfToInternal() { + stp := app.getAppRootObject() + if stp != nil && stp.RapidPvst != nil && len(stp.RapidPvst.Vlan) > 0 { + for vlanId, _ := range stp.RapidPvst.Vlan { + vlanName := "Vlan" + strconv.Itoa(int(vlanId)) + app.vlanTableMap[vlanName] = db.Value{Field: map[string]string{}} + rpvstVlanConf := stp.RapidPvst.Vlan[vlanId] + if rpvstVlanConf.Config != nil { + dbVal := app.vlanTableMap[vlanName] + (&dbVal).Set("vlanid", strconv.Itoa(int(vlanId))) + if rpvstVlanConf.Config.BridgePriority != nil { + (&dbVal).Set("priority", strconv.Itoa(int(*rpvstVlanConf.Config.BridgePriority))) + } else { + (&dbVal).Set("priority", "32768") + } + if rpvstVlanConf.Config.ForwardingDelay != nil { + (&dbVal).Set("forward_delay", strconv.Itoa(int(*rpvstVlanConf.Config.ForwardingDelay))) + } else { + (&dbVal).Set("forward_delay", "15") + } + if rpvstVlanConf.Config.HelloTime != nil { + (&dbVal).Set("hello_time", strconv.Itoa(int(*rpvstVlanConf.Config.HelloTime))) + } else { + (&dbVal).Set("hello_time", "2") + } + if rpvstVlanConf.Config.MaxAge != nil { + (&dbVal).Set("max_age", strconv.Itoa(int(*rpvstVlanConf.Config.MaxAge))) + } else { + (&dbVal).Set("max_age", "20") + } + if rpvstVlanConf.Config.SpanningTreeEnable != nil { + if *rpvstVlanConf.Config.SpanningTreeEnable { + (&dbVal).Set("enabled", "true") + } else { + (&dbVal).Set("enabled", "false") + } + } else { + (&dbVal).Set("enabled", "false") + } + } + if rpvstVlanConf.Interfaces != nil && len(rpvstVlanConf.Interfaces.Interface) > 0 { + app.vlanIntfTableMap[vlanName] = make(map[string]db.Value) + for intfId, _ := range rpvstVlanConf.Interfaces.Interface { + rpvstVlanIntfConf := rpvstVlanConf.Interfaces.Interface[intfId] + app.vlanIntfTableMap[vlanName][intfId] = db.Value{Field: map[string]string{}} + if rpvstVlanIntfConf.Config != nil { + dbVal := app.vlanIntfTableMap[vlanName][intfId] + (&dbVal).Set("vlan-name", vlanName) + (&dbVal).Set("ifname", intfId) + if rpvstVlanIntfConf.Config.Cost != nil { + (&dbVal).Set("path_cost", strconv.Itoa(int(*rpvstVlanIntfConf.Config.Cost))) + } else { + (&dbVal).Set("path_cost", "200") + } + if rpvstVlanIntfConf.Config.PortPriority != nil { + (&dbVal).Set("priority", strconv.Itoa(int(*rpvstVlanIntfConf.Config.PortPriority))) + } else { + (&dbVal).Set("priority", "128") + } + } + } + } + } + } +} + +func (app *StpApp) setRpvstVlanDataInDB(d *db.DB, createFlag bool) error { + var err error + for vlanName := range app.vlanTableMap { + existingEntry, err := d.GetEntry(app.vlanTable, asKey(vlanName)) + if createFlag && existingEntry.IsPopulated() { + return tlerr.AlreadyExists("Vlan %s already configured", vlanName) + } + if createFlag || (!createFlag && err != nil && !existingEntry.IsPopulated()) { + err = d.CreateEntry(app.vlanTable, asKey(vlanName), app.vlanTableMap[vlanName]) + } else { + if existingEntry.IsPopulated() { + err = d.ModEntry(app.vlanTable, asKey(vlanName), app.vlanTableMap[vlanName]) + } + } + } + return err +} + +func (app *StpApp) setRpvstVlanInterfaceDataInDB(d *db.DB, createFlag bool) error { + var err error + for vlanName := range app.vlanIntfTableMap { + for intfId := range app.vlanIntfTableMap[vlanName] { + existingEntry, err := d.GetEntry(app.vlanIntfTable, asKey(vlanName, intfId)) + if createFlag && existingEntry.IsPopulated() { + return tlerr.AlreadyExists("Interface %s already configured", intfId) + } + if createFlag || (!createFlag && err != nil && !existingEntry.IsPopulated()) { + err = d.CreateEntry(app.vlanIntfTable, asKey(vlanName, intfId), app.vlanIntfTableMap[vlanName][intfId]) + log.Error(err) + } else { + if existingEntry.IsPopulated() { + err = d.ModEntry(app.vlanIntfTable, asKey(vlanName, intfId), app.vlanIntfTableMap[vlanName][intfId]) + } + } + } + } + return err +} + +func (app *StpApp) convertDBRpvstVlanConfigToInternal(d *db.DB, vlanKey db.Key) error { + var err error + if vlanKey.Len() > 0 { + entry, err := d.GetEntry(app.vlanTable, vlanKey) + if err != nil { + return err + } + vlanName := vlanKey.Get(0) + if entry.IsPopulated() { + app.vlanTableMap[vlanName] = entry + app.vlanIntfTableMap[vlanName] = make(map[string]db.Value) + err = app.convertDBRpvstVlanInterfaceToInternal(d, vlanName, "", db.Key{}, false) + if err != nil { + return err + } + // Collect operational info from application DB + err = app.convertApplDBRpvstVlanToInternal(vlanName) + } else { + return tlerr.NotFound("Vlan %s is not configured", vlanName) + } + } else { + tbl, err := d.GetTable(app.vlanTable) + if err != nil { + return err + } + keys, err := tbl.GetKeys() + if err != nil { + return err + } + for i, _ := range keys { + app.convertDBRpvstVlanConfigToInternal(d, keys[i]) + } + } + + return err +} + +func (app *StpApp) convertInternalToOCRpvstVlanConfig(vlanName string, rpvst *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst, rpvstVlanConf *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan) { + if len(vlanName) > 0 { + var num uint64 + if rpvstVlanConf != nil { + if rpvstVlanData, ok := app.vlanTableMap[vlanName]; ok { + vlanId, _ := strconv.Atoi(strings.Replace(vlanName, "Vlan", "", 1)) + vlan := uint16(vlanId) + rpvstVlanConf.VlanId = &vlan + rpvstVlanConf.Config.VlanId = &vlan + rpvstVlanConf.State.VlanId = &vlan + + stpEnabled, _ := strconv.ParseBool((&rpvstVlanData).Get("enabled")) + rpvstVlanConf.Config.SpanningTreeEnable = &stpEnabled + //rpvstVlanConf.State.SpanningTreeEnable = &stpEnabled + + num, _ = strconv.ParseUint((&rpvstVlanData).Get("priority"), 10, 32) + priority := uint32(num) + rpvstVlanConf.Config.BridgePriority = &priority + rpvstVlanConf.State.BridgePriority = &priority + + num, _ = strconv.ParseUint((&rpvstVlanData).Get("forward_delay"), 10, 8) + forDelay := uint8(num) + rpvstVlanConf.Config.ForwardingDelay = &forDelay + + num, _ = strconv.ParseUint((&rpvstVlanData).Get("hello_time"), 10, 8) + helloTime := uint8(num) + rpvstVlanConf.Config.HelloTime = &helloTime + + num, _ = strconv.ParseUint((&rpvstVlanData).Get("max_age"), 10, 8) + maxAge := uint8(num) + rpvstVlanConf.Config.MaxAge = &maxAge + } + + // populate operational information + //ygot.BuildEmptyTree(rpvstVlanConf.State) + operDbVal := app.vlanOperTableMap[vlanName] + if operDbVal.IsPopulated() { + num, _ = strconv.ParseUint((&operDbVal).Get("max_age"), 10, 8) + opMaxAge := uint8(num) + rpvstVlanConf.State.MaxAge = &opMaxAge + + num, _ = strconv.ParseUint((&operDbVal).Get("hello_time"), 10, 8) + opHelloTime := uint8(num) + rpvstVlanConf.State.HelloTime = &opHelloTime + + num, _ = strconv.ParseUint((&operDbVal).Get("forward_delay"), 10, 8) + opForwardDelay := uint8(num) + rpvstVlanConf.State.ForwardingDelay = &opForwardDelay + + num, _ = strconv.ParseUint((&operDbVal).Get("hold_time"), 10, 8) + opHoldTime := uint8(num) + rpvstVlanConf.State.HoldTime = &opHoldTime + + /*num, _ = strconv.ParseUint((&operDbVal).Get("root_max_age"), 10, 8) + opRootMaxAge := uint8(num) + rpvstVlanConf.State.RootMaxAge = &opRootMaxAge + + num, _ = strconv.ParseUint((&operDbVal).Get("root_hello_time"), 10, 8) + opRootHelloTime := uint8(num) + rpvstVlanConf.State.RootHelloTime = &opRootHelloTime + + num, _ = strconv.ParseUint((&operDbVal).Get("root_forward_delay"), 10, 8) + opRootForwardDelay := uint8(num) + rpvstVlanConf.State.RootForwardingDelay = &opRootForwardDelay */ + + num, _ = strconv.ParseUint((&operDbVal).Get("stp_instance"), 10, 16) + opStpInstance := uint16(num) + rpvstVlanConf.State.StpInstance = &opStpInstance + + num, _ = strconv.ParseUint((&operDbVal).Get("root_path_cost"), 10, 32) + opRootCost := uint32(num) + rpvstVlanConf.State.RootCost = &opRootCost + + num, _ = strconv.ParseUint((&operDbVal).Get("last_topology_change"), 10, 64) + opLastTopologyChange := num + rpvstVlanConf.State.LastTopologyChange = &opLastTopologyChange + + num, _ = strconv.ParseUint((&operDbVal).Get("topology_change_count"), 10, 64) + opTopologyChanges := num + rpvstVlanConf.State.TopologyChanges = &opTopologyChanges + + bridgeId := (&operDbVal).Get("bridge_id") + rpvstVlanConf.State.BridgeAddress = &bridgeId + + desigRootAddr := (&operDbVal).Get("desig_bridge_id") + rpvstVlanConf.State.DesignatedRootAddress = &desigRootAddr + + //rootPortStr := (&operDbVal).Get("root_port") + //rpvstVlanConf.State.RootPort = &rootPortStr + } + + app.convertInternalToOCRpvstVlanInterface(vlanName, "", rpvstVlanConf, nil) + // populate operational information + app.convertOperInternalToOCVlanInterface(vlanName, "", rpvstVlanConf, nil) + } + } else { + for vlanName := range app.vlanTableMap { + vlanId, _ := strconv.Atoi(strings.Replace(vlanName, "Vlan", "", 1)) + vlan := uint16(vlanId) + + rpvstVlanConfPtr, _ := rpvst.NewVlan(vlan) + ygot.BuildEmptyTree(rpvstVlanConfPtr) + app.convertInternalToOCRpvstVlanConfig(vlanName, rpvst, rpvstVlanConfPtr) + } + } +} + +func (app *StpApp) convertDBRpvstVlanInterfaceToInternal(d *db.DB, vlanName string, intfId string, vlanInterfaceKey db.Key, doGetOperData bool) error { + var err error + if vlanInterfaceKey.Len() > 1 { + rpvstVlanIntfConf, err := d.GetEntry(app.vlanIntfTable, asKey(vlanName, intfId)) + if err != nil { + return err + } + if app.vlanIntfTableMap[vlanName] == nil { + app.vlanIntfTableMap[vlanName] = make(map[string]db.Value) + } + app.vlanIntfTableMap[vlanName][intfId] = rpvstVlanIntfConf + // Collect operational info from application DB + if doGetOperData { + err = app.convertApplDBRpvstVlanInterfaceToInternal(vlanName, intfId) + } + } else { + keys, err := d.GetKeys(app.vlanIntfTable) + if err != nil { + return err + } + for i, _ := range keys { + if vlanName == keys[i].Get(0) { + err = app.convertDBRpvstVlanInterfaceToInternal(d, vlanName, keys[i].Get(1), keys[i], doGetOperData) + } + } + } + return err +} + +func (app *StpApp) convertInternalToOCRpvstVlanInterface(vlanName string, intfId string, rpvstVlanConf *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan, rpvstVlanIntfConf *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan_Interfaces_Interface) { + var num uint64 + + if len(intfId) == 0 { + for intf, _ := range app.vlanIntfTableMap[vlanName] { + app.convertInternalToOCRpvstVlanInterface(vlanName, intf, rpvstVlanConf, rpvstVlanIntfConf) + } + } else { + dbVal := app.vlanIntfTableMap[vlanName][intfId] + + if rpvstVlanIntfConf == nil { + if rpvstVlanConf != nil { + rpvstVlanIntfConf_, _ := rpvstVlanConf.Interfaces.NewInterface(intfId) + rpvstVlanIntfConf = rpvstVlanIntfConf_ + ygot.BuildEmptyTree(rpvstVlanIntfConf) + } + } + + num, _ = strconv.ParseUint((&dbVal).Get("path_cost"), 10, 32) + cost := uint32(num) + rpvstVlanIntfConf.Config.Cost = &cost + + num, _ = strconv.ParseUint((&dbVal).Get("priority"), 10, 8) + portPriority := uint8(num) + rpvstVlanIntfConf.Config.PortPriority = &portPriority + + rpvstVlanIntfConf.Config.Name = &intfId + } +} + +/////////// PVST ////////////////////// +func (app *StpApp) convertOCPvstToInternal() { + stp := app.getAppRootObject() + if stp != nil && stp.Pvst != nil && len(stp.Pvst.Vlan) > 0 { + for vlanId, _ := range stp.Pvst.Vlan { + vlanName := "Vlan" + strconv.Itoa(int(vlanId)) + app.vlanTableMap[vlanName] = db.Value{Field: map[string]string{}} + pvstVlan := stp.Pvst.Vlan[vlanId] + if pvstVlan.Config != nil { + dbVal := app.vlanTableMap[vlanName] + (&dbVal).Set("vlanid", strconv.Itoa(int(vlanId))) + if pvstVlan.Config.BridgePriority != nil { + (&dbVal).Set("priority", strconv.Itoa(int(*pvstVlan.Config.BridgePriority))) + } else { + (&dbVal).Set("priority", "32768") + } + if pvstVlan.Config.ForwardingDelay != nil { + (&dbVal).Set("forward_delay", strconv.Itoa(int(*pvstVlan.Config.ForwardingDelay))) + } else { + (&dbVal).Set("forward_delay", "15") + } + if pvstVlan.Config.HelloTime != nil { + (&dbVal).Set("hello_time", strconv.Itoa(int(*pvstVlan.Config.HelloTime))) + } else { + (&dbVal).Set("hello_time", "2") + } + if pvstVlan.Config.MaxAge != nil { + (&dbVal).Set("max_age", strconv.Itoa(int(*pvstVlan.Config.MaxAge))) + } else { + (&dbVal).Set("max_age", "20") + } + if pvstVlan.Config.SpanningTreeEnable != nil { + if *pvstVlan.Config.SpanningTreeEnable { + (&dbVal).Set("enabled", "true") + } else { + (&dbVal).Set("enabled", "false") + } + } else { + (&dbVal).Set("enabled", "false") + } + } + if pvstVlan.Interfaces != nil && len(pvstVlan.Interfaces.Interface) > 0 { + app.vlanIntfTableMap[vlanName] = make(map[string]db.Value) + for intfId, _ := range pvstVlan.Interfaces.Interface { + pvstVlanIntf := pvstVlan.Interfaces.Interface[intfId] + app.vlanIntfTableMap[vlanName][intfId] = db.Value{Field: map[string]string{}} + if pvstVlanIntf.Config != nil { + dbVal := app.vlanIntfTableMap[vlanName][intfId] + (&dbVal).Set("vlan-name", vlanName) + (&dbVal).Set("ifname", intfId) + if pvstVlanIntf.Config.Cost != nil { + (&dbVal).Set("path_cost", strconv.Itoa(int(*pvstVlanIntf.Config.Cost))) + } else { + (&dbVal).Set("path_cost", "200") + } + if pvstVlanIntf.Config.PortPriority != nil { + (&dbVal).Set("priority", strconv.Itoa(int(*pvstVlanIntf.Config.PortPriority))) + } else { + (&dbVal).Set("priority", "128") + } + } + } + } + } + } +} + +func (app *StpApp) convertInternalToOCPvstVlan(vlanName string, pvst *ocbinds.OpenconfigSpanningTree_Stp_Pvst, pvstVlan *ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan) { + if len(vlanName) > 0 { + var num uint64 + if pvstVlan != nil { + if pvstVlanData, ok := app.vlanTableMap[vlanName]; ok { + vlanId, _ := strconv.Atoi(strings.Replace(vlanName, "Vlan", "", 1)) + vlan := uint16(vlanId) + pvstVlan.VlanId = &vlan + pvstVlan.Config.VlanId = &vlan + pvstVlan.State.VlanId = &vlan + + stpEnabled, _ := strconv.ParseBool((&pvstVlanData).Get("enabled")) + pvstVlan.Config.SpanningTreeEnable = &stpEnabled + //pvstVlan.State.SpanningTreeEnable = &stpEnabled + + num, _ = strconv.ParseUint((&pvstVlanData).Get("priority"), 10, 32) + priority := uint32(num) + pvstVlan.Config.BridgePriority = &priority + pvstVlan.State.BridgePriority = &priority + + num, _ = strconv.ParseUint((&pvstVlanData).Get("forward_delay"), 10, 8) + forDelay := uint8(num) + pvstVlan.Config.ForwardingDelay = &forDelay + + num, _ = strconv.ParseUint((&pvstVlanData).Get("hello_time"), 10, 8) + helloTime := uint8(num) + pvstVlan.Config.HelloTime = &helloTime + + num, _ = strconv.ParseUint((&pvstVlanData).Get("max_age"), 10, 8) + maxAge := uint8(num) + pvstVlan.Config.MaxAge = &maxAge + } + + // populate operational information + operDbVal := app.vlanOperTableMap[vlanName] + if operDbVal.IsPopulated() { + num, _ = strconv.ParseUint((&operDbVal).Get("max_age"), 10, 8) + opMaxAge := uint8(num) + pvstVlan.State.MaxAge = &opMaxAge + + num, _ = strconv.ParseUint((&operDbVal).Get("hello_time"), 10, 8) + opHelloTime := uint8(num) + pvstVlan.State.HelloTime = &opHelloTime + + num, _ = strconv.ParseUint((&operDbVal).Get("forward_delay"), 10, 8) + opForwardDelay := uint8(num) + pvstVlan.State.ForwardingDelay = &opForwardDelay + + num, _ = strconv.ParseUint((&operDbVal).Get("hold_time"), 10, 8) + opHoldTime := uint8(num) + pvstVlan.State.HoldTime = &opHoldTime + + /*num, _ = strconv.ParseUint((&operDbVal).Get("root_max_age"), 10, 8) + opRootMaxAge := uint8(num) + pvstVlan.State.RootMaxAge = &opRootMaxAge + + num, _ = strconv.ParseUint((&operDbVal).Get("root_hello_time"), 10, 8) + opRootHelloTime := uint8(num) + pvstVlan.State.RootHelloTime = &opRootHelloTime + + num, _ = strconv.ParseUint((&operDbVal).Get("root_forward_delay"), 10, 8) + opRootForwardDelay := uint8(num) + pvstVlan.State.RootForwardingDelay = &opRootForwardDelay */ + + num, _ = strconv.ParseUint((&operDbVal).Get("stp_instance"), 10, 16) + opStpInstance := uint16(num) + pvstVlan.State.StpInstance = &opStpInstance + + num, _ = strconv.ParseUint((&operDbVal).Get("root_path_cost"), 10, 32) + opRootCost := uint32(num) + pvstVlan.State.RootCost = &opRootCost + + num, _ = strconv.ParseUint((&operDbVal).Get("last_topology_change"), 10, 64) + opLastTopologyChange := num + pvstVlan.State.LastTopologyChange = &opLastTopologyChange + + num, _ = strconv.ParseUint((&operDbVal).Get("topology_change_count"), 10, 64) + opTopologyChanges := num + pvstVlan.State.TopologyChanges = &opTopologyChanges + + bridgeId := (&operDbVal).Get("bridge_id") + pvstVlan.State.BridgeAddress = &bridgeId + + desigRootAddr := (&operDbVal).Get("desig_bridge_id") + pvstVlan.State.DesignatedRootAddress = &desigRootAddr + + //rootPortStr := (&operDbVal).Get("root_port") + //pvstVlan.State.RootPort = &rootPortStr + } + + app.convertInternalToOCPvstVlanInterface(vlanName, "", pvstVlan, nil) + // populate operational information + app.convertOperInternalToOCVlanInterface(vlanName, "", pvstVlan, nil) + } + } else { + for vlanName := range app.vlanTableMap { + vlanId, _ := strconv.Atoi(strings.Replace(vlanName, "Vlan", "", 1)) + vlan := uint16(vlanId) + + pvstVlanPtr, _ := pvst.NewVlan(vlan) + ygot.BuildEmptyTree(pvstVlanPtr) + app.convertInternalToOCPvstVlan(vlanName, pvst, pvstVlanPtr) + } + } +} + +func (app *StpApp) convertInternalToOCPvstVlanInterface(vlanName string, intfId string, pvstVlan *ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan, pvstVlanIntf *ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan_Interfaces_Interface) { + var num uint64 + + if len(intfId) == 0 { + for intf, _ := range app.vlanIntfTableMap[vlanName] { + app.convertInternalToOCPvstVlanInterface(vlanName, intf, pvstVlan, pvstVlanIntf) + } + } else { + dbVal := app.vlanIntfTableMap[vlanName][intfId] + + if pvstVlanIntf == nil { + if pvstVlan != nil { + pvstVlanIntf_, _ := pvstVlan.Interfaces.NewInterface(intfId) + pvstVlanIntf = pvstVlanIntf_ + ygot.BuildEmptyTree(pvstVlanIntf) + } + } + + num, _ = strconv.ParseUint((&dbVal).Get("path_cost"), 10, 32) + cost := uint32(num) + pvstVlanIntf.Config.Cost = &cost + + num, _ = strconv.ParseUint((&dbVal).Get("priority"), 10, 8) + portPriority := uint8(num) + pvstVlanIntf.Config.PortPriority = &portPriority + + pvstVlanIntf.Config.Name = &intfId + } +} + +/////////// Interfaces ////////// +func (app *StpApp) convertOCStpInterfacesToInternal() { + stp := app.getAppRootObject() + if stp != nil && stp.Interfaces != nil && len(stp.Interfaces.Interface) > 0 { + for intfId, _ := range stp.Interfaces.Interface { + app.intfTableMap[intfId] = db.Value{Field: map[string]string{}} + + stpIntfConf := stp.Interfaces.Interface[intfId] + if stpIntfConf.Config != nil { + dbVal := app.intfTableMap[intfId] + (&dbVal).Set("ifname", intfId) + + if stpIntfConf.Config.BpduGuard != nil { + if *stpIntfConf.Config.BpduGuard == true { + (&dbVal).Set("bpdu_guard", "true") + } else { + (&dbVal).Set("bpdu_guard", "false") + } + } + + if stpIntfConf.Config.BpduFilter != nil { + if *stpIntfConf.Config.BpduFilter == true { + (&dbVal).Set("bpdu_filter", "true") + } else { + (&dbVal).Set("bpdu_filter", "false") + } + } + + if stpIntfConf.Config.BpduGuardPortShutdown != nil { + if *stpIntfConf.Config.BpduGuardPortShutdown == true { + (&dbVal).Set("bpdu_guard_do_disable", "true") + } else { + (&dbVal).Set("bpdu_guard_do_disable", "false") + } + } + + if stpIntfConf.Config.Portfast != nil { + if *stpIntfConf.Config.Portfast == true { + (&dbVal).Set("portfast", "true") + } else { + (&dbVal).Set("portfast", "false") + } + } + + if stpIntfConf.Config.UplinkFast != nil { + if *stpIntfConf.Config.UplinkFast == true { + (&dbVal).Set("uplink_fast", "true") + } else { + (&dbVal).Set("uplink_fast", "false") + } + } + + if stpIntfConf.Config.SpanningTreeEnable != nil { + if *stpIntfConf.Config.SpanningTreeEnable == true { + (&dbVal).Set("enabled", "true") + } else { + (&dbVal).Set("enabled", "false") + } + } + + if stpIntfConf.Config.Cost != nil { + (&dbVal).Set("path_cost", strconv.Itoa(int(*stpIntfConf.Config.Cost))) + } else { + //(&dbVal).Set("path_cost", "200") + } + if stpIntfConf.Config.PortPriority != nil { + (&dbVal).Set("priority", strconv.Itoa(int(*stpIntfConf.Config.PortPriority))) + } else { + //(&dbVal).Set("priority", "128") + } + + if stpIntfConf.Config.Guard == ocbinds.OpenconfigSpanningTree_StpGuardType_ROOT { + (&dbVal).Set("root_guard", "true") + } else { + //(&dbVal).Set("root_guard", "false") + } + //// For RPVST+ ///// + if stpIntfConf.Config.EdgePort == ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_ENABLE { + (&dbVal).Set("edge_port", "true") + } else if stpIntfConf.Config.EdgePort == ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_DISABLE { + (&dbVal).Set("edge_port", "false") + } + + if stpIntfConf.Config.LinkType == ocbinds.OpenconfigSpanningTree_StpLinkType_P2P { + (&dbVal).Set("pt2pt_mac", "true") + } else if stpIntfConf.Config.LinkType == ocbinds.OpenconfigSpanningTree_StpLinkType_SHARED { + (&dbVal).Set("pt2pt_mac", "false") + } + } + } + } +} + +func (app *StpApp) setStpInterfacesDataInDB(d *db.DB, createFlag bool) error { + var err error + for intfName := range app.intfTableMap { + existingEntry, err := d.GetEntry(app.interfaceTable, asKey(intfName)) + if createFlag && existingEntry.IsPopulated() { + return tlerr.AlreadyExists("Stp Interface %s already configured", intfName) + } + if createFlag || (!createFlag && err != nil && !existingEntry.IsPopulated()) { + err = d.CreateEntry(app.interfaceTable, asKey(intfName), app.intfTableMap[intfName]) + } else { + if existingEntry.IsPopulated() { + err = d.ModEntry(app.interfaceTable, asKey(intfName), app.intfTableMap[intfName]) + } + } + } + return err +} + +func (app *StpApp) convertDBStpInterfacesToInternal(d *db.DB, intfKey db.Key) error { + var err error + if intfKey.Len() > 0 { + entry, err := d.GetEntry(app.interfaceTable, intfKey) + if err != nil { + return err + } + intfName := intfKey.Get(0) + if entry.IsPopulated() { + app.intfTableMap[intfName] = entry + } else { + return tlerr.NotFound("STP interface %s is not configured", intfName) + } + err = app.convertApplDBStpInterfacesToInternal(intfName) + } else { + tbl, err := d.GetTable(app.interfaceTable) + if err != nil { + return err + } + keys, err := tbl.GetKeys() + if err != nil { + return err + } + for i, _ := range keys { + app.convertDBStpInterfacesToInternal(d, keys[i]) + } + } + + return err +} + +func (app *StpApp) convertInternalToOCStpInterfaces(intfName string, interfaces *ocbinds.OpenconfigSpanningTree_Stp_Interfaces, intf *ocbinds.OpenconfigSpanningTree_Stp_Interfaces_Interface) { + var err error + if len(intfName) > 0 { + if stpIntfData, ok := app.intfTableMap[intfName]; ok { + if intf != nil { + intf.Config.Name = &intfName + intf.State.Name = &intfName + + stpEnabled, _ := strconv.ParseBool((&stpIntfData).Get("enabled")) + intf.Config.SpanningTreeEnable = &stpEnabled + intf.State.SpanningTreeEnable = &stpEnabled + + bpduGuardEnabled, _ := strconv.ParseBool((&stpIntfData).Get("bpdu_guard")) + intf.Config.BpduGuard = &bpduGuardEnabled + intf.State.BpduGuard = &bpduGuardEnabled + + bpduFilterEnabled, _ := strconv.ParseBool((&stpIntfData).Get("bpdu_filter")) + intf.Config.BpduFilter = &bpduFilterEnabled + intf.State.BpduFilter = &bpduFilterEnabled + + bpduGuardPortShut, _ := strconv.ParseBool((&stpIntfData).Get("bpdu_guard_do_disable")) + intf.Config.BpduGuardPortShutdown = &bpduGuardPortShut + intf.State.BpduGuardPortShutdown = &bpduGuardPortShut + + uplinkFast, _ := strconv.ParseBool((&stpIntfData).Get("uplink_fast")) + intf.Config.UplinkFast = &uplinkFast + intf.State.UplinkFast = &uplinkFast + + portFast, _ := strconv.ParseBool((&stpIntfData).Get("portfast")) + intf.Config.Portfast = &portFast + + rootGuardEnabled, _ := strconv.ParseBool((&stpIntfData).Get("root_guard")) + if rootGuardEnabled { + intf.Config.Guard = ocbinds.OpenconfigSpanningTree_StpGuardType_ROOT + intf.State.Guard = ocbinds.OpenconfigSpanningTree_StpGuardType_ROOT + } else { + intf.Config.Guard = ocbinds.OpenconfigSpanningTree_StpGuardType_NONE + intf.State.Guard = ocbinds.OpenconfigSpanningTree_StpGuardType_NONE + } + + if edgePortEnabled, err := strconv.ParseBool((&stpIntfData).Get("edge_port")); err == nil { + if edgePortEnabled { + intf.Config.EdgePort = ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_ENABLE + intf.State.EdgePort = ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_ENABLE + } else { + intf.Config.EdgePort = ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_DISABLE + intf.State.EdgePort = ocbinds.OpenconfigSpanningTreeTypes_STP_EDGE_PORT_EDGE_DISABLE + } + } + + if linkTypeEnabled, err := strconv.ParseBool((&stpIntfData).Get("pt2pt_mac")); err == nil { + if linkTypeEnabled { + intf.Config.LinkType = ocbinds.OpenconfigSpanningTree_StpLinkType_P2P + intf.State.LinkType = ocbinds.OpenconfigSpanningTree_StpLinkType_P2P + } else { + intf.Config.LinkType = ocbinds.OpenconfigSpanningTree_StpLinkType_SHARED + intf.State.LinkType = ocbinds.OpenconfigSpanningTree_StpLinkType_SHARED + } + } + + var num uint64 + if num, err = strconv.ParseUint((&stpIntfData).Get("priority"), 10, 8); err == nil { + priority := uint8(num) + intf.Config.PortPriority = &priority + intf.State.PortPriority = &priority + } + + if num, err = strconv.ParseUint((&stpIntfData).Get("path_cost"), 10, 32); err == nil { + cost := uint32(num) + intf.Config.Cost = &cost + intf.State.Cost = &cost + } + } + } + + operDbVal := app.intfOperTableMap[intfName] + if operDbVal.IsPopulated() && intf != nil { + var boolVal bool + + bpduGuardShut := (&operDbVal).Get("bpdu_guard_shutdown") + if bpduGuardShut == "yes" { + boolVal = true + } else if bpduGuardShut == "no" { + boolVal = false + } + intf.State.BpduGuardShutdown = &boolVal + + opPortfast := (&operDbVal).Get("port_fast") + if opPortfast == "yes" { + boolVal = true + } else if opPortfast == "no" { + boolVal = false + } + intf.State.Portfast = &boolVal + } + } else { + for intfName := range app.intfTableMap { + intfPtr, _ := interfaces.NewInterface(intfName) + ygot.BuildEmptyTree(intfPtr) + app.convertInternalToOCStpInterfaces(intfName, interfaces, intfPtr) + } + } +} + +func (app *StpApp) convertOperInternalToOCVlanInterface(vlanName string, intfId string, vlan interface{}, vlanIntf interface{}) { + if len(intfId) > 0 { + var pvstVlan *ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan + var pvstVlanIntf *ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan_Interfaces_Interface + var rpvstVlan *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan + var rpvstVlanIntf *ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan_Interfaces_Interface + var num uint64 + + switch reflect.TypeOf(vlan).Elem().Name() { + case "OpenconfigSpanningTree_Stp_Pvst_Vlan": + pvstVlan, _ = vlan.(*ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan) + if vlanIntf == nil { + pvstVlanIntf, _ = pvstVlan.Interfaces.NewInterface(intfId) + ygot.BuildEmptyTree(pvstVlanIntf) + ygot.BuildEmptyTree(pvstVlanIntf.State) + } else { + pvstVlanIntf, _ = vlanIntf.(*ocbinds.OpenconfigSpanningTree_Stp_Pvst_Vlan_Interfaces_Interface) + } + case "OpenconfigSpanningTree_Stp_RapidPvst_Vlan": + rpvstVlan, _ = vlan.(*ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan) + if vlanIntf == nil { + rpvstVlanIntf, _ = rpvstVlan.Interfaces.NewInterface(intfId) + ygot.BuildEmptyTree(rpvstVlanIntf) + ygot.BuildEmptyTree(rpvstVlanIntf.State) + } else { + rpvstVlanIntf, _ = vlanIntf.(*ocbinds.OpenconfigSpanningTree_Stp_RapidPvst_Vlan_Interfaces_Interface) + } + } + + operDbVal := app.vlanIntfOperTableMap[vlanName][intfId] + + if operDbVal.IsPopulated() { + num, _ = strconv.ParseUint((&operDbVal).Get("port_num"), 10, 16) + opPortNum := uint16(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("path_cost"), 10, 32) + opcost := uint32(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("priority"), 10, 8) + opPortPriority := uint8(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("desig_cost"), 10, 32) + opDesigCost := uint32(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("desig_port"), 10, 16) + opDesigPortNum := uint16(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("root_guard_timer"), 10, 16) + opRootGuardTimer := uint16(num) + + num, _ = strconv.ParseUint((&operDbVal).Get("fwd_transitions"), 10, 64) + opFwtrans := num + + desigRootAddr := (&operDbVal).Get("desig_root") + + desigBridgeAddr := (&operDbVal).Get("desig_bridge") + + portState := (&operDbVal).Get("port_state") + + //Counters + num, _ = strconv.ParseUint((&operDbVal).Get("bpdu_sent"), 10, 64) + opBpduSent := num + + num, _ = strconv.ParseUint((&operDbVal).Get("bpdu_received"), 10, 64) + opBpduReceived := num + + num, _ = strconv.ParseUint((&operDbVal).Get("tcn_sent"), 10, 64) + opTcnSent := num + + num, _ = strconv.ParseUint((&operDbVal).Get("tcn_received"), 10, 64) + opTcnReceived := num + + if pvstVlanIntf != nil && pvstVlanIntf.State != nil { + pvstVlanIntf.State.Name = &intfId + pvstVlanIntf.State.PortNum = &opPortNum + pvstVlanIntf.State.Cost = &opcost + pvstVlanIntf.State.PortPriority = &opPortPriority + pvstVlanIntf.State.DesignatedCost = &opDesigCost + pvstVlanIntf.State.DesignatedPortNum = &opDesigPortNum + pvstVlanIntf.State.RootGuardTimer = &opRootGuardTimer + pvstVlanIntf.State.ForwardTransisitions = &opFwtrans + pvstVlanIntf.State.DesignatedRootAddress = &desigRootAddr + pvstVlanIntf.State.DesignatedBridgeAddress = &desigBridgeAddr + switch portState { + case "disabled": + pvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_DISABLED + case "block": + pvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_BLOCKING + case "listen": + pvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_LISTENING + case "learn": + pvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_LEARNING + case "forward": + pvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_FORWARDING + } + if pvstVlanIntf.State.Counters != nil { + pvstVlanIntf.State.Counters.BpduSent = &opBpduSent + pvstVlanIntf.State.Counters.BpduReceived = &opBpduReceived + pvstVlanIntf.State.Counters.TcnSent = &opTcnSent + pvstVlanIntf.State.Counters.TcnReceived = &opTcnReceived + } + } else if rpvstVlanIntf != nil && rpvstVlanIntf.State != nil { + rpvstVlanIntf.State.Name = &intfId + rpvstVlanIntf.State.PortNum = &opPortNum + rpvstVlanIntf.State.Cost = &opcost + rpvstVlanIntf.State.PortPriority = &opPortPriority + rpvstVlanIntf.State.DesignatedCost = &opDesigCost + rpvstVlanIntf.State.DesignatedPortNum = &opDesigPortNum + rpvstVlanIntf.State.RootGuardTimer = &opRootGuardTimer + rpvstVlanIntf.State.ForwardTransisitions = &opFwtrans + rpvstVlanIntf.State.DesignatedRootAddress = &desigRootAddr + rpvstVlanIntf.State.DesignatedBridgeAddress = &desigBridgeAddr + switch portState { + case "disabled": + rpvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_DISABLED + case "block": + rpvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_BLOCKING + case "listen": + rpvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_LISTENING + case "learn": + rpvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_LEARNING + case "forward": + rpvstVlanIntf.State.PortState = ocbinds.OpenconfigSpanningTreeTypes_STP_PORT_STATE_FORWARDING + } + if rpvstVlanIntf.State.Counters != nil { + rpvstVlanIntf.State.Counters.BpduSent = &opBpduSent + rpvstVlanIntf.State.Counters.BpduReceived = &opBpduReceived + rpvstVlanIntf.State.Counters.TcnSent = &opTcnSent + rpvstVlanIntf.State.Counters.TcnReceived = &opTcnReceived + } + } + } + } else { + vlanIntfOperKeys, _ := app.appDB.GetKeys(app.vlanIntfOperTable) + for i, _ := range vlanIntfOperKeys { + entryKey := vlanIntfOperKeys[i] + if vlanName == (&entryKey).Get(0) { + app.convertOperInternalToOCVlanInterface(vlanName, (&entryKey).Get(1), vlan, vlanIntf) + } + } + } +} + +func (app *StpApp) convertApplDBRpvstVlanToInternal(vlanName string) error { + var err error + + rpvstVlanOperState, err := app.appDB.GetEntry(app.vlanOperTable, asKey(vlanName)) + if err != nil { + return err + } + app.vlanOperTableMap[vlanName] = rpvstVlanOperState + + app.convertApplDBRpvstVlanInterfaceToInternal(vlanName, "") + + return err +} + +func (app *StpApp) convertApplDBRpvstVlanInterfaceToInternal(vlanName string, intfId string) error { + var err error + + if app.vlanIntfOperTableMap[vlanName] == nil { + app.vlanIntfOperTableMap[vlanName] = make(map[string]db.Value) + } + + if len(intfId) > 0 { + rpvstVlanIntfOperState, err := app.appDB.GetEntry(app.vlanIntfOperTable, asKey(vlanName, intfId)) + if err != nil { + return err + } + app.vlanIntfOperTableMap[vlanName][intfId] = rpvstVlanIntfOperState + } else { + vlanIntfOperKeys, _ := app.appDB.GetKeys(app.vlanIntfOperTable) + for i, _ := range vlanIntfOperKeys { + entryKey := vlanIntfOperKeys[i] + if vlanName == (&entryKey).Get(0) { + app.convertApplDBRpvstVlanInterfaceToInternal(vlanName, (&entryKey).Get(1)) + } + } + } + + return err +} + +func (app *StpApp) convertApplDBStpInterfacesToInternal(intfId string) error { + var err error + + intfOperState, err := app.appDB.GetEntry(app.intfOperTable, asKey(intfId)) + if err != nil { + return err + } + app.intfOperTableMap[intfId] = intfOperState + + return err +} + +func (app *StpApp) convertOCStpModeToInternal(mode ocbinds.E_OpenconfigSpanningTreeTypes_STP_PROTOCOL) string { + switch mode { + case ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_MSTP: + return "mstp" + case ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_PVST: + return "pvst" + case ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_RAPID_PVST: + return "rpvst" + case ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_RSTP: + return "rstp" + default: + return "" + } +} + +func (app *StpApp) convertInternalStpModeToOC(mode string) []ocbinds.E_OpenconfigSpanningTreeTypes_STP_PROTOCOL { + var stpModes []ocbinds.E_OpenconfigSpanningTreeTypes_STP_PROTOCOL + if len(mode) > 0 { + switch mode { + case "pvst": + stpModes = append(stpModes, ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_PVST) + case "rpvst": + stpModes = append(stpModes, ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_RAPID_PVST) + case "mstp": + stpModes = append(stpModes, ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_MSTP) + case "rstp": + stpModes = append(stpModes, ocbinds.OpenconfigSpanningTreeTypes_STP_PROTOCOL_RSTP) + } + } + return stpModes +} + +func (app *StpApp) getStpModeFromConfigDB(d *db.DB) (string, error) { + stpGlobalDbEntry, err := d.GetEntry(app.globalTable, asKey("GLOBAL")) + if err != nil { + return "", err + } + return (&stpGlobalDbEntry).Get(STP_MODE), nil +} + +func (app *StpApp) getAllInterfacesFromVlanMemberTable(d *db.DB) ([]string, error) { + var intfList []string + + keys, err := d.GetKeys(&db.TableSpec{Name: "VLAN_MEMBER"}) + if err != nil { + return intfList, err + } + for i, _ := range keys { + key := keys[i] + if !contains(intfList, (&key).Get(1)) { + intfList = append(intfList, (&key).Get(1)) + } + } + return intfList, err +} + +func (app *StpApp) enableStpForInterfaces(d *db.DB) error { + defaultDBValues := db.Value{Field: map[string]string{}} + (&defaultDBValues).Set("enabled", "true") + (&defaultDBValues).Set("root_guard", "false") + (&defaultDBValues).Set("bpdu_guard", "false") + (&defaultDBValues).Set("bpdu_filter", "false") + (&defaultDBValues).Set("bpdu_guard_do_disable", "false") + (&defaultDBValues).Set("portfast", "true") + (&defaultDBValues).Set("uplink_fast", "false") + + intfList, err := app.getAllInterfacesFromVlanMemberTable(d) + if err != nil { + return err + } + + portKeys, err := d.GetKeys(&db.TableSpec{Name: "PORT"}) + if err != nil { + return err + } + for i, _ := range portKeys { + portKey := portKeys[i] + if contains(intfList, (&portKey).Get(0)) { + d.CreateEntry(app.interfaceTable, portKey, defaultDBValues) + } + } + + // For portchannels + portchKeys, err := d.GetKeys(&db.TableSpec{Name: "PORTCHANNEL"}) + if err != nil { + return err + } + for i, _ := range portchKeys { + portchKey := portchKeys[i] + if contains(intfList, (&portchKey).Get(0)) { + d.CreateEntry(app.interfaceTable, portchKey, defaultDBValues) + } + } + return err +} + +func (app *StpApp) enableStpForVlans(d *db.DB) error { + stpGlobalVal := app.globalInfo + fDelay := (&stpGlobalVal).Get("forward_delay") + helloTime := (&stpGlobalVal).Get("hello_time") + maxAge := (&stpGlobalVal).Get("max_age") + priority := (&stpGlobalVal).Get("priority") + + vlanKeys, err := d.GetKeys(&db.TableSpec{Name: "VLAN"}) + if err != nil { + return err + } + + var vlanList []string + for i, _ := range vlanKeys { + vlanKey := vlanKeys[i] + vlanList = append(vlanList, (&vlanKey).Get(0)) + } + + // Sort vlanList in natural order such that 'Vlan2' < 'Vlan10' + natsort.Sort(vlanList) + + for i, _ := range vlanList { + if i < PVST_MAX_INSTANCES { + defaultDBValues := db.Value{Field: map[string]string{}} + (&defaultDBValues).Set("enabled", "true") + (&defaultDBValues).Set("forward_delay", fDelay) + (&defaultDBValues).Set("hello_time", helloTime) + (&defaultDBValues).Set("max_age", maxAge) + (&defaultDBValues).Set("priority", priority) + + vlanId := strings.Replace(vlanList[i], "Vlan", "", 1) + (&defaultDBValues).Set("vlanid", vlanId) + d.CreateEntry(app.vlanTable, asKey(vlanList[i]), defaultDBValues) + } + } + return err +} + +func (app *StpApp) updateGlobalFieldsToStpVlanTable(d *db.DB, fldName string, valStr string) error { + stpGlobalDbEntry, err := d.GetEntry(app.globalTable, asKey("GLOBAL")) + if err != nil { + return err + } + globalFldVal := (&stpGlobalDbEntry).Get(fldName) + + vlanKeys, err := d.GetKeys(app.vlanTable) + if err != nil { + return err + } + for i, _ := range vlanKeys { + vlanEntry, _ := d.GetEntry(app.vlanTable, vlanKeys[i]) + if (&vlanEntry).Get(fldName) == globalFldVal { + (&vlanEntry).Set(fldName, valStr) + err := d.ModEntry(app.vlanTable, vlanKeys[i], vlanEntry) + if err != nil { + return err + } + } + } + return nil +} + +func (app *StpApp) disableStpMode(d *db.DB) error { + var err error + err = d.DeleteTable(app.vlanIntfTable) + if err != nil { + return err + } + err = d.DeleteTable(app.vlanTable) + if err != nil { + return err + } + err = d.DeleteTable(app.interfaceTable) + if err != nil { + return err + } + err = d.DeleteTable(app.globalTable) + + return err +} + +func (app *StpApp) handleStpGlobalFieldsDeletion(d *db.DB) error { + stpGlobalDBEntry, err := d.GetEntry(app.globalTable, asKey("GLOBAL")) + if err != nil { + return err + } + + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + log.Infof("Node received for deletion: %s", nodeInfo.Name) + if nodeInfo.IsLeaf() { + var fldName, valStr string + switch nodeInfo.Name { + case "rootguard-timeout": + fldName = "rootguard_timeout" + valStr = STP_DEFAULT_ROOT_GUARD_TIMEOUT + case "hello-time": + fldName = "hello_time" + valStr = STP_DEFAULT_HELLO_INTERVAL + case "max-age": + fldName = "max_age" + valStr = STP_DEFAULT_MAX_AGE + case "forwarding-delay": + fldName = "forward_delay" + valStr = STP_DEFAULT_FORWARD_DELAY + case "bridge-priority": + fldName = "priority" + valStr = STP_DEFAULT_BRIDGE_PRIORITY + } + + (&stpGlobalDBEntry).Set(fldName, valStr) + err := d.ModEntry(app.globalTable, asKey("GLOBAL"), stpGlobalDBEntry) + if err != nil { + return err + } + + if fldName != "rootguard_timeout" { + err := app.updateGlobalFieldsToStpVlanTable(d, fldName, valStr) + if err != nil { + return err + } + } + } + return nil +} + +func (app *StpApp) handleVlanInterfaceFieldsDeletion(d *db.DB, vlanName string, intfId string) error { + dbEntry, err := d.GetEntry(app.vlanIntfTable, asKey(vlanName, intfId)) + if err != nil { + return err + } + + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + log.Infof("Node received for deletion: %s", nodeInfo.Name) + if nodeInfo.IsLeaf() { + switch nodeInfo.Name { + case "cost": + (&dbEntry).Remove("path_cost") + case "port-priority": + (&dbEntry).Remove("priority") + } + } + + err = d.SetEntry(app.vlanIntfTable, asKey(vlanName, intfId), dbEntry) + if err != nil { + return err + } + + return nil +} + +func (app *StpApp) handleVlanFieldsDeletion(d *db.DB, vlanName string) error { + dbEntry, err := d.GetEntry(app.vlanTable, asKey(vlanName)) + if err != nil { + return err + } + + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + log.Infof("Node received for deletion: %s", nodeInfo.Name) + if nodeInfo.IsLeaf() { + switch nodeInfo.Name { + case "hello-time": + (&dbEntry).Remove("hello_time") + case "max-age": + (&dbEntry).Remove("max_age") + case "bridge-priority": + (&dbEntry).Remove("priority") + case "forwarding-delay": + (&dbEntry).Remove("forward_delay") + case "spanning-tree-enable": + (&dbEntry).Remove("enabled") + } + } + + err = d.SetEntry(app.vlanTable, asKey(vlanName), dbEntry) + if err != nil { + return err + } + + return nil +} + +func (app *StpApp) handleInterfacesFieldsDeletion(d *db.DB, intfId string) error { + dbEntry, err := d.GetEntry(app.interfaceTable, asKey(intfId)) + if err != nil { + return err + } + + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + log.Infof("Node received for deletion: %s", nodeInfo.Name) + if nodeInfo.IsLeaf() { + switch nodeInfo.Name { + case "guard": + (&dbEntry).Remove("root_guard") + case "bpdu-guard": + (&dbEntry).Remove("bpdu_guard") + case "bpdu-filter": + (&dbEntry).Remove("bpdu_filter") + case "portfast": + (&dbEntry).Remove("portfast") + case "uplink-fast": + (&dbEntry).Remove("uplink_fast") + case "bpdu-guard-port-shutdown": + (&dbEntry).Remove("bpdu_guard_do_disable") + case "cost": + (&dbEntry).Remove("path_cost") + case "port-priority": + (&dbEntry).Remove("priority") + case "spanning-tree-enable": + (&dbEntry).Remove("enabled") + case "edge-port": + (&dbEntry).Remove("edge_port") + case "link-type": + (&dbEntry).Remove("pt2pt_mac") + } + } + + err = d.SetEntry(app.interfaceTable, asKey(intfId), dbEntry) + if err != nil { + return err + } + + return nil +} diff --git a/src/translib/sys_app.go b/src/translib/sys_app.go index 33f5f8902d..f409436ff9 100644 --- a/src/translib/sys_app.go +++ b/src/translib/sys_app.go @@ -118,6 +118,11 @@ func (app *SysApp) translateGet(dbs [db.MaxDB]*db.DB) error { return err } +func (app *SysApp) translateAction(dbs [db.MaxDB]*db.DB) error { + err := errors.New("Not supported") + return err +} + func (app *SysApp) processCreate(d *db.DB) (SetResponse, error) { var err error var resp SetResponse @@ -155,6 +160,13 @@ func (app *SysApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { return app.doGetSystem(app.path.Path) } +func (app *SysApp) processAction(dbs [db.MaxDB]*db.DB) (ActionResponse, error) { + var resp ActionResponse + err := errors.New("Not implemented") + + return resp, err +} + func (app *SysApp) doGetSystem(path string) (GetResponse, error) { var payload []byte diff --git a/src/translib/test/translibtest.go b/src/translib/test/translibtest.go index 24450736b3..8c47edbbba 100644 --- a/src/translib/test/translibtest.go +++ b/src/translib/test/translibtest.go @@ -31,7 +31,7 @@ import ( func main() { var err error - operationPtr := flag.String("o", "get", "Operation: create,update,replace,delete,get,getmodels,subscribe,supportsubscribe") + operationPtr := flag.String("o", "get", "Operation: create,update,replace,delete,get,action,getmodels,subscribe,supportsubscribe") uriPtr := flag.String("u", "", "URI string") payloadFilePtr := flag.String("p", "", "JSON payload file") flag.Parse() @@ -60,6 +60,10 @@ func main() { req := translib.GetRequest{Path:*uriPtr} resp, _ := translib.Get(req) log.Info("Response payload =", string(resp.Payload)) + } else if *operationPtr == "action" { + req := translib.ActionRequest{Path:*uriPtr} + resp, _ := translib.Action(req) + log.Info("Response payload =", string(resp.Payload)) } else if *operationPtr == "getmodels" { models,_ := translib.GetModels() log.Info("Models =", models) diff --git a/src/translib/translib.go b/src/translib/translib.go index 925e2df53f..f73f9afda1 100644 --- a/src/translib/translib.go +++ b/src/translib/translib.go @@ -22,7 +22,7 @@ Package translib implements APIs like Create, Get, Subscribe etc. to be consumed by the north bound management server implementations -This package take care of translating the incoming requests to +This package take care of translating the incoming requests to Redis ABNF format and persisting them in the Redis DB. @@ -34,12 +34,13 @@ This package can also talk to non-DB clients. package translib import ( - //"errors" - "sync" - "translib/db" - "github.com/Workiva/go-datastructures/queue" - log "github.com/golang/glog" - "translib/tlerr" + //"errors" + "sync" + "translib/db" + "translib/tlerr" + + "github.com/Workiva/go-datastructures/queue" + log "github.com/golang/glog" ) //Write lock for all write operations to be synchronized @@ -51,92 +52,102 @@ var maxSubsInterval = 60 type ErrSource int -const( +const ( ProtoErr ErrSource = iota AppErr ) -type SetRequest struct{ - Path string - Payload []byte +type SetRequest struct { + Path string + Payload []byte +} + +type SetResponse struct { + ErrSrc ErrSource } -type SetResponse struct{ - ErrSrc ErrSource +type GetRequest struct { + Path string } -type GetRequest struct{ - Path string +type GetResponse struct { + Payload []byte + ErrSrc ErrSource } -type GetResponse struct{ - Payload []byte - ErrSrc ErrSource +type ActionRequest struct { + Path string + Payload []byte } -type SubscribeResponse struct{ - Path string +type ActionResponse struct { + Payload []byte + ErrSrc ErrSource +} + +type SubscribeResponse struct { + Path string Payload []byte - Timestamp int64 + Timestamp int64 SyncComplete bool IsTerminated bool } type NotificationType int -const( - Sample NotificationType = iota - OnChange +const ( + Sample NotificationType = iota + OnChange ) -type IsSubscribeResponse struct{ - Path string - IsOnChangeSupported bool - MinInterval int - Err error - PreferredType NotificationType +type IsSubscribeResponse struct { + Path string + IsOnChangeSupported bool + MinInterval int + Err error + PreferredType NotificationType } -type ModelData struct{ - Name string - Org string - Ver string +type ModelData struct { + Name string + Org string + Ver string } type notificationOpts struct { - mInterval int - pType NotificationType // for TARGET_DEFINED + mInterval int + pType NotificationType // for TARGET_DEFINED } //initializes logging and app modules func init() { - log.Flush() + log.Flush() } //Creates entries in the redis DB pertaining to the path and payload -func Create(req SetRequest) (SetResponse, error){ +func Create(req SetRequest) (SetResponse, error) { var keys []db.WatchKeys var resp SetResponse - path := req.Path - payload := req.Payload + path := req.Path + payload := req.Payload - log.Info("Create request received with path =", path) - log.Info("Create request received with payload =", string(payload)) + log.Info("Create request received with path =", path) + log.Info("Create request received with payload =", string(payload)) app, appInfo, err := getAppModule(path) if err != nil { resp.ErrSrc = ProtoErr - return resp, err + return resp, err } - err = appInitialize(app, appInfo, path, &payload, CREATE) + err = appInitialize(app, appInfo, path, &payload, CREATE) - if err != nil { + if err != nil { resp.ErrSrc = AppErr return resp, err - } + } writeMutex.Lock() defer writeMutex.Unlock() @@ -150,329 +161,376 @@ func Create(req SetRequest) (SetResponse, error){ defer d.DeleteDB() - keys, err = (*app).translateCreate(d) + keys, err = (*app).translateCreate(d) if err != nil { resp.ErrSrc = AppErr - return resp, err + return resp, err } err = d.StartTx(keys, appInfo.tablesToWatch) if err != nil { resp.ErrSrc = AppErr - return resp, err + return resp, err } - resp, err = (*app).processCreate (d) + resp, err = (*app).processCreate(d) - if err != nil { + if err != nil { d.AbortTx() resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } err = d.CommitTx() - if err != nil { - resp.ErrSrc = AppErr - } + if err != nil { + resp.ErrSrc = AppErr + } - return resp, err + return resp, err } //Updates entries in the redis DB pertaining to the path and payload -func Update(req SetRequest) (SetResponse, error){ - var keys []db.WatchKeys +func Update(req SetRequest) (SetResponse, error) { + var keys []db.WatchKeys var resp SetResponse - path := req.Path - payload := req.Payload + path := req.Path + payload := req.Payload - log.Info("Update request received with path =", path) - log.Info("Update request received with payload =", string(payload)) + log.Info("Update request received with path =", path) + log.Info("Update request received with payload =", string(payload)) app, appInfo, err := getAppModule(path) - if err != nil { + if err != nil { resp.ErrSrc = ProtoErr - return resp, err - } + return resp, err + } - err = appInitialize(app, appInfo, path, &payload, UPDATE) + err = appInitialize(app, appInfo, path, &payload, UPDATE) - if err != nil { - resp.ErrSrc = AppErr - return resp, err - } + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } - writeMutex.Lock() + writeMutex.Lock() defer writeMutex.Unlock() - d, err := db.NewDB(getDBOptions(db.ConfigDB)) + d, err := db.NewDB(getDBOptions(db.ConfigDB)) - if err != nil { - resp.ErrSrc = ProtoErr - return resp, err - } + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } defer d.DeleteDB() - keys, err = (*app).translateUpdate(d) + keys, err = (*app).translateUpdate(d) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.StartTx(keys, appInfo.tablesToWatch) + err = d.StartTx(keys, appInfo.tablesToWatch) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - resp, err = (*app).processUpdate (d) + resp, err = (*app).processUpdate(d) - if err != nil { - d.AbortTx() + if err != nil { + d.AbortTx() resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.CommitTx() + err = d.CommitTx() - if err != nil { - resp.ErrSrc = AppErr - } + if err != nil { + resp.ErrSrc = AppErr + } - return resp, err + return resp, err } //Replaces entries in the redis DB pertaining to the path and payload -func Replace(req SetRequest) (SetResponse, error){ - var err error - var keys []db.WatchKeys +func Replace(req SetRequest) (SetResponse, error) { + var err error + var keys []db.WatchKeys var resp SetResponse - path := req.Path - payload := req.Payload + path := req.Path + payload := req.Payload - log.Info("Replace request received with path =", path) - log.Info("Replace request received with payload =", string(payload)) + log.Info("Replace request received with path =", path) + log.Info("Replace request received with payload =", string(payload)) app, appInfo, err := getAppModule(path) - if err != nil { + if err != nil { resp.ErrSrc = ProtoErr - return resp, err - } + return resp, err + } - err = appInitialize(app, appInfo, path, &payload, REPLACE) + err = appInitialize(app, appInfo, path, &payload, REPLACE) - if err != nil { - resp.ErrSrc = AppErr - return resp, err - } + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } - writeMutex.Lock() + writeMutex.Lock() defer writeMutex.Unlock() - d, err := db.NewDB(getDBOptions(db.ConfigDB)) + d, err := db.NewDB(getDBOptions(db.ConfigDB)) - if err != nil { - resp.ErrSrc = ProtoErr - return resp, err - } + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } defer d.DeleteDB() - keys, err = (*app).translateReplace(d) + keys, err = (*app).translateReplace(d) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.StartTx(keys, appInfo.tablesToWatch) + err = d.StartTx(keys, appInfo.tablesToWatch) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - resp, err = (*app).processReplace (d) + resp, err = (*app).processReplace(d) - if err != nil { - d.AbortTx() + if err != nil { + d.AbortTx() resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.CommitTx() + err = d.CommitTx() if err != nil { resp.ErrSrc = AppErr - } + } - return resp, err + return resp, err } //Deletes entries in the redis DB pertaining to the path -func Delete(req SetRequest) (SetResponse, error){ - var err error - var keys []db.WatchKeys +func Delete(req SetRequest) (SetResponse, error) { + var err error + var keys []db.WatchKeys var resp SetResponse - path := req.Path + path := req.Path - log.Info("Delete request received with path =", path) + log.Info("Delete request received with path =", path) app, appInfo, err := getAppModule(path) - if err != nil { + if err != nil { resp.ErrSrc = ProtoErr - return resp, err - } + return resp, err + } - err = appInitialize(app, appInfo, path, nil, DELETE) + err = appInitialize(app, appInfo, path, nil, DELETE) - if err != nil { - resp.ErrSrc = AppErr - return resp, err - } + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } - writeMutex.Lock() + writeMutex.Lock() defer writeMutex.Unlock() - d, err := db.NewDB(getDBOptions(db.ConfigDB)) + d, err := db.NewDB(getDBOptions(db.ConfigDB)) - if err != nil { - resp.ErrSrc = ProtoErr - return resp, err - } + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } defer d.DeleteDB() - keys, err = (*app).translateDelete(d) + keys, err = (*app).translateDelete(d) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.StartTx(keys, appInfo.tablesToWatch) + err = d.StartTx(keys, appInfo.tablesToWatch) - if err != nil { + if err != nil { resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - resp, err = (*app).processDelete(d) + resp, err = (*app).processDelete(d) - if err != nil { - d.AbortTx() + if err != nil { + d.AbortTx() resp.ErrSrc = AppErr - return resp, err - } + return resp, err + } - err = d.CommitTx() + err = d.CommitTx() - if err != nil { - resp.ErrSrc = AppErr - } + if err != nil { + resp.ErrSrc = AppErr + } return resp, err } //Gets data from the redis DB and converts it to northbound format -func Get(req GetRequest) (GetResponse, error){ - var payload []byte +func Get(req GetRequest) (GetResponse, error) { + var payload []byte var resp GetResponse path := req.Path - log.Info("Received Get request for path = ",path) + log.Info("Received Get request for path = ", path) app, appInfo, err := getAppModule(path) - if err != nil { - resp = GetResponse{Payload:payload, ErrSrc:ProtoErr} - return resp, err - } + if err != nil { + resp = GetResponse{Payload: payload, ErrSrc: ProtoErr} + return resp, err + } err = appInitialize(app, appInfo, path, nil, GET) - if err != nil { - resp = GetResponse{Payload:payload, ErrSrc:AppErr} + if err != nil { + resp = GetResponse{Payload: payload, ErrSrc: AppErr} return resp, err } dbs, err := getAllDbs() if err != nil { - resp = GetResponse{Payload:payload, ErrSrc:ProtoErr} - return resp, err + resp = GetResponse{Payload: payload, ErrSrc: ProtoErr} + return resp, err } defer closeAllDbs(dbs[:]) - err = (*app).translateGet (dbs) + err = (*app).translateGet(dbs) if err != nil { - resp = GetResponse{Payload:payload, ErrSrc:AppErr} - return resp, err + resp = GetResponse{Payload: payload, ErrSrc: AppErr} + return resp, err } - resp, err = (*app).processGet(dbs) + resp, err = (*app).processGet(dbs) - return resp, err + return resp, err +} + +func Action(req ActionRequest) (ActionResponse, error) { + var payload []byte + var resp ActionResponse + + path := req.Path + + log.Info("Received Action request for path = ", path) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp = ActionResponse{Payload: payload, ErrSrc: ProtoErr} + return resp, err + } + + aInfo := *appInfo + + aInfo.isNative = true + + err = appInitialize(app, &aInfo, path, &req.Payload, GET) + + if err != nil { + resp = ActionResponse{Payload: payload, ErrSrc: AppErr} + return resp, err + } + + dbs, err := getAllDbs() + + if err != nil { + resp = ActionResponse{Payload: payload, ErrSrc: ProtoErr} + return resp, err + } + + defer closeAllDbs(dbs[:]) + + err = (*app).translateAction(dbs) + + if err != nil { + resp = ActionResponse{Payload: payload, ErrSrc: AppErr} + return resp, err + } + + resp, err = (*app).processAction(dbs) + + return resp, err } //Subscribes to the paths requested and sends notifications when the data changes in DB func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*IsSubscribeResponse, error) { - var err error + var err error var sErr error //err = errors.New("Not implemented") dbNotificationMap := make(map[db.DBNum][]*notificationInfo) - resp := make ([]*IsSubscribeResponse, len(paths)) + resp := make([]*IsSubscribeResponse, len(paths)) - for i, _ := range resp { - resp[i] = &IsSubscribeResponse{Path: paths[i], - IsOnChangeSupported: false, - MinInterval: minSubsInterval, - PreferredType:Sample, - Err:nil} - } + for i, _ := range resp { + resp[i] = &IsSubscribeResponse{Path: paths[i], + IsOnChangeSupported: false, + MinInterval: minSubsInterval, + PreferredType: Sample, + Err: nil} + } dbs, err := getAllDbs() - if err != nil { - return resp, err - } + if err != nil { + return resp, err + } //Do NOT close the DBs here as we need to use them during subscribe notification - for i, path := range paths { + for i, path := range paths { app, appInfo, err := getAppModule(path) - if err != nil { + if err != nil { - if sErr == nil { + if sErr == nil { sErr = err } resp[i].Err = err continue - } + } - nOpts, nInfo, errApp := (*app).translateSubscribe (dbs, path) + nOpts, nInfo, errApp := (*app).translateSubscribe(dbs, path) - if errApp != nil { - resp[i].Err = errApp + if errApp != nil { + resp[i].Err = errApp if sErr == nil { sErr = errApp @@ -480,23 +538,23 @@ func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*I resp[i].MinInterval = maxSubsInterval - if nOpts != nil { - if nOpts.mInterval != 0 { - resp[i].MinInterval = nOpts.mInterval - } + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } - resp[i].PreferredType = nOpts.pType - } + resp[i].PreferredType = nOpts.pType + } - continue - } else { + continue + } else { if nOpts != nil { if nOpts.mInterval != 0 { resp[i].MinInterval = nOpts.mInterval } - resp[i].PreferredType = nOpts.pType + resp[i].PreferredType = nOpts.pType } if nInfo == nil { @@ -506,7 +564,7 @@ func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*I continue } - resp[i].IsOnChangeSupported = true + resp[i].IsOnChangeSupported = true nInfo.path = path nInfo.app = app @@ -514,7 +572,7 @@ func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*I nInfo.dbs = dbs dbNotificationMap[nInfo.dbno] = append(dbNotificationMap[nInfo.dbno], nInfo) - } + } } @@ -524,9 +582,9 @@ func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*I return resp, sErr } - sInfo := &subscribeInfo {syncDone:false, - q:q, - stop:stop} + sInfo := &subscribeInfo{syncDone: false, + q: q, + stop: stop} sErr = startSubscribe(sInfo, dbNotificationMap) @@ -536,21 +594,21 @@ func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*I //Check if subscribe is supported on the given paths func IsSubscribeSupported(paths []string) ([]*IsSubscribeResponse, error) { - resp := make ([]*IsSubscribeResponse, len(paths)) + resp := make([]*IsSubscribeResponse, len(paths)) for i, _ := range resp { - resp[i] = &IsSubscribeResponse{Path: paths[i], - IsOnChangeSupported: false, - MinInterval: minSubsInterval, - PreferredType:Sample, - Err:nil} + resp[i] = &IsSubscribeResponse{Path: paths[i], + IsOnChangeSupported: false, + MinInterval: minSubsInterval, + PreferredType: Sample, + Err: nil} } dbs, err := getAllDbs() - if err != nil { - return resp, err - } + if err != nil { + return resp, err + } defer closeAllDbs(dbs[:]) @@ -563,14 +621,14 @@ func IsSubscribeSupported(paths []string) ([]*IsSubscribeResponse, error) { continue } - nOpts, _, errApp := (*app).translateSubscribe (dbs, path) + nOpts, _, errApp := (*app).translateSubscribe(dbs, path) if errApp != nil { resp[i].Err = errApp err = errApp - continue - } else { - resp[i].IsOnChangeSupported= true + continue + } else { + resp[i].IsOnChangeSupported = true if nOpts != nil { if nOpts.mInterval != 0 { @@ -594,18 +652,18 @@ func GetModels() ([]ModelData, error) { //Creates connection will all the redis DBs. To be used for get request func getAllDbs() ([db.MaxDB]*db.DB, error) { var dbs [db.MaxDB]*db.DB - var err error + var err error //Create Application DB connection - dbs[db.ApplDB], err = db.NewDB(getDBOptions(db.ApplDB)) + dbs[db.ApplDB], err = db.NewDB(getDBOptions(db.ApplDB)) if err != nil { closeAllDbs(dbs[:]) return dbs, err } - //Create ASIC DB connection - dbs[db.AsicDB], err = db.NewDB(getDBOptions(db.AsicDB)) + //Create ASIC DB connection + dbs[db.AsicDB], err = db.NewDB(getDBOptions(db.AsicDB)) if err != nil { closeAllDbs(dbs[:]) @@ -613,7 +671,7 @@ func getAllDbs() ([db.MaxDB]*db.DB, error) { } //Create Counter DB connection - dbs[db.CountersDB], err = db.NewDB(getDBOptions(db.CountersDB)) + dbs[db.CountersDB], err = db.NewDB(getDBOptions(db.CountersDB)) if err != nil { closeAllDbs(dbs[:]) @@ -621,7 +679,7 @@ func getAllDbs() ([db.MaxDB]*db.DB, error) { } //Create Log Level DB connection - dbs[db.LogLevelDB], err = db.NewDB(getDBOptions(db.LogLevelDB)) + dbs[db.LogLevelDB], err = db.NewDB(getDBOptions(db.LogLevelDB)) if err != nil { closeAllDbs(dbs[:]) @@ -629,7 +687,7 @@ func getAllDbs() ([db.MaxDB]*db.DB, error) { } //Create Config DB connection - dbs[db.ConfigDB], err = db.NewDB(getDBOptions(db.ConfigDB)) + dbs[db.ConfigDB], err = db.NewDB(getDBOptions(db.ConfigDB)) if err != nil { closeAllDbs(dbs[:]) @@ -637,7 +695,7 @@ func getAllDbs() ([db.MaxDB]*db.DB, error) { } //Create Flex Counter DB connection - dbs[db.FlexCounterDB], err = db.NewDB(getDBOptions(db.FlexCounterDB)) + dbs[db.FlexCounterDB], err = db.NewDB(getDBOptions(db.FlexCounterDB)) if err != nil { closeAllDbs(dbs[:]) @@ -645,7 +703,7 @@ func getAllDbs() ([db.MaxDB]*db.DB, error) { } //Create State DB connection - dbs[db.StateDB], err = db.NewDB(getDBOptions(db.StateDB)) + dbs[db.StateDB], err = db.NewDB(getDBOptions(db.StateDB)) if err != nil { closeAllDbs(dbs[:]) @@ -692,54 +750,54 @@ func getDBOptions(dbNo db.DBNum) db.Options { } func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparator string, keySeparator string) db.Options { - return(db.Options { - DBNo : dbNo, - InitIndicator : initIndicator, - TableNameSeparator: tableSeparator, - KeySeparator : keySeparator, - }) + return (db.Options{ + DBNo: dbNo, + InitIndicator: initIndicator, + TableNameSeparator: tableSeparator, + KeySeparator: keySeparator, + }) } -func getAppModule (path string) (*appInterface, *appInfo, error) { +func getAppModule(path string) (*appInterface, *appInfo, error) { var app appInterface - aInfo, err := getAppModuleInfo(path) + aInfo, err := getAppModuleInfo(path) - if err != nil { - return nil, aInfo, err - } + if err != nil { + return nil, aInfo, err + } - app, err = getAppInterface(aInfo.appType) + app, err = getAppInterface(aInfo.appType) - if err != nil { - return nil, aInfo, err - } + if err != nil { + return nil, aInfo, err + } return &app, aInfo, err } -func appInitialize (app *appInterface, appInfo *appInfo, path string, payload *[]byte, opCode int) error { - var err error - var input []byte - - if payload != nil { - input = *payload - } - - if appInfo.isNative { - log.Info("Native MSFT format") - data := appData{path: path, payload: input} - (*app).initialize(data) - } else { - ygotStruct, ygotTarget, err := getRequestBinder (&path, payload, opCode, &(appInfo.ygotRootType)).unMarshall() - if err != nil { - log.Info("Error in request binding: ", err) - return err - } - - data := appData{path: path, payload: input, ygotRoot: ygotStruct, ygotTarget: ygotTarget} - (*app).initialize(data) - } - - return err +func appInitialize(app *appInterface, appInfo *appInfo, path string, payload *[]byte, opCode int) error { + var err error + var input []byte + + if payload != nil { + input = *payload + } + + if appInfo.isNative { + log.Info("Native MSFT format") + data := appData{path: path, payload: input} + (*app).initialize(data) + } else { + ygotStruct, ygotTarget, err := getRequestBinder(&path, payload, opCode, &(appInfo.ygotRootType)).unMarshall() + if err != nil { + log.Info("Error in request binding: ", err) + return err + } + + data := appData{path: path, payload: input, ygotRoot: ygotStruct, ygotTarget: ygotTarget} + (*app).initialize(data) + } + + return err } diff --git a/tools/pyang/pyang_plugins/openapi.py b/tools/pyang/pyang_plugins/openapi.py index 45a407d88d..4a2d62af60 100644 --- a/tools/pyang/pyang_plugins/openapi.py +++ b/tools/pyang/pyang_plugins/openapi.py @@ -27,6 +27,7 @@ from collections import OrderedDict import copy import os +import mmh3 # globals codegenTypesToYangTypesMap = {"int8": {"type":"integer", "format": "int32"}, @@ -53,7 +54,8 @@ XpathToBodyTagDict = OrderedDict() keysToLeafRefObjSet = set() currentTag = None -base_path = '/restconf/data' +errorList = [] +warnList = [] verbs = ["post", "put", "patch", "get", "delete"] responses = { # Common to all verbs "500": {"description": "Internal Server Error"}, @@ -63,6 +65,11 @@ "415": {"description": "Unsupported Media Type"}, } verb_responses = {} +verb_responses["rpc"] = { + "204": {"description": "No Content"}, + "404": {"description": "Not Found"}, + "403": {"description": "Forbidden"}, +} verb_responses["post"] = { "201": {"description": "Created"}, "409": {"description": "Conflict"}, @@ -112,7 +119,6 @@ def _dict_representer(dumper, data): swaggerDict["info"]["description"] = "Network management Open APIs for Broadcom's Sonic." swaggerDict["info"]["version"] = "1.0.0" swaggerDict["info"]["title"] = "SONiC Network Management APIs" -swaggerDict["basePath"] = base_path swaggerDict["schemes"] = ["https", "http"] swagger_tags = [] swaggerDict["tags"] = swagger_tags @@ -138,7 +144,6 @@ def resetSwaggerDict(): swaggerDict["info"]["description"] = "Network management Open APIs for Sonic." swaggerDict["info"]["version"] = "1.0.0" swaggerDict["info"]["title"] = "Sonic Network Management APIs" - swaggerDict["basePath"] = base_path swaggerDict["schemes"] = ["https", "http"] swagger_tags = [] currentTag = None @@ -170,6 +175,8 @@ def setup_fmt(self, ctx): def emit(self, ctx, modules, fd): global currentTag + global errorList + global warnList if ctx.opts.outdir is None: print("[Error]: Output directory is not mentioned") @@ -189,8 +196,8 @@ def emit(self, ctx, modules, fd): # delete root '/' as we dont support it. if len(swaggerDict["paths"]) > 0: - if "/" in swaggerDict["paths"]: - del(swaggerDict["paths"]["/"]) + if "/restconf/data/" in swaggerDict["paths"]: + del(swaggerDict["paths"]["/restconf/data/"]) if len(swaggerDict["paths"]) <= 0: continue @@ -213,6 +220,18 @@ def emit(self, ctx, modules, fd): else: with open(ctx.opts.outdir + '/' + module.i_modulename + ".yaml", "w") as spec: spec.write(ordered_dump(swaggerDict, Dumper=yaml.SafeDumper)) + + if len(warnList) > 0: + print("========= Warnings observed =======") + for warn in warnList: + print(warn) + + if len(errorList) > 0: + print("========= Errors observed =======") + for err in errorList: + print(err) + print("========= Exiting due to above Errors =======") + sys.exit(2) def walk_module(module): for child in module.i_children: @@ -239,6 +258,7 @@ def swagger_it(child, defName, pathstr, payload, metadata, verb, operId=False): if not verbPathStr.startswith("/"): verbPathStr = "/" + verbPathStr + verbPathStr = "/restconf/data" + verbPathStr if verbPathStr not in swaggerDict["paths"]: swaggerDict["paths"][verbPathStr] = OrderedDict() @@ -316,8 +336,78 @@ def swagger_it(child, defName, pathstr, payload, metadata, verb, operId=False): verbPath["responses"]["200"]["schema"] = OrderedDict() verbPath["responses"]["200"]["schema"]["$ref"] = "#/definitions/" + defName +def handle_rpc(child, actXpath, pathstr): + global currentTag + verbPathStr = "/restconf/operations" + pathstr + verb = "post" + customName = getOpId(child) + DefName = shortenNodeName(child, customName) + opId = "rpc_" + DefName + add_swagger_tag(child.i_module) + + # build input payload + input_payload = OrderedDict() + input_child = child.search_one('input', None, child.i_children) + if input_child is None: + print("There is no input node for RPC ", "Xpath: ", actXpath) + build_payload(input_child, input_payload, pathstr, True, actXpath, True) + input_Defn = "rpc_input_" + DefName + swaggerDict["definitions"][input_Defn] = OrderedDict() + swaggerDict["definitions"][input_Defn]["type"] = "object" + swaggerDict["definitions"][input_Defn]["properties"] = copy.deepcopy(input_payload) + + # build output payload + output_payload = OrderedDict() + output_child = child.search_one('output', None, child.i_children) + if output_child is None: + print("There is no output node for RPC ", "Xpath: ", actXpath) + build_payload(output_child, output_payload, pathstr, True, actXpath, True) + output_Defn = "rpc_output_" + DefName + swaggerDict["definitions"][output_Defn] = OrderedDict() + swaggerDict["definitions"][output_Defn]["type"] = "object" + swaggerDict["definitions"][output_Defn]["properties"] = copy.deepcopy(output_payload) + + if verbPathStr not in swaggerDict["paths"]: + swaggerDict["paths"][verbPathStr] = OrderedDict() + + swaggerDict["paths"][verbPathStr][verb] = OrderedDict() + swaggerDict["paths"][verbPathStr][verb]["tags"] = [currentTag] + + # Set Operation ID + swaggerDict["paths"][verbPathStr][verb]["operationId"] = opId + + # Set Description + desc = child.search_one('description') + if desc is None: + desc = '' + else: + desc = desc.arg + desc = "OperationId: " + opId + "\n" + desc + swaggerDict["paths"][verbPathStr][verb]["description"] = desc + verbPath = swaggerDict["paths"][verbPathStr][verb] + + # Request payload + if len(input_payload[child.i_module.i_modulename + ':input']['properties']) > 0: + verbPath["parameters"] = [] + verbPath["consumes"] = ["application/yang-data+json"] + bodyTag = OrderedDict() + bodyTag["in"] = "body" + bodyTag["name"] = "body" + bodyTag["required"] = True + bodyTag["schema"] = OrderedDict() + bodyTag["schema"]["$ref"] = "#/definitions/" + input_Defn + verbPath["parameters"].append(bodyTag) + + # Response payload + verbPath["responses"] = copy.deepcopy(merge_two_dicts(responses, verb_responses["rpc"])) + if len(output_payload[child.i_module.i_modulename + ':output']['properties']) > 0: + verbPath["produces"] = ["application/yang-data+json"] + verbPath["responses"]["204"]["schema"] = OrderedDict() + verbPath["responses"]["204"]["schema"]["$ref"] = "#/definitions/" + output_Defn + def walk_child(child): global XpathToBodyTagDict + customName = None actXpath = statements.mk_path_str(child, True) metadata = [] @@ -327,6 +417,11 @@ def walk_child(child): if actXpath in keysToLeafRefObjSet: return + if child.keyword == "rpc": + add_swagger_tag(child.i_module) + handle_rpc(child, actXpath, pathstr) + return + if child.keyword in ["list", "container", "leaf", "leaf-list"]: payload = OrderedDict() @@ -344,7 +439,8 @@ def walk_child(child): keysToLeafRefObjSet.add(listKeyPath) return - defName = shortenNodeName(child) + customName = getOpId(child) + defName = shortenNodeName(child, customName) if child.i_config == False: payload_get = OrderedDict() @@ -432,7 +528,8 @@ def walk_child_for_list_base(child, actXpath, pathstr, metadata, nonBaseDefName= if len(payload) == 0 and child.i_config == True: return - defName = shortenNodeName(child) + customName = getOpId(child) + defName = shortenNodeName(child, customName) defName = "list"+'_'+defName if child.i_config == False: @@ -588,6 +685,17 @@ def build_payload(child, payloadDict, uriPath="", oneInstance=False, Xpath="", f elif child.keyword == "choice" or child.keyword == "case": childJson = payloadDict + + elif child.keyword == "input" or child.keyword == "output": + if firstCall: + nodeName = child.i_module.i_modulename + ':' + child.keyword + else: + nodeName = child.keyword + + payloadDict[nodeName] = OrderedDict() + payloadDict[nodeName]["type"] = "object" + payloadDict[nodeName]["properties"] = OrderedDict() + childJson = payloadDict[nodeName]["properties"] if hasattr(child, 'i_children'): for ch in child.i_children: @@ -689,20 +797,50 @@ def handle_leafref(node,xpath): print("leafref not pointing to leaf/leaflist") sys.exit(2) -def shortenNodeName(node): +def getOpId(node): + name = None + for substmt in node.substmts: + if substmt.keyword.__class__.__name__ == 'tuple': + if substmt.keyword[0] == 'sonic-extensions': + if substmt.keyword[1] == 'openapi-opid': + name = substmt.arg + return name + +def shortenNodeName(node, overridenName=None): global nodeDict + global errorList + global warnList + xpath = statements.mk_path_str(node, False) - name = node.i_module.i_modulename + xpath.replace('/','_') + xpath_prefix = statements.mk_path_str(node, True) + if overridenName is None: + name = node.i_module.i_modulename + xpath.replace('/','_') + else: + name = overridenName + name = name.replace('-','_').lower() if name not in nodeDict: nodeDict[name] = xpath else: - while name in nodeDict: - if xpath == nodeDict[name]: - break - name = node.i_module.i_modulename + '_' + name - name = name.replace('-','_').lower() - nodeDict[name] = xpath + if overridenName is None: + while name in nodeDict: + if xpath == nodeDict[name]: + break + name = node.i_module.i_modulename + '_' + name + name = name.replace('-','_').lower() + nodeDict[name] = xpath + else: + if xpath != nodeDict[name]: + print("[Name collision] at ", xpath, " name: ", name, " is used, override using openapi-opid annotation") + sys.exit(2) + if len(name) > 150: + if overridenName is None: + # Generate unique hash + mmhash = mmh3.hash(name, signed=False) + name = node.i_module.i_modulename + '_' + str(mmhash) + warnList.append("[Warn]: Using autogenerated shortened OperId for " + str(xpath_prefix) + " please provide unique manual input through openapi-opid annotation using deviation file if you want to override") + if len(name) > 150: + errorList.append("[Error: ] OpID is too big for " + str(xpath_prefix) +" please provide unique manual input through openapi-opid annotation using deviation file") return name def getCamelForm(moName): @@ -829,4 +967,3 @@ def _iterate(stmt): result = True return result - diff --git a/ygot-modified-files/list.go b/ygot-modified-files/list.go index 62d0460301..ca462f2266 100644 --- a/ygot-modified-files/list.go +++ b/ygot-modified-files/list.go @@ -217,6 +217,9 @@ func validateListSchema(schema *yang.Entry) error { if len(schema.Key) == 0 { return fmt.Errorf("list %s with config set must have a key", schema.Name) } + if schema.IsSchemaValidated == true { + return nil + } keys := strings.Split(schema.Key, " ") keysMissing := make(map[string]bool) for _, v := range keys { @@ -232,6 +235,7 @@ func validateListSchema(schema *yang.Entry) error { } } + schema.IsSchemaValidated = true return nil } @@ -282,10 +286,10 @@ func unmarshalList(schema *yang.Entry, parent interface{}, jsonList interface{}, if util.IsValueNil(jsonList) { return nil } - // Check that the schema itself is valid. + if err := validateListSchema(schema); err != nil { return err - } + } util.DbgPrint("unmarshalList jsonList %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(jsonList), jsonList, parent, schema.Name) @@ -413,7 +417,7 @@ func makeValForInsert(schema *yang.Entry, parent interface{}, keys map[string]st } var nv reflect.Value - if keyLeafKind == yang.Yunion { + if keyLeafKind == yang.Yunion && strings.HasSuffix(keyT.Name(), "_Union") { sks, err := getUnionKindsNotEnums(cschema) if err != nil { return err diff --git a/ygot-modified-files/node.go b/ygot-modified-files/node.go index 9e0af202e8..fceb6f8243 100644 --- a/ygot-modified-files/node.go +++ b/ygot-modified-files/node.go @@ -15,14 +15,13 @@ package ytypes import ( - "reflect" - "github.com/golang/protobuf/proto" "github.com/openconfig/goyang/pkg/yang" "github.com/openconfig/ygot/util" "github.com/openconfig/ygot/ygot" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "reflect" gpb "github.com/openconfig/gnmi/proto/gnmi" ) @@ -129,6 +128,16 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path, if err := util.InitializeStructField(root, ft.Name); err != nil { return nil, status.Errorf(codes.Unknown, "failed to initialize struct field %s in %T, child schema %v, path %v", ft.Name, root, cschema, path) } + + if cschema.IsLeaf() || cschema.IsLeafList() { + if len(path.Elem) == 1 && len(path.Elem[0].Key) == 1 { + var vals []string + vals = append(vals, path.Elem[0].Key[path.Elem[0].Name]) + if args.val, err = ygot.EncodeTypedValue(vals, gpb.Encoding_JSON_IETF); err != nil { + return nil, status.Errorf(codes.Unknown, "failed to get the typed value '%v' for leaf/leaf-list => %s in %T ; because of %v", vals, ft.Name, root, err) + } + } + } } // If val in args is set to a non-nil value and the path is exhausted, we diff --git a/ygot-modified-files/schema.go b/ygot-modified-files/schema.go index 3ad033ef71..a5ada8249a 100644 --- a/ygot-modified-files/schema.go +++ b/ygot-modified-files/schema.go @@ -22,6 +22,8 @@ import ( "github.com/openconfig/goyang/pkg/yang" ) +var schemaPathCache map[reflect.StructTag][][]string = make(map[reflect.StructTag][][]string) + // IsLeafRef reports whether schema is a leafref schema node type. func IsLeafRef(schema *yang.Entry) bool { if schema == nil || schema.Type == nil { @@ -68,17 +70,22 @@ func IsYgotAnnotation(s reflect.StructField) bool { // SchemaPaths returns all the paths in the path tag. func SchemaPaths(f reflect.StructField) ([][]string, error) { - var out [][]string - pathTag, ok := f.Tag.Lookup("path") - if !ok || pathTag == "" { - return nil, fmt.Errorf("field %s did not specify a path", f.Name) - } - - ps := strings.Split(pathTag, "|") - for _, p := range ps { - out = append(out, StripModulePrefixes(strings.Split(p, "/"))) + if tmpOut, ok := schemaPathCache[f.Tag]; ok { + return tmpOut, nil + } else { + var out [][]string + pathTag, ok := f.Tag.Lookup("path") + if !ok || pathTag == "" { + return nil, fmt.Errorf("field %s did not specify a path", f.Name) + } + + ps := strings.Split(pathTag, "|") + for _, p := range ps { + out = append(out, StripModulePrefixes(strings.Split(p, "/"))) + } + schemaPathCache[f.Tag] = out + return out, nil } - return out, nil } // ChildSchema returns the first child schema that matches path from the given diff --git a/ygot-modified-files/string_type.go b/ygot-modified-files/string_type.go new file mode 100644 index 0000000000..32b0d2ec20 --- /dev/null +++ b/ygot-modified-files/string_type.go @@ -0,0 +1,199 @@ +// Copyright 2017 Google 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 ytypes + +import ( + "bytes" + "fmt" + "regexp" + "unicode/utf8" + + "github.com/openconfig/goyang/pkg/yang" +) + +var regexpCache map[string]*regexp.Regexp = make(map[string]*regexp.Regexp) + +// Refer to: https://tools.ietf.org/html/rfc6020#section-9.4. + +// validateString validates value, which must be a Go string type, against the +// given schema. +func validateString(schema *yang.Entry, value interface{}) error { + // Check that the schema itself is valid. + if err := validateStringSchema(schema); err != nil { + return err + } + + // Check that type of value is the type expected from the schema. + stringVal, ok := value.(string) + if !ok { + return fmt.Errorf("non string type %T with value %v for schema %s", value, value, schema.Name) + } + + // Check that the length is within the allowed range. + allowedRanges := schema.Type.Length + strLen := uint64(utf8.RuneCountInString(stringVal)) + if !lengthOk(allowedRanges, strLen) { + return fmt.Errorf("length %d is outside range %v for schema %s", strLen, allowedRanges, schema.Name) + } + + // Check that the value satisfies any regex patterns. + for _, p := range schema.Type.Pattern { + var r *regexp.Regexp + if val, ok := regexpCache[p]; ok { + r = val + } else { + var err error + r, err = regexp.Compile(fixYangRegexp(p)) + if err != nil { + return err + } + regexpCache[p] = r + } + + // fixYangRegexp adds ^(...)$ around the pattern - the result is + // equivalent to a full match of whole string. + if !r.MatchString(stringVal) { + return fmt.Errorf("%q does not match regular expression pattern %q for schema %s", stringVal, r, schema.Name) + } + } + + return nil +} + +// validateStringSlice validates value, which must be a Go string slice type, +// against the given schema. +func validateStringSlice(schema *yang.Entry, value interface{}) error { + // Check that the schema itself is valid. + if err := validateStringSchema(schema); err != nil { + return err + } + + // Check that type of value is the type expected from the schema. + slice, ok := value.([]string) + if !ok { + return fmt.Errorf("non []string type %T with value %v for schema %s", value, value, schema.Name) + } + + // Each slice element must be valid and unique. + tbl := make(map[string]bool, len(slice)) + for i, val := range slice { + if err := validateString(schema, val); err != nil { + return fmt.Errorf("invalid element at index %d: %v for schema %s", i, err, schema.Name) + } + if tbl[val] { + return fmt.Errorf("duplicate string: %q for schema %s", val, schema.Name) + } + tbl[val] = true + } + return nil +} + +// validateStringSchema validates the given string type schema. This is a sanity +// check validation rather than a comprehensive validation against the RFC. +// It is assumed that such a validation is done when the schema is parsed from +// source YANG. +func validateStringSchema(schema *yang.Entry) error { + if schema == nil { + return fmt.Errorf("string schema is nil") + } + if schema.Type == nil { + return fmt.Errorf("string schema %s Type is nil", schema.Name) + } + if schema.Type.Kind != yang.Ystring { + return fmt.Errorf("string schema %s has wrong type %v", schema.Name, schema.Type.Kind) + } + + if schema.IsSchemaValidated { + return nil + } + + var err error + + for _, p := range schema.Type.Pattern { + _, ok := regexpCache[p] + if (ok == false) { + var r *regexp.Regexp + if r, err = regexp.Compile(fixYangRegexp(p)); err != nil { + return fmt.Errorf("error generating regexp %s %v for schema %s", p, err, schema.Name) + } else { + regexpCache[p] = r + } + } + } + + if err = validateLengthSchema(schema); err == nil { + schema.IsSchemaValidated = true + } + + return err +} + +// fixYangRegexp takes a pattern regular expression from a YANG module and +// returns it into a format which can be used by the Go regular expression +// library. YANG uses a W3C standard that is defined to be implicitly anchored +// at the head or tail of the expression. See +// https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#regexs for details. +func fixYangRegexp(pattern string) string { + var buf bytes.Buffer + var inEscape bool + var prevChar rune + addParens := false + + for i, ch := range pattern { + if i == 0 && ch != '^' { + buf.WriteRune('^') + // Add parens around entire expression to prevent logical + // subexpressions associating with leading/trailing ^ / $. + buf.WriteRune('(') + addParens = true + } + + switch ch { + case '$': + // Dollar signs need to be escaped unless they are at + // the end of the pattern, or are already escaped. + if !inEscape && i != len(pattern)-1 { + buf.WriteRune('\\') + } + case '^': + // Carets need to be escaped unless they are already + // escaped, indicating set negation ([^.*]) or at the + // start of the string. + if !inEscape && prevChar != '[' && i != 0 { + buf.WriteRune('\\') + } + } + + // If the previous character was an escape character, then we + // leave the escape, otherwise check whether this is an escape + // char and if so, then enter escape. + inEscape = !inEscape && ch == '\\' + + buf.WriteRune(ch) + + if i == len(pattern)-1 { + if addParens { + buf.WriteRune(')') + } + if ch != '$' { + buf.WriteRune('$') + } + } + + prevChar = ch + } + + return buf.String() +} diff --git a/ygot-modified-files/util_schema.go b/ygot-modified-files/util_schema.go index b12c79abb1..bd547dd19a 100644 --- a/ygot-modified-files/util_schema.go +++ b/ygot-modified-files/util_schema.go @@ -23,6 +23,8 @@ import ( "github.com/openconfig/ygot/util" ) +var pathToSchemaCache map[reflect.StructTag][]string = make(map[reflect.StructTag][]string) + // validateLengthSchema validates whether the given schema has a valid length // specification. func validateLengthSchema(schema *yang.Entry) error { @@ -137,6 +139,12 @@ func isValueScalar(v reflect.Value) bool { // if the struct tag is invalid, or nil if tag is valid but the schema is not // found in the tree at the specified path. func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { + if (schema.ChildSchemaCache == nil) { + schema.ChildSchemaCache = make(map[reflect.StructTag]*yang.Entry) + } else if cschema, ok := schema.ChildSchemaCache[f.Tag]; ok { + return cschema, nil + } + if util.IsDebugSchemaEnabled() { pathTag, _ := f.Tag.Lookup("path") util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) @@ -170,6 +178,7 @@ func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) } if foundSchema { util.DbgSchema(" - found\n") + schema.ChildSchemaCache[f.Tag] = childSchema return childSchema, nil } util.DbgSchema(" - not found\n") @@ -185,6 +194,7 @@ func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) // path element i.e. choice1/case1/leaf1 path in the schema will have // struct tag `path:"leaf1"`. This implies that only paths with length // 1 are eligible for this matching. + schema.ChildSchemaCache[f.Tag] = nil return nil, nil } entries := util.FindFirstNonChoiceOrCase(schema) @@ -196,11 +206,13 @@ func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) if util.StripModulePrefix(name) == p[0] { util.DbgSchema(" - match\n") + schema.ChildSchemaCache[f.Tag] = entry return entry, nil } } util.DbgSchema(" - no matches\n") + schema.ChildSchemaCache[f.Tag] = nil return nil, nil } @@ -242,25 +254,32 @@ func absoluteSchemaDataPath(schema *yang.Entry) string { // leafref. In the latter case, this function returns {"config", "a"}, and the // schema *yang.Entry for the field is given by schema.Dir["config"].Dir["a"]. func pathToSchema(f reflect.StructField) ([]string, error) { - pathAnnotation, ok := f.Tag.Lookup("path") - if !ok { - return nil, fmt.Errorf("field %s did not specify a path", f.Name) - } - - paths := strings.Split(pathAnnotation, "|") - if len(paths) == 1 { - pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") - return strings.Split(pathAnnotation, "/"), nil - } - for _, pv := range paths { - pv = strings.TrimPrefix(pv, "/") - pe := strings.Split(pv, "/") - if len(pe) > 1 { + if pe, ok := pathToSchemaCache[f.Tag]; ok { + return pe, nil + } else { + pathAnnotation, ok := f.Tag.Lookup("path") + if !ok { + return nil, fmt.Errorf("field %s did not specify a path", f.Name) + } + + paths := strings.Split(pathAnnotation, "|") + if len(paths) == 1 { + pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") + pe := strings.Split(pathAnnotation, "/") + pathToSchemaCache[f.Tag] = pe return pe, nil } + for _, pv := range paths { + pv = strings.TrimPrefix(pv, "/") + pe := strings.Split(pv, "/") + if len(pe) > 1 { + pathToSchemaCache[f.Tag] = pe + return pe, nil + } + } + + return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) } - - return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) } // directDescendantSchema returns the direct descendant schema for the struct