diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..900036b7c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ + +.vscode +*.code-workspace +pkg/* +build +gopkgs +__pycache__ +*.pyc +*.rdb +*.swp + +*.yin +*.tree +src/translib/ocbinds/ocbinds.go + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..77f2412f3b --- /dev/null +++ b/Makefile @@ -0,0 +1,166 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +.PHONY: all clean cleanall codegen rest-server rest-clean yamlGen cli + +TOPDIR := $(abspath .) +BUILD_DIR := $(TOPDIR)/build +export TOPDIR + +ifeq ($(BUILD_GOPATH),) +export BUILD_GOPATH=$(TOPDIR)/gopkgs +endif + +export GOPATH=$(BUILD_GOPATH):$(TOPDIR) + +ifeq ($(GO),) +GO := /usr/local/go/bin/go +export GO +endif + +INSTALL := /usr/bin/install + +MAIN_TARGET = sonic-mgmt-framework_1.0-01_amd64.deb + +GO_DEPS_LIST = github.com/gorilla/mux \ + github.com/Workiva/go-datastructures/queue \ + github.com/openconfig/goyang \ + github.com/openconfig/ygot/ygot \ + github.com/go-redis/redis \ + github.com/golang/glog \ + 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/facette/natsort \ + github.com/philopon/go-toposort + + +REST_BIN = $(BUILD_DIR)/rest_server/main +CERTGEN_BIN = $(BUILD_DIR)/rest_server/generate_cert + + +all: build-deps go-deps go-pkg-version go-patch translib rest-server cli + +build-deps: + mkdir -p $(BUILD_DIR) + +go-deps: $(GO_DEPS_LIST) + +go-pkg-version: go-deps + cd $(BUILD_GOPATH)/src/github.com/go-redis/redis; git checkout d19aba07b47683ef19378c4a4d43959672b7cec8 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/go-redis/redis; \ +cd $(BUILD_GOPATH)/src/github.com/gorilla/mux; git checkout 49c01487a141b49f8ffe06277f3dca3ee80a55fa 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/gorilla/mux; \ +cd $(BUILD_GOPATH)/src/github.com/Workiva/go-datastructures; git checkout f07cbe3f82ca2fd6e5ab94afce65fe43319f675f 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/Workiva/go-datastructures; \ +cd $(BUILD_GOPATH)/src/github.com/golang/glog; git checkout 23def4e6c14b4da8ac2ed8007337bc5eb5007998 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/golang/glog; \ +cd $(BUILD_GOPATH)/src/github.com/pkg/profile; git checkout acd64d450fd45fb2afa41f833f3788c8a7797219 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/pkg/profile; \ +cd $(BUILD_GOPATH)/src/github.com/antchfx/xmlquery; git checkout 16f1e6cdc5fe44a7f8e2a8c9faf659a1b3a8fd9b 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/antchfx/xmlquery; \ +cd $(BUILD_GOPATH)/src/github.com/facette/natsort; git checkout 2cd4dd1e2dcba4d85d6d3ead4adf4cfd2b70caf2 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/facette/natsort; \ +cd $(BUILD_GOPATH)/src/github.com/philopon/go-toposort; git checkout 9be86dbd762f98b5b9a4eca110a3f40ef31d0375 2>/dev/null ; true; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/philopon/go-toposort + +$(GO_DEPS_LIST): + $(GO) get -v $@ + +cli: rest-server + $(MAKE) -C src/CLI + +cvl: go-deps go-patch go-pkg-version + $(MAKE) -C src/cvl + $(MAKE) -C src/cvl/schema + $(MAKE) -C src/cvl/testdata/schema + +cvl-test: + $(MAKE) -C src/cvl gotest + +rest-server: translib + $(MAKE) -C src/rest + +rest-clean: + $(MAKE) -C src/rest clean + +translib: cvl + $(MAKE) -C src/translib + +codegen: + $(MAKE) -C models + +yamlGen: + $(MAKE) -C models/yang + $(MAKE) -C models/yang/sonic + +go-patch: go-deps + cd $(BUILD_GOPATH)/src/github.com/openconfig/ygot/; git reset --hard HEAD; git clean -f -d; git checkout 724a6b18a9224343ef04fe49199dfb6020ce132a 2>/dev/null ; true; \ +cd ../; cp $(TOPDIR)/ygot-modified-files/ygot.patch .; \ +patch -p1 < ygot.patch; rm -f ygot.patch; \ +$(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ygot; \ + cd $(BUILD_GOPATH)/src/github.com/openconfig/goyang/; git reset --hard HEAD; git clean -f -d; git checkout 064f9690516f4f72db189f4690b84622c13b7296 >/dev/null ; true; \ + cp $(TOPDIR)/goyang-modified-files/goyang.patch .; \ + patch -p1 < goyang.patch; rm -f goyang.patch; \ + $(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/goyang; \ + cd $(BUILD_GOPATH)/src/github.com/antchfx/jsonquery; git reset --hard HEAD; \ + git checkout 3535127d6ca5885dbf650204eb08eabf8374a274 2>/dev/null ; \ + git apply $(TOPDIR)/patches/jsonquery.patch; \ + $(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/antchfx/jsonquery + +install: + $(INSTALL) -D $(REST_BIN) $(DESTDIR)/usr/sbin/rest_server + $(INSTALL) -D $(CERTGEN_BIN) $(DESTDIR)/usr/sbin/generate_cert + $(INSTALL) -d $(DESTDIR)/usr/sbin/schema/ + $(INSTALL) -d $(DESTDIR)/usr/sbin/lib/ + $(INSTALL) -d $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/models/yang/sonic/*.yang $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/models/yang/sonic/common/*.yang $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/src/cvl/schema/*.yin $(DESTDIR)/usr/sbin/schema/ + $(INSTALL) -D $(TOPDIR)/src/cvl/testdata/schema/*.yin $(DESTDIR)/usr/sbin/schema/ + $(INSTALL) -D $(TOPDIR)/models/yang/*.yang $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/config/transformer/models_list $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/models/yang/common/*.yang $(DESTDIR)/usr/models/yang/ + $(INSTALL) -D $(TOPDIR)/models/yang/annotations/*.yang $(DESTDIR)/usr/models/yang/ + cp -rf $(TOPDIR)/build/rest_server/dist/ui/ $(DESTDIR)/rest_ui/ + cp -rf $(TOPDIR)/build/cli $(DESTDIR)/usr/sbin/ + cp -rf $(TOPDIR)/build/swagger_client_py/ $(DESTDIR)/usr/sbin/lib/ + cp -rf $(TOPDIR)/src/cvl/conf/cvl_cfg.json $(DESTDIR)/usr/sbin/cvl_cfg.json + +ifeq ($(SONIC_COVERAGE_ON),y) + echo "" > $(DESTDIR)/usr/sbin/.test +endif + +$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : + mv $* $(DEST)/ + +clean: rest-clean + $(MAKE) -C src/cvl clean + $(MAKE) -C src/translib clean + $(MAKE) -C src/cvl/schema clean + $(MAKE) -C src/cvl cleanall + rm -rf build/* + rm -rf debian/.debhelper + rm -rf $(BUILD_GOPATH)/src/github.com/openconfig/goyang/annotate.go + +cleanall: + $(MAKE) -C src/cvl cleanall + rm -rf build/* diff --git a/README.md b/README.md new file mode 100644 index 0000000000..b33661e3d8 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +## SONiC Management Framework Repo + +### Build Instruction +Please note that the build instruction in this guide has only been tested on Ubuntu 16.04. +#### Pre-rerequisit +##### User permissions: + `sudo usermod -aG sudo $USER` + `sudo usermod -aG docker $USER` + +##### Packages to be installed: + `sudo apt-get install git docker` + +#### Steps to build and create an installer +1. git clone https://github.com/project-arlo/sonic-buildimage.git +2. cd sonic-buildimage/ +3. sudo modprobe overlay +4. make init +5. make configure PLATFORM=broadcom +6. Run the prefetch python script to download all binaries (see below for the script). +7. To build mgmt-framework container: + `BLDENV=stretch make target/docker-sonic-mgmt-framework.gz` +8. To build Debian Stretch, if not already downloaded: + `BLDENV=stretch make stretch` +9. To build the ONIE installer: + `BLDENV=stretch make target/sonic-broadcom.bin` + +#### Faster builds +In order to speed up the process of build, you can prefetch the latest debian files from Azure server, and just build what you need. + +Here is a python script you could use to fetch latest prebuilt objects (deb, gz, ko, etc) from SONiC Jenkins cluster: + + import os + import shutil + import urllib.request + from html.parser import HTMLParser + + UPSTREAM_PREFIX = 'https://sonic-jenkins.westus2.cloudapp.azure.com/job/broadcom/job/buildimage-brcm-all/lastSuccessfulBuild/artifact/' + + def get_all_bins(target_path, extension): + """Get all files matching the given extension from the target path""" + print('Fetching %s*%s' % (target_path, extension)) + os.makedirs(target_path, exist_ok=True) + + req = urllib.request.urlopen(UPSTREAM_PREFIX + target_path) + data = req.read().decode() + + class Downloader(HTMLParser): + """Class to parse retrieved data, match against the given extension, + and download the matching files to the given target directory""" + def handle_starttag(self, tag, attrs): + """Handle only tags""" + if tag == 'a': + for attr, val in attrs: + if attr == 'href' and val.endswith(extension): + self.download_file(val) + + @staticmethod + def download_file(path): + filename = os.path.join(target_path, path) + freq = urllib.request.urlopen(UPSTREAM_PREFIX + target_path + path) + + print('\t%s' % path) + with open(filename, 'wb') as fp: + shutil.copyfileobj(freq, fp) + + + parser = Downloader() + parser.feed(data) + print() + + get_all_bins('target/debs/stretch/', '.deb') + get_all_bins('target/files/stretch/', '.ko') + get_all_bins('target/python-debs/', '.deb') + get_all_bins('target/python-wheels/', '.whl') + get_all_bins('target/', '.gz') + + + +##### Incremental builds +Just clean up the deb's/gz that require re-build, and build again. Here is an exmple: + +##### To build deb file for sonic-mgmt-framework + + BLDENV=stretch make target/debs/stretch/sonic-mgmt-framework_1.0-01_amd64.deb-clean + BLDENV=stretch make target/debs/stretch/sonic-mgmt-framework_1.0-01_amd64.deb + +##### To build sonic-mgmt-framework docker alone + + BLDENV=stretch make target/docker-sonic-mgmt-framework.gz-clean + BLDENV=stretch make target/docker-sonic-mgmt-framework.gz diff --git a/config/transformer/models_list b/config/transformer/models_list new file mode 100644 index 0000000000..c9f26c1638 --- /dev/null +++ b/config/transformer/models_list @@ -0,0 +1,3 @@ +#List yang models transformer need to load +openconfig-acl.yang +openconfig-acl-annot.yang diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000000..91d0815d54 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +sonic-mgmt-framework (1.0-01) UNRELEASED; urgency=low + + * Initial release. + + -- Prabhu Sreenivasan Tue, 18 Jun 2019 00:25:19 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000000..ec635144f6 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000000..8c53c29642 --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: sonic-mgmt-framework +Maintainer: Prabhu Sreenivasan +Build-Depends: debhelper (>= 8.0.0), + dh-systemd +Standards-Version: 3.9.3 +Section: net + +Package: sonic-mgmt-framework +Priority: extra +Architecture: amd64 +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SONiC Management Framework + +Package: sonic-mgmt-framework-dbg +Priority: extra +Architecture: amd64 +Section: debug +Depends: sonic-mgmt-framework (=${binary:Version}) +Description: debugging symbols for SONiC Management Framework diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000000..c08b68a370 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f +%: + dh $@ --with systemd + + +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info -l$(shell pwd)/build/cli/target/.libs/:$(shell pwd)/build/cli/.libs/ diff --git a/goyang-modified-files/goyang.patch b/goyang-modified-files/goyang.patch new file mode 100644 index 0000000000..3bb6420449 --- /dev/null +++ b/goyang-modified-files/goyang.patch @@ -0,0 +1,530 @@ +diff --git a/README.md b/README.md +index 4d22c1e..805adb5 100644 +--- a/README.md ++++ b/README.md +@@ -14,6 +14,7 @@ The forms include: + + * tree - a simple tree representation + * types - list understood types extracted from the schema ++* annotate - a template file to annotate the yang modules + + The yang package, and the goyang program, are not complete and are a work in + progress. +diff --git a/annotate.go b/annotate.go +new file mode 100644 +index 0000000..243c416 +--- /dev/null ++++ b/annotate.go +@@ -0,0 +1,395 @@ ++// Copyright 2015 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 main ++ ++import ( ++ "fmt" ++ "io" ++ "strings" ++ ++ "github.com/openconfig/goyang/pkg/yang" ++) ++ ++var allimports = make(map[string]string) ++var modules = make(map[string]*yang.Module) ++var allmodules = make(map[string]*yang.Module) ++ ++func init() { ++ register(&formatter{ ++ name: "annotate", ++ f: genAnnotate, ++ utilf: getFile, ++ help: "generate template file for yang annotations", ++ }) ++} ++ ++// Get the modules for which annotation file needs to be generated ++func getFile(files []string, mods map[string]*yang.Module) { ++ allmodules = mods ++ for _, name := range files { ++ slash := strings.Split(name, "/") ++ modname := slash[len(slash)-1] ++ modname = strings.TrimSuffix(modname, ".yang"); ++ /* Save the yang.Module entries we are interested in */ ++ modules[modname] = mods[modname] ++ } ++} ++ ++func genAnnotate(w io.Writer, entries []*yang.Entry) { ++ /* Get all the imported modules in the entries */ ++ GetAllImports(entries) ++ for _, e := range entries { ++ if _, ok := modules[e.Name]; ok { ++ var path string = "" ++ var prefix string = "" ++ generate(w, e, path, prefix) ++ // { Add closing brace for each module ++ fmt.Fprintln(w, "}") ++ fmt.Fprintln(w) ++ } ++ } ++} ++ ++// generate writes to stdoutput a template annotation file entry for the selected modules. ++func generate(w io.Writer, e *yang.Entry, path string, prefix string) { ++ if e.Parent == nil { ++ if e.Name != "" { ++ fmt.Fprintf(w, "module %s-annot {\n", e.Name) //} ++ fmt.Fprintln(w) ++ fmt.Fprintf(w, " yang-version \"%s\";\n", getYangVersion(e.Name, modules)) ++ fmt.Fprintln(w) ++ fmt.Fprintf(w, " namespace \"http://openconfig.net/yang/annotation/%s-annot\";\n", e.Prefix.Name) ++ if e.Prefix != nil { ++ fmt.Fprintf(w, " prefix \"%s-annot\";\n", e.Prefix.Name) ++ } ++ fmt.Fprintln(w) ++ ++ var imports = make(map[string]string) ++ imports = getImportModules(e.Name, modules) ++ for k := range imports { ++ if e.Name != k { ++ fmt.Fprintf(w, " import %s { prefix %s; }\n", k, allimports[k]) ++ } ++ } ++ // Include the module for which annotation is being generated ++ fmt.Fprintf(w, " import %s { prefix %s; }\n", e.Name, e.Prefix.Name) ++ ++ fmt.Fprintln(w) ++ } ++ } ++ ++ name := e.Name ++ if prefix == "" && e.Prefix != nil { ++ prefix = e.Prefix.Name ++ } ++ name = prefix + ":" + name ++ ++ if (e.Node.Kind() != "module") { ++ path = path + "/" + name ++ printDeviation(w, path) ++ } ++ ++ var names []string ++ for k := range e.Dir { ++ names = append(names, k) ++ } ++ ++ if (e.Node.Kind() == "module") { ++ if len(e.Node.(*yang.Module).Augment) > 0 { ++ for _,a := range e.Node.(*yang.Module).Augment { ++ pathList := strings.Split(a.Name, "/") ++ pathList = pathList[1:] ++ for i, pvar := range pathList { ++ if len(pvar) > 0 && !strings.Contains(pvar, ":") { ++ pvar = e.Prefix.Name + ":" + pvar ++ pathList[i] = pvar ++ } ++ } ++ path = "/" + strings.Join(pathList, "/") ++ handleAugments(w, a, e.Node.(*yang.Module).Grouping, e.Prefix.Name, path) ++ } ++ } ++ } ++ ++ for _, k := range names { ++ generate(w, e.Dir[k], path, prefix) ++ } ++ ++} ++ ++func printDeviation(w io.Writer, path string){ ++ fmt.Fprintf(w, " deviation %s {\n", path) ++ fmt.Fprintf(w, " deviate add {\n") ++ fmt.Fprintf(w, " }\n") ++ fmt.Fprintf(w, " }\n") ++ fmt.Fprintln(w) ++} ++ ++ ++// Save to map all imported modules ++func GetAllImports(entries []*yang.Entry) { ++ for _, e := range entries { ++ allimports[e.Name] = e.Prefix.Name ++ } ++} ++ ++func GetModuleFromPrefix(prefix string) string { ++ for m, p := range allimports { ++ if prefix == p { ++ return m ++ } ++ } ++ return "" ++} ++ ++//Get Yang version from the yang.Modules ++func getYangVersion(modname string, mods map[string]*yang.Module) string { ++ if (mods[modname].YangVersion != nil) { ++ return mods[modname].YangVersion.Name ++ } ++ return "" ++ ++} ++ ++// Get imported modules for a given module from yang.Module ++func getImportModules(modname string, mods map[string]*yang.Module) map[string]string { ++ imports := map[string]string{} ++ if (mods[modname].Import != nil) { ++ for _, imp := range mods[modname].Import { ++ imports[imp.Name] = imp.Prefix.Name ++ } ++ } ++ return imports ++} ++ ++func handleAugments(w io.Writer, a *yang.Augment, grp []*yang.Grouping, prefix string, path string) { ++ for _, u := range a.Uses { ++ grpN := u.Name ++ for _, g := range grp { ++ if grpN == g.Name { ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grp, prefix, path) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grp, prefix, path) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, path) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, path) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grp, prefix, path) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grp, prefix, path) ++ } ++ } ++ } ++ } ++ ++} ++ ++func handleUses(w io.Writer, u []*yang.Uses, grp []*yang.Grouping, prefix string, path string) { ++ for _, u := range u { ++ grpN := u.Name ++ if strings.Contains(grpN, ":") { ++ tokens := strings.Split(grpN, ":") ++ nprefix := tokens[0] ++ grpN = tokens[1] ++ mod := GetModuleFromPrefix(nprefix) ++ grp = allmodules[mod].Grouping ++ } ++ for _, g := range grp { ++ if grpN == g.Name { ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grp, prefix, path) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grp, prefix, path) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, path) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, path) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grp, prefix, path) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grp, prefix, path) ++ } ++ ++ } ++ } ++ } ++ ++} ++ ++func handleContainer(w io.Writer, ctr []*yang.Container, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ctr { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Choice) > 0 { ++ handleChoice(w, c.Choice, grp, prefix, npath) ++ } ++ if len(c.Grouping) > 0 { ++ handleGrouping(w, c.Grouping, grp, prefix, npath) ++ } ++ if len(c.Uses) > 0 { ++ handleUses(w, c.Uses, grp, prefix, npath) ++ } ++ } ++} ++ ++func handleList(w io.Writer, lst []*yang.List, grp []*yang.Grouping, prefix string, path string) { ++ for _, l := range lst { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ if len(l.Container) > 0 { ++ handleContainer(w, l.Container, grp, prefix, npath) ++ } ++ if len(l.List) > 0 { ++ handleList(w, l.List, grp, prefix, npath) ++ } ++ if len(l.LeafList) > 0 { ++ handleLeafList(w, l.LeafList, prefix, npath) ++ } ++ if len(l.Leaf) > 0 { ++ handleLeaf(w, l.Leaf, prefix, npath) ++ } ++ if len(l.Choice) > 0 { ++ handleChoice(w, l.Choice, grp, prefix, npath) ++ } ++ if len(l.Grouping) > 0 { ++ handleGrouping(w, l.Grouping, grp, prefix, npath) ++ } ++ if len(l.Uses) > 0 { ++ handleUses(w, l.Uses, grp, prefix, npath) ++ } ++ ++ } ++} ++ ++func handleGrouping(w io.Writer, grp []*yang.Grouping, grptop []*yang.Grouping, prefix string, path string) { ++ for _, g := range grp { ++ npath := path + "/" + prefix + ":" + g.Name ++ printDeviation(w, npath) ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grptop, prefix, npath) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grptop, prefix, npath) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, npath) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, npath) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grptop, prefix, npath) ++ } ++ if len(g.Grouping) > 0 { ++ handleGrouping(w, g.Grouping, grptop, prefix, npath) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grptop, prefix, npath) ++ } ++ ++ } ++} ++ ++func handleLeaf (w io.Writer, lf []*yang.Leaf, prefix string, path string) { ++ if len(lf) > 0 { ++ for _, l := range lf { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ } ++ } ++ ++} ++ ++func handleLeafList (w io.Writer, llst []*yang.LeafList, prefix string, path string) { ++ if len(llst) > 0 { ++ for _, l := range llst { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ } ++ } ++} ++ ++func handleChoice (w io.Writer, ch []*yang.Choice, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ch { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Case) > 0 { ++ handleCase(w, c.Case, grp, prefix, npath) ++ } ++ } ++} ++ ++func handleCase (w io.Writer, ch []*yang.Case, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ch { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Choice) > 0 { ++ handleChoice(w, c.Choice, grp, prefix, npath) ++ } ++ if len(c.Uses) > 0 { ++ handleUses(w, c.Uses, grp, prefix, npath) ++ } ++ ++ } ++} ++ +diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go +index ef658d6..f626dc9 100644 +--- a/pkg/yang/entry.go ++++ b/pkg/yang/entry.go +@@ -80,6 +80,7 @@ type Entry struct { + + // Fields associated with directory nodes + Dir map[string]*Entry `json:",omitempty"` ++ DirOKeys []string // Ordered Keys list in Dir + Key string `json:",omitempty"` // Optional key name for lists (i.e., maps) + + // Fields associated with leaf nodes +@@ -115,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. +@@ -264,6 +269,7 @@ func newDirectory(n Node) *Entry { + return &Entry{ + Kind: DirectoryEntry, + Dir: make(map[string]*Entry), ++ DirOKeys: make([]string, 0), + Node: n, + Name: n.NName(), + Extra: map[string][]interface{}{}, +@@ -366,6 +372,7 @@ func (e *Entry) add(key string, value *Entry) *Entry { + return e + } + e.Dir[key] = value ++ e.DirOKeys = append(e.DirOKeys, key) + return e + } + +@@ -1090,6 +1097,7 @@ func (e *Entry) FixChoice() { + } + ce.Parent = ne + e.Dir[k] = ne ++ e.DirOKeys = append(e.DirOKeys, k) + } + } + } +@@ -1260,6 +1268,14 @@ func (e *Entry) shallowDup() *Entry { + // copied we will have to explicitly uncopy them. + ne := *e + ++ //Copy the ordered Dir keys to new entry ++ if len(e.DirOKeys) > 0 { ++ ne.DirOKeys = make([]string, 0) ++ for _, key := range e.DirOKeys { ++ ne.DirOKeys = append(ne.DirOKeys, key) ++ } ++ } ++ + // Now only copy direct children, clear their Dir, and fix up + // Parent pointers. + if e.Dir != nil { +@@ -1283,6 +1299,14 @@ func (e *Entry) dup() *Entry { + // to do that. + ne := *e + ++ //Copy the ordered Dir keys to new entry ++ if len(e.DirOKeys) > 0 { ++ ne.DirOKeys = make([]string, 0) ++ for _, key := range e.DirOKeys { ++ ne.DirOKeys = append(ne.DirOKeys, key) ++ } ++ } ++ + // Now recurse down to all of our children, fixing up Parent + // pointers as we go. + if e.Dir != nil { +@@ -1317,6 +1341,7 @@ func (e *Entry) merge(prefix *Value, namespace *Value, oe *Entry) { + } else { + v.Parent = e + e.Dir[k] = v ++ e.DirOKeys = append(e.DirOKeys, k) + } + } + } +@@ -1378,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/yang.go b/yang.go +index 2480a4e..515d1b3 100644 +--- a/yang.go ++++ b/yang.go +@@ -58,6 +58,7 @@ import ( + type formatter struct { + name string + f func(io.Writer, []*yang.Entry) ++ utilf func([]string, map[string]*yang.Module) + help string + flags *getopt.Set + } +@@ -208,5 +209,8 @@ Formats: + entries[x] = yang.ToEntry(mods[n]) + } + ++ if format == "annotate" { ++ formatters[format].utilf(files, mods) ++ } + formatters[format].f(os.Stdout, entries) + } diff --git a/models/.gitkeep b/models/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/models/Makefile b/models/Makefile new file mode 100644 index 0000000000..8f542e905d --- /dev/null +++ b/models/Makefile @@ -0,0 +1,164 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := .. +ABS_TOPDIR := $(abspath $(TOPDIR)) + +BUILD_DIR := $(TOPDIR)/build +CODEGEN_TOOLS_DIR := $(TOPDIR)/tools/swagger_codegen + +CODEGEN_VER := 2.4.5 +CODEGEN_JAR := $(CODEGEN_TOOLS_DIR)/swagger-codegen-cli-$(CODEGEN_VER).jar + +SERVER_BUILD_DIR := $(BUILD_DIR)/rest_server +SERVER_CODEGEN_DIR := $(SERVER_BUILD_DIR)/codegen +SERVER_DIST_DIR := $(SERVER_BUILD_DIR)/dist +SERVER_DIST_INIT := $(SERVER_DIST_DIR)/.init_done +SERVER_DIST_GO := $(SERVER_DIST_DIR)/src/swagger +SERVER_DIST_UI := $(SERVER_DIST_DIR)/ui +SERVER_DIST_UI_HOME := $(SERVER_DIST_DIR)/ui/index.html + +YANGAPI_DIR := $(TOPDIR)/build/yaml +YANGAPI_SPECS := $(shell find $(YANGAPI_DIR) -name '*.yaml' | sort) +YANGAPI_NAMES := $(basename $(notdir $(YANGAPI_SPECS))) +YANGAPI_SERVERS := $(addsuffix /.yangapi_done, $(addprefix $(SERVER_CODEGEN_DIR)/, $(YANGAPI_NAMES))) + +OPENAPI_DIR := openapi +OPENAPI_SPECS := $(shell find $(OPENAPI_DIR) -name '*.yaml' | sort) +OPENAPI_NAMES := $(basename $(notdir $(OPENAPI_SPECS))) +OPENAPI_SERVERS := $(addsuffix /.openapi_done, $(addprefix $(SERVER_CODEGEN_DIR)/, $(OPENAPI_NAMES))) + +PY_CLIENT_CODEGEN_DIR := $(BUILD_DIR)/swagger_client_py +PY_CLIENT_TARGETS := $(addsuffix .yangapi_client, $(addprefix $(PY_CLIENT_CODEGEN_DIR)/, $(YANGAPI_NAMES))) \ + $(addsuffix .openapi_client, $(addprefix $(PY_CLIENT_CODEGEN_DIR)/, $(OPENAPI_NAMES))) + +UIGEN_DIR = $(TOPDIR)/tools/ui_gen +UIGEN_SRCS = $(shell find $(UIGEN_DIR) -type f) + + +.PHONY: all clean cleanall go-server py-client + +all: go-server py-client + +go-server: $(YANGAPI_SERVERS) $(OPENAPI_SERVERS) $(SERVER_DIST_INIT) $(SERVER_DIST_UI_HOME) + +$(SERVER_DIST_UI_HOME): $(YANGAPI_SERVERS) $(OPENAPI_SERVERS) $(UIGEN_SRCS) + @echo "+++ Generating landing page for Swagger UI +++" + $(UIGEN_DIR)/src/uigen.py + +py-client: $(PY_CLIENT_TARGETS) + + +.SECONDEXPANSION: + +#====================================================================== +# Common rule for directories. Use "." suffix, like "xyz/." +#====================================================================== +.PRECIOUS: %/. +%/.: + mkdir -p $@ + +#====================================================================== +# Download swagger codegen jar from Maven.org repo. It will be saved as +# build/swagger-codegen-cli.jar file. +#====================================================================== +$(CODEGEN_JAR): | $$(@D)/. + cd $(@D) && \ + wget http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/$(CODEGEN_VER)/$(@F) + +#====================================================================== +# Generate swagger server in GO language for Yang generated OpenAPIs +# specs. +#====================================================================== +%/.yangapi_done: $(YANGAPI_DIR)/$$(*F).yaml | $$(@D)/. $(CODEGEN_JAR) $(SERVER_DIST_INIT) + @echo "+++ Generating GO server for Yang API $$(basename $(@D)).yaml +++" + java -jar $(CODEGEN_JAR) generate \ + --lang go-server \ + --input-spec $(YANGAPI_DIR)/$$(basename $(@D)).yaml \ + --template-dir $(CODEGEN_TOOLS_DIR)/go-server/templates-yang \ + --output $(@D) + cp $(@D)/go/api_* $(SERVER_DIST_GO)/ + cp $(@D)/go/routers.go $(SERVER_DIST_GO)/routers_$$(basename $(@D)).go + cp $(@D)/api/swagger.yaml $(SERVER_DIST_UI)/$$(basename $(@D)).yaml + touch $@ + +#====================================================================== +# Generate swagger server in GO language for handcoded OpenAPI specs +#====================================================================== +%/.openapi_done: $(OPENAPI_DIR)/$$(*F).yaml | $$(@D)/. $(CODEGEN_JAR) $(SERVER_DIST_INIT) + @echo "+++ Generating GO server for OpenAPI $$(basename $(@D)).yaml +++" + java -jar $(CODEGEN_JAR) generate \ + --lang go-server \ + --input-spec $(OPENAPI_DIR)/$$(basename $(@D)).yaml \ + --template-dir $(CODEGEN_TOOLS_DIR)/go-server/templates-nonyang \ + --output $(@D) + cp $(@D)/go/api_* $(@D)/go/model_* $(SERVER_DIST_GO)/ + cp $(@D)/go/routers.go $(SERVER_DIST_GO)/routers_$$(basename $(@D)).go + cp $(@D)/api/swagger.yaml $(SERVER_DIST_UI)/$$(basename $(@D)).yaml + touch $@ + +#====================================================================== +# Initialize dist directory for GO server code +#====================================================================== +$(SERVER_DIST_INIT): | $$(@D)/. + cp -r $(CODEGEN_TOOLS_DIR)/ui-dist $(@D)/ui + cp -r $(CODEGEN_TOOLS_DIR)/go-server/src $(@D)/ + touch $@ + +#====================================================================== +# Generate swagger client in Python for yang generated OpenAPI specs +#====================================================================== +%.yangapi_client: $(YANGAPI_DIR)/$$(*F).yaml | $(CODEGEN_JAR) $$(@D)/. + @echo "+++++ Generating Python client for $(*F).yaml +++++" + java -jar $(CODEGEN_JAR) generate \ + -DpackageName=$(subst -,_,$(*F))_client \ + --lang python \ + --input-spec $(YANGAPI_DIR)/$(*F).yaml \ + --output $(@D) + touch $@ + +#====================================================================== +# Generate swagger client in Python for handcoded OpenAPI specs +#====================================================================== +%.openapi_client: $(OPENAPI_DIR)/$$(*F).yaml | $(CODEGEN_JAR) $$(@D)/. + @echo "+++++ Generating Python client for $(*F).yaml +++++" + java -jar $(CODEGEN_JAR) generate \ + -DpackageName=$(subst -,_,$(*F))_client \ + --lang python \ + --input-spec $(OPENAPI_DIR)/$(*F).yaml \ + --output $(@D) + touch $@ + +#====================================================================== +# Cleanups +#====================================================================== + +clean-server: + rm -rf $(SERVER_DIST_DIR) + rm -rf $(SERVER_CODEGEN_DIR) + +clean-client: + rm -rf $(PY_CLIENT_CODEGEN_DIR) + +clean: clean-server clean-client + make -C yang clean + +cleanall: clean + rm -f $(CODEGEN_JAR) + diff --git a/models/openapi/vlan.yaml.demo b/models/openapi/vlan.yaml.demo new file mode 100644 index 0000000000..8304bc3280 --- /dev/null +++ b/models/openapi/vlan.yaml.demo @@ -0,0 +1,186 @@ +swagger: "2.0" +info: + description: "Sample OpenAPI spec for SONiC VLAN table operations." + version: "1.0.0" + title: "SONiC Management Infra PoC - VLANs" + termsOfService: "" + contact: + email: "noone@broadcom.com" + license: + name: "Apache 2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0.html" +basePath: "/nonyang" +tags: +- name: "vlan" + description: "Vlan configuration APIs" +schemes: +- "https" +- "http" +paths: + /vlan: + post: + tags: + - "vlan" + summary: "Create vlans" + description: "Create vlans by id" + operationId: "createVlans" + consumes: + - "application/json" + parameters: + - in: "body" + name: "body" + description: "Vlans to be configured" + required: true + schema: + type: "array" + items: + type: "integer" + responses: + 201: + description: "Vlans created" + 500: + description: "Vlan creation failed" + get: + tags: + - "vlan" + summary: "Get all vlan" + description: "Returns all vlans" + operationId: "getVlans" + produces: + - "application/json" + responses: + 200: + description: "successful operation" + schema: + type: "array" + items: + $ref: "#/definitions/VlanInfo" + 500: + description: "Internal error" + + /vlan/{id}: + get: + tags: + - "vlan" + summary: "Finds vlan by id" + description: "Returns vlan by id" + operationId: "getVlanById" + produces: + - "application/json" + parameters: + - name: "id" + in: "path" + description: "Vlan id" + required: true + type: "integer" + responses: + 200: + description: "successful operation" + schema: + $ref: "#/definitions/VlanInfo" + 404: + description: "Vlan not found" + delete: + tags: + - "vlan" + summary: "Delete vlan by id" + description: "Delete vlan by id" + operationId: "deleteVlanById" + parameters: + - name: "id" + in: "path" + description: "Vlan id" + required: true + type: "integer" + responses: + 204: + description: "Vlan deleted" + 404: + description: "Vlan not found" + + /vlan/{id}/member: + post: + tags: + - "vlan" + summary: "Add member interfaces" + description: "Add member interfaces" + operationId: "addVlanMembers" + consumes: + - "application/json" + parameters: + - name: "id" + in: "path" + description: "Vlan id" + required: true + type: "integer" + - name: "body" + in: "body" + description: "Member info" + required: true + schema: + type: "array" + items: + $ref: "#/definitions/VlanMember" + responses: + 201: + description: "Members added" + 404: + description: "Vlan not found" + + /vlan/{id}/member/{port}: + delete: + tags: + - "vlan" + summary: "Remove member interfaces" + description: "Remove member interfaces" + operationId: "removeVlanMembers" + parameters: + - name: "id" + in: "path" + description: "Vlan id" + required: true + type: "integer" + - name: "port" + in: "path" + description: "Member port name" + required: true + type: "string" + responses: + 204: + description: "Vlan member deleted" + 404: + description: "Vlan or vlan member config not found" + +definitions: + VlanInfo: + type: "object" + required: [id] + properties: + id: + description: "Vlan id" + type: "integer" + format: "int32" + name: + description: "Vlan name" + type: "string" + format: "string" + members: + description: "Vlan member port details" + type: "array" + items: + $ref: "#/definitions/VlanMember" + + VlanMember: + type: "object" + required: [port] + properties: + port: + description: "Member port name" + type: "string" + format: "string" + mode: + description: "Tagging mode" + type: "string" + enum: + - "tagged" + - "untagged" diff --git a/models/yang/.gitignore b/models/yang/.gitignore new file mode 100644 index 0000000000..d77ad18a85 --- /dev/null +++ b/models/yang/.gitignore @@ -0,0 +1,2 @@ +allyangs_tree.html +allyangs.tree diff --git a/models/yang/Makefile b/models/yang/Makefile new file mode 100644 index 0000000000..0a5b27e230 --- /dev/null +++ b/models/yang/Makefile @@ -0,0 +1,79 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := ../.. +BUILD_DIR := $(TOPDIR)/build + +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 +PYANG_PLUGIN_DIR := $(PYANG_DIR)/pyang_plugins +PYANG_BIN := pyang + +.PHONY: all yamlGen + +all: yamlGen allyangs.tree allyangs_tree.html + +yamlGen: $(YANGAPI_DIR)/.done + +allyangs.tree: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) + $(PYANG_BIN) \ + -f tree \ + -o $(YANGDIR)/$@ \ + -p $(YANGDIR_COMMON):$(YANGDIR) \ + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) + @echo "+++++ Generation of YANG tree for Yang modules completed +++++" + +allyangs_tree.html: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) + $(PYANG_BIN) \ + -f jstree \ + -o $(YANGDIR)/$@ \ + -p $(YANGDIR_COMMON):$(YANGDIR) \ + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) + @echo "+++++ Generation of HTML tree for Yang modules completed +++++" + +#====================================================================== +# Generate YAML files for Yang modules +#====================================================================== +$(YANGAPI_DIR)/.done: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) + @echo "+++++ Generating YAML files for Yang modules +++++" + mkdir -p $(YANGAPI_DIR) + $(PYANG_BIN) \ + -f swaggerapi \ + --outdir $(YANGAPI_DIR) \ + --plugindir $(PYANG_PLUGIN_DIR) \ + -p $(YANGDIR_COMMON):$(YANGDIR) \ + $(YANG_MOD_FILES) $(YANG_ANNOTATIONS_FILES) + @echo "+++++ Generation of YAML files for Yang modules completed +++++" + touch $@ + +#====================================================================== +# Cleanups +#====================================================================== + +clean: + rm -rf $(YANGAPI_DIR) + rm -rf allyangs.tree allyangs_tree.html diff --git a/models/yang/annotations/openconfig-acl-annot.yang b/models/yang/annotations/openconfig-acl-annot.yang new file mode 100644 index 0000000000..8129fe0895 --- /dev/null +++ b/models/yang/annotations/openconfig-acl-annot.yang @@ -0,0 +1,197 @@ +module openconfig-acl-annot { + + yang-version "1"; + + namespace "http://openconfig.net/yang/annotation"; + prefix "oc-acl-annot"; + + import sonic-extensions { prefix sonic-ext; } + import openconfig-acl { prefix oc-acl; } + import openconfig-packet-match { prefix oc-pkt-match; } + + deviation /oc-acl:acl { + deviate add { + sonic-ext:post-transformer "acl_post_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set { + deviate add { + sonic-ext:table-name "ACL_TABLE"; + sonic-ext:key-transformer "acl_set_key_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:name { + deviate add { + sonic-ext:field-transformer "acl_set_name_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:type { + deviate add { + sonic-ext:field-transformer "acl_type_field_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:description { + deviate add { + sonic-ext:field-name "policy_desc"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:name { + deviate add { + sonic-ext:field-transformer "acl_set_name_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:type { + deviate add { + sonic-ext:field-transformer "acl_type_field_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:description { + deviate add { + sonic-ext:field-name "policy_desc"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry { + deviate add { + sonic-ext:table-name "ACL_RULE"; + sonic-ext:key-transformer "acl_entry_key_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:ipv4 { + deviate add { + sonic-ext:get-validate "validate_ipv4"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:ipv6 { + deviate add { + sonic-ext:get-validate "validate_ipv6"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:config/oc-acl:sequence-id { + deviate add { + sonic-ext:field-transformer "acl_entry_sequenceid_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:source-port { + deviate add { + sonic-ext:field-transformer "acl_source_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:destination-port { + deviate add { + sonic-ext:field-transformer "acl_destination_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:state/oc-pkt-match:source-port { + deviate add { + sonic-ext:field-transformer "acl_source_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:state/oc-pkt-match:destination-port { + deviate add { + sonic-ext:field-transformer "acl_destination_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:tcp-flags { + deviate add { + sonic-ext:field-transformer "acl_tcp_flags_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:l2/oc-pkt-match:config/oc-pkt-match:ethertype { + deviate add { + sonic-ext:field-transformer "acl_l2_ethertype_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:actions/oc-acl:config/oc-acl:forwarding-action { + deviate add { + sonic-ext:field-name "PACKET_ACTION"; + sonic-ext:field-transformer "acl_forwarding_action_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:actions/oc-acl:state/oc-acl:forwarding-action { + deviate add { + sonic-ext:field-name "PACKET_ACTION"; + sonic-ext:field-transformer "acl_forwarding_action_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:interfaces { + deviate add { + sonic-ext:subtree-transformer "acl_port_bindings_xfmr"; + } + } + +} + diff --git a/models/yang/annotations/sonic-extensions.yang b/models/yang/annotations/sonic-extensions.yang new file mode 100644 index 0000000000..c1252ef601 --- /dev/null +++ b/models/yang/annotations/sonic-extensions.yang @@ -0,0 +1,88 @@ +module sonic-extensions { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/sonic-ext"; + + prefix "sonic-ext"; + + // meta + organization "Sonic working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides extensions to the YANG language to allow + Sonic specific functionality and meta-data to be defined."; + + revision "2019-08-30" { + description + "Add extensions for redis DB mappings to identify the Redis DB name."; + } + + revision "2019-07-26" { + description + "Add extensionis for redis DB mappings for table, table-keys, table-fields and corresponding transformer methods."; + } + + + // extension statements + extension table-name { + argument "table-name"; + description "Db table name."; + } + + extension key-transformer { + argument "key-transformer-name"; + description "Db table key transformer name indicating that the list keys together form db table keys."; + } + + extension key-delimiter { + argument "key-delimiter-string"; + description "Db table key values delimiter."; + } + + extension field-name { + argument "field-name"; + 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 + or choose a specific DB field based on the type of yang value."; + } + + extension subtree-transformer { + argument "subtree-transformer-name"; + description "Subtree/node level transformer name that will have db mappings for an entire yang subtree."; + } + + extension post-transformer { + argument "post-transformer-name"; + description "Transformer name that will perform post-translation tasks."; + } + + extension get-validate { + argument "get-validate-name"; + description "Validation callpoint used to validate a YANG node during data translation back to YANG as a response to GET."; + } + + extension db-name { + argument "db-name"; + description "DB name that will indicate where data is stored. Eg: Config DB, App DB etc"; + } + extension table-transformer { + argument "table-transformer-name"; + description "Db table transformer name.This can be applied to either transform yang value to some different format + or choose a specific DB table based on the type."; + } +} diff --git a/models/yang/common/iana-if-type.yang b/models/yang/common/iana-if-type.yang new file mode 100644 index 0000000000..74e46b4b2f --- /dev/null +++ b/models/yang/common/iana-if-type.yang @@ -0,0 +1,1554 @@ +module iana-if-type { + namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; + prefix ianaift; + + import ietf-interfaces { + prefix if; + } + + organization "IANA"; + contact + " Internet Assigned Numbers Authority + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + United States + + Tel: +1 310 301 5800 + "; + description + "This YANG module defines YANG identities for IANA-registered + interface types. + + This YANG module is maintained by IANA and reflects the + 'ifType definitions' registry. + + The latest revision of this YANG module can be obtained from + the IANA web site. + + Requests for new values should be made to IANA via + email (iana&iana.org). + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + The initial version of this YANG module is part of RFC 7224; + see the RFC itself for full legal notices."; + reference + "IANA 'ifType definitions' registry. + "; + + revision 2015-06-12 { + description + "Corrected formatting issue."; + } + revision 2014-09-24 { + description + "Registered ifType 280."; + } + revision 2014-09-19 { + description + "Registered ifType 279."; + } + revision 2014-07-03 { + description + "Registered ifTypes 277-278."; + } + revision 2014-05-19 { + description + "Updated the contact address."; + } + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7224: IANA Interface Type YANG Module"; + } + + identity iana-interface-type { + base if:interface-type; + description + "This identity is used as a base for all interface types + defined in the 'ifType definitions' registry."; + } + + identity other { + base iana-interface-type; + } + identity regular1822 { + base iana-interface-type; + } + identity hdh1822 { + base iana-interface-type; + } + identity ddnX25 { + base iana-interface-type; + } + identity rfc877x25 { + base iana-interface-type; + reference + "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; + } + identity ethernetCsmacd { + base iana-interface-type; + description + "For all Ethernet-like interfaces, regardless of speed, + as per RFC 3635."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88023Csmacd { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88024TokenBus { + base iana-interface-type; + } + identity iso88025TokenRing { + base iana-interface-type; + } + identity iso88026Man { + base iana-interface-type; + } + identity starLan { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity proteon10Mbit { + base iana-interface-type; + } + identity proteon80Mbit { + base iana-interface-type; + } + identity hyperchannel { + base iana-interface-type; + } + identity fddi { + base iana-interface-type; + reference + "RFC 1512 - FDDI Management Information Base"; + } + identity lapb { + base iana-interface-type; + reference + "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; + } + identity sdlc { + base iana-interface-type; + } + identity ds1 { + base iana-interface-type; + description + "DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity e1 { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity basicISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity primaryISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity propPointToPointSerial { + base iana-interface-type; + description + "Proprietary serial."; + } + identity ppp { + base iana-interface-type; + } + identity softwareLoopback { + base iana-interface-type; + } + identity eon { + base iana-interface-type; + description + "CLNP over IP."; + } + identity ethernet3Mbit { + base iana-interface-type; + } + identity nsip { + base iana-interface-type; + description + "XNS over IP."; + } + identity slip { + base iana-interface-type; + description + "Generic SLIP."; + } + identity ultra { + base iana-interface-type; + description + "Ultra Technologies."; + } + identity ds3 { + base iana-interface-type; + description + "DS3-MIB."; + reference + "RFC 3896 - Definitions of Managed Objects for the + DS3/E3 Interface Type"; + } + identity sip { + base iana-interface-type; + description + "SMDS, coffee."; + reference + "RFC 1694 - Definitions of Managed Objects for SMDS + Interfaces using SMIv2"; + } + identity frameRelay { + base iana-interface-type; + description + "DTE only."; + reference + "RFC 2115 - Management Information Base for Frame Relay + DTEs Using SMIv2"; + } + identity rs232 { + base iana-interface-type; + reference + "RFC 1659 - Definitions of Managed Objects for RS-232-like + Hardware Devices using SMIv2"; + } + identity para { + base iana-interface-type; + description + "Parallel-port."; + reference + "RFC 1660 - Definitions of Managed Objects for + Parallel-printer-like Hardware Devices using + SMIv2"; + } + identity arcnet { + base iana-interface-type; + description + "ARCnet."; + } + identity arcnetPlus { + base iana-interface-type; + description + "ARCnet Plus."; + } + identity atm { + base iana-interface-type; + description + "ATM cells."; + } + identity miox25 { + base iana-interface-type; + reference + "RFC 1461 - SNMP MIB extension for Multiprotocol + Interconnect over X.25"; + } + identity sonet { + base iana-interface-type; + description + "SONET or SDH."; + } + identity x25ple { + base iana-interface-type; + reference + "RFC 2127 - ISDN Management Information Base using SMIv2"; + } + identity iso88022llc { + base iana-interface-type; + } + identity localTalk { + base iana-interface-type; + } + identity smdsDxi { + base iana-interface-type; + } + identity frameRelayService { + base iana-interface-type; + description + "FRNETSERV-MIB."; + reference + "RFC 2954 - Definitions of Managed Objects for Frame + Relay Service"; + } + identity v35 { + base iana-interface-type; + } + identity hssi { + base iana-interface-type; + } + identity hippi { + base iana-interface-type; + } + identity modem { + base iana-interface-type; + description + "Generic modem."; + } + identity aal5 { + base iana-interface-type; + description + "AAL5 over ATM."; + } + identity sonetPath { + base iana-interface-type; + } + identity sonetVT { + base iana-interface-type; + } + identity smdsIcip { + base iana-interface-type; + description + "SMDS InterCarrier Interface."; + } + identity propVirtual { + base iana-interface-type; + description + "Proprietary virtual/internal."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity propMultiplexor { + base iana-interface-type; + description + "Proprietary multiplexing."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity ieee80212 { + base iana-interface-type; + description + "100BaseVG."; + } + identity fibreChannel { + base iana-interface-type; + description + "Fibre Channel."; + } + identity hippiInterface { + base iana-interface-type; + description + "HIPPI interfaces."; + } + identity frameRelayInterconnect { + base iana-interface-type; + status obsolete; + description + "Obsolete; use either + frameRelay(32) or frameRelayService(44)."; + } + identity aflane8023 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.3."; + } + identity aflane8025 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.5."; + } + identity cctEmul { + base iana-interface-type; + description + "ATM Emulated circuit."; + } + identity fastEther { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity isdn { + base iana-interface-type; + description + "ISDN and X.25."; + reference + "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN + in the Packet Mode"; + } + identity v11 { + base iana-interface-type; + description + "CCITT V.11/X.21."; + } + identity v36 { + base iana-interface-type; + description + "CCITT V.36."; + } + identity g703at64k { + base iana-interface-type; + description + "CCITT G703 at 64Kbps."; + } + identity g703at2mb { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + } + identity qllc { + base iana-interface-type; + description + "SNA QLLC."; + } + identity fastEtherFX { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity channel { + base iana-interface-type; + description + "Channel."; + } + identity ieee80211 { + base iana-interface-type; + description + "Radio spread spectrum."; + } + identity ibm370parChan { + base iana-interface-type; + description + "IBM System 360/370 OEMI Channel."; + } + identity escon { + base iana-interface-type; + description + "IBM Enterprise Systems Connection."; + } + identity dlsw { + base iana-interface-type; + description + "Data Link Switching."; + } + identity isdns { + base iana-interface-type; + description + "ISDN S/T interface."; + } + identity isdnu { + base iana-interface-type; + description + "ISDN U interface."; + } + identity lapd { + base iana-interface-type; + description + "Link Access Protocol D."; + } + identity ipSwitch { + base iana-interface-type; + description + "IP Switching Objects."; + } + identity rsrb { + base iana-interface-type; + description + "Remote Source Route Bridging."; + } + identity atmLogical { + base iana-interface-type; + description + "ATM Logical Port."; + reference + "RFC 3606 - Definitions of Supplemental Managed Objects + for ATM Interface"; + } + identity ds0 { + base iana-interface-type; + description + "Digital Signal Level 0."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity ds0Bundle { + base iana-interface-type; + description + "Group of ds0s on the same ds1."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity bsc { + base iana-interface-type; + description + "Bisynchronous Protocol."; + } + identity async { + base iana-interface-type; + description + "Asynchronous Protocol."; + } + identity cnr { + base iana-interface-type; + description + "Combat Net Radio."; + } + identity iso88025Dtr { + base iana-interface-type; + description + "ISO 802.5r DTR."; + } + identity eplrs { + base iana-interface-type; + description + "Ext Pos Loc Report Sys."; + } + identity arap { + base iana-interface-type; + description + "Appletalk Remote Access Protocol."; + } + identity propCnls { + base iana-interface-type; + description + "Proprietary Connectionless Protocol."; + } + identity hostPad { + base iana-interface-type; + description + "CCITT-ITU X.29 PAD Protocol."; + } + identity termPad { + base iana-interface-type; + description + "CCITT-ITU X.3 PAD Facility."; + } + identity frameRelayMPI { + base iana-interface-type; + description + "Multiproto Interconnect over FR."; + } + identity x213 { + base iana-interface-type; + description + "CCITT-ITU X213."; + } + identity adsl { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop."; + } + identity radsl { + base iana-interface-type; + description + "Rate-Adapt. Digital Subscriber Loop."; + } + identity sdsl { + base iana-interface-type; + description + "Symmetric Digital Subscriber Loop."; + } + identity vdsl { + base iana-interface-type; + description + "Very H-Speed Digital Subscrib. Loop."; + } + identity iso88025CRFPInt { + base iana-interface-type; + description + "ISO 802.5 CRFP."; + } + identity myrinet { + base iana-interface-type; + description + "Myricom Myrinet."; + } + identity voiceEM { + base iana-interface-type; + description + "Voice recEive and transMit."; + } + identity voiceFXO { + base iana-interface-type; + description + "Voice Foreign Exchange Office."; + } + identity voiceFXS { + base iana-interface-type; + description + "Voice Foreign Exchange Station."; + } + identity voiceEncap { + base iana-interface-type; + description + "Voice encapsulation."; + } + identity voiceOverIp { + base iana-interface-type; + description + "Voice over IP encapsulation."; + } + identity atmDxi { + base iana-interface-type; + description + "ATM DXI."; + } + identity atmFuni { + base iana-interface-type; + description + "ATM FUNI."; + } + identity atmIma { + base iana-interface-type; + description + "ATM IMA."; + } + identity pppMultilinkBundle { + base iana-interface-type; + description + "PPP Multilink Bundle."; + } + identity ipOverCdlc { + base iana-interface-type; + description + "IBM ipOverCdlc."; + } + identity ipOverClaw { + base iana-interface-type; + description + "IBM Common Link Access to Workstn."; + } + identity stackToStack { + base iana-interface-type; + description + "IBM stackToStack."; + } + identity virtualIpAddress { + base iana-interface-type; + description + "IBM VIPA."; + } + identity mpc { + base iana-interface-type; + description + "IBM multi-protocol channel support."; + } + identity ipOverAtm { + base iana-interface-type; + description + "IBM ipOverAtm."; + reference + "RFC 2320 - Definitions of Managed Objects for Classical IP + and ARP Over ATM Using SMIv2 (IPOA-MIB)"; + } + identity iso88025Fiber { + base iana-interface-type; + description + "ISO 802.5j Fiber Token Ring."; + } + identity tdlc { + base iana-interface-type; + description + "IBM twinaxial data link control."; + } + identity gigabitEthernet { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity hdlc { + base iana-interface-type; + description + "HDLC."; + } + identity lapf { + base iana-interface-type; + description + "LAP F."; + } + identity v37 { + base iana-interface-type; + description + "V.37."; + } + identity x25mlp { + base iana-interface-type; + description + "Multi-Link Protocol."; + } + identity x25huntGroup { + base iana-interface-type; + description + "X25 Hunt Group."; + } + identity transpHdlc { + base iana-interface-type; + description + "Transp HDLC."; + } + identity interleave { + base iana-interface-type; + description + "Interleave channel."; + } + identity fast { + base iana-interface-type; + description + "Fast channel."; + } + identity ip { + base iana-interface-type; + description + "IP (for APPN HPR in IP networks)."; + } + identity docsCableMaclayer { + base iana-interface-type; + description + "CATV Mac Layer."; + } + identity docsCableDownstream { + base iana-interface-type; + description + "CATV Downstream interface."; + } + identity docsCableUpstream { + base iana-interface-type; + description + "CATV Upstream interface."; + } + identity a12MppSwitch { + base iana-interface-type; + description + "Avalon Parallel Processor."; + } + identity tunnel { + base iana-interface-type; + description + "Encapsulation interface."; + } + identity coffee { + base iana-interface-type; + description + "Coffee pot."; + reference + "RFC 2325 - Coffee MIB"; + } + identity ces { + base iana-interface-type; + description + "Circuit Emulation Service."; + } + identity atmSubInterface { + base iana-interface-type; + description + "ATM Sub Interface."; + } + identity l2vlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using 802.1Q."; + } + identity l3ipvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IP."; + } + identity l3ipxvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IPX."; + } + identity digitalPowerline { + base iana-interface-type; + description + "IP over Power Lines."; + } + identity mediaMailOverIp { + base iana-interface-type; + description + "Multimedia Mail over IP."; + } + identity dtm { + base iana-interface-type; + description + "Dynamic synchronous Transfer Mode."; + } + identity dcn { + base iana-interface-type; + description + "Data Communications Network."; + } + identity ipForward { + base iana-interface-type; + description + "IP Forwarding Interface."; + } + identity msdsl { + base iana-interface-type; + description + "Multi-rate Symmetric DSL."; + } + identity ieee1394 { + base iana-interface-type; + + description + "IEEE1394 High Performance Serial Bus."; + } + identity if-gsn { + base iana-interface-type; + description + "HIPPI-6400."; + } + identity dvbRccMacLayer { + base iana-interface-type; + description + "DVB-RCC MAC Layer."; + } + identity dvbRccDownstream { + base iana-interface-type; + description + "DVB-RCC Downstream Channel."; + } + identity dvbRccUpstream { + base iana-interface-type; + description + "DVB-RCC Upstream Channel."; + } + identity atmVirtual { + base iana-interface-type; + description + "ATM Virtual Interface."; + } + identity mplsTunnel { + base iana-interface-type; + description + "MPLS Tunnel Virtual Interface."; + } + identity srp { + base iana-interface-type; + description + "Spatial Reuse Protocol."; + } + identity voiceOverAtm { + base iana-interface-type; + description + "Voice over ATM."; + } + identity voiceOverFrameRelay { + base iana-interface-type; + description + "Voice Over Frame Relay."; + } + identity idsl { + base iana-interface-type; + description + "Digital Subscriber Loop over ISDN."; + } + identity compositeLink { + base iana-interface-type; + description + "Avici Composite Link Interface."; + } + identity ss7SigLink { + base iana-interface-type; + description + "SS7 Signaling Link."; + } + identity propWirelessP2P { + base iana-interface-type; + description + "Prop. P2P wireless interface."; + } + identity frForward { + base iana-interface-type; + description + "Frame Forward Interface."; + } + identity rfc1483 { + base iana-interface-type; + description + "Multiprotocol over ATM AAL5."; + reference + "RFC 1483 - Multiprotocol Encapsulation over ATM + Adaptation Layer 5"; + } + identity usb { + base iana-interface-type; + description + "USB Interface."; + } + identity ieee8023adLag { + base iana-interface-type; + description + "IEEE 802.3ad Link Aggregate."; + } + identity bgppolicyaccounting { + base iana-interface-type; + description + "BGP Policy Accounting."; + } + identity frf16MfrBundle { + base iana-interface-type; + description + "FRF.16 Multilink Frame Relay."; + } + identity h323Gatekeeper { + base iana-interface-type; + description + "H323 Gatekeeper."; + } + identity h323Proxy { + base iana-interface-type; + description + "H323 Voice and Video Proxy."; + } + identity mpls { + base iana-interface-type; + description + "MPLS."; + } + identity mfSigLink { + base iana-interface-type; + description + "Multi-frequency signaling link."; + } + identity hdsl2 { + base iana-interface-type; + description + "High Bit-Rate DSL - 2nd generation."; + } + identity shdsl { + base iana-interface-type; + description + "Multirate HDSL2."; + } + identity ds1FDL { + base iana-interface-type; + description + "Facility Data Link (4Kbps) on a DS1."; + } + identity pos { + base iana-interface-type; + description + "Packet over SONET/SDH Interface."; + } + identity dvbAsiIn { + base iana-interface-type; + description + "DVB-ASI Input."; + } + identity dvbAsiOut { + base iana-interface-type; + description + "DVB-ASI Output."; + } + identity plc { + base iana-interface-type; + description + "Power Line Communications."; + } + identity nfas { + base iana-interface-type; + description + "Non-Facility Associated Signaling."; + } + identity tr008 { + base iana-interface-type; + description + "TR008."; + } + identity gr303RDT { + base iana-interface-type; + description + "Remote Digital Terminal."; + } + identity gr303IDT { + base iana-interface-type; + description + "Integrated Digital Terminal."; + } + identity isup { + base iana-interface-type; + description + "ISUP."; + } + identity propDocsWirelessMaclayer { + base iana-interface-type; + description + "Cisco proprietary Maclayer."; + } + identity propDocsWirelessDownstream { + base iana-interface-type; + description + "Cisco proprietary Downstream."; + } + identity propDocsWirelessUpstream { + base iana-interface-type; + description + "Cisco proprietary Upstream."; + } + identity hiperlan2 { + base iana-interface-type; + description + "HIPERLAN Type 2 Radio Interface."; + } + identity propBWAp2Mp { + base iana-interface-type; + description + "PropBroadbandWirelessAccesspt2Multipt (use of this value + for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f + is deprecated, and ieee80216WMAN(237) should be used + instead)."; + } + identity sonetOverheadChannel { + base iana-interface-type; + description + "SONET Overhead Channel."; + } + identity digitalWrapperOverheadChannel { + base iana-interface-type; + description + "Digital Wrapper."; + } + identity aal2 { + base iana-interface-type; + description + "ATM adaptation layer 2."; + } + identity radioMAC { + base iana-interface-type; + description + "MAC layer over radio links."; + } + identity atmRadio { + base iana-interface-type; + description + "ATM over radio links."; + } + identity imt { + base iana-interface-type; + description + "Inter-Machine Trunks."; + } + identity mvl { + base iana-interface-type; + description + "Multiple Virtual Lines DSL."; + } + identity reachDSL { + base iana-interface-type; + description + "Long Reach DSL."; + } + identity frDlciEndPt { + base iana-interface-type; + description + "Frame Relay DLCI End Point."; + } + identity atmVciEndPt { + base iana-interface-type; + description + "ATM VCI End Point."; + } + identity opticalChannel { + base iana-interface-type; + description + "Optical Channel."; + } + identity opticalTransport { + base iana-interface-type; + description + "Optical Transport."; + } + identity propAtm { + base iana-interface-type; + description + "Proprietary ATM."; + } + identity voiceOverCable { + base iana-interface-type; + description + "Voice Over Cable Interface."; + } + identity infiniband { + base iana-interface-type; + description + "Infiniband."; + } + identity teLink { + base iana-interface-type; + description + "TE Link."; + } + identity q2931 { + base iana-interface-type; + description + "Q.2931."; + } + identity virtualTg { + base iana-interface-type; + description + "Virtual Trunk Group."; + } + identity sipTg { + base iana-interface-type; + description + "SIP Trunk Group."; + } + identity sipSig { + base iana-interface-type; + description + "SIP Signaling."; + } + identity docsCableUpstreamChannel { + base iana-interface-type; + description + "CATV Upstream Channel."; + } + identity econet { + base iana-interface-type; + description + "Acorn Econet."; + } + identity pon155 { + base iana-interface-type; + description + "FSAN 155Mb Symetrical PON interface."; + } + identity pon622 { + base iana-interface-type; + description + "FSAN 622Mb Symetrical PON interface."; + } + identity bridge { + base iana-interface-type; + description + "Transparent bridge interface."; + } + identity linegroup { + base iana-interface-type; + description + "Interface common to multiple lines."; + } + identity voiceEMFGD { + base iana-interface-type; + description + "Voice E&M Feature Group D."; + } + identity voiceFGDEANA { + base iana-interface-type; + description + "Voice FGD Exchange Access North American."; + } + identity voiceDID { + base iana-interface-type; + description + "Voice Direct Inward Dialing."; + } + identity mpegTransport { + base iana-interface-type; + description + "MPEG transport interface."; + } + identity sixToFour { + base iana-interface-type; + status deprecated; + description + "6to4 interface (DEPRECATED)."; + reference + "RFC 4087 - IP Tunnel MIB"; + } + identity gtp { + base iana-interface-type; + description + "GTP (GPRS Tunneling Protocol)."; + } + identity pdnEtherLoop1 { + base iana-interface-type; + description + "Paradyne EtherLoop 1."; + } + identity pdnEtherLoop2 { + base iana-interface-type; + description + "Paradyne EtherLoop 2."; + } + identity opticalChannelGroup { + base iana-interface-type; + description + "Optical Channel Group."; + } + identity homepna { + base iana-interface-type; + description + "HomePNA ITU-T G.989."; + } + identity gfp { + base iana-interface-type; + description + "Generic Framing Procedure (GFP)."; + } + identity ciscoISLvlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using Cisco ISL."; + } + identity actelisMetaLOOP { + base iana-interface-type; + description + "Acteleis proprietary MetaLOOP High Speed Link."; + } + identity fcipLink { + base iana-interface-type; + description + "FCIP Link."; + } + identity rpr { + base iana-interface-type; + description + "Resilient Packet Ring Interface Type."; + } + identity qam { + base iana-interface-type; + description + "RF Qam Interface."; + } + identity lmp { + base iana-interface-type; + description + "Link Management Protocol."; + reference + "RFC 4327 - Link Management Protocol (LMP) Management + Information Base (MIB)"; + } + identity cblVectaStar { + base iana-interface-type; + description + "Cambridge Broadband Networks Limited VectaStar."; + } + identity docsCableMCmtsDownstream { + base iana-interface-type; + description + "CATV Modular CMTS Downstream Interface."; + } + identity adsl2 { + base iana-interface-type; + status deprecated; + description + "Asymmetric Digital Subscriber Loop Version 2 + (DEPRECATED/OBSOLETED - please use adsl2plus(238) + instead)."; + reference + "RFC 4706 - Definitions of Managed Objects for Asymmetric + Digital Subscriber Line 2 (ADSL2)"; + } + identity macSecControlledIF { + base iana-interface-type; + description + "MACSecControlled."; + } + identity macSecUncontrolledIF { + base iana-interface-type; + description + "MACSecUncontrolled."; + } + identity aviciOpticalEther { + base iana-interface-type; + description + "Avici Optical Ethernet Aggregate."; + } + identity atmbond { + base iana-interface-type; + description + "atmbond."; + } + identity voiceFGDOS { + base iana-interface-type; + description + "Voice FGD Operator Services."; + } + identity mocaVersion1 { + base iana-interface-type; + description + "MultiMedia over Coax Alliance (MoCA) Interface + as documented in information provided privately to IANA."; + } + identity ieee80216WMAN { + base iana-interface-type; + description + "IEEE 802.16 WMAN interface."; + } + identity adsl2plus { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop Version 2 - + Version 2 Plus and all variants."; + } + identity dvbRcsMacLayer { + base iana-interface-type; + description + "DVB-RCS MAC Layer."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbTdm { + base iana-interface-type; + description + "DVB Satellite TDM."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbRcsTdma { + base iana-interface-type; + description + "DVB-RCS TDMA."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity x86Laps { + base iana-interface-type; + description + "LAPS based on ITU-T X.86/Y.1323."; + } + identity wwanPP { + base iana-interface-type; + description + "3GPP WWAN."; + } + identity wwanPP2 { + base iana-interface-type; + description + "3GPP2 WWAN."; + } + identity voiceEBS { + base iana-interface-type; + description + "Voice P-phone EBS physical interface."; + } + identity ifPwType { + base iana-interface-type; + description + "Pseudowire interface type."; + reference + "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; + } + identity ilan { + base iana-interface-type; + description + "Internal LAN on a bridge per IEEE 802.1ap."; + } + identity pip { + base iana-interface-type; + description + "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; + } + identity aluELP { + base iana-interface-type; + description + "Alcatel-Lucent Ethernet Link Protection."; + } + identity gpon { + base iana-interface-type; + description + "Gigabit-capable passive optical networks (G-PON) as per + ITU-T G.948."; + } + identity vdsl2 { + base iana-interface-type; + description + "Very high speed digital subscriber line Version 2 + (as per ITU-T Recommendation G.993.2)."; + reference + "RFC 5650 - Definitions of Managed Objects for Very High + Speed Digital Subscriber Line 2 (VDSL2)"; + } + identity capwapDot11Profile { + base iana-interface-type; + description + "WLAN Profile Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapDot11Bss { + base iana-interface-type; + description + "WLAN BSS Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapWtpVirtualRadio { + base iana-interface-type; + description + "WTP Virtual Radio Interface."; + reference + "RFC 5833 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Base MIB"; + } + identity bits { + base iana-interface-type; + description + "bitsport."; + } + identity docsCableUpstreamRfPort { + base iana-interface-type; + description + "DOCSIS CATV Upstream RF Port."; + } + identity cableDownstreamRfPort { + base iana-interface-type; + description + "CATV downstream RF Port."; + } + identity vmwareVirtualNic { + base iana-interface-type; + description + "VMware Virtual Network Interface."; + } + identity ieee802154 { + base iana-interface-type; + description + "IEEE 802.15.4 WPAN interface."; + reference + "IEEE 802.15.4-2006"; + } + identity otnOdu { + base iana-interface-type; + description + "OTN Optical Data Unit."; + } + identity otnOtu { + base iana-interface-type; + description + "OTN Optical channel Transport Unit."; + } + identity ifVfiType { + base iana-interface-type; + description + "VPLS Forwarding Instance Interface Type."; + } + identity g9981 { + base iana-interface-type; + description + "G.998.1 bonded interface."; + } + identity g9982 { + base iana-interface-type; + description + "G.998.2 bonded interface."; + } + identity g9983 { + base iana-interface-type; + description + "G.998.3 bonded interface."; + } + + identity aluEpon { + base iana-interface-type; + description + "Ethernet Passive Optical Networks (E-PON)."; + } + identity aluEponOnu { + base iana-interface-type; + description + "EPON Optical Network Unit."; + } + identity aluEponPhysicalUni { + base iana-interface-type; + description + "EPON physical User to Network interface."; + } + identity aluEponLogicalLink { + base iana-interface-type; + description + "The emulation of a point-to-point link over the EPON + layer."; + } + identity aluGponOnu { + base iana-interface-type; + description + "GPON Optical Network Unit."; + reference + "ITU-T G.984.2"; + } + identity aluGponPhysicalUni { + base iana-interface-type; + description + "GPON physical User to Network interface."; + reference + "ITU-T G.984.2"; + } + identity vmwareNicTeam { + base iana-interface-type; + description + "VMware NIC Team."; + } + identity docsOfdmDownstream { + base iana-interface-type; + description + "CATV Downstream OFDM interface."; + } + identity docsOfdmaUpstream { + base iana-interface-type; + description + "CATV Upstream OFDMA interface."; + } + identity gfast { + base iana-interface-type; + description + "G.fast port."; + reference + "ITU-T G.9701"; + } + identity sdci { + base iana-interface-type; + description + "SDCI (IO-Link)."; + reference + "IEC 61131-9 Edition 1.0 2013-09"; + } +} diff --git a/models/yang/common/ietf-inet-types.yang b/models/yang/common/ietf-inet-types.yang new file mode 100644 index 0000000000..2f14270dec --- /dev/null +++ b/models/yang/common/ietf-inet-types.yang @@ -0,0 +1,457 @@ +module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - ip-address-no-zone + - ipv4-address-no-zone + - ipv6-address-no-zone"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of types related to protocol fields ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code Point + that may be used for marking packets in a traffic stream. + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The ipv6-flow-label type represents the flow identifier or Flow + Label in an IPv6 packet header that may be used to + discriminate traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport-layer protocol such as UDP, TCP, DCCP, or + SCTP. Port numbers are assigned by IANA. A current list of + all assignments is available from . + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 793: Transmission Control Protocol + RFC 4960: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of types related to autonomous systems ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASes. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4001: Textual Conventions for Internet Network Addresses + RFC 6793: BGP Support for Four-Octet Autonomous System (AS) + Number Space"; + } + + /*** collection of types related to IP addresses and hostnames ***/ + + typedef ip-address { + type union { + type inet:ipv4-address; + type inet:ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representation + implies the IP version. This type supports scoped addresses + by allowing zone identifiers in the address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[\p{N}\p{L}]+)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format of IPv6 addresses uses the textual + representation defined in Section 4 of RFC 5952. The + canonical format for the zone index is the numerical + format as described in Section 11.2 of RFC 4007."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-no-zone { + type union { + type inet:ipv4-address-no-zone; + type inet:ipv6-address-no-zone; + } + description + "The ip-address-no-zone type represents an IP address and is + IP version neutral. The format of the textual representation + implies the IP version. This type does not support scoped + addresses since it does not allow zone identifiers in the + address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address-no-zone { + type inet:ipv4-address { + pattern '[0-9\.]*'; + } + description + "An IPv4 address without a zone index. This type, derived from + ipv4-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + } + + typedef ipv6-address-no-zone { + type inet:ipv6-address { + pattern '[0-9a-fA-F:\.]*'; + } + description + "An IPv6 address without a zone index. This type, derived from + ipv6-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-prefix { + type union { + type inet:ipv4-prefix; + type inet:ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-prefix type represents an IPv6 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The IPv6 address should have all bits that do not belong + to the prefix set to zero. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, the IPv6 address is represented + as defined in Section 4 of RFC 5952."; + reference + "RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + length "1..253"; + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitly or may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be A-labels as per RFC 5890."; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 5890: Internationalized Domain Names in Applications + (IDNA): Definitions and Document Framework"; + } + + typedef host { + type union { + type inet:ip-address; + type inet:domain-name; + } + description + "The host type represents either an IP address or a DNS + domain name."; + } + + typedef uri { + type string; + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by STD 66. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits, which are normalized to uppercase as described in + Section 6.2.2.1. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + +} diff --git a/models/yang/common/ietf-interfaces.yang b/models/yang/common/ietf-interfaces.yang new file mode 100644 index 0000000000..ff2586abe9 --- /dev/null +++ b/models/yang/common/ietf-interfaces.yang @@ -0,0 +1,1121 @@ +module ietf-interfaces { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + prefix if; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (Network Modeling) Working Group"; + + contact + "WG Web: + WG List: + + Editor: Martin Bjorklund + "; + + description + "This module contains a collection of YANG definitions for + managing network interfaces. + + Copyright (c) 2018 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8343; see + the RFC itself for full legal notices."; + + revision 2018-02-20 { + description + "Updated to support NMDA."; + reference + "RFC 8343: A YANG Data Model for Interface Management"; + } + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + + /* + * Typedefs + */ + + typedef interface-ref { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + interfaces."; + } + + /* + * Identities + */ + + identity interface-type { + description + "Base identity from which specific interface types are + derived."; + } + + /* + * Features + */ + + feature arbitrary-names { + description + "This feature indicates that the device allows user-controlled + interfaces to be named arbitrarily."; + } + feature pre-provisioning { + description + "This feature indicates that the device supports + pre-provisioning of interface configuration, i.e., it is + possible to configure an interface whose physical interface + hardware is not present on the device."; + } + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + + /* + * Data nodes + */ + + container interfaces { + description + "Interface parameters."; + + list interface { + key "name"; + + description + "The list of interfaces on the device. + + The status of an interface is available in this list in the + operational state. If the configuration of a + system-controlled interface cannot be used by the system + (e.g., the interface hardware present does not match the + interface type), then the configuration is not applied to + the system-controlled interface shown in the operational + state. If the configuration of a user-controlled interface + cannot be used by the system, the configured interface is + not instantiated in the operational state. + + System-controlled interfaces created by the system are + always present in this list in the operational state, + whether or not they are configured."; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + operational state, the server MAY reject the request if + the implementation does not support pre-provisioning of + interfaces or if the name refers to an interface that can + never exist in the system. A Network Configuration + Protocol (NETCONF) server MUST reply with an rpc-error + with the error-tag 'invalid-value' in this case. + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + operational state. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + configuration."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the intended configuration to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + Changes in this leaf in the intended configuration are + reflected in ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled { + value 1; + description + "The device will generate linkUp/linkDown SNMP + notifications for this interface."; + } + enum disabled { + value 2; + description + "The device will not generate linkUp/linkDown SNMP + notifications for this interface."; + } + } + description + "Controls whether linkUp/linkDown SNMP notifications + should be generated for this interface. + + If this node is not configured, the value 'enabled' is + operationally used by the server for interfaces that do + not operate on top of any other interface (i.e., there are + no 'lower-layer-if' entries), and 'disabled' otherwise."; + reference + "RFC 2863: The Interfaces Group MIB - + ifLinkUpDownTrapEnable"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + enum testing { + value 3; + description + "In some test mode."; + } + } + config false; + mandatory true; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + config false; + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + config false; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + config false; + mandatory true; + description + "The ifIndex value for the ifEntry represented by this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + config false; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-ref; + config false; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-ref; + config false; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + config false; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + container statistics { + config false; + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf out-octets { + type yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + leaf out-multicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + + } + } + + /* + * Legacy typedefs + */ + + typedef interface-state-ref { + type leafref { + path "/if:interfaces-state/if:interface/if:name"; + } + status deprecated; + description + "This type is used by data models that need to reference + the operationally present interfaces."; + } + + /* + * Legacy operational state data nodes + */ + + container interfaces-state { + config false; + status deprecated; + description + "Data nodes for the operational state of interfaces."; + + list interface { + key "name"; + status deprecated; + + description + "The list of interfaces on the device. + + System-controlled interfaces created by the system are + always present in this list, whether or not they are + configured."; + + leaf name { + type string; + status deprecated; + description + "The name of the interface. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + status deprecated; + description + "The type of the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + enum testing { + value 3; + description + "In some test mode."; + } + } + mandatory true; + status deprecated; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + mandatory true; + status deprecated; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + status deprecated; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + mandatory true; + status deprecated; + description + "The ifIndex value for the ifEntry represented by this + interface."; + + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + status deprecated; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-state-ref; + status deprecated; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-state-ref; + status deprecated; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + status deprecated; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + container statistics { + status deprecated; + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + status deprecated; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + status deprecated; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + status deprecated; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf out-octets { + type yang:counter64; + status deprecated; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + leaf out-multicast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + status deprecated; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + } + } +} diff --git a/models/yang/common/ietf-yang-types.yang b/models/yang/common/ietf-yang-types.yang new file mode 100644 index 0000000000..ee58fa3ab0 --- /dev/null +++ b/models/yang/common/ietf-yang-types.yang @@ -0,0 +1,474 @@ +module ietf-yang-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types"; + prefix "yang"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - yang-identifier + - hex-string + - uuid + - dotted-quad"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of counter and gauge types ***/ + + typedef counter32 { + type uint32; + description + "The counter32 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter32 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter32 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter32. + + In the value set and its semantics, this type is equivalent + to the Counter32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter32 { + type yang:counter32; + default "0"; + description + "The zero-based-counter32 type represents a counter32 + that has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter32 textual convention of the SMIv2."; + reference + "RFC 4502: Remote Network Monitoring Management Information + Base Version 2"; + } + + typedef counter64 { + type uint64; + description + "The counter64 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter64 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter64 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter64. + + In the value set and its semantics, this type is equivalent + to the Counter64 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter64 { + type yang:counter64; + default "0"; + description + "The zero-based-counter64 type represents a counter64 that + has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter64 textual convention of the SMIv2."; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + typedef gauge32 { + type uint32; + description + "The gauge32 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^32-1 (4294967295 decimal), and + the minimum value cannot be smaller than 0. The value of + a gauge32 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge32 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the Gauge32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef gauge64 { + type uint64; + description + "The gauge64 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^64-1 (18446744073709551615), and + the minimum value cannot be smaller than 0. The value of + a gauge64 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge64 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the CounterBasedGauge64 SMIv2 textual convention defined + in RFC 2856"; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + /*** collection of identifier-related types ***/ + + typedef object-identifier { + type string { + pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))' + + '(\.(0|([1-9]\d*)))*'; + } + description + "The object-identifier type represents administratively + assigned names in a registration-hierarchical-name tree. + + Values of this type are denoted as a sequence of numerical + non-negative sub-identifier values. Each sub-identifier + value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers + are separated by single dots and without any intermediate + whitespace. + + The ASN.1 standard restricts the value space of the first + sub-identifier to 0, 1, or 2. Furthermore, the value space + of the second sub-identifier is restricted to the range + 0 to 39 if the first sub-identifier is 0 or 1. Finally, + the ASN.1 standard requires that an object identifier + has always at least two sub-identifiers. The pattern + captures these restrictions. + + Although the number of sub-identifiers is not limited, + module designers should realize that there may be + implementations that stick with the SMIv2 limit of 128 + sub-identifiers. + + This type is a superset of the SMIv2 OBJECT IDENTIFIER type + since it is not restricted to 128 sub-identifiers. Hence, + this type SHOULD NOT be used to represent the SMIv2 OBJECT + IDENTIFIER type; the object-identifier-128 type SHOULD be + used instead."; + reference + "ISO9834-1: Information technology -- Open Systems + Interconnection -- Procedures for the operation of OSI + Registration Authorities: General procedures and top + arcs of the ASN.1 Object Identifier tree"; + } + + typedef object-identifier-128 { + type object-identifier { + pattern '\d*(\.\d*){1,127}'; + } + description + "This type represents object-identifiers restricted to 128 + sub-identifiers. + + In the value set and its semantics, this type is equivalent + to the OBJECT IDENTIFIER type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef yang-identifier { + type string { + length "1..max"; + pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*'; + pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*'; + } + description + "A YANG identifier string as defined by the 'identifier' + rule in Section 12 of RFC 6020. An identifier must + start with an alphabetic character or an underscore + followed by an arbitrary sequence of alphabetic or + numeric characters, underscores, hyphens, or dots. + + A YANG identifier MUST NOT start with any possible + combination of the lowercase or uppercase character + sequence 'xml'."; + reference + "RFC 6020: YANG - A Data Modeling Language for the Network + Configuration Protocol (NETCONF)"; + } + + /*** collection of types related to date and time***/ + + typedef date-and-time { + type string { + pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?' + + '(Z|[\+\-]\d{2}:\d{2})'; + } + description + "The date-and-time type is a profile of the ISO 8601 + standard for representation of dates and times using the + Gregorian calendar. The profile is defined by the + date-time production in Section 5.6 of RFC 3339. + + The date-and-time type is compatible with the dateTime XML + schema type with the following notable exceptions: + + (a) The date-and-time type does not allow negative years. + + (b) The date-and-time time-offset -00:00 indicates an unknown + time zone (see RFC 3339) while -00:00 and +00:00 and Z + all represent the same time zone in dateTime. + + (c) The canonical format (see below) of data-and-time values + differs from the canonical format used by the dateTime XML + schema type, which requires all times to be in UTC using + the time-offset 'Z'. + + This type is not equivalent to the DateAndTime textual + convention of the SMIv2 since RFC 3339 uses a different + separator between full-date and full-time and provides + higher resolution of time-secfrac. + + The canonical format for date-and-time values with a known time + zone uses a numeric time zone offset that is calculated using + the device's configured known offset to UTC time. A change of + the device's offset to UTC time will cause date-and-time values + to change accordingly. Such changes might happen periodically + in case a server follows automatically daylight saving time + (DST) time zone offset changes. The canonical format for + date-and-time values with an unknown time zone (usually + referring to the notion of local time) uses the time-offset + -00:00."; + reference + "RFC 3339: Date and Time on the Internet: Timestamps + RFC 2579: Textual Conventions for SMIv2 + XSD-TYPES: XML Schema Part 2: Datatypes Second Edition"; + } + + typedef timeticks { + type uint32; + description + "The timeticks type represents a non-negative integer that + represents the time, modulo 2^32 (4294967296 decimal), in + hundredths of a second between two epochs. When a schema + node is defined that uses this type, the description of + the schema node identifies both of the reference epochs. + + In the value set and its semantics, this type is equivalent + to the TimeTicks type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef timestamp { + type yang:timeticks; + description + "The timestamp type represents the value of an associated + timeticks schema node at which a specific occurrence + happened. The specific occurrence must be defined in the + description of any schema node defined using this type. When + the specific occurrence occurred prior to the last time the + associated timeticks attribute was zero, then the timestamp + value is zero. Note that this requires all timestamp values + to be reset to zero when the value of the associated timeticks + attribute reaches 497+ days and wraps around to zero. + + The associated timeticks schema node must be specified + in the description of any schema node using this type. + + In the value set and its semantics, this type is equivalent + to the TimeStamp textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of generic address types ***/ + + typedef phys-address { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + + description + "Represents media- or physical-level addresses represented + as a sequence octets, each octet represented by two hexadecimal + numbers. Octets are separated by colons. The canonical + representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the PhysAddress textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + typedef mac-address { + type string { + pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'; + } + description + "The mac-address type represents an IEEE 802 MAC address. + The canonical representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the MacAddress textual convention of the SMIv2."; + reference + "IEEE 802: IEEE Standard for Local and Metropolitan Area + Networks: Overview and Architecture + RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of XML-specific types ***/ + + typedef xpath1.0 { + type string; + description + "This type represents an XPATH 1.0 expression. + + When a schema node is defined that uses this type, the + description of the schema node MUST specify the XPath + context in which the XPath expression is evaluated."; + reference + "XPATH: XML Path Language (XPath) Version 1.0"; + } + + /*** collection of string types ***/ + + typedef hex-string { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + description + "A hexadecimal string with octets represented as hex digits + separated by colons. The canonical representation uses + lowercase characters."; + } + + typedef uuid { + type string { + pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-' + + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; + } + description + "A Universally Unique IDentifier in the string representation + defined in RFC 4122. The canonical representation uses + lowercase characters. + + The following is an example of a UUID in string representation: + f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + "; + reference + "RFC 4122: A Universally Unique IDentifier (UUID) URN + Namespace"; + } + + typedef dotted-quad { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + } + description + "An unsigned 32-bit number expressed in the dotted-quad + notation, i.e., four octets written as decimal numbers + and separated with the '.' (full stop) character."; + } +} diff --git a/models/yang/common/openconfig-aaa-radius.yang b/models/yang/common/openconfig-aaa-radius.yang new file mode 100644 index 0000000000..a18b9d68d2 --- /dev/null +++ b/models/yang/common/openconfig-aaa-radius.yang @@ -0,0 +1,186 @@ +submodule openconfig-aaa-radius { + + yang-version "1"; + + belongs-to "openconfig-aaa" { + prefix "oc-aaa"; + } + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa-types { prefix oc-aaa-types; } + import openconfig-types { prefix oc-types; } + import openconfig-yang-types { prefix oc-yang; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to the RADIUS protocol for authentication, + authorization, and accounting."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // extension statements + + // feature statements + + // identity statements + + identity RADIUS { + base oc-aaa-types:AAA_SERVER_TYPE; + description + "Remote Authentication Dial In User Service (RADIUS) AAA + server"; + reference + "RFC 2865 - Remote Authentication Dial In User Service + (RADIUS)"; + } + + // typedef statements + + // grouping statements + + grouping aaa-radius-server-config { + description + "Configuration data for a RADIUS server"; + + leaf auth-port { + type oc-inet:port-number; + default 1812; + description + "Port number for authentication requests"; + } + + leaf acct-port { + type oc-inet:port-number; + default 1813; + description + "Port number for accounting requests"; + } + + leaf secret-key { + type oc-types:routing-password; + description + "The unencrypted shared key used between the authentication + server and the device."; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address to use in messages to the RADIUS server"; + } + + leaf retransmit-attempts { + type uint8; + description + "Number of times the system may resend a request to the + RADIUS server when it is unresponsive"; + } + } + + grouping aaa-radius-server-state { + description + "Operational state data for a RADIUS server"; + + container counters { + description + "A collection of RADIUS related state objects."; + + leaf retried-access-requests { + type oc-yang:counter64; + description + "Retransmitted Access-Request messages."; + } + + leaf access-accepts { + type oc-yang:counter64; + description + "Received Access-Accept messages."; + } + + leaf access-rejects { + type oc-yang:counter64; + description + "Received Access-Reject messages."; + } + + leaf timeout-access-requests { + type oc-yang:counter64; + description + "Access-Request messages that have timed-out, + requiring retransmission."; + } + } + } + + grouping aaa-radius-server-top { + description + "Top-level grouping for RADIUS server data"; + + container radius { + description + "Top-level container for RADIUS server data"; + + container config { + description + "Configuration data for RADIUS servers"; + + uses aaa-radius-server-config; + } + + container state { + + config false; + + description + "Operational state data for RADIUS servers"; + + uses aaa-radius-server-config; + uses aaa-radius-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-aaa-tacacs.yang b/models/yang/common/openconfig-aaa-tacacs.yang new file mode 100644 index 0000000000..1320bd0cf5 --- /dev/null +++ b/models/yang/common/openconfig-aaa-tacacs.yang @@ -0,0 +1,142 @@ +submodule openconfig-aaa-tacacs { + + yang-version "1"; + + belongs-to "openconfig-aaa" { + prefix "oc-aaa"; + } + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa-types { prefix oc-aaa-types; } + import openconfig-types { prefix oc-types; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to the TACACS+ protocol for authentication, + authorization, and accounting."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // extension statements + + // feature statements + + // identity statements + + identity TACACS { + base oc-aaa-types:AAA_SERVER_TYPE; + description + "Terminal Access Controller Access Control System (TACACS+) + AAA server"; + reference + "The TACACS+ Protocol (draft-ietf-opsawg-tacacs-05) + RFC 1492 - An Access Control Protocol, Sometimes Called + TACACS"; + } + + // typedef statements + + // grouping statements + + grouping aaa-tacacs-server-config { + description + "Configuration data for a TACACS+ server"; + + leaf port { + type oc-inet:port-number; + default 49; + description + "The port number on which to contact the TACACS server"; + } + + leaf secret-key { + type oc-types:routing-password; + description + "The unencrypted shared key used between the authentication + server and the device."; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address to use in messages to the TACACS server"; + } + } + + grouping aaa-tacacs-server-state { + description + "Operational state data for a TACACS+ server"; + } + + grouping aaa-tacacs-server-top { + description + "Top-level grouping for TACACS+ sever data"; + + container tacacs { + description + "Top-level container for TACACS+ server data"; + + container config { + description + "Configuration data for TACACS+ server"; + + uses aaa-tacacs-server-config; + } + + container state { + + config false; + + description + "Operational state data for TACACS+ server"; + + uses aaa-tacacs-server-config; + uses aaa-tacacs-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-aaa-types.yang b/models/yang/common/openconfig-aaa-types.yang new file mode 100644 index 0000000000..8385eca79e --- /dev/null +++ b/models/yang/common/openconfig-aaa-types.yang @@ -0,0 +1,172 @@ +module openconfig-aaa-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/aaa/types"; + + prefix "oc-aaa-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 shared types for data related to AAA + (authentication, authorization, accounting)."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2018-04-12" { + description + "Add when conditions, correct identities"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + 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 AAA_SERVER_TYPE { + description + "Base identity for types of AAA servers"; + } + + + identity SYSTEM_DEFINED_ROLES { + description + "Base identity for system_defined roles that can be assigned + to users."; + } + + identity SYSTEM_ROLE_ADMIN { + base SYSTEM_DEFINED_ROLES; + description + "Built-in role that allows the equivalent of superuser + permission for all configuration and operational commands + on the device."; + } + + identity AAA_ACCOUNTING_EVENT_TYPE { + description + "Base identity for specifying events types that should be + sent to AAA server for accounting"; + } + + identity AAA_ACCOUNTING_EVENT_COMMAND { + base AAA_ACCOUNTING_EVENT_TYPE; + description + "Specifies interactive command events for AAA accounting"; + } + + identity AAA_ACCOUNTING_EVENT_LOGIN { + base AAA_ACCOUNTING_EVENT_TYPE; + description + "Specifies login events for AAA accounting"; + } + + identity AAA_AUTHORIZATION_EVENT_TYPE { + description + "Base identity for specifying activities that should be + sent to AAA server for authorization"; + } + + identity AAA_AUTHORIZATION_EVENT_COMMAND { + base AAA_AUTHORIZATION_EVENT_TYPE; + description + "Specifies interactive command events for AAA authorization"; + } + + identity AAA_AUTHORIZATION_EVENT_CONFIG { + base AAA_AUTHORIZATION_EVENT_TYPE; + description + "Specifies configuration (e.g., EXEC) events for AAA + authorization"; + } + + identity AAA_METHOD_TYPE { + description + "Base identity to define well-known methods for AAA + operations"; + } + + identity TACACS_ALL { + base AAA_METHOD_TYPE; + description + "The group of all TACACS+ servers."; + } + + identity RADIUS_ALL { + base AAA_METHOD_TYPE; + description + "The group of all RADIUS servers."; + } + + identity LOCAL { + base AAA_METHOD_TYPE; + description + "Locally configured method for AAA operations."; + } + + + // typedef statements + + typedef crypt-password-type { + type string; + description + "A password that is hashed based on the hash algorithm + indicated by the prefix in the string. The string + takes the following form, based on the Unix crypt function: + + $[$=(,=)*][$[$]] + + Common hash functions include: + + id | hash function + ---+--------------- + 1 | MD5 + 2a| Blowfish + 2y| Blowfish (correct handling of 8-bit chars) + 5 | SHA-256 + 6 | SHA-512 + + These may not all be supported by a target device."; + } + + +} diff --git a/models/yang/common/openconfig-aaa.yang b/models/yang/common/openconfig-aaa.yang new file mode 100644 index 0000000000..7a2fffa003 --- /dev/null +++ b/models/yang/common/openconfig-aaa.yang @@ -0,0 +1,822 @@ +module openconfig-aaa { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/aaa"; + + prefix "oc-aaa"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-aaa-types { prefix oc-aaa-types; } + + include openconfig-aaa-tacacs; + include openconfig-aaa-radius; + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to authorization, authentication, and accounting (AAA) + management. + + Portions of this model reuse data definitions or structure from + RFC 7317 - A YANG Data Model for System Management"; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2018-04-12" { + description + "Add when conditions, correct identities"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + 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 aaa-servergroup-common-config { + description + "Configuration data for AAA server groups"; + + leaf name { + type string; + description + "Name for the server group"; + } + + leaf type { + type identityref { + base oc-aaa-types:AAA_SERVER_TYPE; + } + description + "AAA server type -- all servers in the group must be of this + type"; + } + } + + grouping aaa-servergroup-common-state { + description + "Operational state data for AAA server groups"; + + //TODO: add list of group members as opstate + } + + grouping aaa-servergroup-common-top { + description + "Top-level grouping for AAA server groups"; + + container server-groups { + description + "Enclosing container for AAA server groups"; + + list server-group { + key "name"; + description + "List of AAA server groups. All servers in a group + must have the same type as indicated by the server + type."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to configured name of the server group"; + } + + container config { + description + "Configuration data for each server group"; + + uses aaa-servergroup-common-config; + } + + container state { + config false; + + description + "Operational state data for each server group"; + + uses aaa-servergroup-common-config; + uses aaa-servergroup-common-state; + } + + uses aaa-server-top; + } + } + } + + grouping aaa-server-config { + description + "Common configuration data for AAA servers"; + + leaf name { + type string; + description + "Name assigned to the server"; + } + + + leaf address { + type oc-inet:ip-address; + description "Address of the authentication server"; + } + + leaf timeout { + type uint16; + units seconds; + description + "Set the timeout in seconds on responses from the AAA + server"; + } + } + + grouping aaa-server-state { + description + "Common operational state data for AAA servers"; + + leaf connection-opens { + type oc-yang:counter64; + description + "Number of new connection requests sent to the server, e.g. + socket open"; + } + + leaf connection-closes { + type oc-yang:counter64; + description + "Number of connection close requests sent to the server, e.g. + socket close"; + } + + leaf connection-aborts { + type oc-yang:counter64; + description + "Number of aborted connections to the server. These do + not include connections that are close gracefully."; + } + + leaf connection-failures { + type oc-yang:counter64; + description + "Number of connection failures to the server"; + } + + leaf connection-timeouts { + type oc-yang:counter64; + description + "Number of connection timeouts to the server"; + } + + leaf messages-sent { + type oc-yang:counter64; + description + "Number of messages sent to the server"; + } + + leaf messages-received { + type oc-yang:counter64; + description + "Number of messages received by the server"; + } + + leaf errors-received { + type oc-yang:counter64; + description + "Number of error messages received from the server"; + } + + } + + grouping aaa-server-top { + description + "Top-level grouping for list of AAA servers"; + + container servers { + description + "Enclosing container the list of servers"; + + list server { + key "address"; + description + "List of AAA servers"; + + leaf address { + type leafref { + path "../config/address"; + } + description + "Reference to the configured address of the AAA server"; + } + + container config { + description + "Configuration data "; + + uses aaa-server-config; + } + + container state { + config false; + + description + "Operational state data "; + + uses aaa-server-config; + uses aaa-server-state; + } + + uses aaa-tacacs-server-top { + when "../../config/type = 'oc-aaa-types:TACACS'"; + } + + uses aaa-radius-server-top { + when "../../config/type = 'oc-aaa-types:RADIUS'"; + } + } + } + } + + grouping aaa-admin-config { + description + "Configuration data for the system built-in + administrator / root user account"; + + leaf admin-password { + type string; + oc-ext:openconfig-hashed-value; + description + "The admin/root password, supplied as a cleartext string. + The system should hash and only store the password as a + hashed value."; + } + + leaf admin-password-hashed { + type oc-aaa-types:crypt-password-type; + description + "The admin/root password, supplied as a hashed value + using the notation described in the definition of the + crypt-password-type."; + } + } + + grouping aaa-admin-state { + description + "Operational state data for the root user"; + + leaf admin-username { + type string; + description + "Name of the administrator user account, e.g., admin, root, + etc."; + } + } + + grouping aaa-authentication-admin-top { + description + "Top-level grouping for root user configuration and state + data"; + + container admin-user { + description + "Top-level container for the system root or admin user + configuration and operational state"; + + container config { + description + "Configuration data for the root user account"; + + uses aaa-admin-config; + } + + container state { + config false; + + description + "Operational state data for the root user account"; + + uses aaa-admin-config; + uses aaa-admin-state; + } + } + } + grouping aaa-authentication-user-config { + description + "Configuration data for local users"; + + leaf username { + type string; + description + "Assigned username for this user"; + } + + leaf password { + type string; + oc-ext:openconfig-hashed-value; + description + "The user password, supplied as cleartext. The system + must hash the value and only store the hashed value."; + } + + leaf password-hashed { + type oc-aaa-types:crypt-password-type; + description + "The user password, supplied as a hashed value + using the notation described in the definition of the + crypt-password-type."; + } + + leaf ssh-key { + type string; + description + "SSH public key for the user (RSA or DSA)"; + } + + leaf role { + type union { + type string; + type identityref { + base oc-aaa-types:SYSTEM_DEFINED_ROLES; + } + } + description + "Role assigned to the user. The role may be supplied + as a string or a role defined by the SYSTEM_DEFINED_ROLES + identity."; + } + } + + grouping aaa-authentication-user-state { + description + "Operational state data for local users"; + } + + grouping aaa-authentication-user-top { + description + "Top-level grouping for local users"; + + container users { + description + "Enclosing container list of local users"; + + list user { + key "username"; + description + "List of local users on the system"; + + leaf username { + type leafref { + path "../config/username"; + } + description + "References the configured username for the user"; + } + + container config { + description + "Configuration data for local users"; + + uses aaa-authentication-user-config; + } + + container state { + config false; + + description + "Operational state data for local users"; + + uses aaa-authentication-user-config; + uses aaa-authentication-user-state; + } + } + + } + } + + grouping aaa-accounting-methods-common { + description + "Common definitions for accounting methods"; + + leaf-list accounting-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + //TODO: in YANG 1.1 this should be converted to a leafref to + //point to the server group name. + } + ordered-by user; + description + "An ordered list of methods used for AAA accounting for this + event type. The method is defined by the destination for + accounting data, which may be specified as the group of + all TACACS+/RADIUS servers, a defined server group, or + the local system."; + } + } + + + grouping aaa-accounting-events-config { + description + "Configuration data for AAA accounting events"; + + leaf event-type { + type identityref { + base oc-aaa-types:AAA_ACCOUNTING_EVENT_TYPE; + } + description + "The type of activity to record at the AAA accounting + server"; + } + + leaf record { + type enumeration { + enum START_STOP { + description + "Send START record to the accounting server at the + beginning of the activity, and STOP record at the + end of the activity."; + } + enum STOP { + description + "Send STOP record to the accounting server when the + user activity completes"; + } + } + description + "Type of record to send to the accounting server for this + activity type"; + } + } + + grouping aaa-accounting-events-state { + description + "Operational state data for accounting events"; + } + + grouping aaa-accounting-events-top { + description + "Top-level grouping for accounting events"; + + container events { + description + "Enclosing container for defining handling of events + for accounting"; + + list event { + key "event-type"; + description + "List of events subject to accounting"; + + leaf event-type { + type leafref { + path "../config/event-type"; + } + description + "Reference to the event-type being logged at the + accounting server"; + } + + container config { + description + "Configuration data for accounting events"; + + uses aaa-accounting-events-config; + } + + container state { + config false; + + description + "Operational state data for accounting events"; + + uses aaa-accounting-events-config; + uses aaa-accounting-events-state; + } + } + } + } + + grouping aaa-accounting-config { + description + "Configuration data for event accounting"; + + uses aaa-accounting-methods-common; + + } + + grouping aaa-accounting-state { + description + "Operational state data for event accounting services"; + } + + grouping aaa-accounting-top { + description + "Top-level grouping for user activity accounting"; + + container accounting { + description + "Top-level container for AAA accounting"; + + container config { + description + "Configuration data for user activity accounting."; + + uses aaa-accounting-config; + } + + container state { + config false; + + description + "Operational state data for user accounting."; + + uses aaa-accounting-config; + uses aaa-accounting-state; + } + + uses aaa-accounting-events-top; + + } + } + + grouping aaa-authorization-methods-config { + description + "Common definitions for authorization methods for global + and per-event type"; + + leaf-list authorization-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + } + ordered-by user; + description + "Ordered list of methods for authorizing commands. The first + method that provides a response (positive or negative) should + be used. The list may contain a well-defined method such + as the set of all TACACS or RADIUS servers, or the name of + a defined AAA server group. The system must validate + that the named server group exists."; + } + } + + grouping aaa-authorization-events-config { + description + "Configuration data for AAA authorization events"; + + leaf event-type { + type identityref { + base oc-aaa-types:AAA_AUTHORIZATION_EVENT_TYPE; + } + description + "The type of event to record at the AAA authorization + server"; + } + } + + grouping aaa-authorization-events-state { + description + "Operational state data for AAA authorization events"; + } + + grouping aaa-authorization-events-top { + description + "Top-level grouping for authorization events"; + + container events { + description + "Enclosing container for the set of events subject + to authorization"; + + list event { + key "event-type"; + description + "List of events subject to AAA authorization"; + + leaf event-type { + type leafref { + path "../config/event-type"; + } + description + "Reference to the event-type list key"; + } + + container config { + description + "Configuration data for each authorized event"; + + uses aaa-authorization-events-config; + } + + container state { + config false; + + description + "Operational state data for each authorized activity"; + + uses aaa-authorization-events-config; + uses aaa-authorization-events-state; + } + } + } + } + + grouping aaa-authorization-config { + description + "Configuration data for AAA authorization"; + + uses aaa-authorization-methods-config; + } + + grouping aaa-authorization-state { + description + "Operational state data for AAA authorization"; + } + + grouping aaa-authorization-top { + description + "Top-level grouping for AAA authorization"; + + container authorization { + description + "Top-level container for AAA authorization configuration + and operational state data"; + + container config { + description + "Configuration data for authorization based on AAA + methods"; + + uses aaa-authorization-config; + } + + container state { + config false; + + description + "Operational state data for authorization based on AAA"; + + uses aaa-authorization-config; + uses aaa-authorization-state; + } + + uses aaa-authorization-events-top; + + } + } + + grouping aaa-authentication-config { + description + "Configuration data for global authentication"; + + leaf-list authentication-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + //TODO: string should be a leafref to a defined + //server group. this will be possible in YANG 1.1 + //type leafref { + //path "/aaa/server-groups/server-group/config/name"; + //} + } + ordered-by user; + description + "Ordered list of authentication methods for users. This + can be either a reference to a server group, or a well- + defined designation in the AAA_METHOD_TYPE identity. If + authentication fails with one method, the next defined + method is tried -- failure of all methods results in the + user being denied access."; + } + } + + grouping aaa-authentication-state { + description + "Operational state data for global authentication"; + } + + grouping aaa-authentication-top { + description + "Top-level grouping for top-level authentication"; + + container authentication { + description + "Top-level container for global authentication data"; + + container config { + description + "Configuration data for global authentication services"; + + uses aaa-authentication-config; + } + + container state { + config false; + + description + "Operational state data for global authentication + services"; + + uses aaa-authentication-config; + uses aaa-authentication-state; + } + + uses aaa-authentication-admin-top; + uses aaa-authentication-user-top; + } + } + + grouping aaa-config { + description + "Configuration data for top level AAA"; + } + + grouping aaa-state { + description + "Operational state data for top level AAA"; + } + + grouping aaa-top { + description + "Top-level grouping for AAA services"; + + container aaa { + description + "Top-level container for AAA services"; + + container config { + description + "Configuration data for top level AAA services"; + + uses aaa-config; + } + + container state { + config false; + + description + "Operational state data for top level AAA services "; + + uses aaa-config; + uses aaa-state; + } + + uses aaa-authentication-top; + uses aaa-authorization-top; + uses aaa-accounting-top; + uses aaa-servergroup-common-top; + + } + } + + + + // data definition statements + + +} diff --git a/models/yang/common/openconfig-alarm-types.yang b/models/yang/common/openconfig-alarm-types.yang new file mode 100644 index 0000000000..c4617b5e6b --- /dev/null +++ b/models/yang/common/openconfig-alarm-types.yang @@ -0,0 +1,150 @@ +module openconfig-alarm-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/alarms/types"; + + prefix "oc-alarm-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 operational state data related to alarms + that the device is reporting. + + This model reuses some data items defined in the draft IETF + YANG Alarm Module: + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02 + + Portions of this code were derived from the draft IETF YANG Alarm + Module. Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-01-16" { + description + "Moved alarm identities into separate types module"; + reference "0.2.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + identity OPENCONFIG_ALARM_TYPE_ID { + description + "Base identity for alarm type ID profiles"; + } + + identity AIS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines an alarm indication signal type of alarm"; + } + + identity EQPT { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines an equipment related type of alarm that is specific + to the physical hardware"; + } + + identity LOS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines a loss of signal type of alarm"; + } + + identity OTS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines a optical transport signal type of alarm"; + } + + identity OPENCONFIG_ALARM_SEVERITY { + description + "Base identity for alarm severity profiles. Derived + identities are based on contents of the draft + IETF YANG Alarm Module"; + reference + "IETF YANG Alarm Module: Draft - typedef severity + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + + } + + identity UNKNOWN { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that the severity level could not be determined. + This level SHOULD be avoided."; + } + + identity MINOR { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates the existence of a non-service affecting fault + condition and that corrective action should be taken in + order to prevent a more serious (for example, service + affecting) fault. Such a severity can be reported, for + example, when the detected alarm condition is not currently + degrading the capacity of the resource"; + } + + identity WARNING { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates the detection of a potential or impending service + affecting fault, before any significant effects have been felt. + Action should be taken to further diagnose (if necessary) and + correct the problem in order to prevent it from becoming a more + serious service affecting fault."; + } + + identity MAJOR { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that a service affecting condition has developed + and an urgent corrective action is required. Such a severity + can be reported, for example, when there is a severe + degradation in the capability of the resource and its full + capability must be restored."; + } + + identity CRITICAL { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that a service affecting condition has occurred + and an immediate corrective action is required. Such a + severity can be reported, for example, when a resource becomes + totally out of service and its capability must be restored."; + } + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-alarms.yang b/models/yang/common/openconfig-alarms.yang new file mode 100644 index 0000000000..5bcc563149 --- /dev/null +++ b/models/yang/common/openconfig-alarms.yang @@ -0,0 +1,231 @@ +module openconfig-alarms { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/alarms"; + + prefix "oc-alarms"; + + // import some basic types + import openconfig-alarm-types { prefix oc-alarm-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-types { prefix oc-types; } + import openconfig-platform { prefix oc-platform; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines operational state data related to alarms + that the device is reporting. + + This model reuses some data items defined in the draft IETF + YANG Alarm Module: + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02 + + Portions of this code were derived from the draft IETF YANG Alarm + Module. Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2018-01-16" { + description + "Moved alarm identities into separate types module"; + reference "0.3.0"; + } + + revision "2018-01-10" { + description + "Make alarms list read only"; + reference "0.2.0"; + } + + revision "2017-08-24" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping alarm-state { + description + "Operational state data for device alarms"; + + leaf id { + type string; + description + "Unique ID for the alarm -- this will not be a + configurable parameter on many implementations"; + } + + leaf resource { + type string; + description + "The item that is under alarm within the device. The + resource may be a reference to an item which is + defined elsewhere in the model. For example, it + may be a platform/component, interfaces/interface, + terminal-device/logical-channels/channel, etc. In this + case the system should match the name of the referenced + item exactly. The referenced item could alternatively be + the path of the item within the model."; + reference + "IETF YANG Alarm Module: Draft - typedef resource + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf text { + type string; + description + "The string used to inform operators about the alarm. This + MUST contain enough information for an operator to be able + to understand the problem. If this string contains structure, + this format should be clearly documented for programs to be + able to parse that information"; + reference + "IETF YANG Alarm Module: Draft - typedef alarm-text + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf time-created { + type oc-types:timeticks64; + description + "The time at which the alarm was raised by the system. + This value is expressed as nanoseconds since the Unix Epoch"; + } + + leaf severity { + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_SEVERITY; + } + description + "The severity level indicating the criticality and impact + of the alarm"; + reference + "IETF YANG Alarm Module: Draft - typedef severity + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf type-id { + type union { + type string; + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_TYPE_ID; + } + } + description + "The abbreviated name of the alarm, for example LOS, + EQPT, or OTS. Also referred to in different systems as + condition type, alarm identifier, or alarm mnemonic. It + is recommended to use the OPENCONFIG_ALARM_TYPE_ID + identities where possible and only use the string type + when the desired identityref is not yet defined"; + reference + "IETF YANG Alarm Module: Draft - typedef alarm-type-id + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + } + + grouping alarm-config { + description + "Configuration data for device alarms"; + } + + grouping alarms-top { + description + "Top-level grouping for device alarms"; + + container alarms { + description + "Top-level container for device alarms"; + + config false; + + list alarm { + key "id"; + description + "List of alarms, keyed by a unique id"; + + leaf id { + type leafref { + path "../state/id"; + } + + description + "References the unique alarm id"; + } + + container config { + description + "Configuration data for each alarm"; + + uses alarm-config; + } + + container state { + config false; + + description + "Operational state data for a device alarm"; + + uses alarm-config; + uses alarm-state; + } + } + } + } + + + // augments + + augment "/oc-platform:components/oc-platform:component/oc-platform:state" { + description + "Adds specific alarms related to a component."; + + leaf equipment-failure { + type boolean; + default "false"; + description + "If true, the hardware indicates that the component's physical equipment + has failed"; + } + + leaf equipment-mismatch { + type boolean; + default "false"; + description + "If true, the hardware indicates that the component inserted into the + affected component's physical location is of a different type than what + is configured"; + } + } + +} diff --git a/models/yang/common/openconfig-extensions.yang b/models/yang/common/openconfig-extensions.yang new file mode 100644 index 0000000000..361e55426d --- /dev/null +++ b/models/yang/common/openconfig-extensions.yang @@ -0,0 +1,175 @@ +module openconfig-extensions { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/openconfig-ext"; + + prefix "oc-ext"; + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides extensions to the YANG language to allow + OpenConfig specific functionality and meta-data to be defined."; + + revision "2018-10-17" { + description + "Add extension for regular expression type."; + reference "0.4.0"; + } + + revision "2017-04-11" { + description + "rename password type to 'hashed' and clarify description"; + reference "0.3.0"; + } + + revision "2017-01-29" { + description + "Added extension for annotating encrypted values."; + reference "0.2.0"; + } + + revision "2015-10-09" { + description + "Initial OpenConfig public release"; + reference "0.1.0"; + } + + + // extension statements + extension openconfig-version { + argument "semver" { + yin-element false; + } + description + "The OpenConfig version number for the module. This is + expressed as a semantic version number of the form: + x.y.z + where: + * x corresponds to the major version, + * y corresponds to a minor version, + * z corresponds to a patch version. + This version corresponds to the model file within which it is + defined, and does not cover the whole set of OpenConfig models. + Where several modules are used to build up a single block of + functionality, the same module version is specified across each + file that makes up the module. + + A major version number of 0 indicates that this model is still + in development (whether within OpenConfig or with industry + partners), and is potentially subject to change. + + Following a release of major version 1, all modules will + increment major revision number where backwards incompatible + changes to the model are made. + + The minor version is changed when features are added to the + model that do not impact current clients use of the model. + + The patch-level version is incremented when non-feature changes + (such as bugfixes or clarifications to human-readable + descriptions that do not impact model functionality) are made + that maintain backwards compatibility. + + The version number is stored in the module meta-data."; + } + + extension openconfig-hashed-value { + description + "This extension provides an annotation on schema nodes to + indicate that the corresponding value should be stored and + reported in hashed form. + + Hash algorithms are by definition not reversible. Clients + reading the configuration or applied configuration for the node + should expect to receive only the hashed value. Values written + in cleartext will be hashed. This annotation may be used on + nodes such as secure passwords in which the device never reports + a cleartext value, even if the input is provided as cleartext."; + } + + extension regexp-posix { + description + "This extension indicates that the regular expressions included + within the YANG module specified are conformant with the POSIX + regular expression format rather than the W3C standard that is + specified by RFC6020 and RFC7950."; + } + + extension telemetry-on-change { + description + "The telemetry-on-change annotation is specified in the context + of a particular subtree (container, or list) or leaf within the + YANG schema. Where specified, it indicates that the value stored + by the nodes within the context change their value only in response + to an event occurring. The event may be local to the target, for + example - a configuration change, or external - such as the failure + of a link. + + When a telemetry subscription allows the target to determine whether + to export the value of a leaf in a periodic or event-based fashion + (e.g., TARGET_DEFINED mode in gNMI), leaves marked as + telemetry-on-change should only be exported when they change, + i.e., event-based."; + } + + extension telemetry-atomic { + description + "The telemetry-atomic annotation is specified in the context of + a subtree (containre, or list), and indicates that all nodes + within the subtree are always updated together within the data + model. For example, all elements under the subtree may be updated + as a result of a new alarm being raised, or the arrival of a new + protocol message. + + Transport protocols may use the atomic specification to determine + optimisations for sending or storing the corresponding data."; + } + + extension operational { + description + "The operational annotation is specified in the context of a + grouping, leaf, or leaf-list within a YANG module. It indicates + that the nodes within the context are derived state on the device. + + OpenConfig data models divide nodes into the following three categories: + + - intended configuration - these are leaves within a container named + 'config', and are the writable configuration of a target. + - applied configuration - these are leaves within a container named + 'state' and are the currently running value of the intended configuration. + - derived state - these are the values within the 'state' container which + are not part of the applied configuration of the device. Typically, they + represent state values reflecting underlying operational counters, or + protocol statuses."; + } + + extension catalog-organization { + argument "org" { + yin-element false; + } + description + "This extension specifies the organization name that should be used within + the module catalogue on the device for the specified YANG module. It stores + a pithy string where the YANG organization statement may contain more + details."; + } + + extension origin { + argument "origin" { + yin-element false; + } + description + "This extension specifies the name of the origin that the YANG module + falls within. This allows multiple overlapping schema trees to be used + on a single network element without requiring module based prefixing + of paths."; + } +} diff --git a/models/yang/common/openconfig-if-aggregate.yang b/models/yang/common/openconfig-if-aggregate.yang new file mode 100644 index 0000000000..a8f18f7f82 --- /dev/null +++ b/models/yang/common/openconfig-if-aggregate.yang @@ -0,0 +1,232 @@ +module openconfig-if-aggregate { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/aggregate"; + + prefix "oc-lag"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import iana-if-type { prefix ift; } + import openconfig-if-types { prefix oc-ift; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing aggregated (aka bundle, LAG) interfaces."; + + oc-ext:openconfig-version "2.3.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.2"; + } + + revision "2018-03-23" { + description + "Fix/cleanup when statements in aggregates model."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-22" { + description + "Add IPv4 proxy ARP configuration."; + reference "2.2.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + // typedef statements + + typedef aggregation-type { + type enumeration { + enum LACP { + description "LAG managed by LACP"; + } + enum STATIC { + description "Statically configured bundle / LAG"; + } + } + description + "Type to define the lag-type, i.e., how the LAG is + defined and managed"; + } + + // grouping statements + + + grouping aggregation-logical-config { + description + "Configuration data for aggregate interfaces"; + + + leaf lag-type { + type aggregation-type; + description + "Sets the type of LAG, i.e., how it is + configured / maintained"; + } + + leaf min-links { + type uint16; + description + "Specifies the mininum number of member + interfaces that must be active for the aggregate interface + to be available"; + } + } + + grouping aggregation-logical-state { + description + "Operational state data for aggregate interfaces"; + + leaf lag-speed { + type uint32; + units Mbps; + description + "Reports effective speed of the aggregate interface, + based on speed of active member interfaces"; + } + + leaf-list member { + when "../../config/lag-type = 'STATIC'" { + description + "The simple list of member interfaces is active + when the aggregate is statically configured"; + } + type oc-if:base-interface-ref; + description + "List of current member interfaces for the aggregate, + expressed as references to existing interfaces"; + } + } + + grouping aggregation-logical-top { + description "Top-level data definitions for LAGs"; + + container aggregation { + + description + "Options for logical interfaces representing + aggregates"; + + container config { + description + "Configuration variables for logical aggregate / + LAG interfaces"; + + uses aggregation-logical-config; + } + + container state { + + config false; + description + "Operational state variables for logical + aggregate / LAG interfaces"; + + uses aggregation-logical-config; + uses aggregation-logical-state; + + } + } + } + + grouping ethernet-if-aggregation-config { + description + "Adds configuration items for Ethernet interfaces + belonging to a logical aggregate / LAG"; + + leaf aggregate-id { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Specify the logical aggregate interface to which + this interface belongs"; + } + } + + // data definition statements + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface" { + + description "Adds LAG configuration to the interface module"; + + uses aggregation-logical-top { + when "oc-if:state/oc-if:type = 'ift:ieee8023adLag' or " + + "oc-if:state/oc-if:type = 'oc-ift:IF_AGGREGATE'" { + description + "active when the interface is set to type LAG"; + } + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:config" { + description + "Adds LAG settings to individual Ethernet interfaces"; + + uses ethernet-if-aggregation-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:state" { + description + "Adds LAG settings to individual Ethernet interfaces"; + + uses ethernet-if-aggregation-config; + } + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-if-ethernet-ext.yang b/models/yang/common/openconfig-if-ethernet-ext.yang new file mode 100644 index 0000000000..f64773b22d --- /dev/null +++ b/models/yang/common/openconfig-if-ethernet-ext.yang @@ -0,0 +1,117 @@ +module openconfig-if-ethernet-ext { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ethernet-ext"; + + prefix "oc-eth-ext"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + 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 adds extensions to the base ethernet configuration + and operational state model to support additional use cases."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2018-07-10" { + description + "Initial version of Ethernet extensions module to add frame + size distribution stats"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + grouping ethernet-in-frames-size-dist { + description + "Grouping for defining the size distribution of the frames + received"; + + container in-distribution { + description + "The size distribution of the received frames."; + + leaf in-frames-64-octets { + type oc-yang:counter64; + description + "Number of packets (including bad packets) received that + were 64 bytes in length (excluding framing bits but + including FCS bytes)."; + } + + leaf in-frames-65-127-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 65 and 127 bytes in length (excluding framing bits + but including FCS bytes)."; + } + + leaf in-frames-128-255-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 128 and 255 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-256-511-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 256 and 511 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-512-1023-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 512 and 1023 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-1024-1518-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 1024 and 1518 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:state/oc-eth:counters" { + description + "Adds size distribution to the ethernet counters"; + + uses ethernet-in-frames-size-dist; + } + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-if-ip-ext.yang b/models/yang/common/openconfig-if-ip-ext.yang new file mode 100644 index 0000000000..2f28934971 --- /dev/null +++ b/models/yang/common/openconfig-if-ip-ext.yang @@ -0,0 +1,179 @@ +module openconfig-if-ip-ext { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ip-ext"; + + prefix "oc-ip-ext"; + + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ip { prefix oc-ip; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module adds extensions to the base IP configuration and + operational state model to support additional use cases."; + + oc-ext:openconfig-version "2.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping ipv6-autoconf-config { + description + "Configuration data for IPv6 address autoconfiguration"; + + leaf create-global-addresses { + type boolean; + default true; + description + "[adapted from IETF IP model RFC 7277] + + If enabled, the host creates global addresses as + described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration + Section 5.5"; + } + leaf create-temporary-addresses { + type boolean; + default false; + description + "[adapted from IETF IP model RFC 7277] + + If enabled, the host creates temporary addresses as + described in RFC 4941."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + leaf temporary-valid-lifetime { + type uint32; + units "seconds"; + default 604800; + description + "[adapted from IETF IP model RFC 7277] + + The time period during which the temporary address + is valid."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_VALID_LIFETIME"; + } + + leaf temporary-preferred-lifetime { + type uint32; + units "seconds"; + default 86400; + description + "[adapted from IETF IP model RFC 7277] + + The time period during which the temporary address is + preferred."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_PREFERRED_LIFETIME"; + } + } + + grouping ipv6-autoconf-state { + description + "Operational state data for IPv6 address autoconfiguration"; + + //TODO: placeholder for additional opstate for IPv6 autoconf + } + + grouping ipv6-autoconf-top { + description + "Top-level grouping for IPv6 address autoconfiguration"; + + container autoconf { + description + "Top-level container for IPv6 autoconf"; + + container config { + description + "[adapted from IETF IP model RFC 7277] + + Parameters to control the autoconfiguration of IPv6 + addresses, as described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + uses ipv6-autoconf-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses ipv6-autoconf-config; + uses ipv6-autoconf-state; + } + } + } + + // data definition statements + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6" { + description + "Adds address autoconfiguration to the base IP model"; + + uses ipv6-autoconf-top; + } + +} diff --git a/models/yang/common/openconfig-if-poe.yang b/models/yang/common/openconfig-if-poe.yang new file mode 100644 index 0000000000..7758ea3186 --- /dev/null +++ b/models/yang/common/openconfig-if-poe.yang @@ -0,0 +1,110 @@ +module openconfig-if-poe { + + yang-version "1"; + + namespace "http://openconfig.net/yang/poe"; + + prefix "oc-poe"; + + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-extensions { prefix oc-ext; } + + organization "OpenConfig working group"; + + contact + "Openconfig working group + www.openconfig.net"; + + description + "This module defines configuration and state data for + Power over Ethernet (PoE) based on the IEEE 802.3af + standard."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2017-09-14" { + 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"; + + grouping poe-ethernet-config { + description + "PoE ethernet config grouping"; + + leaf enabled { + type boolean; + default "true"; + description + "Enable or disable PoE in the ethernet interface."; + } + } + + grouping poe-ethernet-state { + description + "PoE ethernet state grouping"; + + leaf power-used { + type decimal64 { + fraction-digits 2; + } + units Watts; + description + "Power used by the ethernet interface in Watts."; + } + + leaf power-class { + type uint8; + description + "IEEE 802.3af Power class detected for this ethernet + interface."; + } + } + + grouping poe-ethernet-top { + description + "Ethernet top level grouping"; + + container poe { + description + "Top-level container for PoE configuration and state data"; + + container config { + description + "Configuration data for PoE"; + + uses poe-ethernet-config; + } + + container state { + config false; + + description + "Operational state data for PoE"; + + uses poe-ethernet-config; + uses poe-ethernet-state; + } + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet" { + description + "Adds PoE to the ethernet model."; + + uses poe-ethernet-top; + } + +} diff --git a/models/yang/common/openconfig-if-tunnel.yang b/models/yang/common/openconfig-if-tunnel.yang new file mode 100644 index 0000000000..3003699d52 --- /dev/null +++ b/models/yang/common/openconfig-if-tunnel.yang @@ -0,0 +1,120 @@ +module openconfig-if-tunnel { + yang-version "1"; + + namespace "http://openconfig.net/yang/interfaces/tunnel"; + + prefix "oc-tun"; + + import openconfig-interfaces { prefix oc-if; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + import openconfig-if-ip { prefix oc-ip; } + + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This model adds extensions to the OpenConfig interfaces + model to configure tunnel interfaces on a network + device."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2018-01-05" { + description + "Initial tunnel model"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + grouping tunnel-top { + description + "Top-level grouping for parameters related to + a tunnel interface."; + + container tunnel { + description + "In the case that the interface is logical tunnel + interface, the parameters for the tunnel are + specified within this subtree. Tunnel interfaces + have only a single logical subinterface associated + with them."; + + container config { + description + "Configuration parameters associated with the + tunnel interface"; + uses tunnel-config; + } + + container state { + config false; + description + "Operational state parameters associated with + the tunnel interface."; + uses tunnel-config; + } + + uses oc-ip:ipv4-top; + uses oc-ip:ipv6-top; + } + } + + grouping tunnel-config { + description + "Configuraton parameters relating to a tunnel + interface."; + + leaf src { + type oc-inet:ip-address; + description + "The source address that should be used for the + tunnel."; + } + + leaf dst { + type oc-inet:ip-address; + description + "The destination address for the tunnel."; + } + + leaf ttl { + type uint8 { + range "1..255"; + } + description + "The time-to-live (or hop limit) that should be utilised + for the IP packets used for the tunnel transport."; + } + + leaf gre-key { + type uint32; + description + "The GRE key to be specified for the tunnel. The + key is used to identify a traffic flow within + a tunnel."; + reference + "RFC2890: Key and Sequence Number Extensions to GRE"; + } + } + + augment "/oc-if:interfaces/oc-if:interface" { + description + "Augment to add tunnel configuration to interfaces"; + uses tunnel-top; + } +} diff --git a/models/yang/common/openconfig-if-types.yang b/models/yang/common/openconfig-if-types.yang new file mode 100644 index 0000000000..27d2dc1d89 --- /dev/null +++ b/models/yang/common/openconfig-if-types.yang @@ -0,0 +1,108 @@ +module openconfig-if-types { + yang-version "1"; + + namespace "http://openconfig.net/yang/openconfig-if-types"; + + prefix "oc-ift"; + + // import statements + import openconfig-extensions { prefix oc-ext; } + + // meta + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module contains a set of interface type definitions that + are used across OpenConfig models. These are generally physical + or logical interfaces, distinct from hardware ports (which are + described by the OpenConfig platform model)."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-01-05" { + description + "Add tunnel types into the INTERFACE_TYPE identity."; + reference "0.2.0"; + } + + revision "2016-11-14" { + description + "Initial version"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + identity INTERFACE_TYPE { + description + "Base identity from which interface types are derived."; + } + + identity IF_ETHERNET { + base INTERFACE_TYPE; + description + "Ethernet interfaces based on IEEE 802.3 standards, as well + as FlexEthernet"; + reference + "IEEE 802.3-2015 - IEEE Standard for Ethernet + OIF Flex Ethernet Implementation Agreement 1.0"; + } + + identity IF_AGGREGATE { + base INTERFACE_TYPE; + description + "An aggregated, or bonded, interface forming a + Link Aggregation Group (LAG), or bundle, most often based on + the IEEE 802.1AX (or 802.3ad) standard."; + reference + "IEEE 802.1AX-2008"; + } + + identity IF_LOOPBACK { + base INTERFACE_TYPE; + description + "A virtual interface designated as a loopback used for + various management and operations tasks."; + } + + identity IF_ROUTED_VLAN { + base INTERFACE_TYPE; + description + "A logical interface used for routing services on a VLAN. + Such interfaces are also known as switch virtual interfaces + (SVI) or integrated routing and bridging interfaces (IRBs)."; + } + + identity IF_SONET { + base INTERFACE_TYPE; + description + "SONET/SDH interface"; + } + + identity IF_TUNNEL_GRE4 { + base INTERFACE_TYPE; + description + "A GRE tunnel over IPv4 transport."; + } + + identity IF_TUNNEL_GRE6 { + base INTERFACE_TYPE; + description + "A GRE tunnel over IPv6 transport."; + } + +} diff --git a/models/yang/common/openconfig-inet-types.yang b/models/yang/common/openconfig-inet-types.yang new file mode 100644 index 0000000000..7c23d2b38b --- /dev/null +++ b/models/yang/common/openconfig-inet-types.yang @@ -0,0 +1,343 @@ +module openconfig-inet-types { + + yang-version "1"; + namespace "http://openconfig.net/yang/types/inet"; + prefix "oc-inet"; + + import openconfig-extensions { prefix "oc-ext"; } + + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module contains a set of Internet address related + types for use in OpenConfig modules. + + Portions of this code were derived from IETF RFC 6021. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.3.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.2"; + } + + revision 2017-08-24 { + description + "Minor formatting fixes."; + reference "0.3.1"; + } + + revision 2017-07-06 { + description + "Add domain-name and host typedefs"; + reference "0.3.0"; + } + + revision 2017-04-03 { + description + "Add ip-version typedef."; + reference "0.2.0"; + } + + revision 2017-04-03 { + description + "Update copyright notice."; + reference "0.1.1"; + } + + revision 2017-01-26 { + description + "Initial module for inet types"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // IPv4 and IPv6 types. + + typedef ipv4-address { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])$'; + } + description + "An IPv4 address in dotted quad notation using the default + zone."; + } + + typedef ipv4-address-zoned { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])(%[a-zA-Z0-9_]+)$'; + } + description + "An IPv4 address in dotted quad notation. This type allows + specification of a zone index to disambiguate identical + address values. For link-local addresses, the index is + typically the interface index or interface name."; + } + + typedef ipv6-address { + type string { + pattern + // Must support compression through different lengths + // therefore this regexp is complex. + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')$'; + } + description + "An IPv6 address represented as either a full address; shortened + or mixed-shortened formats, using the default zone."; + } + + typedef ipv6-address-zoned { + type string { + pattern + // Must support compression through different lengths + // therefore this regexp is complex. + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')(%[a-zA-Z0-9_]+)$'; + } + description + "An IPv6 address represented as either a full address; shortened + or mixed-shortened formats. This type allows specification of + a zone index to disambiguate identical address values. For + link-local addresses, the index is typically the interface + index or interface name."; + } + + typedef ipv4-prefix { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])/(([0-9])|([1-2][0-9])|(3[0-2]))$'; + } + description + "An IPv4 prefix represented in dotted quad notation followed by + a slash and a CIDR mask (0 <= mask <= 32)."; + } + + typedef ipv6-prefix { + type string { + pattern + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9])$'; + } + description + "An IPv6 prefix represented in full, shortened, or mixed + shortened format followed by a slash and CIDR mask + (0 <= mask <= 128)."; + } + + typedef ip-address { + type union { + type ipv4-address; + type ipv6-address; + } + description + "An IPv4 or IPv6 address with no prefix specified."; + } + + typedef ip-prefix { + type union { + type ipv4-prefix; + type ipv6-prefix; + } + description + "An IPv4 or IPv6 prefix."; + } + + typedef ip-version { + type enumeration { + enum UNKNOWN { + value 0; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum IPV4 { + value 4; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum IPV6 { + value 6; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + Note that integer representation of the enumerated values + are not specified, and are not required to follow the + InetVersion textual convention in SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef domain-name { + type string { + length "1..253"; + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + } + description + "The domain-name type represents a DNS domain name. + Fully quallified left to the models which utilize this type. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be encoded in punycode as described in RFC + 3492"; + } + + typedef host { + type union { + type ip-address; + type domain-name; + } + description + "The host type represents either an unzoned IP address or a DNS + domain name."; + } + + typedef as-number { + type uint32; + description + "A numeric identifier for an autonomous system (AS). An AS is a + single domain, under common administrative control, which forms + a unit of routing policy. Autonomous systems can be assigned a + 2-byte identifier, or a 4-byte identifier which may have public + or private scope. Private ASNs are assigned from dedicated + ranges. Public ASNs are assigned from ranges allocated by IANA + to the regional internet registries (RIRs)."; + reference + "RFC 1930 Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271 A Border Gateway Protocol 4 (BGP-4)"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "A differentiated services code point (DSCP) marking within the + IP header."; + reference + "RFC 2474 Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The IPv6 flow-label is a 20-bit value within the IPv6 header + which is optionally used by the source of the IPv6 packet to + label sets of packets for which special handling may be + required."; + reference + "RFC 2460 Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16; + description + "A 16-bit port number used by a transport protocol such as TCP + or UDP."; + reference + "RFC 768 User Datagram Protocol + RFC 793 Transmission Control Protocol"; + } + + typedef uri { + type string; + description + "An ASCII-encoded Uniform Resource Identifier (URI) as defined + in RFC 3986."; + reference + "RFC 3986 Uniform Resource Identifier (URI): Generic Syntax"; + } + + typedef url { + type string; + description + "An ASCII-encoded Uniform Resource Locator (URL) as defined + in RFC 3986, section 1.1.3"; + reference + "RFC 3986, paragraph 1.1.3"; + } + +} diff --git a/models/yang/common/openconfig-lldp-types.yang b/models/yang/common/openconfig-lldp-types.yang new file mode 100644 index 0000000000..6c4a0ac172 --- /dev/null +++ b/models/yang/common/openconfig-lldp-types.yang @@ -0,0 +1,306 @@ +module openconfig-lldp-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/lldp/types"; + + prefix "oc-lldp-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 LLDP protocol model."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2016-05-16" { + 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 LLDP_SYSTEM_CAPABILITY { + description + "Base identity for standard LLDP system capabilities. + The system capabilities field contains a bit-map of the + capabilities that define the primary function(s) of + the system. A system may advertise more than one capability."; + reference + "Table 8-4 System Capabilities, IEEE 802.1AB-2009"; + } + + identity OTHER { + base LLDP_SYSTEM_CAPABILITY; + description + "Other capability not specified; bit position 1"; + } + + identity REPEATER { + base LLDP_SYSTEM_CAPABILITY; + description + "Repeater capability; bit position 2"; + reference + "IETF RFC 2108"; + } + + identity MAC_BRIDGE { + base LLDP_SYSTEM_CAPABILITY; + description + "MAC bridge capability; bit position 3"; + reference + "IEEE Std 802.1D"; + } + + identity WLAN_ACCESS_POINT { + base LLDP_SYSTEM_CAPABILITY; + description + "WLAN access point capability; bit position 4"; + reference + "IEEE Std 802.11 MIB"; + } + + identity ROUTER { + base LLDP_SYSTEM_CAPABILITY; + description + "Router; bit position 5"; + reference + "IETF RFC 1812"; + } + + identity TELEPHONE { + base LLDP_SYSTEM_CAPABILITY; + description + "Telephone capability; bit position 6"; + reference + "IETF RFC 4293"; + } + + identity DOCSIS_CABLE_DEVICE { + base LLDP_SYSTEM_CAPABILITY; + description + "DOCSIS cable device; bit position 7"; + reference + "IETF RFC 4639 and IETF RFC 4546"; + } + + identity STATION_ONLY { + base LLDP_SYSTEM_CAPABILITY; + description + "Station only capability, for devices that implement only an + end station capability, and for which none of the other + capabilities apply; bit position 8"; + reference + "IETF RFC 4293"; + } + + identity C_VLAN { + base LLDP_SYSTEM_CAPABILITY; + description + "C-VLAN component of a VLAN Bridge; bit position 9"; + reference + "IEEE Std 802.1Q"; + } + + identity S_VLAN { + base LLDP_SYSTEM_CAPABILITY; + description + "S-VLAN component of a VLAN Bridge; bit position 10"; + reference + "IEEE Std 802.1Q"; + } + + identity TWO_PORT_MAC_RELAY { + base LLDP_SYSTEM_CAPABILITY; + description + "Two-port MAC Relay (TPMR) capability; bit position 11"; + reference + "IEEE Std 802.1Q"; + } + + identity LLDP_TLV { + description + "A base identity which describes the TLVs in LLDP"; + } + + identity CHASSIS_ID { + base LLDP_TLV; + description + "The chassis identifier of the device associated with + the transmitting LLDP agent"; + reference "IEEE Std 802.1AB"; + } + + identity PORT_ID { + base LLDP_TLV; + description + "The port identifier associated with the interface + on with the LLDP agent is transmitting"; + reference "IEEE Std 802.1AB"; + } + + identity PORT_DESCRIPTION { + base LLDP_TLV; + description + "The description of the port that is associated with + the interface on which the LLDP agent is transmitting"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_NAME { + base LLDP_TLV; + description + "The assigned name (sysName or hostname) of the device + which is transmitting the LLDP PDU"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_DESCRIPTION { + base LLDP_TLV; + description + "The description (sysDescr) of the device which is + transmitting the LLDP PDU"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_CAPABILITIES { + base LLDP_TLV; + description + "The primary functions of the device transmitting the + LLDP PDU and their administrative status"; + reference "IEEE Std 802.1AB"; + } + + identity MANAGEMENT_ADDRESS { + base LLDP_TLV; + description + "The address associated with the device transmitting the + LLDP PDU which can be used for higher-layer network + management"; + reference "IEEE Std 802.1AB"; + } + + // typedef statements + + typedef chassis-id-type { + type enumeration { + enum CHASSIS_COMPONENT { + description + "Chassis identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737"; + } + enum INTERFACE_ALIAS { + description + "Chassis identifier based on the value of ifAlias object + defined in IETF RFC 2863"; + } + enum PORT_COMPONENT { + description + "Chassis identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737 for a port or backplane + component"; + } + enum MAC_ADDRESS { + description + "Chassis identifier based on the value of a unicast source + address (encoded in network byte order and IEEE 802.3 + canonical bit order), of a port on the containing chassis + as defined in IEEE Std 802-2001"; + } + enum NETWORK_ADDRESS { + description + "Chassis identifier based on a network address, + associated with a particular chassis. The encoded address + is composed of two fields. The first field is a single + octet, representing the IANA AddressFamilyNumbers value + for the specific address type, and the second field is the + network address value"; + } + enum INTERFACE_NAME { + description + "Chassis identifier based on the name of the interface, + e.g., the value of ifName object defined in IETF RFC 2863"; + } + enum LOCAL { + description + "Chassis identifier based on a locally defined value"; + } + } + description + "Type definition with enumerations describing the source of + the chassis identifier"; + reference + "IEEE 802.1AB LLDP MIB"; + } + + typedef port-id-type { + type enumeration { + enum INTERFACE_ALIAS { + description + "Chassis identifier based on the value of ifAlias object + defined in IETF RFC 2863"; + } + enum PORT_COMPONENT { + description + "Port identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737 for a port component"; + } + enum MAC_ADDRESS { + description + "Port identifier based on the value of a unicast source + address (encoded in network byte order and IEEE 802.3 + canonical bit order) associated with a port"; + } + enum NETWORK_ADDRESS { + description + "Port identifier based on a network address, + associated with a particular port"; + } + enum INTERFACE_NAME { + description + "Port identifier based on the name of the interface, + e.g., the value of ifName object defined in IETF RFC 2863"; + } + enum AGENT_CIRCUIT_ID { + description + "Port identifer based on the circuit id in the DHCP + relay agent information option as defined in IETF + RFC 3046"; + } + enum LOCAL { + description + "Port identifier based on a locally defined alphanumeric + string"; + } + } + description + "Type definition with enumerations describing the basis of + the port identifier"; + reference + "IEEE 802.1AB LLDP MIB"; + } + + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-messages.yang b/models/yang/common/openconfig-messages.yang new file mode 100644 index 0000000000..894704479d --- /dev/null +++ b/models/yang/common/openconfig-messages.yang @@ -0,0 +1,221 @@ +module openconfig-messages { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/messages"; + + prefix "oc-messages"; + + // import some basic types + import openconfig-extensions { prefix "oc-ext"; } + import openconfig-system-logging { prefix "oc-log"; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to Syslog messages that a device may generate. + + These messages are historically obtained through the Syslog + transport, however this module allows for obtaining them through + an alternative transport, such as a Subscribe operation over an + RPC. + + This module does not usurp traditional syslog servers, which may + still be configured through the + /yang/system/openconfig-system.yang model, rather it provies the + Operator with an alternative method of consuming messages."; + + oc-ext:openconfig-version "0.0.1"; + + revision "2018-08-13" { + description + "Initial draft."; + reference "0.0.1"; + } + + // identity statements + + identity DEBUG_SERVICE { + description + "Base identity for debug services. Identities within this base + identity are to be augmented in by vendors."; + } + + // grouping statements + + grouping messages-config { + description + "Configuration data for defining Syslog message severity."; + + leaf severity { + type oc-log:syslog-severity; + description + "Specifies that only messages of the given severity (or + greater severity) are sent over the RPC. + + This is analogous to differentiating which severity is to be + sent to legacy Syslog servers, as opposed to local buffer or + files."; + } + } + + grouping messages-state { + description + "Operational state data for Syslog messages."; + + container message { + oc-ext:telemetry-atomic; + config false; + description + "Syslog messages the client is Subscribing to. This is all + messages currently configured to be sent according to + syslog-severity."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + + // Decide if it is OK to include ALL in this leaf. + leaf msg { + type string; + description + "Message payload. If other leafs within this container not + supported, this leaf MAY include the entire message, + inclding pri, procid, app-name etc.."; + } + + leaf priority { + type uint8; + description + "The Priority value (PRIVAL) represents both the + Facility and Severity."; + reference + "IETF RFC 5424, Section 6.2.1"; + } + + leaf app-name { + type string; + description + "The APP-NAME field SHOULD identify the device or + application that originated the message."; + reference + "IETF RFC 5424, Section 6.2.5."; + } + + leaf procid { + type string; + description + "PROCID is a value that is included in the message, having + no interoperable meaning, except that a change in the value + indicates there has been a discontinuity in syslog + reporting."; + reference + "IETF RFC 5424, Section 6.2.6."; + } + + leaf msgid { + type string; + description + "The MSGID SHOULD identify the type of message. For + example, a firewall might use the MSGID 'TCPIN' for + incoming TCP traffic and the MSGID 'TCPOUT' for outgoing + TCP traffic."; + reference + "IETF RFC 5424, Section 6.2.7."; + } + } + } + + grouping debug-messages-config { + description + "Configuration data for enabling debug messages."; + + leaf service { + type identityref { + base DEBUG_SERVICE; + } + description + "Enumeration of all services which can have debugging enabled. + Vendors are to augment this base identity with their platform + or OS specific debug options."; + } + + leaf enabled { + type boolean; + default false; + description + "Enable and disable debugging."; + } + } + + grouping debug-messages-top { + description + "Configuration data for enabling Syslog debug messages."; + + container debug-entries { + description + "Enclosing container for list of debugs to enable."; + + list debug-service { + key "service"; + description + "List of debugging entries."; + + leaf service { + type leafref { + path "../config/service"; + } + description + "Reference to the debug-enable service key."; + } + + container config { + description + "Configuration data for debug service entries."; + + uses debug-messages-config; + } + + container state { + config false; + description + "Operational state data for enabled debugs."; + uses debug-messages-config; + } + } + } + } + + grouping messages-top { + description + "Top-level grouping for Syslog messages."; + + container messages { + description + "Top-level container for Syslog messages."; + + container config { + description + "Configuration data for Syslog messages."; + + uses messages-config; + } + + container state { + config false; + description + "Operational state data for a Syslog messages."; + + uses messages-config; + uses messages-state; + } + uses debug-messages-top; + } + } + uses messages-top; +} diff --git a/models/yang/common/openconfig-packet-match-types.yang b/models/yang/common/openconfig-packet-match-types.yang new file mode 100644 index 0000000000..1b93d52059 --- /dev/null +++ b/models/yang/common/openconfig-packet-match-types.yang @@ -0,0 +1,309 @@ +module openconfig-packet-match-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/packet-match-types"; + + prefix "oc-pkt-match-types"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines common types for use in models requiring + data definitions related to packet matches."; + + oc-ext:openconfig-version "1.0.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "1.0.2"; + } + + revision "2018-04-15" { + description + "Corrected description and range for ethertype typedef"; + reference "1.0.1"; + } + + revision "2017-05-26" { + description + "Separated IP matches into AFs"; + reference "1.0.0"; + } + + revision "2016-08-08" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + revision "2016-04-27" { + description + "Initial revision"; + reference "TBD"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + + // extension statements + + // feature statements + + // identity statements + + + //TODO: should replace this with an official IEEE module + // when available. Only a select number of types are + // defined in this identity. + identity ETHERTYPE { + description + "Base identity for commonly used Ethertype values used + in packet header matches on Ethernet frames. The Ethertype + indicates which protocol is encapsulated in the Ethernet + payload."; + reference + "IEEE 802.3"; + } + + identity ETHERTYPE_IPV4 { + base ETHERTYPE; + description + "IPv4 protocol (0x0800)"; + } + + identity ETHERTYPE_ARP { + base ETHERTYPE; + description + "Address resolution protocol (0x0806)"; + } + + identity ETHERTYPE_VLAN { + base ETHERTYPE; + description + "VLAN-tagged frame (as defined by IEEE 802.1q) (0x8100). Note + that this value is also used to represent Shortest Path + Bridging (IEEE 801.1aq) frames."; + } + + identity ETHERTYPE_IPV6 { + base ETHERTYPE; + description + "IPv6 protocol (0x86DD)"; + } + + identity ETHERTYPE_MPLS { + base ETHERTYPE; + description + "MPLS unicast (0x8847)"; + } + + identity ETHERTYPE_LLDP { + base ETHERTYPE; + description + "Link Layer Discovery Protocol (0x88CC)"; + } + + identity ETHERTYPE_ROCE { + base ETHERTYPE; + description + "RDMA over Converged Ethernet (0x8915)"; + } + + + //TODO: should replace this with an official IANA module when + //available. Only a select set of protocols are defined with + //this identity. + identity IP_PROTOCOL { + description + "Base identity for commonly used IP protocols used in + packet header matches"; + reference + "IANA Assigned Internet Protocol Numbers"; + } + + identity IP_TCP { + base IP_PROTOCOL; + description + "Transmission Control Protocol (6)"; + } + + identity IP_UDP { + base IP_PROTOCOL; + description + "User Datagram Protocol (17)"; + } + + identity IP_ICMP { + base IP_PROTOCOL; + description + "Internet Control Message Protocol (1)"; + } + + identity IP_IGMP { + base IP_PROTOCOL; + description + "Internet Group Membership Protocol (2)"; + } + + identity IP_PIM { + base IP_PROTOCOL; + description + "Protocol Independent Multicast (103)"; + } + + identity IP_RSVP { + base IP_PROTOCOL; + description + "Resource Reservation Protocol (46)"; + } + + identity IP_GRE { + base IP_PROTOCOL; + description + "Generic Routing Encapsulation (47)"; + } + + identity IP_AUTH { + base IP_PROTOCOL; + description + "Authentication header, e.g., for IPSEC (51)"; + } + + identity IP_L2TP { + base IP_PROTOCOL; + description + "Layer Two Tunneling Protocol v.3 (115)"; + } + + + + identity TCP_FLAGS { + description + "Common TCP flags used in packet header matches"; + reference + "IETF RFC 793 - Transmission Control Protocol + IETF RFC 3168 - The Addition of Explicit Congestion + Notification (ECN) to IP"; + } + + identity TCP_SYN { + base TCP_FLAGS; + description + "TCP SYN flag"; + } + + identity TCP_FIN { + base TCP_FLAGS; + description + "TCP FIN flag"; + } + + identity TCP_RST { + base TCP_FLAGS; + description + "TCP RST flag"; + } + + identity TCP_PSH { + base TCP_FLAGS; + description + "TCP push flag"; + } + + identity TCP_ACK { + base TCP_FLAGS; + description + "TCP ACK flag"; + } + + identity TCP_URG { + base TCP_FLAGS; + description + "TCP urgent flag"; + } + + identity TCP_ECE { + base TCP_FLAGS; + description + "TCP ECN-Echo flag. If the SYN flag is set, indicates that + the TCP peer is ECN-capable, otherwise indicates that a + packet with Congestion Experienced flag in the IP header + is set"; + } + + identity TCP_CWR { + base TCP_FLAGS; + description + "TCP Congestion Window Reduced flag"; + } + + // typedef statements + + typedef port-num-range { + type union { + type string { + pattern '^(6[0-5][0-5][0-3][0-5]|[0-5]?[0-9]?[0-9]?[0-9]?' + + '[0-9]?)\.\.(6[0-5][0-5][0-3][0-5]|[0-5]?[0-9]?[0-9]?' + + '[0-9]?[0-9]?)$'; + } + type oc-inet:port-number; + type enumeration { + enum ANY { + description + "Indicates any valid port number (e.g., wildcard)"; + } + } + } + description + "Port numbers may be represented as a single value, + an inclusive range as .., or as ANY to + indicate a wildcard."; + } + + typedef ip-protocol-type { + type union { + type uint8 { + range 0..254; + } + type identityref { + base IP_PROTOCOL; + } + } + description + "The IP protocol number may be expressed as a valid protocol + number (integer) or using a protocol type defined by the + IP_PROTOCOL identity"; + } + + typedef ethertype-type { + type union { + type uint16 { + range 1536..65535; + } + type identityref { + base ETHERTYPE; + } + } + description + "The Ethertype value may be expressed as a 16-bit number in + decimal notation, or using a type defined by the + ETHERTYPE identity"; + } + +} diff --git a/models/yang/common/openconfig-packet-match.yang b/models/yang/common/openconfig-packet-match.yang new file mode 100644 index 0000000000..510bc57686 --- /dev/null +++ b/models/yang/common/openconfig-packet-match.yang @@ -0,0 +1,371 @@ +module openconfig-packet-match { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/header-fields"; + + prefix "oc-pkt-match"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-packet-match-types { prefix oc-pkt-match-types; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines data related to packet header fields + used in matching operations, for example in ACLs. When a + field is omitted from a match expression, the effect is a + wildcard ('any') for that field."; + + oc-ext:openconfig-version "1.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "1.1.1"; + } + + revision "2017-12-15" { + description + "Add MPLS packet field matches"; + reference "1.1.0"; + } + + revision "2017-05-26" { + description + "Separated IP matches into AFs"; + reference "1.0.0"; + } + + revision "2016-08-08" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + revision "2016-04-27" { + description + "Initial revision"; + reference "TBD"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + + // Physical Layer fields + // ethernet-header + grouping ethernet-header-config { + description + "Configuration data of fields in Ethernet header."; + + leaf source-mac { + type oc-yang:mac-address; + description + "Source IEEE 802 MAC address."; + } + + leaf source-mac-mask { + type oc-yang:mac-address; + description + "Source IEEE 802 MAC address mask."; + } + + leaf destination-mac { + type oc-yang:mac-address; + description + "Destination IEEE 802 MAC address."; + } + + leaf destination-mac-mask { + type oc-yang:mac-address; + description + "Destination IEEE 802 MAC address mask."; + } + + leaf ethertype { + type oc-pkt-match-types:ethertype-type; + description + "Ethertype field to match in Ethernet packets"; + } + } + + grouping ethernet-header-state { + description + "State information of fields in Ethernet header."; + } + + grouping ethernet-header-top { + description + "Top level container for fields in Ethernet header."; + + container l2 { + description + "Ethernet header fields"; + + container config { + description + "Configuration data"; + uses ethernet-header-config; + } + + container state { + config false; + description + "State Information."; + uses ethernet-header-config; + uses ethernet-header-state; + } + } + } + + grouping mpls-header-top { + description + "Top-level container for fields in an MPLS header."; + + container mpls { + description + "MPLS header fields"; + + container config { + description + "Configuration parameters relating to fields within + the MPLS header."; + uses mpls-header-config; + } + + container state { + config false; + description + "Operational state parameters relating to fields + within the MPLS header"; + uses mpls-header-config; + } + } + } + + grouping mpls-header-config { + description + "Configuration parameters relating to matches within + MPLS header fields."; + + leaf traffic-class { + type uint8 { + range "0..7"; + } + description + "The value of the MPLS traffic class (TC) bits, + formerly known as the EXP bits."; + } + } + + grouping ip-protocol-fields-common-config { + description + "IP protocol fields common to IPv4 and IPv6"; + + leaf dscp { + type oc-inet:dscp; + description + "Value of diffserv codepoint."; + } + + leaf protocol { + type oc-pkt-match-types:ip-protocol-type; + description + "The protocol carried in the IP packet, expressed either + as its IP protocol number, or by a defined identity."; + } + + leaf hop-limit { + type uint8 { + range 0..255; + } + description + "The IP packet's hop limit -- known as TTL (in hops) in + IPv4 packets, and hop limit in IPv6"; + } + } + + // IP Layer + // ip-protocol-fields + grouping ipv4-protocol-fields-config { + description + "Configuration data of IP protocol fields + for IPv4"; + + leaf source-address { + type oc-inet:ipv4-prefix; + description + "Source IPv4 address prefix."; + } + + leaf destination-address { + type oc-inet:ipv4-prefix; + description + "Destination IPv4 address prefix."; + } + + uses ip-protocol-fields-common-config; + + } + + grouping ipv4-protocol-fields-state { + description + "State information of IP header fields for IPv4"; + } + + grouping ipv4-protocol-fields-top { + description + "IP header fields for IPv4"; + + container ipv4 { + description + "Top level container for IPv4 match field data"; + + container config { + description + "Configuration data for IPv4 match fields"; + uses ipv4-protocol-fields-config; + } + + container state { + config false; + description + "State information for IPv4 match fields"; + uses ipv4-protocol-fields-config; + uses ipv4-protocol-fields-state; + } + } + } + + grouping ipv6-protocol-fields-config { + description + "Configuration data for IPv6 match fields"; + + leaf source-address { + type oc-inet:ipv6-prefix; + description + "Source IPv6 address prefix."; + } + + leaf source-flow-label { + type oc-inet:ipv6-flow-label; + description + "Source IPv6 Flow label."; + } + + leaf destination-address { + type oc-inet:ipv6-prefix; + description + "Destination IPv6 address prefix."; + } + + leaf destination-flow-label { + type oc-inet:ipv6-flow-label; + description + "Destination IPv6 Flow label."; + } + + uses ip-protocol-fields-common-config; + } + + grouping ipv6-protocol-fields-state { + description + "Operational state data for IPv6 match fields"; + } + + grouping ipv6-protocol-fields-top { + description + "Top-level grouping for IPv6 match fields"; + + container ipv6 { + description + "Top-level container for IPv6 match field data"; + + container config { + description + "Configuration data for IPv6 match fields"; + + uses ipv6-protocol-fields-config; + } + + container state { + + config false; + + description + "Operational state data for IPv6 match fields"; + + uses ipv6-protocol-fields-config; + uses ipv6-protocol-fields-state; + } + } + } + + // Transport fields + grouping transport-fields-config { + description + "Configuration data of transport-layer packet fields"; + + leaf source-port { + type oc-pkt-match-types:port-num-range; + description + "Source port or range"; + } + + leaf destination-port { + type oc-pkt-match-types:port-num-range; + description + "Destination port or range"; + } + + leaf-list tcp-flags { + type identityref { + base oc-pkt-match-types:TCP_FLAGS; + } + description + "List of TCP flags to match"; + } + } + + grouping transport-fields-state { + description + "State data of transport-fields"; + } + + grouping transport-fields-top { + description + "Destination transport-fields top level grouping"; + + container transport { + description + "Transport fields container"; + + container config { + description + "Configuration data"; + uses transport-fields-config; + } + + container state { + config false; + description + "State data"; + uses transport-fields-config; + uses transport-fields-state; + } + } + } + +} diff --git a/models/yang/common/openconfig-platform-types.yang b/models/yang/common/openconfig-platform-types.yang new file mode 100644 index 0000000000..8dc3ffc1fd --- /dev/null +++ b/models/yang/common/openconfig-platform-types.yang @@ -0,0 +1,347 @@ +module openconfig-platform-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/platform-types"; + + prefix "oc-platform-types"; + + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines data types (e.g., YANG identities) + to support the OpenConfig component inventory model."; + + oc-ext:openconfig-version "1.0.0"; + + revision "2019-06-03" { + description + "Add OpenConfig component operating system patch type."; + reference "1.0.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.10.1"; + } + + revision "2018-11-16" { + description + "Added FEC_MODE_TYPE and FEC_STATUS_TYPE"; + reference "0.10.0"; + } + + revision "2018-05-05" { + description + "Added min-max-time to + avg-min-max-instant-stats-precision1-celsius, + added new CONTROLLER_CARD identity"; + reference "0.9.0"; + } + + revision "2018-01-16" { + description + "Added new per-component common data; add temp alarm"; + reference "0.8.0"; + } + + revision "2017-12-14" { + description + "Added anchor containers for component data, added new + component types"; + reference "0.7.0"; + } + + revision "2017-08-16" { + description + "Added power state enumerated type"; + reference "0.6.0"; + } + + revision "2016-12-22" { + description + "Added temperature state variable to component"; + reference "0.5.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + + grouping avg-min-max-instant-stats-precision1-celsius { + description + "Common grouping for recording temperature values in + Celsius with 1 decimal precision. Values include the + instantaneous, average, minimum, and maximum statistics"; + + leaf instant { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The arithmetic mean value of the statistic over the + sampling period."; + } + + leaf min { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The minimum value of the statistic over the sampling + period"; + } + + leaf max { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The maximum value of the statistic over the sampling + period"; + } + + uses oc-types:stat-interval-state; + uses oc-types:min-max-time; + } + + // identity statements + + identity OPENCONFIG_HARDWARE_COMPONENT { + description + "Base identity for hardware related components in a managed + device. Derived identities are partially based on contents + of the IANA Entity MIB."; + reference + "IANA Entity MIB and RFC 6933"; + } + + + identity OPENCONFIG_SOFTWARE_COMPONENT { + description + "Base identity for software-related components in a managed + device"; + } + + // hardware types + + identity CHASSIS { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Chassis component, typically with multiple slots / shelves"; + } + + identity BACKPLANE { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Backplane component for aggregating traffic, typically + contained in a chassis component"; + } + + identity FABRIC { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Interconnect between ingress and egress ports on the + device (e.g., a crossbar switch)."; + } + + identity POWER_SUPPLY { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Component that is supplying power to the device"; + } + + identity FAN { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Cooling fan, or could be some other heat-reduction component"; + } + + identity SENSOR { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Physical sensor, e.g., a temperature sensor in a chassis"; + } + + identity FRU { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Replaceable hardware component that does not have a more + specific defined schema."; + } + + identity LINECARD { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Linecard component, typically inserted into a chassis slot"; + } + + identity CONTROLLER_CARD { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A type of linecard whose primary role is management or control + rather than data forwarding."; + } + + identity PORT { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Physical port, e.g., for attaching pluggables and networking + cables"; + } + + identity TRANSCEIVER { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Pluggable module present in a port"; + } + + identity CPU { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Processing unit, e.g., a management processor"; + } + + identity STORAGE { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A storage subsystem on the device (disk, SSD, etc.)"; + } + + identity INTEGRATED_CIRCUIT { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A special purpose processing unit, typically for traffic + switching/forwarding (e.g., switching ASIC, NPU, forwarding + chip, etc.)"; + } + + identity OPERATING_SYSTEM { + base OPENCONFIG_SOFTWARE_COMPONENT; + description + "Operating system running on a component"; + } + + identity OPERATING_SYSTEM_UPDATE { + base OPENCONFIG_SOFTWARE_COMPONENT; + description + "An operating system update - which should be a subcomponent + of the `OPERATING_SYSTEM` running on a component. An update is + defined to be a set of software changes that are atomically + installed (and uninstalled) together. Multiple updates may be + present for the Operating System. A system should not list all + installed software packages using this type -- but rather + updates that are bundled together as a single installable + item"; + } + + identity COMPONENT_OPER_STATUS { + description + "Current operational status of a platform component"; + } + + identity ACTIVE { + base COMPONENT_OPER_STATUS; + description + "Component is enabled and active (i.e., up)"; + } + + identity INACTIVE { + base COMPONENT_OPER_STATUS; + description + "Component is enabled but inactive (i.e., down)"; + } + + identity DISABLED { + base COMPONENT_OPER_STATUS; + description + "Component is administratively disabled."; + } + + identity FEC_MODE_TYPE { + description + "Base identity for FEC operational modes."; + } + + identity FEC_ENABLED { + base FEC_MODE_TYPE; + description + "FEC is administratively enabled."; + } + + identity FEC_DISABLED { + base FEC_MODE_TYPE; + description + "FEC is administratively disabled."; + } + + identity FEC_AUTO { + base FEC_MODE_TYPE; + description + "System will determine whether to enable or disable + FEC on a transceiver."; + } + + identity FEC_STATUS_TYPE { + description + "Base identity for FEC operational statuses."; + } + + identity FEC_STATUS_LOCKED { + base FEC_STATUS_TYPE; + description + "FEC is operationally locked."; + } + + identity FEC_STATUS_UNLOCKED { + base FEC_STATUS_TYPE; + description + "FEC is operationally unlocked."; + } + + // typedef statements + + typedef component-power-type { + type enumeration { + enum POWER_ENABLED { + description + "Enable power on the component"; + } + enum POWER_DISABLED { + description + "Disable power on the component"; + } + } + description + "A generic type reflecting whether a hardware component + is powered on or off"; + } + +} diff --git a/models/yang/common/openconfig-procmon.yang b/models/yang/common/openconfig-procmon.yang new file mode 100644 index 0000000000..3c1013f47b --- /dev/null +++ b/models/yang/common/openconfig-procmon.yang @@ -0,0 +1,175 @@ +module openconfig-procmon { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/procmon"; + + prefix "oc-proc"; + + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-types { prefix oc-types; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides data definitions for process health + monitoring of one or more processes running on the system."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping procmon-processes-top { + description + "Top level grouping for attributes for processes."; + + container processes { + description + "Parameters related to all monitored processes"; + + list process { + key "pid"; + config false; + description + "List of monitored processes"; + + leaf pid { + type leafref { + path "../state/pid"; + } + description + "Reference to the process pid key"; + } + + container state { + config false; + description + "State parameters related to monitored processes"; + + uses procmon-process-attributes-state; + } + } + } + } + + grouping procmon-process-attributes-state { + description + "Attributes state definitions for a process"; + + leaf pid { + type uint64; + description + "The process pid"; + } + + leaf name { + type string; + description + "The process name"; + } + + leaf-list args { + type string; + description + "Current process command line arguments. Arguments with + a parameter (e.g., --option 10 or -option=10) should be + represented as a single element of the list with the + argument name and parameter together. Flag arguments, i.e., + those without a parameter should also be in their own list + element."; + } + + leaf start-time { + type uint64; + units "ns"; + description + "The time at which this process started, + reported as nanoseconds since the UNIX epoch. The + system must be synchronized such that the start-time + can be reported accurately, otherwise it should not be + reported."; + } + + leaf uptime { + type oc-types:timeticks64; + description + "Amount of time elapsed since this process started."; + } + + leaf cpu-usage-user { + type oc-types:timeticks64; + description + "CPU time consumed by this process in user mode."; + } + + leaf cpu-usage-system { + type oc-types:timeticks64; + description + "CPU time consumed by this process in kernel mode."; + } + + leaf cpu-utilization { + type oc-types:percentage; + description + "The percentage of CPU that is being used by the process."; + } + + leaf memory-usage { + type uint64; + units "bytes"; + description + "Bytes allocated and still in use by the process"; + } + + leaf memory-utilization { + type oc-types:percentage; + description + "The percentage of RAM that is being used by the process."; + } + } + + // augment statements + + // rpc statements + + // notification statements +} diff --git a/models/yang/common/openconfig-system-logging.yang b/models/yang/common/openconfig-system-logging.yang new file mode 100644 index 0000000000..1602cb1c66 --- /dev/null +++ b/models/yang/common/openconfig-system-logging.yang @@ -0,0 +1,503 @@ +module openconfig-system-logging { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/logging"; + + prefix "oc-log"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + for common logging facilities on network systems."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity SYSLOG_FACILITY { + description + "Base identity for Syslog message facilities."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity ALL { + base SYSLOG_FACILITY; + description + "All supported facilities"; + } + + identity KERNEL { + base SYSLOG_FACILITY; + description + "The facility for kernel messages"; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity USER { + base SYSLOG_FACILITY; + description + "The facility for user-level messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity MAIL { + base SYSLOG_FACILITY; + description + "The facility for the mail system."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity SYSTEM_DAEMON { + base SYSLOG_FACILITY; + description + "The facility for the system daemons."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUTH { + base SYSLOG_FACILITY; + description + "The facility for security/authorization messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity SYSLOG { + base SYSLOG_FACILITY; + description + "The facility for messages generated internally by syslogd + facility."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUTHPRIV { + base SYSLOG_FACILITY; + description + "The facility for privileged security/authorization messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + + identity NTP { + base SYSLOG_FACILITY; + description + "The facility for the NTP subsystem."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUDIT { + base SYSLOG_FACILITY; + description + "The facility for log audit messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity CONSOLE { + base SYSLOG_FACILITY; + description + "The facility for log alert messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL0 { + base SYSLOG_FACILITY; + description + "The facility for local use 0 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL1 { + base SYSLOG_FACILITY; + description + "The facility for local use 1 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL2 { + base SYSLOG_FACILITY; + description + "The facility for local use 2 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL3 { + base SYSLOG_FACILITY; + description + "The facility for local use 3 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL4 { + base SYSLOG_FACILITY; + description + "The facility for local use 4 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL5 { + base SYSLOG_FACILITY; + description + "The facility for local use 5 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL6 { + base SYSLOG_FACILITY; + description + "The facility for local use 6 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL7 { + base SYSLOG_FACILITY; + description + "The facility for local use 7 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOG_DESTINATION_TYPE { + description + "Base identity for destination for logging messages"; + } + + identity DEST_CONSOLE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to the console"; + } + + identity DEST_BUFFER { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to and in-memory circular buffer"; + } + + identity DEST_FILE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to a local file"; + } + + identity DEST_REMOTE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to a remote syslog server"; + } + + // typedef statements + + typedef syslog-severity { + type enumeration { + enum EMERGENCY { + description + "Emergency: system is unusable (0)"; + } + enum ALERT { + description + "Alert: action must be taken immediately (1)"; + } + enum CRITICAL { + description + "Critical: critical conditions (2)"; + } + enum ERROR { + description + "Error: error conditions (3)"; + } + enum WARNING { + description + "Warning: warning conditions (4)"; + } + enum NOTICE { + description + "Notice: normal but significant condition(5)"; + } + enum INFORMATIONAL { + description + "Informational: informational messages (6)"; + } + enum DEBUG { + description + "Debug: debug-level messages (7)"; + } + } + description + "Syslog message severities"; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + // grouping statements + + grouping logging-selectors-config { + description + "Configuration data for logging selectors"; + + leaf facility { + type identityref { + base SYSLOG_FACILITY; + } + description + "Specifies the facility, or class of messages to log"; + } + + leaf severity { + type syslog-severity; + description + "Specifies that only messages of the given severity (or + greater severity) for the corresonding facility are logged"; + } + } + + grouping logging-selectors-state { + description + "Operational state data for logging selectors"; + } + + grouping logging-selectors-top { + description + "Top-level grouping for the logging selector list"; + + container selectors { + description + "Enclosing container "; + + list selector { + key "facility severity"; + description + "List of selectors for log messages"; + + leaf facility { + type leafref { + path "../config/facility"; + } + description + "Reference to facility list key"; + } + + leaf severity { + type leafref { + path "../config/severity"; + } + description + "Reference to severity list key"; + } + + container config { + description + "Configuration data "; + + uses logging-selectors-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses logging-selectors-config; + uses logging-selectors-state; + } + } + } + } + + grouping logging-console-config { + description + "Configuration data for console logging"; + } + + grouping logging-console-state { + description + "Operational state data for console logging"; + } + + grouping logging-console-top { + description + "Top-level grouping for console logging data"; + + container console { + description + "Top-level container for data related to console-based + logging"; + + container config { + description + "Configuration data for console logging"; + + uses logging-console-config; + } + + container state { + + config false; + + description + "Operational state data for console logging"; + + uses logging-console-config; + uses logging-console-state; + } + + uses logging-selectors-top; + } + } + + grouping logging-remote-config { + description + "Configuration data for remote log servers"; + + leaf host { + type oc-inet:host; + description + "IP address or hostname of the remote log server"; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address for packets to the log server"; + } + + leaf remote-port { + type oc-inet:port-number; + default 514; + description + "Sets the destination port number for syslog UDP messages to + the server. The default for syslog is 514."; + } + } + + grouping logging-remote-state { + description + "Operational state data for remote log servers"; + } + + grouping logging-remote-top { + description + "Top-level grouping for remote log servers"; + + container remote-servers { + description + "Enclosing container for the list of remote log servers"; + + list remote-server { + key "host"; + description + "List of remote log servers"; + + leaf host { + type leafref { + path "../config/host"; + } + description + "Reference to the host list key"; + } + + container config { + description + "Configuration data for remote log servers"; + + uses logging-remote-config; + } + + container state { + + config false; + + description + "Operational state data for remote log servers"; + + uses logging-remote-config; + uses logging-remote-state; + } + uses logging-selectors-top; + } + } + } + + grouping logging-top { + description + "Top-level grouping for logging data"; + + container logging { + description + "Top-level container for data related to logging / syslog"; + + uses logging-console-top; + uses logging-remote-top; + } + } + // data definition statements + + // augment statements + + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-system-management.yang b/models/yang/common/openconfig-system-management.yang new file mode 100644 index 0000000000..00494b7a76 --- /dev/null +++ b/models/yang/common/openconfig-system-management.yang @@ -0,0 +1,138 @@ +module openconfig-system-management { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/management"; + + prefix "oc-sys-mgmt"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to management services."; + + oc-ext:openconfig-version "0.1.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.2"; + } + + revision "2018-08-28" { + description + "Update description of the ANY enum."; + reference "0.1.1"; + } + + revision "2018-07-26" { + description + "Initial public release"; + 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 + + // typedef statements + + // grouping statements + + grouping system-grpc-server-config { + description + "Configuration data for the gRPC server"; + + leaf enable { + type boolean; + default true; + description + "Enables the gRPC server. The gRPC server is enabled by + default"; + } + + leaf port { + type oc-inet:port-number; + default 9339; + description + "TCP port on which the gRPC server should listen"; + } + + leaf transport-security { + type boolean; + description + "Enables gRPC transport security (e.g., TLS or SSL)"; + } + + leaf certificate-id { + type string; + description + "The certificate ID to be used for authentication"; + } + + leaf-list listen-addresses { + type union { + type oc-inet:ip-address; + type enumeration { + enum ANY { + description + "The gRPC daemon should listen on any address + bound to an interface on the system."; + } + } + } + description + "The IP addresses that the gRPC server should listen + on. This may be an IPv4 or an IPv6 address"; + } + } + + grouping system-grpc-server-top { + description + "Top-level grouping for system gRPC server data"; + + container grpc-server { + description + "Top-level container for the gRPC server"; + + container config { + description + "Configuration data for the system gRPC server"; + + uses system-grpc-server-config; + } + + container state { + config false; + + description + "Operational state data for the system gRPC server"; + + uses system-grpc-server-config; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-system-terminal.yang b/models/yang/common/openconfig-system-terminal.yang new file mode 100644 index 0000000000..b34811c999 --- /dev/null +++ b/models/yang/common/openconfig-system-terminal.yang @@ -0,0 +1,249 @@ +module openconfig-system-terminal { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/terminal"; + + prefix "oc-sys-term"; + + // 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 configuration and operational state data + related to remote terminal services such as ssh and telnet."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + 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 + + // typedef statements + + // grouping statements + + grouping system-terminal-common-config { + description + "Common configuration data for terminal services"; + + leaf timeout { + type uint16; + units seconds; + description + "Set the idle timeout in seconds on terminal connections to + the system for the protocol."; + } + + leaf rate-limit { + type uint16; + units "conn/min"; + description + "Set a limit on the number of connection attempts per + minute to the system for the protocol."; + } + + leaf session-limit { + type uint16; + description + "Set a limit on the number of simultaneous active terminal + sessions to the system for the protocol (e.g., ssh, + telnet, ...) "; + } + } + + grouping system-terminal-common-state { + description + "Common operational state data for terminal services"; + } + + grouping system-terminal-common-top { + description + "Top-level grouping for common terminal service data"; + + container terminal-servers { + description + "Top-level container for terminal services"; + + container config { + description + "Configuration data for terminal services"; + + uses system-terminal-common-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses system-terminal-common-config; + uses system-terminal-common-state; + } + } + } + + grouping system-ssh-server-config { + description + "Configuration data for system ssh configuration"; + + leaf enable { + type boolean; + default true; + description + "Enables the ssh server. The ssh server is enabled by + default."; + } + + leaf protocol-version { + type enumeration { + enum V2 { + description + "Use SSH v2 only"; + } + enum V1 { + description + "Use SSH v1 only"; + } + enum V1_V2 { + description + "Use either SSH v1 or v2"; + } + } + default V2; + description + "Set the protocol version for SSH connections to the system"; + } + + uses system-terminal-common-config; + } + + grouping system-ssh-server-state { + description + "Operational state data for ssh server"; + } + + grouping system-ssh-server-top { + description + "Top-level grouping for ssh server data"; + + container ssh-server { + description + "Top-level container for ssh server"; + + container config { + description + "Configuration data for the system ssh server"; + + uses system-ssh-server-config; + } + + container state { + + config false; + + description + "Operational state data for the system ssh server"; + + uses system-ssh-server-config; + uses system-ssh-server-state; + } + } + } + + grouping system-telnet-server-config { + description + "Configuration data for telnet server"; + + leaf enable { + type boolean; + default false; + description + "Enables the telnet server. Telnet is disabled by + default"; + } + uses system-terminal-common-config; + + } + + grouping system-telnet-server-state { + description + "Operational state data for telnet server"; + } + + grouping system-telnet-server-top { + description + "Top-level grouping for telnet server "; + + container telnet-server { + description + "Top-level container for telnet terminal servers"; + + container config { + description + "Configuration data for telnet"; + + uses system-telnet-server-config; + } + + container state { + + config false; + + description + "Operational state data for telnet"; + + uses system-telnet-server-config; + uses system-telnet-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-types.yang b/models/yang/common/openconfig-types.yang new file mode 100644 index 0000000000..a7ba45d91b --- /dev/null +++ b/models/yang/common/openconfig-types.yang @@ -0,0 +1,466 @@ +module openconfig-types { + yang-version "1"; + + namespace "http://openconfig.net/yang/openconfig-types"; + + prefix "oc-types"; + + // import statements + import openconfig-extensions { prefix oc-ext; } + + // meta + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module contains a set of general type definitions that + are used across OpenConfig models. It can be imported by modules + that make use of these types."; + + oc-ext:openconfig-version "0.5.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.5.1"; + } + + revision "2018-05-05" { + description + "Add grouping of min-max-time and + included them to all stats with min/max/avg"; + reference "0.5.0"; + } + + revision "2018-01-16" { + description + "Add interval to min/max/avg stats; add percentage stat"; + reference "0.4.0"; + } + + revision "2017-08-16" { + description + "Apply fix for ieetfloat32 length parameter"; + reference "0.3.3"; + } + + revision "2017-01-13" { + description + "Add ADDRESS_FAMILY identity"; + reference "0.3.2"; + } + + revision "2016-11-14" { + description + "Correct length of ieeefloat32"; + reference "0.3.1"; + } + + revision "2016-11-11" { + description + "Additional types - ieeefloat32 and routing-password"; + reference "0.3.0"; + } + + revision "2016-05-31" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + typedef percentage { + type uint8 { + range "0..100"; + } + description + "Integer indicating a percentage value"; + } + + typedef std-regexp { + type string; + description + "This type definition is a placeholder for a standard + definition of a regular expression that can be utilised in + OpenConfig models. Further discussion is required to + consider the type of regular expressions that are to be + supported. An initial proposal is POSIX compatible."; + } + + typedef timeticks64 { + type uint64; + description + "This type is based on the timeticks type defined in + RFC 6991, but with 64-bit width. It represents the time, + modulo 2^64, in hundredths of a second between two epochs."; + reference + "RFC 6991 - Common YANG Data Types"; + } + + typedef ieeefloat32 { + type binary { + length "4"; + } + description + "An IEEE 32-bit floating point number. The format of this number + is of the form: + 1-bit sign + 8-bit exponent + 23-bit fraction + The floating point value is calculated using: + (-1)**S * 2**(Exponent-127) * (1+Fraction)"; + } + + typedef routing-password { + type string; + description + "This type is indicative of a password that is used within + a routing protocol which can be returned in plain text to the + NMS by the local system. Such passwords are typically stored + as encrypted strings. Since the encryption used is generally + well known, it is possible to extract the original value from + the string - and hence this format is not considered secure. + Leaves specified with this type should not be modified by + the system, and should be returned to the end-user in plain + text. This type exists to differentiate passwords, which + may be sensitive, from other string leaves. It could, for + example, be used by the NMS to censor this data when + viewed by particular users."; + } + + typedef stat-interval { + type uint64; + units nanoseconds; + description + "A time interval over which a set of statistics is computed. + A common usage is to report the interval over which + avg/min/max stats are computed and reported."; + } + + grouping stat-interval-state { + description + "Reusable leaf definition for stats computation interval"; + + leaf interval { + type oc-types:stat-interval; + description + "If supported by the system, this reports the time interval + over which the min/max/average statistics are computed by + the system."; + } + } + + grouping min-max-time { + description + "Common grouping for recording the absolute time at which + the minimum and maximum values occurred in the statistics"; + + leaf min-time { + type oc-types:timeticks64; + description + "The absolute time at which the minimum value occurred. + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + + leaf max-time { + type oc-types:timeticks64; + description + "The absolute time at which the maximum value occurred. + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + } + + grouping avg-min-max-stats-precision1 { + description + "Common nodes for recording average, minimum, and + maximum values for a statistic. These values all have + fraction-digits set to 1. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed is also reported."; + + leaf avg { + type decimal64 { + fraction-digits 1; + } + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 1; + } + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 1; + } + description + "The maximum value of the statitic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision1 { + description + "Common grouping for recording an instantaneous statistic value + in addition to avg-min-max stats"; + + leaf instant { + type decimal64 { + fraction-digits 1; + } + description + "The instantaneous value of the statistic."; + } + + uses avg-min-max-stats-precision1; + } + + grouping avg-min-max-instant-stats-precision2-dB { + description + "Common grouping for recording dB values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The minimum value of the statistic over the time interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The maximum value of the statistic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision2-dBm { + description + "Common grouping for recording dBm values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The maximum value of the statistic over the time interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision2-mA { + description + "Common grouping for recording mA values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The maximum value of the statistic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-pct { + description + "Common grouping for percentage statistics. + Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type oc-types:percentage; + description + "The instantaneous percentage value."; + } + + leaf avg { + type oc-types:percentage; + description + "The arithmetic mean value of the percentage measure of the + statistic over the time interval."; + } + + leaf min { + type oc-types:percentage; + description + "The minimum value of the percentage measure of the + statistic over the time interval."; + } + + leaf max { + type oc-types:percentage; + description + "The maximum value of the percentage measure of the + statistic over the time interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + identity ADDRESS_FAMILY { + description + "A base identity for all address families"; + } + + identity IPV4 { + base ADDRESS_FAMILY; + description + "The IPv4 address family"; + } + + identity IPV6 { + base ADDRESS_FAMILY; + description + "The IPv6 address family"; + } + + identity MPLS { + base ADDRESS_FAMILY; + description + "The MPLS address family"; + } + + identity L2_ETHERNET { + base ADDRESS_FAMILY; + description + "The 802.3 Ethernet address family"; + } + +} diff --git a/models/yang/common/openconfig-vlan-types.yang b/models/yang/common/openconfig-vlan-types.yang new file mode 100644 index 0000000000..7778e19f0b --- /dev/null +++ b/models/yang/common/openconfig-vlan-types.yang @@ -0,0 +1,206 @@ +module openconfig-vlan-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/vlan-types"; + + prefix "oc-vlan-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module defines configuration and state variables for VLANs, + in addition to VLAN parameters associated with interfaces"; + + oc-ext:openconfig-version "3.0.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "3.0.1"; + } + + revision "2018-02-14" { + description + "Fix bug with name of 802.1ad identity."; + reference "3.0.0"; + } + + revision "2017-07-14" { + description + "Move top-level vlan data to network-instance; Update + identities to comply to style guide; fixed pattern + quoting; corrected trunk vlan types; added TPID config to + base interface."; + reference "2.0.0"; + } + + revision "2016-05-26" { + description + "OpenConfig public release"; + reference "1.0.2"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity TPID_TYPES { + description + "Base identity for TPID values that can override the VLAN + ethertype value"; + } + + identity TPID_0X8100 { + base TPID_TYPES; + description + "Default TPID value for 802.1q single-tagged VLANs."; + } + + identity TPID_0X88A8 { + base TPID_TYPES; + description + "TPID value for 802.1ad provider bridging, QinQ or + stacked VLANs."; + } + + identity TPID_0X9100 { + base TPID_TYPES; + description + "Alternate TPID value"; + } + + identity TPID_0X9200 { + base TPID_TYPES; + description + "Alternate TPID value"; + } + + // typedef statements + + // TODO: typedefs should be defined in a vlan-types.yang file. + typedef vlan-id { + type uint16 { + range 1..4094; + } + description + "Type definition representing a single-tagged VLAN"; + } + + typedef vlan-range { + type string { + // range specified as [lower]..[upper] + pattern '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.(409[0-4]|' + + '40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{1,2}|' + + '[1-9])$'; + } + description + "Type definition representing a range of single-tagged + VLANs. A range is specified as x..y where x and y are + valid VLAN IDs (1 <= vlan-id <= 4094). The range is + assumed to be inclusive, such that any VLAN-ID matching + x <= VLAN-ID <= y falls within the range."; + } + + typedef qinq-id { + type string { + pattern + '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.' + + '((409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])|\*)$'; + } + description + "Type definition representing a single double-tagged/QinQ VLAN + identifier. The format of a QinQ VLAN-ID is x.y where X is the + 'outer' VLAN identifier, and y is the 'inner' VLAN identifier. + Both x and y must be valid VLAN IDs (1 <= vlan-id <= 4094) + with the exception that y may be equal to a wildcard (*). In + cases where y is set to the wildcard, this represents all inner + VLAN identifiers where the outer VLAN identifier is equal to + x"; + } + + typedef qinq-id-range { + type union { + type string { + // match cases where the range is specified as x..y.z + pattern + '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.' + + '((409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])|\*)$'; + } + type string { + // match cases where the range is specified as x.y..z + pattern + '^(\*|(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9]))\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])$'; + } + } + description + "A type definition representing a range of double-tagged/QinQ + VLAN identifiers. The format of a QinQ VLAN-ID range can be + specified in three formats. Where the range is outer VLAN IDs + the range is specified as x..y.z. In this case outer VLAN + identifiers meeting the criteria x <= outer-vlan-id <= y are + accepted iff the inner VLAN-ID is equal to y - or any inner-tag + if the wildcard is specified. Alternatively the range can be + specified as x.y..z. In this case only VLANs with an + outer-vlan-id qual to x are accepted (x may again be the + wildcard). Inner VLANs are accepted if they meet the inequality + y <= inner-vlan-id <= z."; + } + + typedef vlan-mode-type { + type enumeration { + enum ACCESS { + description "Access mode VLAN interface (No 802.1q header)"; + } + enum TRUNK { + description "Trunk mode VLAN interface"; + } + } + description + "VLAN interface mode (trunk or access)"; + } + + typedef vlan-ref { + type union { + type vlan-id; + type string; + // TODO: string should be changed to leafref to reference + // an existing VLAN. this is not allowed in YANG 1.0 but + // is expected to be in YANG 1.1. + // type leafref { + // path "vlan:vlans/vlan:vlan/vlan:config/vlan:name"; + // } + } + description + "Reference to a VLAN by name or id"; + } + +} diff --git a/models/yang/common/openconfig-vlan.yang b/models/yang/common/openconfig-vlan.yang new file mode 100644 index 0000000000..1cae824760 --- /dev/null +++ b/models/yang/common/openconfig-vlan.yang @@ -0,0 +1,449 @@ +module openconfig-vlan { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/vlan"; + + prefix "oc-vlan"; + + // import some basic types + import openconfig-vlan-types { prefix oc-vlan-types; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-if-aggregate { prefix oc-lag; } + import iana-if-type { prefix ift; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module defines configuration and state variables for VLANs, + in addition to VLAN parameters associated with interfaces"; + + oc-ext:openconfig-version "3.0.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "3.0.2"; + } + + revision "2018-06-05" { + description + "Fix bugs in when statements."; + reference "3.0.1"; + } + + revision "2018-02-14" { + description + "Fix bug with name of 802.1ad identity."; + reference "3.0.0"; + } + + revision "2017-07-14" { + description + "Move top-level vlan data to network-instance; Update + identities to comply to style guide; fixed pattern + quoting; corrected trunk vlan types; added TPID config to + base interface."; + reference "2.0.0"; + } + + revision "2016-05-26" { + description + "OpenConfig public release"; + reference "1.0.2"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping vlan-config { + description "VLAN configuration container."; + + leaf vlan-id { + type oc-vlan-types:vlan-id; + description "Interface VLAN id."; + } + + leaf name { + type string; + description "Interface VLAN name."; + } + + leaf status { + type enumeration { + enum ACTIVE { + description "VLAN is active"; + } + enum SUSPENDED { + description "VLAN is inactive / suspended"; + } + } + default ACTIVE; + description "Admin state of the VLAN"; + } + + } + + grouping vlan-state { + description "State variables for VLANs"; + + // placeholder + + } + + grouping vlan-tpid-config { + description + "TPID configuration for dot1q-enabled interfaces"; + + leaf tpid { + type identityref { + base oc-vlan-types:TPID_TYPES; + } + default oc-vlan-types:TPID_0X8100; + description + "Optionally set the tag protocol identifier field (TPID) that + is accepted on the VLAN"; + } + } + + grouping vlan-tpid-state { + description + "TPID opstate for dot1q-enabled interfaces"; + + // placeholder + + } + + grouping vlan-members-state { + description + "List of interfaces / subinterfaces belonging to the VLAN."; + + container members { + description + "Enclosing container for list of member interfaces"; + + list member { + config false; + description + "List of references to interfaces / subinterfaces + associated with the VLAN."; + + uses oc-if:base-interface-ref-state; + } + } + } + + grouping vlan-switched-config { + description + "VLAN related configuration that is part of the physical + Ethernet interface."; + + leaf interface-mode { + type oc-vlan-types:vlan-mode-type; + description + "Set the interface to access or trunk mode for + VLANs"; + } + + leaf native-vlan { + when "../interface-mode = 'TRUNK'" { + description + "Native VLAN is valid for trunk mode interfaces"; + } + type oc-vlan-types:vlan-id; + description + "Set the native VLAN id for untagged frames arriving on + a trunk interface. Tagged frames sent on an interface + configured with a native VLAN should have their tags + stripped prior to transmission. This configuration is only + valid on a trunk interface."; + } + + leaf access-vlan { + when "../interface-mode = 'ACCESS'" { + description + "Access VLAN assigned to the interfaces"; + } + type oc-vlan-types:vlan-id; + description + "Assign the access vlan to the access port."; + } + + leaf-list trunk-vlans { + when "../interface-mode = 'TRUNK'" { + description + "Allowed VLANs may be specified for trunk mode + interfaces."; + } + type union { + type oc-vlan-types:vlan-id; + type oc-vlan-types:vlan-range; + } + description + "Specify VLANs, or ranges thereof, that the interface may + carry when in trunk mode. If not specified, all VLANs are + allowed on the interface. Ranges are specified in the form + x..y, where x.'"; + } + } + + grouping acl-interfaces-state { + description + "Operational state data for interface references"; + } + + grouping acl-interfaces-top { + description + "Top-level grouping for interface-specific ACL data"; + + container interfaces { + description + "Enclosing container for the list of interfaces on which + ACLs are set"; + + list interface { + key "id"; + description + "List of interfaces on which ACLs are set"; + + leaf id { + type leafref { + path "../config/id"; + } + description + "Reference to the interface id list key"; + } + + container config { + description + "Configuration for ACL per-interface data"; + + uses acl-interfaces-config; + } + + container state { + + config false; + + description + "Operational state for ACL per-interface data"; + + uses acl-interfaces-config; + uses acl-interfaces-state; + } + + uses oc-if:interface-ref; + uses interface-ingress-acl-top; + uses interface-egress-acl-top; + } + } + } + + grouping acl-config { + description + "Global configuration data for ACLs"; + } + + grouping acl-state { + description + "Global operational state data for ACLs"; + + leaf counter-capability { + type identityref { + base ACL_COUNTER_CAPABILITY; + } + description + "System reported indication of how ACL counters are reported + by the target"; + } + } + grouping acl-top { + description + "Top level grouping for ACL data and structure"; + + container acl { + description + "Top level enclosing container for ACL model config + and operational state data"; + + container config { + description + "Global config data for ACLs"; + + uses acl-config; + } + + container state { + + config false; + + description + "Global operational state data for ACLs"; + + uses acl-config; + uses acl-state; + } + + uses acl-set-top; + uses acl-interfaces-top; + } + } + + // data definition statements + uses acl-top; + + // augment statements + + +} diff --git a/models/yang/openconfig-if-ethernet.yang b/models/yang/openconfig-if-ethernet.yang new file mode 100644 index 0000000000..e917bba4d7 --- /dev/null +++ b/models/yang/openconfig-if-ethernet.yang @@ -0,0 +1,438 @@ +module openconfig-if-ethernet { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ethernet"; + + prefix "oc-eth"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import iana-if-type { prefix ift; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing Ethernet interfaces -- augments the OpenConfig + model for interface configuration and state."; + + oc-ext:openconfig-version "2.6.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.6.2"; + } + + revision "2018-09-04" { + description + "Remove in-crc-align-errors as it is a duplicate of + in-crc-errors"; + reference "2.6.1"; + } + + revision "2018-08-28" { + description + "Add Ethernet counter in-block-errors"; + reference "2.6.0"; + } + + revision "2018-07-02" { + description + "Add new ethernet counters of in-undersize-frames, + in-crc-align-errors and the distribution container"; + reference "2.5.0"; + } + + revision "2018-04-10" { + description + "Add identities for 2.5 and 5 Gbps."; + reference "2.4.0"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + identity ETHERNET_SPEED { + description "base type to specify available Ethernet link + speeds"; + } + + identity SPEED_10MB { + base ETHERNET_SPEED; + description "10 Mbps Ethernet"; + } + + identity SPEED_100MB { + base ETHERNET_SPEED; + description "100 Mbps Ethernet"; + } + + identity SPEED_1GB { + base ETHERNET_SPEED; + description "1 Gbps Ethernet"; + } + + identity SPEED_2500MB { + base ETHERNET_SPEED; + description "2.5 Gbps Ethernet"; + } + + identity SPEED_5GB { + base ETHERNET_SPEED; + description "5 Gbps Ethernet"; + } + + identity SPEED_10GB { + base ETHERNET_SPEED; + description "10 Gbps Ethernet"; + } + + identity SPEED_25GB { + base ETHERNET_SPEED; + description "25 Gbps Ethernet"; + } + + identity SPEED_40GB { + base ETHERNET_SPEED; + description "40 Gbps Ethernet"; + } + + identity SPEED_50GB { + base ETHERNET_SPEED; + description "50 Gbps Ethernet"; + } + + identity SPEED_100GB { + base ETHERNET_SPEED; + description "100 Gbps Ethernet"; + } + + identity SPEED_UNKNOWN { + base ETHERNET_SPEED; + description + "Interface speed is unknown. Systems may report + speed UNKNOWN when an interface is down or unpopuplated (e.g., + pluggable not present)."; + } + + // typedef statements + + + // grouping statements + + grouping ethernet-interface-config { + description "Configuration items for Ethernet interfaces"; + + leaf mac-address { + type oc-yang:mac-address; + description + "Assigns a MAC address to the Ethernet interface. If not + specified, the corresponding operational state leaf is + expected to show the system-assigned MAC address."; + } + + leaf auto-negotiate { + type boolean; + default true; + description + "Set to TRUE to request the interface to auto-negotiate + transmission parameters with its peer interface. When + set to FALSE, the transmission parameters are specified + manually."; + reference + "IEEE 802.3-2012 auto-negotiation transmission parameters"; + } + + leaf duplex-mode { + type enumeration { + enum FULL { + description "Full duplex mode"; + } + enum HALF { + description "Half duplex mode"; + } + } + description + "When auto-negotiate is TRUE, this optionally sets the + duplex mode that will be advertised to the peer. If + unspecified, the interface should negotiate the duplex mode + directly (typically full-duplex). When auto-negotiate is + FALSE, this sets the duplex mode on the interface directly."; + } + + leaf port-speed { + type identityref { + base ETHERNET_SPEED; + } + description + "When auto-negotiate is TRUE, this optionally sets the + port-speed mode that will be advertised to the peer for + negotiation. If unspecified, it is expected that the + interface will select the highest speed available based on + negotiation. When auto-negotiate is set to FALSE, sets the + link speed to a fixed value -- supported values are defined + by ETHERNET_SPEED identities"; + } + + leaf enable-flow-control { + type boolean; + default false; + description + "Enable or disable flow control for this interface. + Ethernet flow control is a mechanism by which a receiver + may send PAUSE frames to a sender to stop transmission for + a specified time. + + This setting should override auto-negotiated flow control + settings. If left unspecified, and auto-negotiate is TRUE, + flow control mode is negotiated with the peer interface."; + reference + "IEEE 802.3x"; + } + } + + grouping ethernet-interface-state-counters { + description + "Ethernet-specific counters and statistics"; + + // ingress counters + + leaf in-mac-control-frames { + type oc-yang:counter64; + description + "MAC layer control frames received on the interface"; + } + + leaf in-mac-pause-frames { + type oc-yang:counter64; + description + "MAC layer PAUSE frames received on the interface"; + } + + leaf in-oversize-frames { + type oc-yang:counter64; + description + "The total number of frames received that were + longer than 1518 octets (excluding framing bits, + but including FCS octets) and were otherwise + well formed."; + } + + leaf in-undersize-frames { + type oc-yang:counter64; + description + "The total number of frames received that were + less than 64 octets long (excluding framing bits, + but including FCS octets) and were otherwise well + formed."; + reference + "RFC 2819: Remote Network Monitoring MIB - + etherStatsUndersizePkts"; + } + + leaf in-jabber-frames { + type oc-yang:counter64; + description + "Number of jabber frames received on the + interface. Jabber frames are typically defined as oversize + frames which also have a bad CRC. Implementations may use + slightly different definitions of what constitutes a jabber + frame. Often indicative of a NIC hardware problem."; + } + + leaf in-fragment-frames { + type oc-yang:counter64; + description + "The total number of frames received that were less than + 64 octets in length (excluding framing bits but including + FCS octets) and had either a bad Frame Check Sequence + (FCS) with an integral number of octets (FCS Error) or a + bad FCS with a non-integral number of octets (Alignment + Error)."; + } + + leaf in-8021q-frames { + type oc-yang:counter64; + description + "Number of 802.1q tagged frames received on the interface"; + } + + leaf in-crc-errors { + type oc-yang:counter64; + description + "The total number of frames received that + had a length (excluding framing bits, but + including FCS octets) of between 64 and 1518 + octets, inclusive, but had either a bad + Frame Check Sequence (FCS) with an integral + number of octets (FCS Error) or a bad FCS with + a non-integral number of octets (Alignment Error)"; + reference + "RFC 2819: Remote Network Monitoring MIB - + etherStatsCRCAlignErrors"; + } + + leaf in-block-errors { + type oc-yang:counter64; + description + "The number of received errored blocks. Error detection codes + are capable of detecting whether one or more errors have + occurred in a given sequence of bits – the block. It is + normally not possible to determine the exact number of errored + bits within the block"; + } + + // egress counters + + leaf out-mac-control-frames { + type oc-yang:counter64; + description + "MAC layer control frames sent on the interface"; + } + + leaf out-mac-pause-frames { + type oc-yang:counter64; + description + "MAC layer PAUSE frames sent on the interface"; + } + + leaf out-8021q-frames { + type oc-yang:counter64; + description + "Number of 802.1q tagged frames sent on the interface"; + } + } + + grouping ethernet-interface-state { + description + "Grouping for defining Ethernet-specific operational state"; + + leaf hw-mac-address { + type oc-yang:mac-address; + description + "Represenets the 'burned-in', or system-assigned, MAC + address for the Ethernet interface."; + } + + leaf negotiated-duplex-mode { + type enumeration { + enum FULL { + description "Full duplex mode"; + } + enum HALF { + description "Half duplex mode"; + } + } + description + "When auto-negotiate is set to TRUE, and the interface has + completed auto-negotiation with the remote peer, this value + shows the duplex mode that has been negotiated."; + } + + leaf negotiated-port-speed { + type identityref { + base ETHERNET_SPEED; + } + description + "When auto-negotiate is set to TRUE, and the interface has + completed auto-negotiation with the remote peer, this value + shows the interface speed that has been negotiated."; + } + + container counters { + description "Ethernet interface counters"; + + uses ethernet-interface-state-counters; + + } + + } + + // data definition statements + + grouping ethernet-top { + description "top-level Ethernet config and state containers"; + + container ethernet { + description + "Top-level container for ethernet configuration + and state"; + + container config { + description "Configuration data for ethernet interfaces"; + + uses ethernet-interface-config; + + } + + container state { + + config false; + description "State variables for Ethernet interfaces"; + + uses ethernet-interface-config; + uses ethernet-interface-state; + + } + + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface" { + description "Adds addtional Ethernet-specific configuration to + interfaces model"; + + uses ethernet-top { + when "oc-if:state/oc-if:type = 'ift:ethernetCsmacd'" { + description "Additional interface configuration parameters when + the interface type is Ethernet"; + } + } + } + + // rpc statements + + // notification statements + +} diff --git a/models/yang/openconfig-if-ip.yang b/models/yang/openconfig-if-ip.yang new file mode 100644 index 0000000000..df89662f83 --- /dev/null +++ b/models/yang/openconfig-if-ip.yang @@ -0,0 +1,1322 @@ +module openconfig-if-ip { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ip"; + + prefix "oc-ip"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-vlan { prefix oc-vlan; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This model defines data for managing configuration and + operational state on IP (IPv4 and IPv6) interfaces. + + This model reuses data items defined in the IETF YANG model for + interfaces described by RFC 7277 with an alternate structure + (particularly for operational state data) and with + additional configuration items. + + Portions of this code were derived from IETF RFC 7277. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "2.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2017-04-03"{ + description + "Update copyright notice."; + reference "1.1.1"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // typedef statements + + typedef ip-address-origin { + type enumeration { + enum OTHER { + description + "None of the following."; + } + enum STATIC { + description + "Indicates that the address has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum DHCP { + description + "Indicates an address that has been assigned to this + system by a DHCP server."; + } + enum LINK_LAYER { + description + "Indicates an address created by IPv6 stateless + autoconfiguration that embeds a link-layer address in its + interface identifier."; + } + enum RANDOM { + description + "Indicates an address chosen by the system at + random, e.g., an IPv4 address within 169.254/16, an + RFC 4941 temporary address, or an RFC 7217 semantically + opaque address."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + RFC 7217: A Method for Generating Semantically Opaque + Interface Identifiers with IPv6 Stateless + Address Autoconfiguration (SLAAC)"; + } + } + description + "The origin of an address."; + } + + typedef neighbor-origin { + type enumeration { + enum OTHER { + description + "None of the following."; + } + enum STATIC { + description + "Indicates that the mapping has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum DYNAMIC { + description + "Indicates that the mapping has been dynamically resolved + using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol."; + } + } + description + "The origin of a neighbor entry."; + } + + // grouping statements + + grouping ip-common-global-config { + description + "Shared configuration data for IPv4 or IPv6 assigned + globally on an interface."; + + leaf dhcp-client { + type boolean; + default false; + description + "Enables a DHCP client on the interface in order to request + an address"; + } + } + + grouping ip-common-counters-state { + description + "Operational state for IP traffic statistics for IPv4 and + IPv6"; + + container counters { + description + "Packet and byte counters for IP transmission and + reception for the address family."; + + + leaf in-pkts { + type oc-yang:counter64; + description + "The total number of IP packets received for the specified + address family, including those received in error"; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-octets { + type oc-yang:counter64; + description + "The total number of octets received in input IP packets + for the specified address family, including those received + in error."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-error-pkts { + // TODO: this counter combines several error conditions -- + // could consider breaking them out to separate leaf nodes + type oc-yang:counter64; + description + "Number of IP packets discarded due to errors for the + specified address family, including errors in the IP + header, no route found to the IP destination, invalid + address, unknown protocol, etc."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-forwarded-pkts { + type oc-yang:counter64; + description + "The number of input packets for which the device was not + their final IP destination and for which the device + attempted to find a route to forward them to that final + destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-forwarded-octets { + type oc-yang:counter64; + description + "The number of octets received in input IP packets + for the specified address family for which the device was + not their final IP destination and for which the + device attempted to find a route to forward them to that + final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-discarded-pkts { + type oc-yang:counter64; + description + "The number of input IP packets for the + specified address family, for which no problems were + encountered to prevent their continued processing, but + were discarded (e.g., for lack of buffer space)."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-pkts { + type oc-yang:counter64; + description + "The total number of IP packets for the + specified address family that the device supplied + to the lower layers for transmission. This includes + packets generated locally and those forwarded by the + device."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-octets { + type oc-yang:counter64; + description + "The total number of octets in IP packets for the + specified address family that the device + supplied to the lower layers for transmission. This + includes packets generated locally and those forwarded by + the device."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-error-pkts { + // TODO: this counter combines several error conditions -- + // could consider breaking them out to separate leaf nodes + type oc-yang:counter64; + description + "Number of IP packets for the specified address family + locally generated and discarded due to errors, including + no route found to the IP destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-forwarded-pkts { + type oc-yang:counter64; + description + "The number of packets for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-forwarded-octets { + type oc-yang:counter64; + description + "The number of octets in packets for which this entity was + not their final IP destination and for which it was + successful in finding a path to their final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-discarded-pkts { + type oc-yang:counter64; + description + "The number of output IP packets for the + specified address family for which no problem was + encountered to prevent their transmission to their + destination, but were discarded (e.g., for lack of + buffer space)."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + } + + } + + + + grouping ipv4-global-config { + description + "Configuration data for IPv4 interfaces across + all addresses assigned to the interface"; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv4 is enabled or disabled on this + interface. When IPv4 is enabled, this interface is + connected to an IPv4 stack, and the interface can send + and receive IPv4 packets."; + } + + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 791: Internet Protocol"; + } + + uses ip-common-global-config; + + + } + + grouping ipv4-address-config { + + description + "Per IPv4 adresss configuration data for the + interface."; + + leaf ip { + type oc-inet:ipv4-address; + description + "The IPv4 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + } + + grouping ipv4-neighbor-config { + description + "Per IPv4 neighbor configuration data. Neighbor + entries are analagous to static ARP entries, i.e., they + create a correspondence between IP and link-layer addresses"; + + leaf ip { + type oc-inet:ipv4-address; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type oc-yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + grouping ipv4-address-state { + description + "State variables for IPv4 addresses on the interface"; + + leaf origin { + type ip-address-origin; + description + "The origin of this address, e.g., statically configured, + assigned by DHCP, etc.."; + } + } + + grouping ipv4-neighbor-state { + description + "State variables for IPv4 neighbor entries on the interface."; + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry, static or dynamic."; + } + } + + grouping ipv6-global-config { + description + "Configuration data at the global level for each + IPv6 interface"; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv6 is enabled or disabled on this + interface. When IPv6 is enabled, this interface is + connected to an IPv6 stack, and the interface can send + and receive IPv6 packets."; + } + + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + + leaf dup-addr-detect-transmits { + type uint32; + default 1; + description + "The number of consecutive Neighbor Solicitation messages + sent while performing Duplicate Address Detection on a + tentative address. A value of zero indicates that + Duplicate Address Detection is not performed on + tentative addresses. A value of one indicates a single + transmission with no follow-up retransmissions."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + + uses ip-common-global-config; + } + + grouping ipv6-address-config { + description "Per-address configuration data for IPv6 interfaces"; + + leaf ip { + type oc-inet:ipv6-address; + description + "The IPv6 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + } + + grouping ipv6-address-state { + description + "Per-address operational state data for IPv6 interfaces"; + + leaf origin { + type ip-address-origin; + description + "The origin of this address, e.g., static, dhcp, etc."; + } + + leaf status { + type enumeration { + enum PREFERRED { + description + "This is a valid address that can appear as the + destination or source address of a packet."; + } + enum DEPRECATED { + description + "This is a valid but deprecated address that should + no longer be used as a source address in new + communications, but packets addressed to such an + address are processed as expected."; + } + enum INVALID { + description + "This isn't a valid address, and it shouldn't appear + as the destination or source address of a packet."; + } + enum INACCESSIBLE { + description + "The address is not accessible because the interface + to which this address is assigned is not + operational."; + } + enum UNKNOWN { + description + "The status cannot be determined for some reason."; + } + enum TENTATIVE { + description + "The uniqueness of the address on the link is being + verified. Addresses in this state should not be + used for general communication and should only be + used to determine the uniqueness of the address."; + } + enum DUPLICATE { + description + "The address has been determined to be non-unique on + the link and so must not be used."; + } + enum OPTIMISTIC { + description + "The address is available for use, subject to + restrictions, while its uniqueness on a link is + being verified."; + } + } + description + "The status of an address. Most of the states correspond + to states from the IPv6 Stateless Address + Autoconfiguration protocol."; + reference + "RFC 4293: Management Information Base for the + Internet Protocol (IP) + - IpAddressStatusTC + RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + } + + grouping ipv6-neighbor-config { + description + "Per-neighbor configuration data for IPv6 interfaces"; + + leaf ip { + type oc-inet:ipv6-address; + description + "The IPv6 address of the neighbor node."; + } + + leaf link-layer-address { + type oc-yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + grouping ipv6-neighbor-state { + description "Per-neighbor state variables for IPv6 interfaces"; + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + leaf is-router { + type empty; + description + "Indicates that the neighbor node acts as a router."; + } + leaf neighbor-state { + type enumeration { + enum INCOMPLETE { + description + "Address resolution is in progress, and the link-layer + address of the neighbor has not yet been + determined."; + } + enum REACHABLE { + description + "Roughly speaking, the neighbor is known to have been + reachable recently (within tens of seconds ago)."; + } + enum STALE { + description + "The neighbor is no longer known to be reachable, but + until traffic is sent to the neighbor no attempt + should be made to verify its reachability."; + } + enum DELAY { + description + "The neighbor is no longer known to be reachable, and + traffic has recently been sent to the neighbor. + Rather than probe the neighbor immediately, however, + delay sending probes for a short while in order to + give upper-layer protocols a chance to provide + reachability confirmation."; + } + enum PROBE { + description + "The neighbor is no longer known to be reachable, and + unicast Neighbor Solicitation probes are being sent + to verify reachability."; + } + } + description + "The Neighbor Unreachability Detection state of this + entry."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 7.3.2"; + } + } + + grouping ip-vrrp-ipv6-config { + description + "IPv6-specific configuration data for VRRP on IPv6 + interfaces"; + + leaf virtual-link-local { + type oc-inet:ip-address; + description + "For VRRP on IPv6 interfaces, sets the virtual link local + address"; + } + } + + grouping ip-vrrp-ipv6-state { + description + "IPv6-specific operational state for VRRP on IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + grouping ip-vrrp-tracking-config { + description + "Configuration data for tracking interfaces + in a VRRP group"; + + leaf-list track-interface { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + // TODO: we may need to add some restriction to ethernet + // or IP interfaces. + description + "Sets a list of one or more interfaces that should + be tracked for up/down events to dynamically change the + priority state of the VRRP group, and potentially + change the mastership if the tracked interface going + down lowers the priority sufficiently. Any of the tracked + interfaces going down will cause the priority to be lowered. + Some implementations may only support a single + tracked interface."; + } + + leaf priority-decrement { + type uint8 { + range 0..254; + } + default 0; + description "Set the value to subtract from priority when + the tracked interface goes down"; + } + } + + grouping ip-vrrp-tracking-state { + description + "Operational state data for tracking interfaces in a VRRP + group"; + } + + grouping ip-vrrp-tracking-top { + description + "Top-level grouping for VRRP interface tracking"; + + container interface-tracking { + description + "Top-level container for VRRP interface tracking"; + + container config { + description + "Configuration data for VRRP interface tracking"; + + uses ip-vrrp-tracking-config; + } + + container state { + + config false; + + description + "Operational state data for VRRP interface tracking"; + + uses ip-vrrp-tracking-config; + uses ip-vrrp-tracking-state; + } + } + } + + grouping ip-vrrp-config { + description + "Configuration data for VRRP on IP interfaces"; + + leaf virtual-router-id { + type uint8 { + range 1..255; + } + description + "Set the virtual router id for use by the VRRP group. This + usually also determines the virtual MAC address that is + generated for the VRRP group"; + } + + leaf-list virtual-address { + type oc-inet:ip-address; + description + "Configure one or more virtual addresses for the + VRRP group"; + } + + leaf priority { + type uint8 { + range 1..254; + } + default 100; + description + "Specifies the sending VRRP interface's priority + for the virtual router. Higher values equal higher + priority"; + } + + leaf preempt { + type boolean; + default true; + description + "When set to true, enables preemption by a higher + priority backup router of a lower priority master router"; + } + + leaf preempt-delay { + type uint16 { + range 0..3600; + } + default 0; + description + "Set the delay the higher priority router waits + before preempting"; + } + + leaf accept-mode { + type boolean; + // TODO: should we adopt the RFC default given the common + // operational practice of setting to true? + default false; + description + "Configure whether packets destined for + virtual addresses are accepted even when the virtual + address is not owned by the router interface"; + } + + leaf advertisement-interval { + type uint16 { + range 1..4095; + } + // TODO this range is theoretical -- needs to be validated + // against major implementations. + units "centiseconds"; + default 100; + description + "Sets the interval between successive VRRP + advertisements -- RFC 5798 defines this as a 12-bit + value expressed as 0.1 seconds, with default 100, i.e., + 1 second. Several implementation express this in units of + seconds"; + } + } + + grouping ip-vrrp-state { + description + "Operational state data for VRRP on IP interfaces"; + + leaf current-priority { + type uint8; + description "Operational value of the priority for the + interface in the VRRP group"; + } + } + + grouping ip-vrrp-top { + description + "Top-level grouping for Virtual Router Redundancy Protocol"; + + container vrrp { + description + "Enclosing container for VRRP groups handled by this + IP interface"; + + reference "RFC 5798 - Virtual Router Redundancy Protocol + (VRRP) Version 3 for IPv4 and IPv6"; + + list vrrp-group { + key "virtual-router-id"; + description + "List of VRRP groups, keyed by virtual router id"; + + leaf virtual-router-id { + type leafref { + path "../config/virtual-router-id"; + } + description + "References the configured virtual router id for this + VRRP group"; + } + + container config { + description + "Configuration data for the VRRP group"; + + uses ip-vrrp-config; + } + + container state { + + config false; + + description + "Operational state data for the VRRP group"; + + uses ip-vrrp-config; + uses ip-vrrp-state; + } + + uses ip-vrrp-tracking-top; + } + } + } + + grouping ipv6-ra-config { + description + "Configuration parameters for IPv6 router advertisements."; + + leaf interval { + type uint32; + units seconds; + description + "The interval between periodic router advertisement neighbor + discovery messages sent on this interface expressed in + seconds."; + } + + leaf lifetime { + type uint32; + units seconds; + description + "The lifetime advertised in the router advertisement neighbor + discovery message on this interface."; + } + + leaf suppress { + type boolean; + default false; + description + "When set to true, router advertisement neighbor discovery + messages are not transmitted on this interface."; + } + } + + grouping ipv4-proxy-arp-config { + description + "Configuration parameters for IPv4 proxy ARP"; + + leaf mode { + type enumeration { + enum DISABLE { + description + "The system should not respond to ARP requests that + do not specify an IP address configured on the local + subinterface as the target address."; + } + enum REMOTE_ONLY { + description + "The system responds to ARP requests only when the + sender and target IP addresses are in different + subnets."; + } + enum ALL { + description + "The system responds to ARP requests where the sender + and target IP addresses are in different subnets, as well + as those where they are in the same subnet."; + } + } + default "DISABLE"; + description + "When set to a value other than DISABLE, the local system should + respond to ARP requests that are for target addresses other than + those that are configured on the local subinterface using its own + MAC address as the target hardware address. If the REMOTE_ONLY + value is specified, replies are only sent when the target address + falls outside the locally configured subnets on the interface, + whereas with the ALL value, all requests, regardless of their + target address are replied to."; + reference "RFC1027: Using ARP to Implement Transparent Subnet Gateways"; + } + } + + grouping ipv4-top { + description "Top-level configuration and state for IPv4 + interfaces"; + + container ipv4 { + description + "Parameters for the IPv4 address family."; + + container addresses { + description + "Enclosing container for address list"; + + list address { + key "ip"; + description + "The list of configured IPv4 addresses on the interface."; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description "Configuration data for each configured IPv4 + address on the interface"; + + uses ipv4-address-config; + + } + + container state { + + config false; + description "Operational state data for each IPv4 address + configured on the interface"; + + uses ipv4-address-config; + uses ipv4-address-state; + } + + } + } + + container proxy-arp { + description + "Configuration and operational state parameters + relating to proxy ARP. This functionality allows a + system to respond to ARP requests that are not + explicitly destined to the local system."; + + container config { + description + "Configuration parameters for proxy ARP"; + uses ipv4-proxy-arp-config; + } + + container state { + config false; + description + "Operational state parameters for proxy ARP"; + uses ipv4-proxy-arp-config; + } + } + + container neighbors { + description + "Enclosing container for neighbor list"; + + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description "Configuration data for each configured IPv4 + address on the interface"; + + uses ipv4-neighbor-config; + + } + + container state { + + config false; + description "Operational state data for each IPv4 address + configured on the interface"; + + uses ipv4-neighbor-config; + uses ipv4-neighbor-state; + } + } + } + + uses oc-if:sub-unnumbered-top; + + container config { + description + "Top-level IPv4 configuration data for the interface"; + + uses ipv4-global-config; + } + + container state { + + config false; + description + "Top level IPv4 operational state data"; + + uses ipv4-global-config; + uses ip-common-counters-state; + } + } + } + + grouping ipv6-top { + description + "Top-level configuration and state for IPv6 interfaces"; + + container ipv6 { + description + "Parameters for the IPv6 address family."; + + container addresses { + description + "Enclosing container for address list"; + + list address { + key "ip"; + description + "The list of configured IPv6 addresses on the interface."; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description + "Configuration data for each IPv6 address on + the interface"; + + uses ipv6-address-config; + + } + + container state { + + config false; + description + "State data for each IPv6 address on the + interface"; + + uses ipv6-address-config; + uses ipv6-address-state; + } + } + } + + container router-advertisement { + description + "Configuration and operational state parameters relating to + router advertisements."; + + container config { + description + "Configuration parameters relating to router advertisements + for IPv6."; + uses ipv6-ra-config; + } + + container state { + config false; + description + "Operational state parameters relating to router + advertisements for IPv6."; + uses ipv6-ra-config; + } + } + + container neighbors { + description + "Enclosing container for list of IPv6 neighbors"; + + list neighbor { + key "ip"; + description + "List of IPv6 neighbors"; + + leaf ip { + type leafref { + path "../config/ip"; + } + description + "References the configured IP neighbor address"; + } + + container config { + description "Configuration data for each IPv6 address on + the interface"; + + uses ipv6-neighbor-config; + + } + + container state { + + config false; + description "State data for each IPv6 address on the + interface"; + + uses ipv6-neighbor-config; + uses ipv6-neighbor-state; + } + } + } + uses oc-if:sub-unnumbered-top; + + container config { + description "Top-level config data for the IPv6 interface"; + + uses ipv6-global-config; + } + + container state { + config false; + description + "Top-level operational state data for the IPv6 interface"; + + uses ipv6-global-config; + uses ip-common-counters-state; + + } + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface" { + description + "IPv4 address family configuration for + interfaces"; + + uses ipv4-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface" { + description + "IPv6 address family configuration for + interfaces"; + + uses ipv6-top; + + } + + // VRRP for IPv4 interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv4/oc-ip:addresses/oc-ip:address" { + + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + // VRRP for IPv6 interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/" + + "vrrp/vrrp-group/config" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/" + + "vrrp-group/state" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-state; + } + + // Augments for for routed VLANs + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan" { + description + "IPv4 address family configuration for + interfaces"; + + uses ipv4-top; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan" { + description + "IPv6 address family configuration for + interfaces"; + + uses ipv6-top; + } + + // VRRP for routed VLAN interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv4/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/vrrp-group/config" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/vrrp-group/state" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-state; + } + + // rpc statements + + // notification statements +} diff --git a/models/yang/openconfig-interfaces.yang b/models/yang/openconfig-interfaces.yang new file mode 100644 index 0000000000..f3e0feeace --- /dev/null +++ b/models/yang/openconfig-interfaces.yang @@ -0,0 +1,1067 @@ +module openconfig-interfaces { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces"; + + prefix "oc-if"; + + // import some basic types + import ietf-interfaces { prefix ietf-if; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing network interfaces and subinterfaces. This + module also defines convenience types / groupings for other + models to create references to interfaces: + + base-interface-ref (type) - reference to a base interface + interface-ref (grouping) - container for reference to a + interface + subinterface + interface-ref-state (grouping) - container for read-only + (opstate) reference to interface + subinterface + + This model reuses data items defined in the IETF YANG model for + interfaces described by RFC 7223 with an alternate structure + (particularly for operational state data) and with + additional configuration items. + + Portions of this code were derived from IETF RFC 7223. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "2.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.4.1"; + } + + revision "2018-08-07" { + description + "Add leaf to indicate whether an interface is physical or + logical."; + reference "2.4.0"; + } + + revision "2018-07-02" { + description + "Add in-pkts and out-pkts in counters"; + reference "2.3.2"; + } + + revision "2018-04-24" { + description + "Clarified behavior of last-change state leaf"; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-22" { + description + "Add IPv4 proxy ARP configuration."; + reference "2.2.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2017-04-03" { + description + "Update copyright notice."; + reference "1.1.1"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // typedef statements + + typedef base-interface-ref { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Reusable type for by-name reference to a base interface. + This type may be used in cases where ability to reference + a subinterface is not required."; + } + + typedef interface-id { + type string; + description + "User-defined identifier for an interface, generally used to + name a interface reference. The id can be arbitrary but a + useful convention is to use a combination of base interface + name and subinterface index."; + } + + // grouping statements + + grouping interface-ref-common { + description + "Reference leafrefs to interface / subinterface"; + + leaf interface { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Reference to a base interface. If a reference to a + subinterface is required, this leaf must be specified + to indicate the base interface."; + } + + leaf subinterface { + type leafref { + path "/oc-if:interfaces/" + + "oc-if:interface[oc-if:name=current()/../interface]/" + + "oc-if:subinterfaces/oc-if:subinterface/oc-if:index"; + } + description + "Reference to a subinterface -- this requires the base + interface to be specified using the interface leaf in + this container. If only a reference to a base interface + is requuired, this leaf should not be set."; + } + } + + grouping interface-ref-state-container { + description + "Reusable opstate w/container for a reference to an + interface or subinterface"; + + container state { + config false; + description + "Operational state for interface-ref"; + + uses interface-ref-common; + } + } + + grouping interface-ref { + description + "Reusable definition for a reference to an interface or + subinterface"; + + container interface-ref { + description + "Reference to an interface or subinterface"; + + container config { + description + "Configured reference to interface / subinterface"; + oc-ext:telemetry-on-change; + + uses interface-ref-common; + } + + uses interface-ref-state-container; + } + } + + grouping interface-ref-state { + description + "Reusable opstate w/container for a reference to an + interface or subinterface"; + + container interface-ref { + description + "Reference to an interface or subinterface"; + + uses interface-ref-state-container; + } + } + + grouping base-interface-ref-state { + description + "Reusable opstate w/container for a reference to a + base interface (no subinterface)."; + + container state { + config false; + description + "Operational state for base interface reference"; + + leaf interface { + type base-interface-ref; + description + "Reference to a base interface."; + } + } + } + + + grouping interface-common-config { + description + "Configuration data data nodes common to physical interfaces + and subinterfaces"; + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + datastore. + + Specifically, if the device supports ':startup', when + ifAlias is read the device MUST return the value of + 'description' in the 'startup' datastore, and when it is + written, it MUST be written to the 'running' and 'startup' + datastores. Note that it is up to the implementation to + + decide whether to modify this single leaf in 'startup' or + perform an implicit copy-config from 'running' to + 'startup'. + + If the device does not support ':startup', ifAlias MUST + be mapped to the 'description' leaf in the 'running' + datastore."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the 'running' datastore to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + Changes in this leaf in the 'running' datastore are + reflected in ifAdminStatus, but if ifAdminStatus is + changed over SNMP, this leaf is not affected."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + } + + grouping interface-phys-config { + description + "Configuration data for physical interfaces"; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. The 'config false' + list interfaces/interface[name]/state contains the currently + existing interfaces on the device. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + corresponding state list, the server MAY reject + the request if the implementation does not support + pre-provisioning of interfaces or if the name refers to + an interface that can never exist in the system. A + NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case. + + The IETF model in RFC 7223 provides YANG features for the + following (i.e., pre-provisioning and arbitrary-names), + however they are omitted here: + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + /interfaces/interface[name]/state list."; + } + + leaf type { + type identityref { + base ietf-if:interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf mtu { + type uint16; + description + "Set the max transmission unit size in octets + for the physical interface. If this is not set, the mtu is + set to the operational default -- e.g., 1514 bytes on an + Ethernet interface."; + } + + leaf loopback-mode { + type boolean; + default false; + description + "When set to true, the interface is logically looped back, + such that packets that are forwarded via the interface + are received on the same interface."; + } + + uses interface-common-config; + } + + grouping interface-phys-holdtime-config { + description + "Configuration data for interface hold-time settings -- + applies to physical interfaces."; + + leaf up { + type uint32; + units milliseconds; + default 0; + description + "Dampens advertisement when the interface + transitions from down to up. A zero value means dampening + is turned off, i.e., immediate notification."; + } + + leaf down { + type uint32; + units milliseconds; + default 0; + description + "Dampens advertisement when the interface transitions from + up to down. A zero value means dampening is turned off, + i.e., immediate notification."; + } + } + + grouping interface-phys-holdtime-state { + description + "Operational state data for interface hold-time."; + } + + grouping interface-phys-holdtime-top { + description + "Top-level grouping for setting link transition + dampening on physical and other types of interfaces."; + + container hold-time { + description + "Top-level container for hold-time settings to enable + dampening advertisements of interface transitions."; + + container config { + description + "Configuration data for interface hold-time settings."; + oc-ext:telemetry-on-change; + + uses interface-phys-holdtime-config; + } + + container state { + + config false; + + description + "Operational state data for interface hold-time."; + + uses interface-phys-holdtime-config; + uses interface-phys-holdtime-state; + } + } + } + + grouping interface-common-state { + description + "Operational state data (in addition to intended configuration) + at the global level for this interface"; + + oc-ext:operational; + + leaf ifindex { + type uint32; + description + "System assigned number for each interface. Corresponds to + ifIndex object in SNMP Interface MIB"; + reference + "RFC 2863 - The Interfaces Group MIB"; + oc-ext:telemetry-on-change; + } + + leaf admin-status { + type enumeration { + enum UP { + description + "Ready to pass packets."; + } + enum DOWN { + description + "Not ready to pass packets and not in some test mode."; + } + enum TESTING { + //TODO: This is generally not supported as a configured + //admin state, though it's in the standard interfaces MIB. + //Consider removing it. + description + "In some test mode."; + } + } + //TODO:consider converting to an identity to have the + //flexibility to remove some values defined by RFC 7223 that + //are not used or not implemented consistently. + mandatory true; + description + "The desired state of the interface. In RFC 7223 this leaf + has the same read semantics as ifAdminStatus. Here, it + reflects the administrative state as set by enabling or + disabling the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + oc-ext:telemetry-on-change; + } + + leaf oper-status { + type enumeration { + enum UP { + value 1; + description + "Ready to pass packets."; + } + enum DOWN { + value 2; + description + "The interface does not pass any packets."; + } + enum TESTING { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum UNKNOWN { + value 4; + description + "Status cannot be determined for some reason."; + } + enum DORMANT { + value 5; + description + "Waiting for some external event."; + } + enum NOT_PRESENT { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum LOWER_LAYER_DOWN { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + //TODO:consider converting to an identity to have the + //flexibility to remove some values defined by RFC 7223 that + //are not used or not implemented consistently. + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + oc-ext:telemetry-on-change; + } + + leaf last-change { + type oc-types:timeticks64; + units nanoseconds; + description + "This timestamp indicates the absolute time of the last + state change of the interface (e.g., up-to-down transition). + This is different than the SNMP ifLastChange object in the + standard interface MIB in that it is not relative to the + system boot time (i.e,. sysUpTime). + + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + oc-ext:telemetry-on-change; + } + + leaf logical { + type boolean; + description + "When set to true, the interface is a logical interface + which does not have an associated physical port or + channel on the system."; + oc-ext:telemetry-on-change; + } + } + + + grouping interface-counters-state { + description + "Operational state representing interface counters + and statistics."; + + //TODO: we may need to break this list of counters into those + //that would appear for physical vs. subinterface or logical + //interfaces. For now, just replicating the full stats + //grouping to both interface and subinterface. + + oc-ext:operational; + + container counters { + description + "A collection of interface-related statistics objects."; + + leaf in-octets { + type oc-yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-pkts { + type oc-yang:counter64; + description + "The total number of packets received on the interface, + including all unicast, multicast, broadcast and bad packets + etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information + Base"; + } + + leaf in-unicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type oc-yang:counter64; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + + + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf in-fcs-errors { + type oc-yang:counter64; + description + "Number of received packets which had errors in the + frame check sequence (FCS), i.e., framing errors. + + Discontinuities in the value of this counter can occur + when the device is re-initialization as indicated by the + value of 'last-clear'."; + } + + leaf out-octets { + type oc-yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-pkts { + type oc-yang:counter64; + description + "The total number of packets transmitted out of the + interface, including all unicast, multicast, broadcast, + and bad packets etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information + Base"; + } + + leaf out-unicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + + leaf out-multicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type oc-yang:counter64; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + + leaf carrier-transitions { + type oc-yang:counter64; + description + "Number of times the interface state has transitioned + between up and down since the time the device restarted + or the last-clear time, whichever is most recent."; + oc-ext:telemetry-on-change; + } + + leaf last-clear { + type oc-types:timeticks64; + units nanoseconds; + description + "Timestamp of the last time the interface counters were + cleared. + + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + oc-ext:telemetry-on-change; + } + } + } + + // data definition statements + + grouping sub-unnumbered-config { + description + "Configuration data for unnumbered subinterfaces"; + + leaf enabled { + type boolean; + default false; + description + "Indicates that the subinterface is unnumbered. By default + the subinterface is numbered, i.e., expected to have an + IP address configuration."; + } + } + + grouping sub-unnumbered-state { + description + "Operational state data unnumbered subinterfaces"; + } + + grouping sub-unnumbered-top { + description + "Top-level grouping unnumbered subinterfaces"; + + container unnumbered { + description + "Top-level container for setting unnumbered interfaces. + Includes reference the interface that provides the + address information"; + + container config { + description + "Configuration data for unnumbered interface"; + oc-ext:telemetry-on-change; + + uses sub-unnumbered-config; + } + + container state { + + config false; + + description + "Operational state data for unnumbered interfaces"; + + uses sub-unnumbered-config; + uses sub-unnumbered-state; + } + + uses oc-if:interface-ref; + } + } + + grouping subinterfaces-config { + description + "Configuration data for subinterfaces"; + + leaf index { + type uint32; + default 0; + description + "The index of the subinterface, or logical interface number. + On systems with no support for subinterfaces, or not using + subinterfaces, this value should default to 0, i.e., the + default subinterface."; + } + + uses interface-common-config; + + } + + grouping subinterfaces-state { + description + "Operational state data for subinterfaces"; + + oc-ext:operational; + + leaf name { + type string; + description + "The system-assigned name for the sub-interface. This MAY + be a combination of the base interface name and the + subinterface index, or some other convention used by the + system."; + oc-ext:telemetry-on-change; + } + + uses interface-common-state; + uses interface-counters-state; + } + + grouping subinterfaces-top { + description + "Subinterface data for logical interfaces associated with a + given interface"; + + container subinterfaces { + description + "Enclosing container for the list of subinterfaces associated + with a physical interface"; + + list subinterface { + key "index"; + + description + "The list of subinterfaces (logical interfaces) associated + with a physical interface"; + + leaf index { + type leafref { + path "../config/index"; + } + description + "The index number of the subinterface -- used to address + the logical interface"; + } + + container config { + description + "Configurable items at the subinterface level"; + oc-ext:telemetry-on-change; + + uses subinterfaces-config; + } + + container state { + + config false; + description + "Operational state data for logical interfaces"; + + uses subinterfaces-config; + uses subinterfaces-state; + } + } + } + } + + grouping interfaces-top { + description + "Top-level grouping for interface configuration and + operational state data"; + + container interfaces { + description + "Top level container for interfaces, including configuration + and state data."; + + + list interface { + key "name"; + + description + "The list of named interfaces on the device."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "References the configured name of the interface"; + //TODO: need to consider whether this should actually + //reference the name in the state subtree, which + //presumably would be the system-assigned name, or the + //configured name. Points to the config/name now + //because of YANG 1.0 limitation that the list + //key must have the same "config" as the list, and + //also can't point to a non-config node. + } + + container config { + description + "Configurable items at the global, physical interface + level"; + oc-ext:telemetry-on-change; + + uses interface-phys-config; + } + + container state { + + config false; + description + "Operational state data at the global interface level"; + + uses interface-phys-config; + uses interface-common-state; + uses interface-counters-state; + } + + uses interface-phys-holdtime-top; + uses subinterfaces-top; + } + } + } + + uses interfaces-top; + +} diff --git a/models/yang/openconfig-lldp.yang b/models/yang/openconfig-lldp.yang new file mode 100644 index 0000000000..e687b7c61b --- /dev/null +++ b/models/yang/openconfig-lldp.yang @@ -0,0 +1,660 @@ +module openconfig-lldp { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/lldp"; + + prefix "oc-lldp"; + + import openconfig-lldp-types { prefix oc-lldp-types; } + import openconfig-interfaces { prefix oc-if; } + import ietf-yang-types { prefix 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 LLDP protocol."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-07-17" { + description + "Adds ttl to lldp-neighbor-state"; + reference "0.2.0"; + } + + revision "2016-05-16" { + 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 lldp-common-counters { + description + "Definition of global and per-interface counters"; + + leaf frame-in { + type yang:counter64; + description + "The number of lldp frames received."; + } + + leaf frame-out { + type yang:counter64; + description + "The number of frames transmitted out."; + } + + leaf frame-error-in { + type yang:counter64; + description + "The number of LLDP frames received with errors."; + } + + leaf frame-discard { + type yang:counter64; + description + "The number of LLDP frames received and discarded."; + } + + leaf tlv-discard { + type yang:counter64; + description + "The number of TLV frames received and discarded."; + } + + leaf tlv-unknown { + type yang:counter64; + description + "The number of frames received with unknown TLV."; + } + + leaf last-clear { + type yang:date-and-time; + description + "Indicates the last time the counters were + cleared."; + } + } + + grouping lldp-global-counters { + description + "Definition of global LLDP counters"; + + uses lldp-common-counters; + + leaf tlv-accepted { + type yang:counter64; + description + "The number of valid TLVs received."; + } + + leaf entries-aged-out { + type yang:counter64; + description + "The number of entries aged out due to timeout."; + } + + } + + grouping lldp-interface-counters { + description + "Definition of per-interface LLDP counters"; + + uses lldp-common-counters; + + leaf frame-error-out { + type yang:counter64; + description + "The number of frame transmit errors on the + interface."; + } + } + + grouping lldp-system-info-config { + description + "Configuration data for system-level local and remote + LLDP information"; + + leaf system-name { + type string { + length 0..255; + } + description + "The system name field shall contain an alpha-numeric string + that indicates the system's administratively assigned name. + The system name should be the system's fully qualified domain + name. If implementations support IETF RFC 3418, the sysName + object should be used for this field."; + } + + leaf system-description { + type string { + length 0..255; + } + description + "The system description field shall contain an alpha-numeric + string that is the textual description of the network entity. + The system description should include the full name and + version identification of the system's hardware type, + software operating system, and networking software. If + implementations support IETF RFC 3418, the sysDescr object + should be used for this field."; + } + + leaf chassis-id { + type string; + description + "The Chassis ID is a mandatory TLV which identifies the + chassis component of the endpoint identifier associated with + the transmitting LLDP agent"; + } + + leaf chassis-id-type { + type oc-lldp-types:chassis-id-type; + description + "This field identifies the format and source of the chassis + identifier string. It is an enumerator defined by the + LldpChassisIdSubtype object from IEEE 802.1AB MIB."; + } + } + + grouping lldp-system-info-state { + description + "Operational state data reported for the local and remote + systems"; + + } + + grouping lldp-neighbor-config { + description + "Configuration data for LLDP neighbors"; + + } + + grouping lldp-neighbor-state { + description + "Operational state data for LLDP neighbors"; + + leaf id { + type string; + description + "System generated identifier for the neighbor on the + interface."; + } + + leaf age { + type uint64; + units "seconds"; + description + "Age since discovery"; + } + + leaf last-update { + type int64; + description + "Seconds since last update received."; + } + + leaf ttl { + type uint16; + units "seconds"; + description + "The time-to-live (TTL) is a mandatory TLV which indicates + how long information from the neighbor should be considered + valid."; + } + + leaf port-id { + type string; + description + "The Port ID is a mandatory TLV which identifies the port + component of the endpoint identifier associated with the + transmitting LLDP agent. If the specified port is an IEEE + 802.3 Repeater port, then this TLV is optional."; + } + + leaf port-id-type { + type oc-lldp-types:port-id-type; + description + "This field identifies the format and source of the port + identifier string. It is an enumerator defined by the + PtopoPortIdType object from RFC2922."; + } + + leaf port-description { + type string; + description + "The binary string containing the actual port identifier for + the port which this LLDP PDU was transmitted. The source and + format of this field is defined by PtopoPortId from + RFC2922."; + } + + leaf management-address { + type string; + description + "The Management Address is a mandatory TLV which identifies a + network address associated with the local LLDP agent, which + can be used to reach the agent on the port identified in the + Port ID TLV."; + } + + leaf management-address-type { + type string; + description + "The enumerated value for the network address type + identified in this TLV. This enumeration is defined in the + 'Assigned Numbers' RFC [RFC3232] and the + ianaAddressFamilyNumbers object."; + } + } + + grouping lldp-capabilities-config { + description + "Configuration data for LLDP capabilities"; + } + + grouping lldp-capabilities-state { + description + "Operational state data for LLDP capabilities"; + + leaf name { + type identityref { + base oc-lldp-types:LLDP_SYSTEM_CAPABILITY; + } + description + "Name of the system capability advertised by the neighbor. + Capabilities are represented in a bitmap that defines the + primary functions of the system. The capabilities are + defined in IEEE 802.1AB."; + } + + leaf enabled { + type boolean; + description + "Indicates whether the corresponding system capability is + enabled on the neighbor."; + reference + "Sec 8.5.8.2 of IEEE 802.1AB-2009"; + } + } + + grouping lldp-capabilities-top { + description + "Top-level grouping for LLDP capabilities"; + + container capabilities { + config false; + description + "Enclosing container for list of LLDP capabilities"; + + list capability { + key "name"; + description + "List of LLDP system capabilities advertised by the + neighbor"; + + leaf name { + type leafref { + path "../state/name"; + } + description + "Reference to capabilities list key"; + } + + container config { + description + "Configuration data for LLDP capabilities"; + + uses lldp-capabilities-config; + } + + container state { + + config false; + + description + "Operational state data for LLDP capabilities"; + + uses lldp-capabilities-config; + uses lldp-capabilities-state; + } + } + } + } + + grouping lldp-custom-tlv-config { + description + "Configuration data for custom LLDP TLVs"; + } + + grouping lldp-custom-tlv-state { + description + "Operational state data for custom LLDP TLVs"; + + leaf type { + type int32; + description + "The integer value identifying the type of information + contained in the value field."; + } + + leaf oui { + type string; + description + "The organizationally unique identifier field shall contain + the organization's OUI as defined in Clause 9 of IEEE Std + 802. The high-order octet is 0 and the low-order 3 octets + are the SMI Network Management Private Enterprise Code of + the Vendor in network byte order, as defined in the + 'Assigned Numbers' RFC [RFC3232]."; + } + + leaf oui-subtype { + type string; + description + "The organizationally defined subtype field shall contain a + unique subtype value assigned by the defining organization."; + } + + // TODO: consider making this string type + leaf value { + type binary; + description + "A variable-length octet-string containing the + instance-specific information for this TLV."; + } + } + + grouping lldp-custom-tlv-top { + description + "Top-level grouping for custom LLDP TLVs"; + + container custom-tlvs { + config false; + description + "Enclosing container for list of custom TLVs from a + neighbor"; + + list tlv { + key "type oui oui-subtype"; + description + "List of custom LLDP TLVs from a neighbor"; + + leaf type { + type leafref { + path "../state/type"; + } + description + "Reference to type list key"; + } + + leaf oui { + type leafref { + path "../state/oui"; + } + description + "Reference to oui list key"; + } + + leaf oui-subtype { + type leafref { + path "../state/oui-subtype"; + } + description + "Reference to oui-subtype list key"; + } + + container config { + description + "Configuration data "; + + uses lldp-custom-tlv-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-custom-tlv-config; + uses lldp-custom-tlv-state; + } + } + } + } + + grouping lldp-neighbor-top { + description + "Top-level grouping for the LLDP neighbor list"; + + container neighbors { + config false; + description + "Enclosing container for list of LLDP neighbors on an + interface"; + + list neighbor { + key "id"; + description + "List of LLDP neighbors"; + + leaf id { + type leafref { + path "../state/id"; + } + description + " "; + } + + container config { + description + "Configuration data "; + + uses lldp-neighbor-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-system-info-config; + uses lldp-system-info-state; + uses lldp-neighbor-config; + uses lldp-neighbor-state; + } + + uses lldp-custom-tlv-top; + uses lldp-capabilities-top; + } + } + } + + grouping lldp-interface-config { + description + "Configuration data for LLDP on each interface"; + + leaf name { + type oc-if:base-interface-ref; + description + "Reference to the LLDP Ethernet interface"; + } + + leaf enabled { + type boolean; + default "true"; + description + "Enable or disable the LLDP protocol on the interface."; + } + } + + grouping lldp-interface-state { + description + "Operational state data for LLDP on each interface"; + + container counters { + description + "LLDP counters on each interface"; + + uses lldp-interface-counters; + } + } + + grouping lldp-interface-top { + description + "Top-level grouping "; + + container interfaces { + description + "Enclosing container "; + + list interface { + key "name"; + description + "List of interfaces on which LLDP is enabled / available"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for LLDP on each interface"; + + uses lldp-interface-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-interface-config; + uses lldp-interface-state; + } + + uses lldp-neighbor-top; + } + } + } + + + grouping lldp-config { + description + "Configuration data for global LLDP parameters"; + + leaf enabled { + type boolean; + default "true"; + description + "System level state of the LLDP protocol."; + } + + leaf hello-timer { + type uint64; + units "seconds"; + description + "System level hello timer for the LLDP protocol."; + } + + leaf-list suppress-tlv-advertisement { + type identityref { + base oc-lldp-types:LLDP_TLV; + } + description + "Indicates whether the local system should suppress the + advertisement of particular TLVs with the LLDP PDUs that it + transmits. Where a TLV type is specified within this list, it + should not be included in any LLDP PDU transmitted by the + local agent."; + } + } + + grouping lldp-state { + description + "Operational state data for global LLDP parameters"; + + container counters { + description + "Global LLDP counters"; + + uses lldp-global-counters; + } + } + + grouping lldp-top { + description + "Top-level grouping for LLDP model"; + + container lldp { + description + "Top-level container for LLDP configuration and state data"; + + container config { + description + "Configuration data "; + + uses lldp-config; + uses lldp-system-info-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-config; + uses lldp-system-info-config; + uses lldp-system-info-state; + uses lldp-state; + } + + uses lldp-interface-top; + } + } + + // data definition statements + + uses lldp-top; + + +} diff --git a/models/yang/openconfig-platform.yang b/models/yang/openconfig-platform.yang new file mode 100644 index 0000000000..ecf38cd1af --- /dev/null +++ b/models/yang/openconfig-platform.yang @@ -0,0 +1,779 @@ +module openconfig-platform { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/platform"; + + prefix "oc-platform"; + + import openconfig-platform-types { prefix oc-platform-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-alarm-types { prefix oc-alarm-types; } + import openconfig-yang-types { prefix oc-yang; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines a data model for representing a system + component inventory, which can include hardware or software + elements arranged in an arbitrary structure. The primary + relationship supported by the model is containment, e.g., + components containing subcomponents. + + It is expected that this model reflects every field replacable + unit on the device at a minimum (i.e., additional information + may be supplied about non-replacable components). + + Every element in the inventory is termed a 'component' with each + component expected to have a unique name and type, and optionally + a unique system-assigned identifier and FRU number. The + uniqueness is guaranteed by the system within the device. + + Components may have properties defined by the system that are + modeled as a list of key-value pairs. These may or may not be + user-configurable. The model provides a flag for the system + to optionally indicate which properties are user configurable. + + Each component also has a list of 'subcomponents' which are + references to other components. Appearance in a list of + subcomponents indicates a containment relationship as described + above. For example, a linecard component may have a list of + references to port components that reside on the linecard. + + This schema is generic to allow devices to express their own + platform-specific structure. It may be augmented by additional + component type-specific schemas that provide a common structure + for well-known component types. In these cases, the system is + expected to populate the common component schema, and may + optionally also represent the component and its properties in the + generic structure. + + The properties for each component may include dynamic values, + e.g., in the 'state' part of the schema. For example, a CPU + component may report its utilization, temperature, or other + physical properties. The intent is to capture all platform- + specific physical data in one location, including inventory + (presence or absence of a component) and state (physical + attributes or status)."; + + oc-ext:openconfig-version "0.12.2"; + + revision "2019-04-16" { + description + "Fix bug in parent path reference"; + reference "0.12.2"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.12.1"; + } + + revision "2018-06-29" { + description + "Added location description for components"; + reference "0.12.0"; + } + + revision "2018-06-03" { + description + "Added parent reference, empty flag and preconfiguration + for components"; + reference "0.11.0"; + } + + revision "2018-04-20" { + description + "Added new per-component state data: mfg-date and removable"; + reference "0.10.0"; + } + + revision "2018-01-30" { + description + "Amended approach for modelling CPU - rather than having + a local CPU utilisation state variable, a component with + a CPU should create a subcomponent of type CPU to report + statistics."; + reference "0.9.0"; + } + + revision "2018-01-16" { + description + "Added new per-component common data; add temp alarm; + moved hardware-port reference to port model"; + reference "0.8.0"; + } + + revision "2017-12-14" { + description + "Added anchor containers for component data, added new + component types"; + reference "0.7.0"; + } + + revision "2017-08-16" { + description + "Added power state enumerated type"; + reference "0.6.0"; + } + + revision "2016-12-22" { + description + "Added temperature state variable to component"; + reference "0.5.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + + grouping platform-component-properties-config { + description + "System-defined configuration data for component properties"; + + leaf name { + type string; + description + "System-supplied name of the property -- this is typically + non-configurable"; + } + + leaf value { + type union { + type string; + type boolean; + type int64; + type uint64; + type decimal64 { + fraction-digits 2; + } + } + description + "Property values can take on a variety of types. Signed and + unsigned integer types may be provided in smaller sizes, + e.g., int8, uint16, etc."; + } + } + + grouping platform-component-properties-state { + description + "Operational state data for component properties"; + + leaf configurable { + type boolean; + description + "Indication whether the property is user-configurable"; + } + } + + grouping platform-component-properties-top { + description + "Top-level grouping "; + + container properties { + description + "Enclosing container "; + + list property { + key "name"; + description + "List of system properties for the component"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the property name."; + } + + container config { + description + "Configuration data for each property"; + + uses platform-component-properties-config; + } + + container state { + + config false; + + description + "Operational state data for each property"; + + uses platform-component-properties-config; + uses platform-component-properties-state; + } + } + } + } + + grouping platform-subcomponent-ref-config { + description + "Configuration data for subcomponent references"; + + leaf name { + type leafref { + path "../../../../../component/config/name"; + } + description + "Reference to the name of the subcomponent"; + } + } + + grouping platform-subcomponent-ref-state { + description + "Operational state data for subcomponent references"; + + } + + grouping platform-subcomponent-ref-top { + description + "Top-level grouping for list of subcomponent references"; + + container subcomponents { + description + "Enclosing container for subcomponent references"; + + list subcomponent { + key "name"; + description + "List of subcomponent references"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the name list key"; + } + + container config { + description + "Configuration data for the subcomponent"; + + uses platform-subcomponent-ref-config; + } + + container state { + + config false; + + description + "Operational state data for the subcomponent"; + + uses platform-subcomponent-ref-config; + uses platform-subcomponent-ref-state; + } + } + } + } + + grouping platform-component-config { + description + "Configuration data for components"; + + leaf name { + type string; + description + "Device name for the component -- this may not be a + configurable parameter on many implementations. Where + component preconfiguration is supported, for example, + the component name may be configurable."; + } + } + + grouping platform-component-state { + description + "Operational state data for device components."; + + leaf type { + type union { + type identityref { + base oc-platform-types:OPENCONFIG_HARDWARE_COMPONENT; + } + type identityref { + base oc-platform-types:OPENCONFIG_SOFTWARE_COMPONENT; + } + } + description + "Type of component as identified by the system"; + } + + leaf id { + type string; + description + "Unique identifier assigned by the system for the + component"; + } + + leaf location { + type string; + description + "System-supplied description of the location of the + component within the system. This could be a bay position, + slot number, socket location, etc. For component types that + have an explicit slot-id attribute, such as linecards, the + system should populate the more specific slot-id."; + } + + leaf description { + type string; + description + "System-supplied description of the component"; + } + + leaf mfg-name { + type string; + description + "System-supplied identifier for the manufacturer of the + component. This data is particularly useful when a + component manufacturer is different than the overall + device vendor."; + } + + leaf mfg-date { + type oc-yang:date; + description + "System-supplied representation of the component's + manufacturing date."; + } + + leaf hardware-version { + type string; + description + "For hardware components, this is the hardware revision of + the component."; + } + + leaf firmware-version { + type string; + description + "For hardware components, this is the version of associated + firmware that is running on the component, if applicable."; + } + + leaf software-version { + type string; + description + "For software components such as operating system or other + software module, this is the version of the currently + running software."; + } + + leaf serial-no { + type string; + description + "System-assigned serial number of the component."; + } + + leaf part-no { + type string; + description + "System-assigned part number for the component. This should + be present in particular if the component is also an FRU + (field replaceable unit)"; + } + + leaf removable { + type boolean; + description + "If true, this component is removable or is a field + replaceable unit"; + } + + leaf oper-status { + type identityref { + base oc-platform-types:COMPONENT_OPER_STATUS; + } + description + "If applicable, this reports the current operational status + of the component."; + } + + leaf empty { + type boolean; + default false; + description + "The empty leaf may be used by the device to indicate that a + component position exists but is not populated. Using this + flag, it is possible for the management system to learn how + many positions are available (e.g., occupied vs. empty + linecard slots in a chassis)."; + } + + leaf parent { + type leafref { + path "../../../component/config/name"; + } + description + "Reference to the name of the parent component. Note that + this reference must be kept synchronized with the + corresponding subcomponent reference from the parent + component."; + } + } + + grouping platform-component-temp-alarm-state { + description + "Temperature alarm data for platform components"; + + // TODO(aashaikh): consider if these leaves could be in a + // reusable grouping (not temperature-specific); threshold + // may always need to be units specific. + + leaf alarm-status { + type boolean; + description + "A value of true indicates the alarm has been raised or + asserted. The value should be false when the alarm is + cleared."; + } + + leaf alarm-threshold { + type uint32; + description + "The threshold value that was crossed for this alarm."; + } + + leaf alarm-severity { + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_SEVERITY; + } + description + "The severity of the current alarm."; + } + } + + grouping platform-component-power-state { + description + "Power-related operational state for device components."; + + leaf allocated-power { + type uint32; + units watts; + description + "Power allocated by the system for the component."; + } + + leaf used-power { + type uint32; + units watts; + description + "Actual power used by the component."; + } + } + + grouping platform-component-temp-state { + description + "Temperature state data for device components"; + + container temperature { + description + "Temperature in degrees Celsius of the component. Values include + the instantaneous, average, minimum, and maximum statistics. If + avg/min/max statistics are not supported, the target is expected + to just supply the instant value"; + + uses oc-platform-types:avg-min-max-instant-stats-precision1-celsius; + uses platform-component-temp-alarm-state; + } + } + + grouping platform-component-memory-state { + description + "Per-component memory statistics"; + + container memory { + description + "For components that have associated memory, these values + report information about available and utilized memory."; + + leaf available { + type uint64; + units bytes; + description + "The available memory physically installed, or logically + allocated to the component."; + } + + // TODO(aashaikh): consider if this needs to be a + // min/max/avg statistic + leaf utilized { + type uint64; + units bytes; + description + "The memory currently in use by processes running on + the component, not considering reserved memory that is + not available for use."; + } + } + } + + grouping platform-anchors-top { + description + "This grouping is used to add containers for components that + are common across systems, but do not have a defined schema + within the openconfig-platform module. Containers should be + added to this grouping for components that are expected to + exist in multiple systems, with corresponding modules + augmenting the config/state containers directly."; + + container chassis { + description + "Data for chassis components"; + + container config { + description + "Configuration data for chassis components"; + } + + container state { + config false; + description + "Operational state data for chassis components"; + } + } + +// TODO(aashaikh): linecard container is already defined in +// openconfig-platform-linecard; will move to this module +// in future. + /* + container linecard { + description + "Data for linecard components"; + + container config { + description + "Configuration data for linecard components"; + } + + container state { + config false; + description + "Operational state data for linecard components"; + } + } + */ + + container port { + description + "Data for physical port components"; + + container config { + description + "Configuration data for physical port components"; + } + + container state { + config false; + description + "Operational state data for physical port components"; + } + } + +// TODO(aashaikh): transceiver container is already defined in +// openconfig-platform-transceiver; will move to this module +// in future. + /* + container transceiver { + description + "Data for transceiver components"; + + container config { + description + "Configuration data for transceiver components"; + } + + container state { + config false; + description + "Operational state data for transceiver components"; + } + } + */ + + container power-supply { + description + "Data for power supply components"; + + container config { + description + "Configuration data for power supply components"; + } + + container state { + config false; + description + "Operational state data for power supply components"; + } + } + + container fan { + description + "Data for fan components"; + + container config { + description + "Configuration data for fan components"; + } + + container state { + config false; + description + "Operational state data for fan components"; + } + } + + container fabric { + description + "Data for fabric components"; + + container config { + description + "Configuration data for fabric components"; + } + + container state { + config false; + description + "Operational state data for fabric components"; + } + } + + container storage { + description + "Data for storage components"; + + container config { + description + "Configuration data for storage components"; + } + + container state { + config false; + description + "Operational state data for storage components"; + } + } + + container cpu { + description + "Data for cpu components"; + + container config { + description + "Configuration data for cpu components"; + } + + container state { + config false; + description + "Operational state data for cpu components"; + } + } + + container integrated-circuit { + description + "Data for chip components, such as ASIC, NPUs, etc."; + + container config { + description + "Configuration data for chip components"; + } + + container state { + config false; + description + "Operational state data for chip components"; + } + } + + container backplane { + description + "Data for backplane components"; + + container config { + description + "Configuration data for backplane components"; + } + + container state { + config false; + description + "Operational state data for backplane components"; + } + } + } + + grouping platform-component-top { + description + "Top-level grouping for components in the device inventory"; + + container components { + description + "Enclosing container for the components in the system."; + + list component { + key "name"; + description + "List of components, keyed by component name."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "References the component name"; + } + + container config { + description + "Configuration data for each component"; + + uses platform-component-config; + } + + container state { + + config false; + + description + "Operational state data for each component"; + + uses platform-component-config; + uses platform-component-state; + uses platform-component-temp-state; + uses platform-component-memory-state; + uses platform-component-power-state; + } + + uses platform-component-properties-top; + uses platform-subcomponent-ref-top; + uses platform-anchors-top; + } + } + } + + + // data definition statements + + uses platform-component-top; + + + // augments + + +} diff --git a/models/yang/openconfig-system.yang b/models/yang/openconfig-system.yang new file mode 100644 index 0000000000..fd05a3f771 --- /dev/null +++ b/models/yang/openconfig-system.yang @@ -0,0 +1,997 @@ +module openconfig-system { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system"; + + prefix "oc-sys"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa { prefix oc-aaa; } + import openconfig-system-logging { prefix oc-log; } + import openconfig-system-management { prefix oc-sys-mgmt; } + import openconfig-system-terminal { prefix oc-sys-term; } + import openconfig-procmon { prefix oc-proc; } + import openconfig-alarms { prefix oc-alarms; } + import openconfig-messages { prefix oc-messages; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing system-wide services and functions on + network devices. + + Portions of this code were derived from IETF RFC 7317. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.7.0"; + + revision "2019-01-29" { + description + "Add messages module to the system model"; + reference "0.7.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.6.1"; + } + + revision "2018-07-17" { + description + "Add gRPC server data"; + reference "0.6.0"; + } + + revision "2018-01-21" { + description + "Add cpu utilization data"; + reference "0.5.0"; + } + + revision "2017-12-15" { + description + "Add alarms to the system model"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity NTP_AUTH_TYPE { + description + "Base identity for encryption schemes supported for NTP + authentication keys"; + } + + identity NTP_AUTH_MD5 { + base NTP_AUTH_TYPE; + description + "MD5 encryption method"; + } + + // typedef statements + + typedef timezone-name-type { + type string; + description + "A time zone name as used by the Time Zone Database, + sometimes referred to as the 'Olson Database'. + + The exact set of valid values is an implementation-specific + matter. Client discovery of the exact set of time zone names + for a particular server is out of scope."; + reference + "BCP 175: Procedures for Maintaining the Time Zone Database"; + } + + // grouping statements + + grouping system-clock-config { + description + "Configuration data for system-wide clock configuration"; + + leaf timezone-name { + type timezone-name-type; + description + "The TZ database name to use for the system, such + as 'Europe/Stockholm'."; + reference "IANA Time Zone Database + http://www.iana.org/time-zones"; + } + } + + grouping system-clock-state { + description + "Operational state data for system-wide clock configuration"; + } + + grouping system-clock-top { + description + "Top-level grouping for system-wide clock configuration"; + + container clock { + description + "Top-level container for clock configuration data"; + + container config { + description + "Configuration data for system clock"; + + uses system-clock-config; + } + + container state { + + config false; + + description + "Operational state data for system clock"; + + uses system-clock-config; + uses system-clock-state; + } + } + } + + grouping system-global-config { + description "system-wide configuration parameters"; + + leaf hostname { + type oc-inet:domain-name; + description + "The hostname of the device -- should be a single domain + label, without the domain."; + } + + leaf domain-name { + type oc-inet:domain-name; + description + "Specifies the domain name used to form fully qualified name + for unqualified hostnames."; + } + + leaf login-banner { + type string; + description + "The console login message displayed before the login prompt, + i.e., before a user logs into the system."; + } + + leaf motd-banner { + type string; + description + "The console message displayed after a user logs into the + system. They system may append additional standard + information such as the current system date and time, uptime, + last login timestamp, etc."; + } + } + + grouping system-global-state { + description + "Global operational state data for the system"; + + leaf current-datetime { + type oc-yang:date-and-time; + description + "The current system date and time."; + } + + leaf boot-time { + type oc-types:timeticks64; + description + "This timestamp indicates the time that the system was last + restarted. The value is the timestamp in seconds relative + to the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + + } + + grouping system-dns-config { + description "DNS / resolver related configuration data"; + + leaf-list search { + type oc-inet:domain-name; + ordered-by user; + description + "An ordered list of domains to search when resolving + a host name."; + } + } + + grouping system-dns-state { + description + "Operational state data for system DNS resolver"; + + } + + grouping system-dns-servers-config { + description + "Configuration data for DNS resolvers"; + + //RFC 7317 includes a single-value choice statement to for + //TCP and UDP transport. This has been removed since it the + //transport protocol is not generally available as an options + //on target devices. It may be added back if and when needed. + + leaf address { + type oc-inet:ip-address; + description + "The address of the DNS server, can be either IPv4 + or IPv6."; + } + + leaf port { + type oc-inet:port-number; + default 53; + description + "The port number of the DNS server."; + } + + //RFC 7317 includes resolver timeout and attempts options. These + //have been omitted as they are not available on many targets. If + //and when they are required, they may be added back in. + } + + grouping system-dns-static-config { + description + "Configuration data for static host entries"; + + leaf hostname { + type string; + description + "Hostname for the static DNS entry"; + } + + leaf-list alias { + type string; + description + "Additional aliases for the hostname"; + } + + leaf-list ipv4-address { + type oc-inet:ipv4-address; + description + "List of IPv4 addressses for the host entry"; + } + + leaf-list ipv6-address { + type oc-inet:ipv6-address; + description + "List of IPv6 addresses for the host entry"; + } + } + + grouping system-dns-static-state { + description + "Operational state data for static host entries"; + } + + grouping system-dns-static-top { + description + "Top-level grouping for static DNS host entries"; + + container host-entries { + description + "Enclosing container for list of static host entries"; + + list host-entry { + key "hostname"; + description + "List of static host entries"; + + leaf hostname { + type leafref { + path "../config/hostname"; + } + description + "Reference to the hostname list key"; + } + + container config { + description + "Configuration data for static host entries"; + + uses system-dns-static-config; + } + + container state { + + config false; + + description + "Operational state data for static host entries"; + + uses system-dns-static-config; + uses system-dns-static-state; + } + } + } + } + + grouping system-dns-servers-state { + description + "Operational state data for DNS resolvers"; + + } + + grouping system-dns-servers-top { + description + "Top-level grouping for the list of DNS resolvers."; + + container servers { + description + "Enclosing container for DNS resolver list"; + + list server { + key "address"; + ordered-by user; + description + "List of the DNS servers that the resolver should query. + + When the resolver is invoked by a calling application, it + sends the query to the first name server in this list. If + no response has been received within 'timeout' seconds, + the resolver continues with the next server in the list. + If no response is received from any server, the resolver + continues with the first server again. When the resolver + has traversed the list 'attempts' times without receiving + any response, it gives up and returns an error to the + calling application. + + Implementations MAY limit the number of entries in this + list."; + + leaf address { + type leafref { + path "../config/address"; + } + description + "References the configured address of the DNS server"; + } + + container config { + description + "Configuration data for each DNS resolver"; + + uses system-dns-servers-config; + } + + container state { + + config false; + + description + "Operational state data for each DNS resolver"; + + uses system-dns-servers-config; + uses system-dns-servers-state; + } + + } + } + } + + grouping system-dns-top { + description + "Top-level grouping for DNS / resolver config and operational + state data"; + + container dns { + description + "Enclosing container for DNS resolver data"; + + container config { + description + "Configuration data for the DNS resolver"; + + uses system-dns-config; + + } + + container state { + + config false; + + description + "Operational state data for the DNS resolver"; + + uses system-dns-config; + uses system-dns-state; + + } + + uses system-dns-servers-top; + uses system-dns-static-top; + } + } + + grouping system-ntp-server-config { + description + "Configuration data for NTP servers"; + + leaf address { + type oc-inet:host; + description + "The address or hostname of the NTP server."; + } + + leaf port { + type oc-inet:port-number; + default 123; + description + "The port number of the NTP server."; + } + + leaf version { + type uint8 { + range 1..4; + } + default 4; + description + "Version number to put in outgoing NTP packets"; + } + + leaf association-type { + type enumeration { + enum SERVER { + description + "Use client association mode. This device + will not provide synchronization to the + configured NTP server."; + } + enum PEER { + description + "Use symmetric active association mode. + This device may provide synchronization + to the configured NTP server."; + } + enum POOL { + description + "Use client association mode with one or + more of the NTP servers found by DNS + resolution of the domain name given by + the 'address' leaf. This device will not + provide synchronization to the servers."; + } + } + default SERVER; + description + "The desired association type for this NTP server."; + } + leaf iburst { + type boolean; + default false; + description + "Indicates whether this server should enable burst + synchronization or not."; + } + leaf prefer { + type boolean; + default false; + description + "Indicates whether this server should be preferred + or not."; + } + } + + grouping system-ntp-server-state { + description + "Operational state data for NTP servers"; + + leaf stratum { + type uint8; + description + "Indicates the level of the server in the NTP hierarchy. As + stratum number increases, the accuracy is degraded. Primary + servers are stratum while a maximum value of 16 indicates + unsynchronized. The values have the following specific + semantics: + + | 0 | unspecified or invalid + | 1 | primary server (e.g., equipped with a GPS receiver) + | 2-15 | secondary server (via NTP) + | 16 | unsynchronized + | 17-255 | reserved"; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf root-delay { + type uint32; + // TODO: reconsider units for these values -- the spec defines + // rootdelay and rootdisperson as 2 16-bit integers for seconds + // and fractional seconds, respectively. This gives a + // precision of ~15 us (2^-16). Using milliseconds here based + // on what implementations typically provide and likely lack + // of utility for less than millisecond precision with NTP + // time sync. + units "milliseconds"; + description + "The round-trip delay to the server, in milliseconds."; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf root-dispersion { + type uint64; + units "milliseconds"; + description + "Dispersion (epsilon) represents the maximum error inherent + in the measurement"; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf offset { + type uint64; + units "milliseconds"; + description + "Estimate of the current time offset from the peer. This is + the time difference between the local and reference clock."; + } + + leaf poll-interval { + type uint32; + units "seconds"; + description + "Polling interval of the peer"; + } + } + + grouping system-ntp-server-top { + description + "Top-level grouping for the list of NTP servers"; + + container servers { + description + "Enclosing container for the list of NTP servers"; + + list server { + key "address"; + description + "List of NTP servers to use for system clock + synchronization. If '/system/ntp/enabled' + is 'true', then the system will attempt to + contact and utilize the specified NTP servers."; + + leaf address { + type leafref { + path "../config/address"; + } + description + "References the configured address or hostname of the + NTP server."; + } + + container config { + description + "Configuration data for an NTP server."; + + uses system-ntp-server-config; + } + + container state { + + config false; + + description + "Operational state data for an NTP server."; + + uses system-ntp-server-config; + uses system-ntp-server-state; + } + + } + } + } + + grouping system-ntp-auth-keys-config { + description + "Configuration data "; + + leaf key-id { + type uint16; + description + "Integer identifier used by the client and server to + designate a secret key. The client and server must use + the same key id."; + } + + leaf key-type { + type identityref { + base NTP_AUTH_TYPE; + } + description + "Encryption type used for the NTP authentication key"; + } + + leaf key-value { + type string; + description + "NTP authentication key value"; + } + } + + grouping system-ntp-auth-keys-state { + description + "Operational state data for NTP auth key data"; + } + + grouping system-ntp-auth-keys-top { + description + "Top-level grouping for NTP auth key data"; + + container ntp-keys { + description + "Enclosing container for list of NTP authentication keys"; + + list ntp-key { + key "key-id"; + description + "List of NTP authentication keys"; + + leaf key-id { + type leafref { + path "../config/key-id"; + } + description + "Reference to auth key-id list key"; + } + + container config { + description + "Configuration data for NTP auth keys"; + + uses system-ntp-auth-keys-config; + } + + container state { + + config false; + + description + "Operational state data for NTP auth keys"; + + uses system-ntp-auth-keys-config; + uses system-ntp-auth-keys-state; + } + } + } + } + + grouping system-ntp-config { + description + "Configuration data for system-wide NTP operation."; + + leaf enabled { + type boolean; + default false; + description + "Enables the NTP protocol and indicates that the system should + attempt to synchronize the system clock with an NTP server + from the servers defined in the 'ntp/server' list."; + } + + leaf ntp-source-address { + type oc-inet:ip-address; + description + "Source address to use on outgoing NTP packets"; + } + + leaf enable-ntp-auth { + type boolean; + default false; + description + "Enable or disable NTP authentication -- when enabled, the + system will only use packets containing a trusted + authentication key to synchronize the time."; + } + } + + grouping system-ntp-state { + description + "Operational state data for system-wide NTP operation."; + + leaf auth-mismatch { + type oc-yang:counter64; + description + "Count of the number of NTP packets received that were not + processed due to authentication mismatch."; + } + } + + grouping system-ntp-top { + description + "Top-level grouping for configuration and state data for NTP"; + + container ntp { + description + "Top-level container for NTP configuration and state"; + + container config { + description + "Configuration data for NTP client."; + + uses system-ntp-config; + } + + container state { + config false; + description + "Operational state data for NTP services."; + + uses system-ntp-config; + uses system-ntp-state; + } + uses system-ntp-auth-keys-top; + uses system-ntp-server-top; + } + } + + grouping system-memory-config { + description + "Configuration data for system memory"; + } + + grouping system-memory-state { + description + "Operational state data for system memory"; + + leaf physical { + type uint64; + units bytes; + // TODO: consider making units in megabytes + description + "Reports the total physical memory available on the + system."; + } + + leaf reserved { + type uint64; + units bytes; + description + "Memory reserved for system use"; + } + } + + grouping system-memory-top { + description + "Top-level grouping for system memory data definitions"; + + container memory { + description + "Top-level container for system memory data"; + + container config { + description + "Configuration data for system memory"; + + uses system-memory-config; + } + + container state { + config false; + description + "Operational state data for system memory"; + + uses system-memory-config; + uses system-memory-state; + } + } + } + + grouping system-cpu-state { + description + "Operational state data for the system CPU(s)"; + + leaf index { + type union { + type enumeration { + enum ALL { + description + "Index value indicating all CPUs in the system"; + } + } + type uint32; + } + description + "The CPU index for each processor core on the system. On a + single-core system, the index should be zero. The ALL + index signifies an aggregation of the CPU utilization + statistics over all cores in the system."; + } + + container total { + description + "Total CPU utilization."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container user { + description + "Percentage of CPU time spent running in user space."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container kernel { + description + "Percentage of CPU time spent running in kernel space."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container nice { + description + "Percentage of CPU time spent running low-priority (niced) + user processes."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container idle { + description + "Percentage of CPU time spent idle."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container wait { + description + "Percentage of CPU time spent waiting for I/O."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container hardware-interrupt { + description + "Percentage of CPU time spent servicing hardware interrupts."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container software-interrupt { + description + "Percentage of CPU time spent servicing software interrupts"; + + uses oc-types:avg-min-max-instant-stats-pct; + } + } + + grouping system-cpu-top { + description + "Top-level grouping for system CPU data"; + + container cpus { + config false; + description + "Enclosing container for the list of CPU cores on the + system"; + + list cpu { + key "index"; + description + "List of CPU cores on the system (including logical CPUs + on hyperthreaded systems), keyed by either a numerical + index, or the ALL value for an entry representing the + aggregation across all CPUs."; + + leaf index { + type leafref { + path "../state/index"; + } + description + "Reference to list key"; + } + + container state { + + description + "Operational state data for the system CPU(s)"; + + uses system-cpu-state; + } + } + } + } + + grouping system-top { + description + "Top level system data containers"; + + container system { + description + "Enclosing container for system-related configuration and + operational state data"; + + container config { + description "Global configuration data for the system"; + + uses system-global-config; + + } + + container state { + config false; + description "Global operational state data for the system"; + + uses system-global-config; + uses system-global-state; + } + + uses system-clock-top; + uses system-dns-top; + uses system-ntp-top; + uses oc-sys-mgmt:system-grpc-server-top; + uses oc-sys-term:system-ssh-server-top; + uses oc-sys-term:system-telnet-server-top; + uses oc-log:logging-top; + uses oc-aaa:aaa-top; + uses system-memory-top; + uses system-cpu-top; + uses oc-proc:procmon-processes-top; + uses oc-alarms:alarms-top; + uses oc-messages:messages-top; + } + } + + // data definition statements + + uses system-top; + +} diff --git a/models/yang/sonic/Makefile b/models/yang/sonic/Makefile new file mode 100644 index 0000000000..9141ff952a --- /dev/null +++ b/models/yang/sonic/Makefile @@ -0,0 +1,53 @@ +TOPDIR := ../../../ +SONIC_YANGAPI_DIR := $(TOPDIR)/build/yaml +SONIC_YANGDIR := $(TOPDIR)/models/yang/sonic +SONIC_YANGDIR_DEVIATION := $(TOPDIR)/models/yang/sonic/deviation +SONIC_YANGDIR_COMMON := $(TOPDIR)/models/yang/sonic/common +SONIC_YANGDIR_COMMON_IETF := $(TOPDIR)/models/yang/sonic/common/ietf +SONIC_YANG_MOD_FILES := $(shell find $(SONIC_YANGDIR) -maxdepth 1 -name '*.yang' | sort) +SONIC_YANG_COMMON_FILES := $(shell find $(SONIC_YANGDIR_COMMON) -name '*.yang' | sort) +SONIC_YANG_COMMON_FILES += $(shell find $(SONIC_YANGDIR_COMMON_IETF) -name '*.yang' | sort) + +SONIC_TOOLS_DIR := $(TOPDIR)/tools +SONIC_PYANG_DIR := $(SONIC_TOOLS_DIR)/pyang +SONIC_PYANG_PLUGIN_DIR := $(SONIC_PYANG_DIR)/pyang_plugins +SONIC_PYANG_BIN := pyang + +all: yamlGen allyangs.tree allyangs_tree.html + +#yamlGen: $(SONIC_YANGAPI_DIR)/.done + +allyangs.tree: $(SONIC_YANG_MOD_FILES) $(SONIC_YANG_COMMON_FILES) + $(SONIC_PYANG_BIN) \ + -f tree \ + -o $(SONIC_YANGDIR)/$@ \ + -p $(SONIC_YANGDIR_COMMON):$(SONIC_YANGDIR) \ + $(SONIC_YANG_MOD_FILES) + @echo "+++++ Generation of YANG tree for Sonic Yang modules completed +++++" + +allyangs_tree.html: $(SONIC_YANG_MOD_FILES) $(SONIC_YANG_COMMON_FILES) + $(SONIC_PYANG_BIN) \ + -f jstree \ + -o $(SONIC_YANGDIR)/$@ \ + -p $(SONIC_YANGDIR_COMMON):$(SONIC_YANGDIR) \ + $(SONIC_YANG_MOD_FILES) + @echo "+++++ Generation of HTML tree for Sonic Yang modules completed +++++" + +#====================================================================== +# Generate YAML files for SONiC YANG modules +#====================================================================== +yamlGen: + @echo "+++++ Generating YAML files for Sonic Yang modules +++++" + mkdir -p $(SONIC_YANGAPI_DIR) + $(SONIC_PYANG_BIN) \ + -f swaggerapi \ + --outdir $(SONIC_YANGAPI_DIR) \ + --plugindir $(SONIC_PYANG_PLUGIN_DIR) \ + -p $(SONIC_YANGDIR_COMMON):$(SONIC_YANGDIR) \ + $(SONIC_YANG_MOD_FILES) + @echo "+++++ Generation of YAML files for Sonic Yang modules completed +++++" + +clean: + @echo "Removing files ..." + rm -rf $(SONIC_YANGAPI_DIR) + rm -rf allyangs.tree allyangs_tree.html diff --git a/models/yang/sonic/common/sonic-common.yang b/models/yang/sonic/common/sonic-common.yang new file mode 100644 index 0000000000..45d608b684 --- /dev/null +++ b/models/yang/sonic/common/sonic-common.yang @@ -0,0 +1,50 @@ + +module sonic-common { + namespace "http://github.com/Azure/sonic-common"; + prefix cmn; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC common definitions"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + typedef tagging_mode { + type enumeration { + enum untagged; + enum tagged; + enum priority_tagged; + } + } + + typedef admin-status { + type enumeration { + enum up; + enum down; + } + } + + container operation { + description "This definition is used internally by CVL and + is not exposed in NBI. Leaf 'operation' allows + evaluation of must expression for CREATE/UPDATE/DELETE + operation."; + + leaf operation { + type enumeration { + enum NOP; + enum CREATE; + enum UPDATE; + enum DELETE; + } + } + } +} diff --git a/models/yang/sonic/common/sonic-extension.yang b/models/yang/sonic/common/sonic-extension.yang new file mode 100644 index 0000000000..f488809586 --- /dev/null +++ b/models/yang/sonic/common/sonic-extension.yang @@ -0,0 +1,61 @@ + +module sonic-extension { + namespace "http://github.com/Azure/sonic-extension"; + prefix sonic-ext; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC Extension"; + + revision 2019-09-18 { + description + "Initial revision."; + } + + 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"; + } +} diff --git a/models/yang/sonic/sonic-acl.yang b/models/yang/sonic/sonic-acl.yang new file mode 100644 index 0000000000..fba2aad4a9 --- /dev/null +++ b/models/yang/sonic/sonic-acl.yang @@ -0,0 +1,221 @@ +module sonic-acl { + namespace "http://github.com/Azure/sonic-acl"; + prefix acl; + yang-version 1.1; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC ACL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-acl { + + container ACL_TABLE { + + list ACL_TABLE_LIST { + key "aclname"; + + leaf aclname { + type string { + pattern '[a-zA-Z0-9]{1}([-a-zA-Z0-9_]{1,63})' { + error-app-tag aclname-invalid; + } + } + } + + leaf policy_desc { + type string { + length 1..255 { + error-app-tag policy-desc-invalid-length; + } + } + } + + leaf stage { + type enumeration { + enum INGRESS; + enum EGRESS; + } + } + + leaf type { + type enumeration { + enum MIRROR; + enum MIRRORV6; + enum L3; + enum L3V6; + } + } + + leaf-list ports { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + + container ACL_RULE { + + list ACL_RULE_LIST { + key "aclname rulename"; + + leaf aclname { + type leafref { + path "../../../ACL_TABLE/ACL_TABLE_LIST/aclname"; + } + } + + leaf rulename { + type string { + pattern '[a-zA-Z0-9]{1}([-a-zA-Z0-9_]{1,63})' { + error-app-tag rulename-invalid; + } + } + } + + leaf PRIORITY { + type uint16 { + range "1..65535"{ + error-app-tag priority-invalid-range; + error-message "Invalid ACL rule priority."; + } + } + } + + leaf RULE_DESCRIPTION { + type string { + length 1..255 { + error-app-tag ruledesc-invalid-length; + } + } + } + + leaf PACKET_ACTION { + mandatory true; + type enumeration { + enum FORWARD; + enum DROP; + enum REDIRECT; + } + } + + leaf IP_TYPE { + mandatory true; + type enumeration { + enum ANY; + enum IP; + enum IPV4; + enum IPV4ANY; + enum NON_IPV4; + enum IPV6ANY; + enum NON_IPV6; + } + } + + leaf IP_PROTOCOL { + type uint8 { + range "1|2|6|17|46|47|51|103|115"; + } + } + + leaf ETHER_TYPE { + type string { + pattern "(0x88CC)|(0x8100)|(0x8915)|(0x0806)|(0x0800)|(0x86DD)|(0x8847)" { + error-message "Invalid ACL Rule Ether Type"; + error-app-tag ether-type-invalid; + } + } + } + + choice ip_src_dst { + case ipv4_src_dst { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV4' or .='IPV4ANY'])"; + leaf SRC_IP { + mandatory true; + type inet:ipv4-prefix; + } + leaf DST_IP { + mandatory true; + type inet:ipv4-prefix; + } + } + case ipv6_src_dst { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV6' or .='IPV6ANY'])"; + leaf SRC_IPV6 { + mandatory true; + type inet:ipv6-prefix; + } + leaf DST_IPV6 { + mandatory true; + type inet:ipv6-prefix; + } + } + } + + choice src_port { + case l4_src_port { + leaf L4_SRC_PORT { + type inet:port-number; + } + } + case l4_src_port_range { + leaf L4_SRC_PORT_RANGE { + type string { + pattern "[0-9]{1,5}(-)[0-9]{1,5}" { + error-app-tag src-port-range-invalid; + } + } + } + } + } + + choice dst_port { + case l4_dst_port { + leaf L4_DST_PORT { + type inet:port-number; + } + } + case l4_dst_port_range { + leaf L4_DST_PORT_RANGE { + type string { + pattern "[0-9]{1,5}(-)[0-9]{1,5}" { + error-app-tag dst-port-range-invalid; + } + } + } + } + } + + leaf TCP_FLAGS { + type string { + pattern "0[xX][0-9a-fA-F]{2}[/]0[xX][0-9a-fA-F]{2}" { + error-app-tag tcp-flag-invalid; + } + } + } + + leaf DSCP { + type inet:dscp; + } + } + } + } +} diff --git a/models/yang/sonic/sonic-interface.yang b/models/yang/sonic/sonic-interface.yang new file mode 100644 index 0000000000..3d22195fc5 --- /dev/null +++ b/models/yang/sonic/sonic-interface.yang @@ -0,0 +1,67 @@ +module sonic-interface { + namespace "http://github.com/Azure/sonic-interface"; + prefix sint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-interface { + + container INTERFACE { + + list INTERFACE_LIST { + key "portname"; + + leaf portname{ + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + /* Add a leafref, once VRF YANG is supported + leaf vrf-name { + type string { + pattern 'Vrf([-a-zA-Z0-9_]{1,60})' { + error-app-tag vrf-name-invalid; + } + } + } + */ + + } + + list INTERFACE_IPADDR_LIST { + key "portname ip_prefix"; + + leaf portname{ + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf ip_prefix { + type inet:ip-prefix; + } + } + } + } +} diff --git a/models/yang/sonic/sonic-port.yang b/models/yang/sonic/sonic-port.yang new file mode 100644 index 0000000000..4050bd3705 --- /dev/null +++ b/models/yang/sonic/sonic-port.yang @@ -0,0 +1,93 @@ +module sonic-port { + namespace "http://github.com/Azure/sonic-port"; + prefix prt; + + import sonic-common { + prefix cmn; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-port { + + container PORT { + + list PORT_LIST { + key "ifname"; + + leaf ifname { + type string { + pattern "Ethernet([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])" { + error-message "Invalid interface name"; + error-app-tag interface-name-invalid; + } + } + } + + leaf index { + type uint16; + mandatory true; + + } + + leaf speed { + type uint64 { + range "1000|10000|25000|40000|50000|100000|400000" { + error-message "Invalid Ethernet interface speed"; + error-app-tag port-speed-invalid; + } + } + } + + leaf valid_speeds { + type string; + } + + leaf alias { + type string { + pattern '[ -~]{0,64}'; + } + } + + leaf description { + type string { + pattern '[ -~]{0,64}'; + } + } + + leaf mtu{ + type uint32 { + range "1312..9216" { + error-message "Invalid MTU value"; + error-app-tag mtu-invalid; + } + } + default 9100; + } + + leaf lanes { + type string; + mandatory true; + } + + leaf admin_status { + type cmn:admin-status; + default "down"; + } + } + } + } +} diff --git a/patches/jsonquery.patch b/patches/jsonquery.patch new file mode 100644 index 0000000000..122ef8e014 --- /dev/null +++ b/patches/jsonquery.patch @@ -0,0 +1,14 @@ +diff --git a/node.go b/node.go +index 76032bb..db73a1e 100644 +--- a/node.go ++++ b/node.go +@@ -155,3 +155,9 @@ func Parse(r io.Reader) (*Node, error) { + } + return parse(b) + } ++ ++func ParseJsonMap(jsonMap *map[string]interface{}) (*Node, error) { ++ doc := &Node{Type: DocumentNode} ++ parseValue(*jsonMap, doc, 1) ++ return doc, nil ++} diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/CLI/.gitkeep b/src/CLI/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/CLI/Makefile b/src/CLI/Makefile new file mode 100644 index 0000000000..a12bdaad28 --- /dev/null +++ b/src/CLI/Makefile @@ -0,0 +1,53 @@ +SHELL = /bin/bash +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +.ONESHELL: +.SHELLFLAGS += -e + +SUBDIRS := clitree renderer klish +export SONIC_CLI_ROOT=$(TOPDIR)/build +TGT_DIR := $(SONIC_CLI_ROOT)/target + +all: packages $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ + +.PHONY: clean + +all: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir -f Makefile $@; \ + done + + rm -rf $(TOPDIR)/build/cli/* + mv -f $(TGT_DIR) $(TOPDIR)/build/cli + +clean: + make --directory=klish clean + rm -rf $(TOPDIR)/build/cli + rm -rf $(TGT_DIR) + +packages: + if ! dpkg -l | grep autoconf -c >>/dev/null; then sudo apt-get install autoconf; fi + if ! dpkg -l | grep m4 -c >>/dev/null; then sudo apt-get install m4; fi + if ! dpkg -l | grep libxml2-utils -c >>/dev/null; then sudo apt-get install libxml2-utils; fi + if ! dpkg -l | grep xsltproc -c >>/dev/null; then sudo apt-get install xsltproc; fi + if ! dpkg -l | grep python-lxml -c >>/dev/null; then sudo apt-get install python-lxml; fi + if ! dpkg -l | grep libexpat1-dev -c >>/dev/null; then sudo apt-get install libexpat1-dev; fi + diff --git a/src/CLI/actioner/sonic-cli-acl.py b/src/CLI/actioner/sonic-cli-acl.py new file mode 100755 index 0000000000..f694c29db9 --- /dev/null +++ b/src/CLI/actioner/sonic-cli-acl.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +import sys +import time +import json +import collections +import re +import ast +import openconfig_acl_client +from rpipe_utils import pipestr +from openconfig_acl_client.rest import ApiException +from scripts.render_cli import show_cli_output + +import urllib3 +urllib3.disable_warnings() + +plugins = dict() + +def register(func): + """Register sdk client method as a plug-in""" + plugins[func.__name__] = func + return func + + +def call_method(name, args): + method = plugins[name] + return method(args) + +def generate_body(func, args): + body = None + # Get the rules of all ACL table entries. + if func.__name__ == 'get_openconfig_acl_acl_acl_sets': + keypath = [] + + # Get Interface binding to ACL table info + elif func.__name__ == 'get_openconfig_acl_acl_interfaces': + keypath = [] + + # Get all the rules specific to an ACL table. + elif func.__name__ == 'get_openconfig_acl_acl_acl_sets_acl_set_acl_entries': + keypath = [ args[0], args[1] ] + + # Configure ACL table + elif func.__name__ == 'patch_openconfig_acl_acl_acl_sets_acl_set' : + keypath = [ args[0], args[1] ] + body = { "openconfig-acl:config": { + "name": args[0], + "type": args[1], + "description": "" + } + } + + # Configure ACL rule specific to an ACL table + elif func.__name__ == 'patch_list_openconfig_acl_acl_acl_sets_acl_set_acl_entries_acl_entry' : + keypath = [ args[0], args[1] ] + forwarding_action = "ACCEPT" if args[3] == 'permit' else 'DROP' + proto_number = {"icmp":"IP_ICMP","tcp":"IP_TCP","udp":"IP_UDP","6":"IP_TCP","17":"IP_UDP","1":"IP_ICMP", + "2":"IP_IGMP","103":"IP_PIM","46":"IP_RSVP","47":"IP_GRE","51":"IP_AUTH","115":"IP_L2TP"} + if args[4] not in proto_number.keys(): + print("%Error: Invalid protocol number") + exit(1) + else: + protocol = proto_number.get(args[4]) + body=collections.defaultdict(dict) + body["acl-entry"]=[{ + "sequence-id": int(args[2]), + "config": { + "sequence-id": int(args[2]) + }, + "ipv4":{ + "config":{ + "protocol": protocol + } + }, + "transport": { + "config": { + } + }, + "actions": { + "config": { + "forwarding-action": forwarding_action + } + } + }] + re_ip = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") + if re_ip.match(args[5]): + body["acl-entry"][0]["ipv4"]["config"]["source-address"]=args[5] + elif args[5]=="any": + body["acl-entry"][0]["ipv4"]["config"]["source-address"]="0.0.0.0/0" + flags_list=[] + i=6 + while(i%s : %s\n" %(func.__name__, e)) + if e.body != "": + body = json.loads(e.body) + if "ietf-restconf:errors" in body: + err = body["ietf-restconf:errors"] + if "error" in err: + errList = err["error"] + + errDict = {} + for dict in errList: + for k, v in dict.iteritems(): + errDict[k] = v + + if "error-message" in errDict: + print "%Error: " + errDict["error-message"] + return + print "%Error: Transaction Failure" + return + print "%Error: Transaction Failure" + + +if __name__ == '__main__': + + pipestr().write(sys.argv) + #pdb.set_trace() + func = eval(sys.argv[1], globals(), openconfig_acl_client.OpenconfigAclApi.__dict__) + run(func, sys.argv[2:]) diff --git a/src/CLI/actioner/sonic-cli-if.py b/src/CLI/actioner/sonic-cli-if.py new file mode 100755 index 0000000000..9ed5f27006 --- /dev/null +++ b/src/CLI/actioner/sonic-cli-if.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +import sys +import time +import json +import ast +import openconfig_interfaces_client +from rpipe_utils import pipestr +from openconfig_interfaces_client.rest import ApiException +from scripts.render_cli import show_cli_output + +import urllib3 +urllib3.disable_warnings() + + +plugins = dict() + +def register(func): + """Register sdk client method as a plug-in""" + plugins[func.__name__] = func + return func + + +def call_method(name, args): + method = plugins[name] + return method(args) + +def generate_body(func, args): + body = None + # Get the rules of all ACL table entries. + if func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_description': + keypath = [ args[0] ] + body = { "openconfig-interfaces:description": args[1] } + elif func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_enabled': + keypath = [ args[0] ] + if args[1] == "True": + body = { "openconfig-interfaces:enabled": True } + else: + body = { "openconfig-interfaces:enabled": False } + elif func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_mtu': + keypath = [ args[0] ] + body = { "openconfig-interfaces:mtu": int(args[1]) } + elif func.__name__ == 'patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config': + sp = args[1].split('/') + keypath = [ args[0], 0, sp[0] ] + body = { "openconfig-if-ip:config": {"ip" : sp[0], "prefix-length" : int(sp[1])} } + elif func.__name__ == 'patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config': + sp = args[1].split('/') + keypath = [ args[0], 0, sp[0] ] + body = { "openconfig-if-ip:config": {"ip" : sp[0], "prefix-length" : int(sp[1])} } + elif func.__name__ == 'delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config_prefix_length': + keypath = [args[0], 0, args[1]] + elif func.__name__ == 'delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config_prefix_length': + keypath = [args[0], 0, args[1]] + elif func.__name__ == 'get_openconfig_interfaces_interfaces_interface': + keypath = [args[0]] + elif func.__name__ == 'get_openconfig_interfaces_interfaces': + keypath = [] + else: + body = {} + + return keypath,body + +def getId(item): + prfx = "Ethernet" + state_dict = item['state'] + ifName = state_dict['name'] + + if ifName.startswith(prfx): + ifId = int(ifName[len(prfx):]) + return ifId + return ifName + +def run(func, args): + + c = openconfig_interfaces_client.Configuration() + c.verify_ssl = False + aa = openconfig_interfaces_client.OpenconfigInterfacesApi(api_client=openconfig_interfaces_client.ApiClient(configuration=c)) + + # create a body block + keypath, body = generate_body(func, args) + + try: + if body is not None: + api_response = getattr(aa,func.__name__)(*keypath, body=body) + else : + api_response = getattr(aa,func.__name__)(*keypath) + + if api_response is None: + print ("Success") + else: + # Get Command Output + api_response = aa.api_client.sanitize_for_serialization(api_response) + if 'openconfig-interfaces:interfaces' in api_response: + value = api_response['openconfig-interfaces:interfaces'] + if 'interface' in value: + tup = value['interface'] + value['interface'] = sorted(tup, key=getId) + + if api_response is None: + print("Failed") + else: + if func.__name__ == 'get_openconfig_interfaces_interfaces_interface': + show_cli_output(args[1], api_response) + elif func.__name__ == 'get_openconfig_interfaces_interfaces': + show_cli_output(args[0], api_response) + else: + return + except ApiException as e: + #print("Exception when calling OpenconfigInterfacesApi->%s : %s\n" %(func.__name__, e)) + if e.body != "": + body = json.loads(e.body) + if "ietf-restconf:errors" in body: + err = body["ietf-restconf:errors"] + if "error" in err: + errList = err["error"] + + errDict = {} + for dict in errList: + for k, v in dict.iteritems(): + errDict[k] = v + + if "error-message" in errDict: + print "%Error: " + errDict["error-message"] + return + print "%Error: Transaction Failure" + return + print "%Error: Transaction Failure" + else: + print "Failed" + +if __name__ == '__main__': + + pipestr().write(sys.argv) + func = eval(sys.argv[1], globals(), openconfig_interfaces_client.OpenconfigInterfacesApi.__dict__) + + run(func, sys.argv[2:]) diff --git a/src/CLI/actioner/sonic-cli-lldp.py b/src/CLI/actioner/sonic-cli-lldp.py new file mode 100644 index 0000000000..11d71479b2 --- /dev/null +++ b/src/CLI/actioner/sonic-cli-lldp.py @@ -0,0 +1,99 @@ +#!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +import sys +import time +import json +import ast +import openconfig_lldp_client +from rpipe_utils import pipestr +from openconfig_lldp_client.rest import ApiException +from scripts.render_cli import show_cli_output + + +import urllib3 +urllib3.disable_warnings() +plugins = dict() + + +def register(func): + """Register sdk client method as a plug-in""" + plugins[func.__name__] = func + return func + +def call_method(name, args): + method = plugins[name] + return method(args) + +def generate_body(func, args): + body = None + if func.__name__ == 'get_openconfig_lldp_lldp_interfaces': + keypath = [] + elif func.__name__ == 'get_openconfig_lldp_lldp_interfaces_interface': + keypath = [args[1]] + else: + body = {} + + return keypath,body + + +def run(func, args): + c = openconfig_lldp_client.Configuration() + c.verify_ssl = False + aa = openconfig_lldp_client.OpenconfigLldpApi(api_client=openconfig_lldp_client.ApiClient(configuration=c)) + + # create a body block + keypath, body = generate_body(func, args) + + try: + if body is not None: + api_response = getattr(aa,func.__name__)(*keypath, body=body) + else : + api_response = getattr(aa,func.__name__)(*keypath) + if api_response is None: + print ("Success") + else: + response = api_response.to_dict() + if 'openconfig_lldpinterfaces' in response.keys(): + if not response['openconfig_lldpinterfaces']: + return + neigh_list = response['openconfig_lldpinterfaces']['interface'] + if neigh_list is None: + return + show_cli_output(sys.argv[2],neigh_list) + elif 'openconfig_lldpinterface' in response.keys(): + neigh = response['openconfig_lldpinterface']#[0]['neighbors']['neighbor'] + if neigh is None: + return + if sys.argv[3] is not None: + if neigh[0]['neighbors']['neighbor'][0]['state'] is None: + print('No LLDP neighbor interface') + else: + show_cli_output(sys.argv[2],neigh) + else: + show_cli_output(sys.argv[2],neigh) + else: + print("Failed") + except ApiException as e: + print("Exception when calling OpenconfigLldpApi->%s : %s\n" %(func.__name__, e)) + +if __name__ == '__main__': + pipestr().write(sys.argv) + func = eval(sys.argv[1], globals(), openconfig_lldp_client.OpenconfigLldpApi.__dict__) + run(func, sys.argv[2:]) diff --git a/src/CLI/actioner/sonic-cli-pfm.py b/src/CLI/actioner/sonic-cli-pfm.py new file mode 100644 index 0000000000..155e404961 --- /dev/null +++ b/src/CLI/actioner/sonic-cli-pfm.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +import sys +import time +import json +import ast +import openconfig_platform_client +from rpipe_utils import pipestr +from openconfig_platform_client.rest import ApiException +from scripts.render_cli import show_cli_output + + +import urllib3 +urllib3.disable_warnings() + +blocked_fields = {'parent':0, 'used_power':0, 'allocated_power':0, 'temperature':0} +plugins = dict() + +def filter_json_value(value): + for key,val in value.items(): + if key in blocked_fields: + del value[key] + else: + temp = key.split('_') + alt_key = '' + for i in temp: + alt_key = alt_key + i.capitalize() + ' ' + value[alt_key]=value.pop(key) + + return value + +def register(func): + """Register sdk client method as a plug-in""" + plugins[func.__name__] = func + return func + + +def call_method(name, args): + method = plugins[name] + return method(args) + +def generate_body(func, args): + body = None + # Get the rules of all ACL table entries. + + if func.__name__ == 'get_openconfig_platform_components': + keypath = [] + + else: + body = {} + + return keypath,body + + +def run(func, args): + c = openconfig_platform_client.Configuration() + c.verify_ssl = False + aa = openconfig_platform_client.OpenconfigPlatformApi(api_client=openconfig_platform_client.ApiClient(configuration=c)) + + # create a body block + keypath, body = generate_body(func, args) + + try: + if body is not None: + api_response = getattr(aa,func.__name__)(*keypath, body=body) + + else : + api_response = getattr(aa,func.__name__)(*keypath) + + if api_response is None: + print ("Success") + else: + api_response = aa.api_client.sanitize_for_serialization(api_response) + value = api_response['openconfig-platform:components']['component'][0]['state'] + if value is None: + return + if 'oper-status' in value: + temp = value['oper-status'].split(':') + if temp[len(temp) - 1] is not None: + value['oper-status'] = temp[len(temp) - 1] + show_cli_output(sys.argv[2],filter_json_value(value)) + + except ApiException as e: + if e.body != "": + body = json.loads(e.body) + if "ietf-restconf:errors" in body: + err = body["ietf-restconf:errors"] + if "error" in err: + errList = err["error"] + + errDict = {} + for dict in errList: + for k, v in dict.iteritems(): + errDict[k] = v + + if "error-message" in errDict: + print "%Error: " + errDict["error-message"] + return + print "%Error: Application Failure" + return + print "%Error: Application Failure" + else: + print "Failed" + +if __name__ == '__main__': + + pipestr().write(sys.argv) + #pdb.set_trace() + func = eval(sys.argv[1], globals(), openconfig_platform_client.OpenconfigPlatformApi.__dict__) + run(func, sys.argv[2:]) + diff --git a/src/CLI/actioner/sonic-cli-sys.py b/src/CLI/actioner/sonic-cli-sys.py new file mode 100644 index 0000000000..1fbbb7317d --- /dev/null +++ b/src/CLI/actioner/sonic-cli-sys.py @@ -0,0 +1,164 @@ +#:!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +import sys +import time +import json +import ast +import openconfig_system_client +from rpipe_utils import pipestr +from openconfig_system_client.rest import ApiException +from scripts.render_cli import show_cli_output + + +import urllib3 +urllib3.disable_warnings() + + +plugins = dict() + +def util_capitalize(value): + for key,val in value.items(): + temp = key.split('_') + alt_key = '' + for i in temp: + alt_key = alt_key + i.capitalize() + ' ' + value[alt_key]=value.pop(key) + return value + +def system_state_key_change(value): + value.pop('motd_banner') + value.pop('login_banner') + return util_capitalize(value) + + +def memory_key_change(value): + value['Total']=value.pop('physical') + value['Used']=value.pop('reserved') + return value + +def register(func): + """Register sdk client method as a plug-in""" + plugins[func.__name__] = func + return func + + +def call_method(name, args): + method = plugins[name] + return method(args) + +def generate_body(func, args): + body = None + # Get the rules of all ACL table entries. + + if func.__name__ == 'get_openconfig_system_system_state': + keypath = [] + elif func.__name__ == 'get_openconfig_system_system_clock': + keypath = [] + elif func.__name__ == 'get_openconfig_system_system_memory': + keypath = [] + elif func.__name__ == 'get_openconfig_system_system_cpus': + keypath = [] + elif func.__name__ == 'get_openconfig_system_system_processes': + keypath = [] + elif func.__name__ == 'get_openconfig_system_components': + keypath = [] + + else: + body = {} + + return keypath,body + + +def run(func, args): + c = openconfig_system_client.Configuration() + c.verify_ssl = False + aa = openconfig_system_client.OpenconfigSystemApi(api_client=openconfig_system_client.ApiClient(configuration=c)) + + # create a body block + keypath, body = generate_body(func, args) + + try: + if body is not None: + api_response = getattr(aa,func.__name__)(*keypath, body=body) + + else : + api_response = getattr(aa,func.__name__)(*keypath) + if api_response is None: + print ("Success") + else: + response = api_response.to_dict() + if 'openconfig_systemstate' in response.keys(): + value = response['openconfig_systemstate'] + if value is None: + return + show_cli_output(sys.argv[2], system_state_key_change(value)) + + elif 'openconfig_systemmemory' in response.keys(): + value = response['openconfig_systemmemory'] + if value is None: + return + show_cli_output(sys.argv[2], memory_key_change(value['state'])) + elif 'openconfig_systemcpus' in response.keys(): + value = response['openconfig_systemcpus'] + if value is None: + return + show_cli_output(sys.argv[2], value['cpu']) + elif 'openconfig_systemprocesses' in response.keys(): + value = response['openconfig_systemprocesses'] + if 'pid' not in sys.argv: + if value is None: + return + show_cli_output(sys.argv[2],value['process']) + else: + for proc in value['process']: + if proc['pid'] == int(sys.argv[3]): + show_cli_output(sys.argv[2],util_capitalize(proc['state'])) + return + else: + print("Failed") + except ApiException as e: + if e.body != "": + body = json.loads(e.body) + if "ietf-restconf:errors" in body: + err = body["ietf-restconf:errors"] + if "error" in err: + errList = err["error"] + + errDict = {} + for dict in errList: + for k, v in dict.iteritems(): + errDict[k] = v + + if "error-message" in errDict: + print "%Error: " + errDict["error-message"] + return + print "%Error: Application Failure" + return + print "%Error: Application Failure" + else: + print "Failed" + +if __name__ == '__main__': + + pipestr().write(sys.argv) + #pdb.set_trace() + func = eval(sys.argv[1], globals(), openconfig_system_client.OpenconfigSystemApi.__dict__) + run(func, sys.argv[2:]) + diff --git a/src/CLI/clicfg/mgmt_clish_entities.xsl b/src/CLI/clicfg/mgmt_clish_entities.xsl new file mode 100644 index 0000000000..a21cca8d99 --- /dev/null +++ b/src/CLI/clicfg/mgmt_clish_entities.xsl @@ -0,0 +1,26 @@ + + + + + + + + +<!ENTITY ""> + + diff --git a/src/CLI/clicfg/mgmt_clish_entities_macro.xsl b/src/CLI/clicfg/mgmt_clish_entities_macro.xsl new file mode 100644 index 0000000000..f20fd62e6f --- /dev/null +++ b/src/CLI/clicfg/mgmt_clish_entities_macro.xsl @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clicfg/mgmt_clish_feature_master.xsd b/src/CLI/clicfg/mgmt_clish_feature_master.xsd new file mode 100644 index 0000000000..fd7c330370 --- /dev/null +++ b/src/CLI/clicfg/mgmt_clish_feature_master.xsd @@ -0,0 +1,98 @@ + + + + + + + This contains the allowed feature names for any platform specific customizable feature names. If you are adding a new platform customizable feature, add a record in the enumerated list here. + + + + + + + + + This contains the allowed feature-value names for any platform specific customizable feature-value strings. If you are adding a new platform customizable entity, add a record in the enumerated list here. + + + + + + + + + + + + This represents a feature-value entity. Do not add or delete anything here. +For adding a new feature-value, use entityname_t. For adding constraints on the count of +entities, use entity_list_t + + + + + + + + + + + + + + + This represents a feature-value entity. Do not add or delete anything here. +For adding a new feature-value, use entityname_t. For adding constraints on the count +of entities, use entity_list_t + + + + + + + + + + + + + + + + + + + + This is the top level module that lists the master list of all features and feature-value names. +If you are adding a new feature, add a record in the featurename_t. +If you are adding a new feature-value, add a record in the entityname_t + + + + + + + + + + + diff --git a/src/CLI/clicfg/mgmt_clish_features.xsl b/src/CLI/clicfg/mgmt_clish_features.xsl new file mode 100644 index 0000000000..2c9ebad834 --- /dev/null +++ b/src/CLI/clicfg/mgmt_clish_features.xsl @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clicfg/mgmt_clish_platform.xml b/src/CLI/clicfg/mgmt_clish_platform.xml new file mode 100644 index 0000000000..f421e8ce26 --- /dev/null +++ b/src/CLI/clicfg/mgmt_clish_platform.xml @@ -0,0 +1,38 @@ + + + + + + + + + START_PORT_ID + MAX_PORT_ID + START_SUB_PORT_ID + MAX_SUB_PORT_ID + MAX_MTU + + diff --git a/src/CLI/clitree/Makefile b/src/CLI/clitree/Makefile new file mode 100644 index 0000000000..344db6e848 --- /dev/null +++ b/src/CLI/clitree/Makefile @@ -0,0 +1,42 @@ +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +.PHONY: install + +TGT_DIR := $(SONIC_CLI_ROOT)/target + +all: + mkdir -p ${TGT_DIR}/command-tree ${TGT_DIR}/cli-macro ${TGT_DIR}/render-templates ${TGT_DIR}/scripts + cp -r cli-xml/include ${TGT_DIR}/command-tree + cp cli-xml/*.xml ${TGT_DIR}/command-tree + cp macro/*.xml ${TGT_DIR}/cli-macro + (cd scripts;./klish_platform_features_process.sh ../../clicfg ${TGT_DIR}) + python scripts/klish_preproc_cmdtree.py ${TGT_DIR}/command-tree ${TGT_DIR}/cli-macro 3 + cp ./../actioner/*.py ${TGT_DIR}/. + cp ../renderer/scripts/*.py ${TGT_DIR}/scripts + cp ../renderer/templates/* ${TGT_DIR}/render-templates + cp scripts/sonic-clish.xsd ${TGT_DIR}/command-tree + (cd ${TGT_DIR}/command-tree ; xmllint --noout --schema sonic-clish.xsd ${TGT_DIR}/command-tree/*.xml && \ + xmllint --noout --schema sonic-clish.xsd ${TGT_DIR}/command-tree/include/*.xml) || exit 1 + rm -rf ${TGT_DIR}/cli-macro + rm -rf scripts/*.pyc + +clean: + rm -rf ${TGT_DIR} + @echo "Clitree Cleaned" + diff --git a/src/CLI/clitree/cli-xml/acl.xml b/src/CLI/clitree/cli-xml/acl.xml new file mode 100644 index 0000000000..b78c72f459 --- /dev/null +++ b/src/CLI/clitree/cli-xml/acl.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + if test "${access-list-name}" = ""; then + python $SONIC_CLI_ROOT/sonic-cli-acl.py get_openconfig_acl_acl_acl_sets show_access_list.j2 + else + python $SONIC_CLI_ROOT/sonic-cli-acl.py get_openconfig_acl_acl_acl_sets_acl_set_acl_entries ${access-list-name} ACL_IPV4 show_access_list.j2 + fi + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-acl.py get_openconfig_acl_acl_interfaces show_access_group.j2 + + + + + + + + + + + + + + + if test "${direction-switch}" = "in"; then + python $SONIC_CLI_ROOT/sonic-cli-acl.py patch_list_openconfig_acl_acl_interfaces_interface ${access-list-name} ACL_IPV4 ${iface} ingress + else + python $SONIC_CLI_ROOT/sonic-cli-acl.py patch_list_openconfig_acl_acl_interfaces_interface ${access-list-name} ACL_IPV4 ${iface} egress + fi + + + + + + + + + + if test "${direction-switch}" = "in"; then + python $SONIC_CLI_ROOT/sonic-cli-acl.py delete_openconfig_acl_acl_interfaces_interface_ingress_acl_sets_ingress_acl_set ${iface} ${access-list-name} ACL_IPV4 + else + python $SONIC_CLI_ROOT/sonic-cli-acl.py delete_openconfig_acl_acl_interfaces_interface_egress_acl_sets_egress_acl_set ${iface} ${access-list-name} ACL_IPV4 + fi + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-acl.py patch_openconfig_acl_acl_acl_sets_acl_set ${access-list-name} ACL_IPV4 + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-acl.py delete_openconfig_acl_acl_acl_sets_acl_set ${access-list-name} ACL_IPV4 + + + + + + + + + + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-acl.py delete_openconfig_acl_acl_acl_sets_acl_set_acl_entries_acl_entry ${name} ACL_IPV4 ${seq-no} + + + + + + + + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-acl.py patch_list_openconfig_acl_acl_acl_sets_acl_set_acl_entries_acl_entry ${name} ACL_IPV4 ${__params} + + + + + diff --git a/src/CLI/clitree/cli-xml/configure_mode.xml b/src/CLI/clitree/cli-xml/configure_mode.xml new file mode 100644 index 0000000000..72c70bac16 --- /dev/null +++ b/src/CLI/clitree/cli-xml/configure_mode.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/src/CLI/clitree/cli-xml/enable_mode.xml b/src/CLI/clitree/cli-xml/enable_mode.xml new file mode 100644 index 0000000000..3ba8c146ba --- /dev/null +++ b/src/CLI/clitree/cli-xml/enable_mode.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + ${cmd} + + + + diff --git a/src/CLI/clitree/cli-xml/include/pipe.xml b/src/CLI/clitree/cli-xml/include/pipe.xml new file mode 100644 index 0000000000..758ae50897 --- /dev/null +++ b/src/CLI/clitree/cli-xml/include/pipe.xml @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clitree/cli-xml/include/pipe_without_display_xml.xml b/src/CLI/clitree/cli-xml/include/pipe_without_display_xml.xml new file mode 100644 index 0000000000..50f99bf013 --- /dev/null +++ b/src/CLI/clitree/cli-xml/include/pipe_without_display_xml.xml @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clitree/cli-xml/interface.xml b/src/CLI/clitree/cli-xml/interface.xml new file mode 100644 index 0000000000..3341170d76 --- /dev/null +++ b/src/CLI/clitree/cli-xml/interface.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + if test "${if-subcommands}" = "status"; then + python $SONIC_CLI_ROOT/sonic-cli-if.py get_openconfig_interfaces_interfaces show_interface_status.j2 ${__full_line} + elif test "${if-subcommands}" = "counters"; then + python $SONIC_CLI_ROOT/sonic-cli-if.py get_openconfig_interfaces_interfaces show_interface_counters.j2 ${__full_line} + else + if test "${phy-if-id}" = ""; then + python $SONIC_CLI_ROOT/sonic-cli-if.py get_openconfig_interfaces_interfaces show_interface.j2 ${__full_line} + else + python $SONIC_CLI_ROOT/sonic-cli-if.py get_openconfig_interfaces_interfaces_interface Ethernet${phy-if-id} show_interface_id.j2 ${__full_line} + fi + fi + + + + + + + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_enabled ${iface} False + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_enabled ${iface} True + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_description ${iface} ${desc} + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_description ${iface} "" + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_mtu ${iface} ${mtu} + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_interfaces_interfaces_interface_config_mtu ${iface} 9100 + + + + diff --git a/src/CLI/clitree/cli-xml/ip.xml b/src/CLI/clitree/cli-xml/ip.xml new file mode 100644 index 0000000000..1d974a54b2 --- /dev/null +++ b/src/CLI/clitree/cli-xml/ip.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/src/CLI/clitree/cli-xml/ipv4.xml b/src/CLI/clitree/cli-xml/ipv4.xml new file mode 100644 index 0000000000..80ba50ac79 --- /dev/null +++ b/src/CLI/clitree/cli-xml/ipv4.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config ${iface} ${addr} + + + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config_prefix_length ${iface} ${addr} + + + + + diff --git a/src/CLI/clitree/cli-xml/ipv6.xml b/src/CLI/clitree/cli-xml/ipv6.xml new file mode 100644 index 0000000000..bad92d0215 --- /dev/null +++ b/src/CLI/clitree/cli-xml/ipv6.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config ${iface} ${addr} + + + + + python $SONIC_CLI_ROOT/sonic-cli-if.py delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config_prefix_length ${iface} ${addr} + + + + diff --git a/src/CLI/clitree/cli-xml/lldp.xml b/src/CLI/clitree/cli-xml/lldp.xml new file mode 100644 index 0000000000..d03352e24b --- /dev/null +++ b/src/CLI/clitree/cli-xml/lldp.xml @@ -0,0 +1,54 @@ + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-lldp.py get_openconfig_lldp_lldp_interfaces lldp_show.j2 ${__full_line} + + + python $SONIC_CLI_ROOT/sonic-cli-lldp.py get_openconfig_lldp_lldp_interfaces lldp_show.j2 ${__full_line} + + + + + + if test "${ifname}" = ""; then + python $SONIC_CLI_ROOT/sonic-cli-lldp.py get_openconfig_lldp_lldp_interfaces lldp_neighbor_show.j2 ${__full_line} + else + python $SONIC_CLI_ROOT/sonic-cli-lldp.py get_openconfig_lldp_lldp_interfaces_interface lldp_neighbor_show.j2 ${ifname} ${__full_line} + fi + + + + + + + diff --git a/src/CLI/clitree/cli-xml/platform.xml b/src/CLI/clitree/cli-xml/platform.xml new file mode 100644 index 0000000000..1ab3f0400a --- /dev/null +++ b/src/CLI/clitree/cli-xml/platform.xml @@ -0,0 +1,50 @@ + + + + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-pfm.py get_openconfig_platform_components platform_show.j2 ${__full_line} + + + + + + + python $SONIC_CLI_ROOT/sonic-cli-pfm.py get_openconfig_platform_components platform_show.j2 ${__full_line} + + + + + + + diff --git a/src/CLI/clitree/cli-xml/sonic_types.xml b/src/CLI/clitree/cli-xml/sonic_types.xml new file mode 100644 index 0000000000..6b33089605 --- /dev/null +++ b/src/CLI/clitree/cli-xml/sonic_types.xml @@ -0,0 +1,465 @@ + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clitree/cli-xml/startup.xml b/src/CLI/clitree/cli-xml/startup.xml new file mode 100644 index 0000000000..825fceb4a4 --- /dev/null +++ b/src/CLI/clitree/cli-xml/startup.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/CLI/clitree/cli-xml/system.xml b/src/CLI/clitree/cli-xml/system.xml new file mode 100644 index 0000000000..ba3b9277f9 --- /dev/null +++ b/src/CLI/clitree/cli-xml/system.xml @@ -0,0 +1,54 @@ + + + + + + + + +]> + + + + python $SONIC_CLI_ROOT/sonic-cli-sys.py get_openconfig_system_system_state system_show.j2 ${__full_line} + + + + python $SONIC_CLI_ROOT/sonic-cli-sys.py get_openconfig_system_system_memory system_show.j2 ${__full_line} + + + + python $SONIC_CLI_ROOT/sonic-cli-sys.py get_openconfig_system_system_cpus system_cpu_show.j2 ${__full_line} + + + + python $SONIC_CLI_ROOT/sonic-cli-sys.py get_openconfig_system_system_processes system_processes_show.j2 ${__full_line} + + + + + + python $SONIC_CLI_ROOT/sonic-cli-sys.py get_openconfig_system_system_processes system_show.j2 ${pid-no} ${__full_line} + + + + diff --git a/src/CLI/clitree/macro/acl_macro.xml b/src/CLI/clitree/macro/acl_macro.xml new file mode 100644 index 0000000000..329717c992 --- /dev/null +++ b/src/CLI/clitree/macro/acl_macro.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/clitree/scripts/klish_ins_def_cmd.py b/src/CLI/clitree/scripts/klish_ins_def_cmd.py new file mode 100755 index 0000000000..01032c07b6 --- /dev/null +++ b/src/CLI/clitree/scripts/klish_ins_def_cmd.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + + +""" The script klish_Ins_Def_Cmd.py is used to append the "exit" and + "end"commands to the views of the klish XML models except for the views + in list SKIP_VIEW_LIST + + The script accepts input directory and output directory as parameters. + It reads each XML file in the input directory and iterates through + each VIEW tag in the XML.If "exit" and "end" command are not already + appended for the view,the script will append them for the view. + + On successful iteration through all the views, the resultant XML tree is + written to a file in the output directory with same name as in source + directory. + + Script maintains a list VIEW_LIST to hold the list of views for which the + exit and end commands are updated already. + + Note that a view could be present in multiple files.Appending in one of + the files for a view is enough for the command to appear for that mode. + + A special list SKIP_VIEW_LIST is being maintained which holds the + list of views for which we don't want the exit and end command to be + appended (Eg enable-view") + + The SKIP_VIEW_LIST of the script should be updated with any new view being + created for which we don't want exit and end command + + Usage: klish_ins_def_cmd.py inDir [outDir] [--debug]""" + +import sys +import os +from lxml import etree + +EXIT_CMD = """ + + """ + +COMMENT_NS = """""" + +INHERIT_ENABLE_MODE_CMD = """""" + +""" Bring all enable mode commands to config + modes directly (Commands hidden to the user) + so that all enable mode commands can be + executed from config mode itself.""" + +INHERIT_ENABLE_MODE_CMD_WITHOUT_PREFIX = """""" + + +END_CMD = """""" + +VIEW_TAG_STR = """{http://www.dellemc.com/sonic/XMLSchema}VIEW""" +ENABLE_VIEW_STR = """enable-view""" +SKIP_VIEW_LIST = ["enable-view", "hidden-view", "ping-view"] +#DBG_FLAG = False +DBG_FLAG = True + + + +def update_view_tag(root, viewlist, filename, out_dirpath): + + """ The function iterates through the VIEW tags in the + file,and appends exit and end commands to the view + if not added already""" + + out_file = out_dirpath+'/'+filename + file_modified = False + + for element_inst in root.iter(VIEW_TAG_STR): + if DBG_FLAG == True: + print "Processed view name %s" % str(element_inst.keys()) + if (element_inst.get('prompt') != None) and (element_inst.get('name') not in SKIP_VIEW_LIST): + view_name = element_inst.get('name') + + if view_name not in viewlist: + exit_element = etree.XML(EXIT_CMD) + end_element = etree.XML(END_CMD) + inherit_enable_element = etree.XML(INHERIT_ENABLE_MODE_CMD) + inherit_enable_element_without_prefix = etree.XML(INHERIT_ENABLE_MODE_CMD_WITHOUT_PREFIX) + comment_element = etree.XML(COMMENT_NS) + + if DBG_FLAG == True: + print "Appending to view %s ..." %view_name + element_inst.insert(0,end_element) + element_inst.insert(0,exit_element) + element_inst.insert(0,inherit_enable_element) + element_inst.insert(0,inherit_enable_element_without_prefix) + element_inst.insert(0,comment_element) + + file_modified = True + viewlist.append(view_name) + + if DBG_FLAG == True: + print etree.tostring(element_inst, pretty_print=True) + print "VIEW_LIST:" + # print VIEW_LIST + + if file_modified == True: + if DBG_FLAG == True: + print "Writing File %s ..." %out_file + root.write(out_file, xml_declaration=True, encoding=root.docinfo.encoding, pretty_print=True) + else: + + if DBG_FLAG == True: + print "Skipping File %s ..." %filename + return viewlist + +def ins_def_cmd (in_dirpath, out_dirpath, debug): + IN_DIR_PATH = in_dirpath + VIEW_LIST = [] + DBG_FLAG = debug + + parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) + + for dir_name, subdir_list, file_list in os.walk(IN_DIR_PATH): + for fname in file_list: + if fname.endswith(".xml"): + if DBG_FLAG == True: + print '\tInput File:%s' % fname + tree = etree.parse(dir_name+'/'+fname, parser) + VIEW_LIST = update_view_tag(tree, VIEW_LIST, fname, out_dirpath) + +if __name__ == "__main__": + + debug = False + if len(sys.argv) < 2: + print ("Error: Missing Parameter " + os.linesep + + "Usage: klish_ins_def_cmd.py inDir [outDir] [--debug]") + sys.exit(0) + + if sys.argv[1] == "--help": + print "Usage: klish_ins_def_cmd.py inDir [outDir] [--debug]" + sys.exit(0) + + if len(sys.argv) < 3: + out_dirpath = sys.argv[1] + elif sys.argv[2] == "--debug": + out_dirpath = sys.argv[1] + else: + out_dirpath = sys.argv[2] + + if len(sys.argv) < 3: + debug = False + elif sys.argv[2] == "--debug": + debug = True + + if (len(sys.argv) == 4) and (sys.argv[3] == "--debug"): + debug = True + + debug = True + print sys.argv[1], out_dirpath, 1 + ins_def_cmd (sys.argv[1], out_dirpath, debug) + + diff --git a/src/CLI/clitree/scripts/klish_insert_pipe.py b/src/CLI/clitree/scripts/klish_insert_pipe.py new file mode 100755 index 0000000000..27c074cad3 --- /dev/null +++ b/src/CLI/clitree/scripts/klish_insert_pipe.py @@ -0,0 +1,197 @@ +#!/usr/bin/python +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + + +''' This script extends every show and get COMMAND with pipe option + ----------------------------------------------------------------''' + +import sys +import os +import re +from lxml import etree +import xml.etree.ElementTree as ET + +DBG_FLAG = False +PIPE_XML = "include/pipe.xml" +PIPE_WITHOUT_DISPLAY_XML = "include/pipe_without_display_xml.xml" +DEFAULT_NS_HREF = "http://www.dellemc.com/sonic/XMLSchema" +XSI_NS_HREF = "http://www.dellemc.com/sonic/XMLSchema-instance" +XI_NS_HREF = "http://www.w3.org/2001/XInclude" +COMMAND_XPATH_EXPR = ".//{"+DEFAULT_NS_HREF+"}VIEW/{"+DEFAULT_NS_HREF+"}COMMAND" +HIDDEN_CMD_XPATH_EXPR = ".//{"+DEFAULT_NS_HREF+"}VIEW[@name='hidden-view']/{"+DEFAULT_NS_HREF+"}COMMAND" +ACTION_XPATH_EXPR = "{"+DEFAULT_NS_HREF+"}ACTION" + +# +# *** NOTE : List of action plugins for which display-xml need not be appended *** +# +actionlst = ['clish_history', 'clish_file_print', 'clish_show_alias_plugin', \ + 'clish_logger_on_off', 'clish_show_batch_plugin'] + +''' +@brief Convert the escaped characters back to their original form +@param[in] Input Text +@return Output Text +''' +def unescape(s): + s = re.sub("<", "<", s) + s = re.sub(">", ">", s) + s = re.sub("&", "&", s) + return s + + +''' +@brief Align and Save tempfile to outputfile +@param[in] Temporary file name +@param[in] Output file name +''' +def align_and_save(temp_file_name, output_file_name): + try: + parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) + root = etree.parse(temp_file_name, parser) + text = etree.tostring(root, pretty_print=True, xml_declaration=True, encoding=root.docinfo.encoding) + text = unescape(text) + root.write(output_file_name, pretty_print=True, xml_declaration=True, encoding=root.docinfo.encoding) + except: + error = parser.error_log[0] + print "Error parsing ", os.path.basename(outputfile.name), error.message + print "Error writing ", out_file_name, sys.exc_info()[0] + sys.exit(102) + + +''' +@brief Test whether pipe can be added to given command and insert it +@param[in] COMMAND tag found in xml +@return COMMAND tag with/without pipe sub-element added +''' +def addpipe(command): + splitstr = command.get('name').split() + action = command.find(ACTION_XPATH_EXPR).get('builtin') + if (splitstr[0] == 'show' or splitstr[0] == 'get'): + if action in actionlst: + etree.SubElement(command, "{"+XI_NS_HREF+"}include", href = PIPE_WITHOUT_DISPLAY_XML) + else: + etree.SubElement(command, "{"+XI_NS_HREF+"}include", href = PIPE_XML) + + if DBG_FLAG == True: + print "Adding Pipe for cmd: ", splitstr + return command + + +''' +@brief Register Namespaces so that XPATH expression matches +''' +def registerns(): + ET.register_namespace("", DEFAULT_NS_HREF) + ET.register_namespace("xsi", XSI_NS_HREF) + ET.register_namespace("xi", XI_NS_HREF) + + +''' +@brief Convert the escaped characters back to their original form +@param[in] Input filename +@param[out] Temporary file +''' +def process_input_file(input_file_name, tempfile): + try: + if True: + registerns() + parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) + tree = etree.parse(input_file_name,parser) + root = tree.getroot() + #root.set("xmlns:" + "xi", XI_NS_HREF) + if DBG_FLAG == True: + print "Root Tag: ",root.tag + for command in root.findall(COMMAND_XPATH_EXPR): + if len(command) != 0: + command = addpipe(command) + for command in root.findall(HIDDEN_CMD_XPATH_EXPR): + if len(command) != 0: + command = addpipe(command) + + tree.write(tempfile, xml_declaration=True, encoding=tree.docinfo.encoding, pretty_print=True) + except IOError as e: + print "Cannot open file: ", e.filename, ":", e.strerror + sys.exit(100) + except : + print "process_input_file:Unknown error: ", sys.exc_info()[0] + sys.exit(100) + +def insert_pipe (dirpath, debug): + + DBG_FLAG = debug + + tmp_dirpath = dirpath + '/tmp' + dirpath = dirpath + "/" + tmp_dirpath = tmp_dirpath + "/" + try: + os.mkdir(tmp_dirpath) + except OSError: + print 'The directory', tmp_dirpath, 'already exists. Using it.' + except: + error = parser.error_log[0] + print "Unknown error", error.message + sys.exit (98) + temp_file_name = tmp_dirpath + "out.xml" + + ''' The following loops go through each directory in the given + input directory and reads each *.xml file in the directory + for inserting the pipe ''' + for fname in os.listdir(dirpath): + fname = dirpath + fname + if not os.path.isfile (fname): + if DBG_FLAG == True: + print 'Skipping', fname, 'since it is not a file' + continue + if DBG_FLAG == True: + print 'Parsing ', fname + if fname.endswith(".xml", re.I): + try: + temp_file = open(temp_file_name, "w") + except IOError as e: + print e.filename, ":", e.strerror + sys.exit(99) + if DBG_FLAG == True: + print fname + process_input_file(fname, temp_file) + temp_file.close() + align_and_save(temp_file_name, fname) + + if os.path.exists(temp_file_name): + os.remove(temp_file_name) + if os.path.exists(tmp_dirpath): + os.rmdir(tmp_dirpath) + +''' +@brief Main Routine to insert pipe for every show and get COMMAND in all + xml files, present in input-dir and save them in output-dir +''' +if __name__ == "__main__": + + if len(sys.argv) == 1 or sys.argv[1] == "--help": + print "Usage:", sys.argv[0], "working-dir [--debug]" + sys.exit(0) + + if len(sys.argv) == 3 and sys.argv[2] == "--debug": + debug = True + else: + debug = False + + insert_pipe (sys.argv[1], debug) + sys.exit(0) + diff --git a/src/CLI/clitree/scripts/klish_platform_features_process.sh b/src/CLI/clitree/scripts/klish_platform_features_process.sh new file mode 100755 index 0000000000..976d55e8e9 --- /dev/null +++ b/src/CLI/clitree/scripts/klish_platform_features_process.sh @@ -0,0 +1,122 @@ +#!/bin/bash +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +#set -x +# Validate all platform xml files +# For all platform_*.xml, run xmllint feature_master.xsd $i.xml +# Create entities_platform.xml and features_platform.xml +# For all platforms_*/xml, run xsltproc $i.xml with entities.xml and features.xsl +# Copy a clish_prepare.py to sysroot +# Run clish_prepare.py with first platform and update entities +# +# Run xsltproc on feature_master.xsd to create an xml file of all features - fullfeatures.xml +# +# During clish start: +# a. Open fullfeatures.xml to populate a list of features vs enabled-flag mappping. +# b. While preparing the pfeature list, consult this list instead of consulting the list of #defines +# c. Report errors on processing the fullfeatures.xml file + +PLATFORM_CONFIGS=platform_dummy.xml +BUILD_DIR=$2/tmp +PARSER_XML_PATH=$2 +ENTITIES_TEMPLATE=$1/mgmt_clish_entities.xsl +FEATURES_MASTER=$1/mgmt_clish_feature_master.xsd + +function insert_in() +{ + # Insert in file - $1 with $value set in calling routine + filename=$1 + outfile=$filename.bak + grep -q DOCTYPE $filename + # If there are ENTITY definitions, add it as part of it + # Else add a new DOCTYPE + if [ $? -eq 0 ]; then + option=1 + matchpattern=".*DOCTYPE.*" + printvalue="${value}" + else + option=2 + matchpattern="" + fi + #echo Insert_in $filename. Option $option + while read -r line; do + echo ${line} >> $outfile + if [[ "${line}" =~ ${matchpattern} ]]; then + #echo Match found for ${line} + echo "${printvalue}" >> $outfile + #set +x + fi + done < $filename + #set -x + touch $outfile $filename + mv -f $outfile $filename +} + +# Do a simple text based insertion of the feature-val entities +# TBD - Replace them with an xml parser based insertion in future, if required +function insert_entities() +{ + value=`cat $1` + parser=$2 + echo insert_entities: $1 $parser + list=`echo ${parser}/*.xml` + for i in ${list}; do + echo Processing $i + insert_in $i + xmllint $i >& /dev/null + if [ $? -ne 0 ]; then + echo ENTITY insertion in $i failed + exit 1 + fi + done + #echo $i + #insert_in $i +} + + +echo Sanity check of platform config files with feature master ... + xmllint --schema $FEATURES_MASTER $PLATFORM_CONFIGS >& /dev/null + if [ $? -ne 0 ]; then + echo Failed to validate $PLATFORM_CONFIGS + exit 1 + fi + +mkdir -p ${BUILD_DIR} +echo Done. Generating platform specific files ... + base=${PLATFORM_CONFIGS%*.xml} # Strip of the .xml suffix + platform=${base#platform_*} # Get the platform name + xsltproc $ENTITIES_TEMPLATE $PLATFORM_CONFIGS > $BUILD_DIR/${platform}_entities.ent #2>/dev/null + # echo ${platform}_entities.ent ready + if [ $? -ne 0 ]; then + echo Failed to apply entities xsl template for $PLATFORM_CONFIGS + exit 1 + fi +echo Done + +# Use the last platform's file for compilation purpose +pwd=${PWD} +cd ${PARSER_XML_PATH} +echo Inserting platform features +insert_entities ${BUILD_DIR}/${platform}_entities.ent ${PARSER_XML_PATH}/command-tree +cp ${BUILD_DIR}/*.xml ${PARSER_XML_PATH}/command-tree + +rm -r ${BUILD_DIR} +exit 0 diff --git a/src/CLI/clitree/scripts/klish_preproc_cmdtree.py b/src/CLI/clitree/scripts/klish_preproc_cmdtree.py new file mode 100755 index 0000000000..140c859f07 --- /dev/null +++ b/src/CLI/clitree/scripts/klish_preproc_cmdtree.py @@ -0,0 +1,58 @@ +#!/usr/bin/python2.7 +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +''' CLI parser tree preprocessing script before the parser xml-s are copied + to sysroot. These are the steps performed: + a. Macro replacement + b. Platform specific feature xml and feature-val xml creation + c. Insert the |' for post processing support of show commands + d. Insert the default default end and exit command for all config modes + + The Script Usage: + python klish_preproc_cmdtree.py buildpath macros-dir depth +''' +import sys +import os +import re +from lxml import etree +import klish_replace_macro, klish_insert_pipe, klish_ins_def_cmd + +if __name__ == "__main__": + + if len(sys.argv) == 1 or sys.argv[1] == "--help": + print "Usage:", sys.argv[0], "working-dir macrodir nested-macro-levels [--debug]" + sys.exit(0) + + dirpath = sys.argv[1] + macro_dir_path = sys.argv[2] + nested_levels = sys.argv[3] + + if len(sys.argv) == 5 and sys.argv[4] == "--debug": + debug = True + else: + debug = False + + print "Replacing the macros ..." + klish_replace_macro.replace_macros (dirpath, macro_dir_path, nested_levels, debug) + print "Inserting the pipe parameters ..." + klish_insert_pipe.insert_pipe (dirpath, debug) + print "Insert the end, exit commands ..." + klish_ins_def_cmd.ins_def_cmd (dirpath, dirpath, debug) + + diff --git a/src/CLI/clitree/scripts/klish_replace_macro.py b/src/CLI/clitree/scripts/klish_replace_macro.py new file mode 100755 index 0000000000..436b1dea82 --- /dev/null +++ b/src/CLI/clitree/scripts/klish_replace_macro.py @@ -0,0 +1,408 @@ +#!/usr/bin/python2.7 +########################################################################### +# +# Copyright 2019 Dell, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################### + +''' This script does macro replacement on the xml + files which are used by klish to defind CLI + strucuture. + + The script assumes that xml files using macro's are + kept in some input directory, macro definition files + are kept under another directory and expects a + directory where it keeps all the processed files. + + The script expect that macro definition are kept in a + file with *_macro.xml. + + The Script Usage: + python klish_replace_macro.py indir macrodir outdir [--debug] + + The format requirement for using and defining macro's + are given as follows: + + MACRO Definition file: + example_macro.xml + + + + + + + + + + + + + + + + + + + + + Macro Usage File Example: + + abc.xml + + + + + + + + + + + + ----------------------------------------------------------------''' +import sys +import os +import re +from lxml import etree + +MACRO_START = '' +MACRODEF_START = '' +DBG_FLAG = False + +def align_and_save(temp_file_name, out_file_name, replace_entities): + print "Writing ", out_file_name + try: + parser = etree.XMLParser(remove_blank_text=True, resolve_entities=replace_entities) + root = etree.parse(temp_file_name, parser) + root.write(out_file_name,xml_declaration=True, encoding=root.docinfo.encoding, pretty_print=True) + #root.write(out_file_name,pretty_print=True) + #outputfile.write(etree.tostring(root,xml_declaration=True, encoding=root.docinfo.encoding, pretty_print=True)) + #outputfile.write(etree.tostring(root, pretty_print=True)) + #outputfile.close() + except: + #error = parser.error_log[0] + #print "Error parsing ", os.path.basename(outputfile.name), error.message + print "Error writing ", out_file_name, sys.exc_info() + sys.exit(102) + +def process_spaces(line): + line = re.sub(" =", "=", line) + line = re.sub(" = ", "=", line) + line = re.sub("= ", "=", line) + line = re.sub("< ", "<", line) + line = re.sub(" >", ">", line) + line = re.sub(" />", "/>", line) + line = re.sub(' "', '"', line) + line = re.sub("!=", "!= ", line) + line = re.sub("==", " == ", line) + return line + +def endoflinehandling(line): + if re.search("/>", line, 0) != None: + retstr = re.sub("/>", "", line) + elif re.search(">", line, 0) != None: + retstr = re.sub(">", "", line) + return retstr.strip() +''' +## +# @brief Replace the macro references with the actual macro definition for the +# requested parser xml file +# +# @param macname Name of the macro to be replaced +# @param argcnt Number of arguments in the macro +# @param argval List of argument values in the macro +# @param fd Descriptor for the input xml file where replacement is requested. +# The cursor of fd already points to the place where replacement should +# be done +# @param macro_data List of all macro definitions +# +# @return +''' +def expand_macro(macname, argcnt, argval, fd, macro_data): + matchfound = 0 + try: + macro_start = MACRODEF_START + macname + '>' + gothit = 0 + for macro_line in macro_data: + macro_line = process_spaces(macro_line) + if re.search(macro_start, macro_line, 0) != None: + matchfound = 1 + gothit = 1 + if DBG_FLAG == True: + print "Macro Line Match found", macro_start + continue + else: + if DBG_FLAG == True: + print "macro ***not*** found part" + if macro_line.find(MACRODEF_END) != -1: + matchfound = 0 + continue + if matchfound == 1: + if argcnt == 0: + fd.write(macro_line) + fd.write(" ") + else: + i = 0 + for i in range(argcnt): + param = "arg" + str(i+1) + if macro_line.find(param) != -1: + value = str(argval[i]) + macro_line = re.sub(param, value, macro_line) + + if DBG_FLAG == True: + print "param : " + param + print "argval" + str(i) + ": " + argval[i] + print "macro_line : " + macro_line + fd.write(macro_line) + fd.write(" ") + if gothit == 0: + # A macro match is not found. Possibly a typo in macro name + # Flag fatal error + print "Macro", macname, "not defined in list of macros" + sys.exit(102) + + except: + error = parser.error_log[0] + print "Error expand_macro ", os.path.basename(fd.name), error.message + sys.exit(101) + +''' +## +# @brief Read the requested input file, identify the macro references along +# with the arguments and call expand_macro() for macro replacement +# +# @param filename Input file +# @param fd descriptor of a temporary file +# @param macro_data Array of all the macro definition lines +# +# @return Nothing +''' +def process_input_file(filename, fd, macro_data): + + try: + with open(filename, "r") as input_file: + multi_line = False + macroname = [] + data = input_file.readlines() + for line in data: + nargs = 0 + line = ' '.join(line.split()) + if DBG_FLAG == True: + print line, multi_line + if re.search(MACRO_START, line, 0) != None or multi_line == True: + if DBG_FLAG == True: + print "MACRO is found", line + line = process_spaces(line) + line = line.replace('\,', '#') + if DBG_FLAG == True: + print line + mname = re.sub(MACRO_START, "", line) + if line.find("arg") != -1: + nargs = line.count(',') + 1 + multi_line = False + elif line.find('>') == -1: + multi_line = True + macroname = mname.strip() + if DBG_FLAG == True: + print macroname, multi_line + continue + nargs = int(nargs) + if DBG_FLAG == True: + print nargs + if nargs == 0: + macroname = endoflinehandling(mname) + if DBG_FLAG == True: + print macroname + expand_macro(macroname, 0, None, fd, macro_data) + else: + args = [] + repmname = 'arg="' + if multi_line == False: + if re.search(' ', mname, 0) != None: + macroname = mname[0:mname.find(' ')] + repmname = macroname + ' ' + 'arg="' + if DBG_FLAG == True: + print macroname, nargs + mname = re.sub(repmname, "", mname) + for i in range(nargs): + e = mname.find(',') + if e != -1: + argval = mname[0:e].strip() + mname = mname[e+1:] + else: + e = mname.find('"') + argval = mname[0:e].strip() + argval = argval.replace('#', ',') + args.append(argval) + if DBG_FLAG == True: + print "about to expand_macro", macroname + expand_macro(macroname, nargs, args, fd, macro_data) + elif re.search(MACRO_END, line, 0) != None: + continue + else: + fd.write(line) + fd.write(" ") + fd.close() + input_file.close() + except IOError as e: + print "Cannot open file: ", filename, e.strerror + sys.exit(100) + except : + print "Unknown error: ", sys.exc_info()[0], filename, input_file + sys.exit(100) + +def load_all_macros (macro_dir_path): + ''' Loop through each directory in the given macro directory and + reads each *macro.xml file in the directory into a buffer and return it''' + macro_data = [] + for dirName, subdirList, fileList in os.walk(macro_dir_path): + for macro_file_name in fileList: + macro_file_name = macro_dir_path + "/" + macro_file_name + with open(macro_file_name, "r") as macrofile: + data = macrofile.readlines() + + for line in data: + line = ' '.join(line.split()) + macro_data.append(line) + macrofile.close() + return macro_data + +def process_dir (dirpath, macro_data, replace_entities): + ''' Loop through each file in the given input directory and replace + all macro references with the actual definitions along with + argument substitutions''' + tmp_dirpath = dirpath + '/tmp/' + temp_file_name = tmp_dirpath + "out.xml" + + try: + os.mkdir(tmp_dirpath) + except OSError: + print 'The directory', tmp_dirpath, 'already exists. Using it.' + except: + print "Unknown error" + sys.exit (98) + + if DBG_FLAG == True: + print "process_dir ", dirpath, tmp_dirpath, temp_file_name + + for fname in os.listdir(dirpath): + fname = dirpath + fname + if not os.path.isfile (fname): + if DBG_FLAG == True: + print 'Skipping', fname, 'since it is not a file' + continue + if DBG_FLAG == True: + print 'Parsing ', fname + if fname.endswith(".xml", re.I): + try: + temp_file = open(temp_file_name, "w") + except IOError as e: + print e.filename, ":", e.strerror + sys.exit(99) + if DBG_FLAG == True: + print fname + process_input_file(fname, temp_file, macro_data) + align_and_save(temp_file_name, fname, replace_entities) + temp_file.close() + + if os.path.exists(temp_file_name): + os.remove(temp_file_name) + if os.path.exists(tmp_dirpath): + os.rmdir(tmp_dirpath) + +''' +## +# @brief Resolve all nested MACRO references in the macro definitions +# +# @param macro_dir_path Directory where the macro xml files are defined +# @param nested_levels Maximum nested level of macro references expected. +# Giving a bigger number than the actual nested level is harmless. It +# would just add an additional loop +# +# @return array of all the lines containing the macro defintions +''' +def fix_macros (macro_dir_path, nested_levels): + for i in range(nested_levels): + macro_data = load_all_macros (macro_dir_path) + if DBG_FLAG == True: + print "All macros loaded from", macro_dir_path + process_dir (macro_dir_path, macro_data, True) + + return macro_data + +def replace_macros (dirpath, macro_dir_path, nested_levels, debug): + dirpath = dirpath + "/" + macro_dir_path = macro_dir_path + "/" + DBG_FLAG = debug + + print "Resolving nested macro references in", macro_dir_path + macro_data = fix_macros (macro_dir_path, int(nested_levels)) + + if DBG_FLAG == True: + print macro_data + + print "Processing directory:", dirpath + process_dir (dirpath, macro_data, False) + print "Done" + +if __name__ == "__main__": + + if len(sys.argv) == 1 or sys.argv[1] == "--help": + print "Usage:", sys.argv[0], "working-dir macrodir nested-macro-levels [--debug]" + sys.exit(0) + + dirpath = sys.argv[1] + macro_dir_path = sys.argv[2] + nested_levels = sys.argv[3] + + if len(sys.argv) == 5 and sys.argv[4] == "--debug": + debug = True + else: + debug = False + + replace_macros (dirpath, macro_dir_path, nested_levels, debug) diff --git a/src/CLI/clitree/scripts/platform_dummy.xml b/src/CLI/clitree/scripts/platform_dummy.xml new file mode 100644 index 0000000000..8cf798a09f --- /dev/null +++ b/src/CLI/clitree/scripts/platform_dummy.xml @@ -0,0 +1,40 @@ + + + + + + + NOT_SUPPORTED + + + START_PORT_ID + MAX_PORT_ID + START_SUB_PORT_ID + MAX_SUB_PORT_ID + MAX_MTU + + diff --git a/src/CLI/clitree/scripts/sonic-clish.xsd b/src/CLI/clitree/scripts/sonic-clish.xsd new file mode 100644 index 0000000000..bfae8de77f --- /dev/null +++ b/src/CLI/clitree/scripts/sonic-clish.xsd @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CLI/klish/Makefile b/src/CLI/klish/Makefile new file mode 100644 index 0000000000..c71e96aeaa --- /dev/null +++ b/src/CLI/klish/Makefile @@ -0,0 +1,27 @@ +SHELL = /bin/bash +.ONESHELL: +.SHELLFLAGS += -e + +KLISH_VERSION = 2.1.4 +PYTHONVER=2.7 + +KLISH_SRC = $(SONIC_CLI_ROOT)/klish-$(KLISH_VERSION) + +SRC_REPLACEMENTS:=$(shell find patches -type f) +all : $(SRC_REPLACEMENTS) + tar xzvf klish-$(KLISH_VERSION).tgz -C $(SONIC_CLI_ROOT) + ./patches/scripts/patchmake.sh -p VER=${KLISH_VERSION} TSP=${SONIC_CLI_ROOT} DSP=${CURDIR}/patches TWP=${SONIC_CLI_ROOT} + + cd ${KLISH_SRC} && sh autogen.sh && ./configure --with-libxml2=/usr && make + + mkdir -p $(SONIC_CLI_ROOT)/target/.libs + cp $(CURDIR)/clish_start $(SONIC_CLI_ROOT)/target/. + + cp -r ${KLISH_SRC}/bin/.libs/clish ${SONIC_CLI_ROOT}/target/. + cp -r ${KLISH_SRC}/.libs/*.so* ${SONIC_CLI_ROOT}/target/.libs + cp -r ${KLISH_SRC}/.libs/*.a ${SONIC_CLI_ROOT}/target/.libs + @echo "complete klish build" + +.PHONY: clean +clean: + rm -rf ${KLISH_SRC}/klish-$(KLISH_VERSION) diff --git a/src/CLI/klish/clish_start b/src/CLI/klish/clish_start new file mode 100755 index 0000000000..ddf43a7676 --- /dev/null +++ b/src/CLI/klish/clish_start @@ -0,0 +1,11 @@ +#!/bin/bash + +export SONIC_MGMT_ROOT=/usr/sbin +export SONIC_CLI_ROOT=$SONIC_MGMT_ROOT/cli +export SYSTEM_NAME="${HOSTNAME%%.*}" +export PYTHONPATH=$SONIC_MGMT_ROOT:$SONIC_MGMT_ROOT/lib/swagger_client_py:$SONIC_CLI_ROOT:$SONIC_CLI_ROOT/scripts:$PYTHONPATH +export CLISH_PATH=$SONIC_CLI_ROOT/command-tree +export LD_LIBRARY_PATH=/usr/local/lib:$SONIC_CLI_ROOT/.libs:$LD_LIBRARY_PATH +export PATH=$PATH:/usr/local/sbin:/usr/sbin:/sbin:$SONIC_CLI_ROOT + +$SONIC_CLI_ROOT/clish "$@" diff --git a/src/CLI/klish/klish-2.1.4.tgz b/src/CLI/klish/klish-2.1.4.tgz new file mode 100644 index 0000000000..e83e2f8188 Binary files /dev/null and b/src/CLI/klish/klish-2.1.4.tgz differ diff --git a/src/CLI/klish/patches/klish-2.1.4/clish/shell/shell_libxml2.c.diff b/src/CLI/klish/patches/klish-2.1.4/clish/shell/shell_libxml2.c.diff new file mode 100644 index 0000000000..c9bb5cc847 --- /dev/null +++ b/src/CLI/klish/patches/klish-2.1.4/clish/shell/shell_libxml2.c.diff @@ -0,0 +1,102 @@ +diff --git a/clish/shell/shell_libxml2.c b/clish/shell/shell_libxml2.c +index 7acca05..0d51607 100644 +--- a/clish/shell/shell_libxml2.c ++++ b/clish/shell/shell_libxml2.c +@@ -2,7 +2,7 @@ + * ------------------------------------------------------ + * shell_roxml.c + * +- * This file implements the means to read an XML encoded file ++ * This file implements the means to read an XML encoded file + * and populate the CLI tree based on the contents. It implements + * the clish_xml API using roxml + * ------------------------------------------------------ +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include "xmlapi.h" + + #ifdef HAVE_LIB_LIBXSLT +@@ -69,7 +70,7 @@ static inline clish_xmlnode_t *node_to_xmlnode(xmlNode *node) + int clish_xmldoc_start(void) + { + #ifdef HAVE_LIB_LIBXSLT +- /* The XSLT example contain these settings but I doubt ++ /* The XSLT example contain these settings but I doubt + * it's really necessary. + */ + /* xmlSubstituteEntitiesDefault(1); +@@ -91,7 +92,8 @@ int clish_xmldoc_stop(void) + clish_xmldoc_t *clish_xmldoc_read(const char *filename) + { + xmlDoc *doc; +- doc = xmlReadFile(filename, NULL, 0); ++ doc = xmlReadFile(filename, NULL, 1026); ++ xmlXIncludeProcess(doc); + return doc_to_xmldoc(doc); + } + +@@ -131,15 +133,15 @@ int clish_xmlnode_get_type(clish_xmlnode_t *node) + if (node) { + xmlNode *n = xmlnode_to_node(node); + switch (n->type) { +- case XML_ELEMENT_NODE: ++ case XML_ELEMENT_NODE: + return CLISH_XMLNODE_ELM; +- case XML_TEXT_NODE: ++ case XML_TEXT_NODE: + return CLISH_XMLNODE_TEXT; +- case XML_COMMENT_NODE: ++ case XML_COMMENT_NODE: + return CLISH_XMLNODE_COMMENT; +- case XML_PI_NODE: ++ case XML_PI_NODE: + return CLISH_XMLNODE_PI; +- case XML_ATTRIBUTE_NODE: ++ case XML_ATTRIBUTE_NODE: + return CLISH_XMLNODE_ATTR; + default: + break; +@@ -169,7 +171,7 @@ clish_xmlnode_t *clish_xmlnode_parent(clish_xmlnode_t *node) + return NULL; + } + +-clish_xmlnode_t *clish_xmlnode_next_child(clish_xmlnode_t *parent, ++clish_xmlnode_t *clish_xmlnode_next_child(clish_xmlnode_t *parent, + clish_xmlnode_t *curchild) + { + xmlNode *child; +@@ -208,11 +210,11 @@ char *clish_xmlnode_fetch_attr(clish_xmlnode_t *node, + a = a->next; + } + } +- ++ + return NULL; + } + +-int clish_xmlnode_get_content(clish_xmlnode_t *node, char *content, ++int clish_xmlnode_get_content(clish_xmlnode_t *node, char *content, + unsigned int *contentlen) + { + xmlNode *n; +@@ -258,7 +260,7 @@ int clish_xmlnode_get_content(clish_xmlnode_t *node, char *content, + } + } + +-int clish_xmlnode_get_name(clish_xmlnode_t *node, char *name, ++int clish_xmlnode_get_name(clish_xmlnode_t *node, char *name, + unsigned int *namelen) + { + int rlen; +@@ -276,7 +278,7 @@ int clish_xmlnode_get_name(clish_xmlnode_t *node, char *name, + *name = 0; + n = xmlnode_to_node(node); + rlen = strlen((char*)n->name) + 1; +- ++ + if (rlen <= *namelen) { + snprintf(name, *namelen, "%s", (char*)n->name); + name[*namelen - 1] = '\0'; diff --git a/src/CLI/klish/patches/scripts/patchmake.sh b/src/CLI/klish/patches/scripts/patchmake.sh new file mode 100755 index 0000000000..1dddfcaa06 --- /dev/null +++ b/src/CLI/klish/patches/scripts/patchmake.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# +# This script walks through all files in a directory and patches / copies them to the +# requested destination directory. +# If the file name has .diff suffix, it is patched. Otherwise the file is copied + +CLEAN_ALL="no" +CLEAN="no" +PATCH="no" +SKIP_PATCH="no" +MAKE_CLEAN="no" +MAKE_SKIP="no" + +TMP_VAR="" + +echo "## Executing `pwd`/$0" + +pre_exec(){ + if [ -z "$CODE_VER" ]; then + echo "## Specify the klish version in x.x.x format" + exit -1 + fi + TMP_SRC_PATH2="./klish-$CODE_VER" + TMP_SRC_PATH2="./klish-$CODE_VER" + +} + + +while [[ $# -gt 0 ]] +do + opt="$1" + shift + + case $opt in + #Removes the temporary directory and start the process of sync, patch and make. + -c|--clean) + CLEAN="yes" + ;; + + #Removes the temporary directory only and exits. All other options are ignored. + -C|--clean-all) + CLEAN_ALL="yes" + ;; + + #Displays the help for the patchmake.sh script. + -h|--help) + echo -ne "\rVariables:\n" + echo -ne "\rVER - Set the code version\n" + echo -ne "\rDSP - Set the .diff files path\n" + echo -ne "\rTSP - Set the source path to which the code need to extracted\n" + echo -ne "\rTWP - Set the code work path where the source will be copied and patched with .diff files\n" + echo -ne "\r\nOptions:\n" + echo -ne "\r-c, --clean\n\tRemoves the temporary directory for current version and start the process of sync, patch and make.\n\n" + echo -ne "\r-C, --clean-all\n\tRemoves the temporary directory of all version and exits. All other options are ignored.\n\n" + echo -ne "\r-h, --help\n\tDisplays the help for the make.sh script.\n\n" + echo -ne "\r-m, --make-clean\n\tDoes the make for \"clean\" target before building the actual target.\n\n" + echo -ne "\r-M, --skip-make\n\tThe make for the actual target is skipped. Ignored when used along with option -P --skip-patch\n\n." + echo -ne "\r-p, --patch-only\n\tThe script exits after patching the .diff files. Ignored when used along with option -P, --skip-patch.\n\n" + echo -ne "\r-P, --skip-patch\n\tThe patching of the .diff files is alone skipped. Used when required to build the target without any patches.\n\n" + exit 0 + ;; + + #Does the make for "clean" target before building the actual target. + -m|--make-clean) + MAKE_CLEAN="yes" + ;; + + #The make for the actual target is skipped. + -M|--skip-make) + MAKE_SKIP="yes" + ;; + + #The script exits after patching the .diff files. + -p|--patch-only) + PATCH="yes" + ;; + + #The patching of the .diff files is alone skipped. + -P|--skip-patch) + SKIP_PATCH="yes" + ;; + + #The source version to be compiled + VER=[0-9].[0-9].[0-9]) + CODE_VER=`echo $opt | awk -F'=' '{print $2}'` + ;; + + #Temporary source path + TSP=*) + TMP_SRC_PATH=`echo $opt | awk -F'=' '{print $2}'` + ;; + + #Temporary work path + TWP=*) + TMP_PATH=`echo $opt | awk -F'=' '{print $2}'` + ;; + + #Diff files path + DSP=*) + DIFF_SRC_PATH=`echo $opt | awk -F'=' '{print $2}'` + ;; + + #Unknown input + *) + echo "Unknown option or input $opt" + exit -1 + ;; + esac +done + +if [ "$TMP_SRC_PATH" == "" ]; then + echo "Temporary source path 'TSP' not set" + exit -1 +fi +if [ "$TMP_PATH" == "" ]; then + echo "Temporary work path 'TWP' not set" + TWP=${TSP} +fi +if [ "$DIFF_SRC_PATH" == "" ]; then + echo "Diff file(s) path 'DSP' not set" + exit -1 +fi +if [ "$CODE_VER" == "" ]; then + echo "Code version not set" + exit -1 +fi + +#Handling of clean only +if [ "$CLEAN_ALL" == "yes" ]; then + echo "## Removing $TMP_PATH directory" + rm -rf $TMP_PATH + exit 0 +fi + +pre_exec + +#Handling of clean option +if [ "$CLEAN" == "yes" ]; then + echo "## Cleaning the existing files in $TMP_PATH/$TMP_SRC_PATH2" + rm -rf $TMP_PATH/$TMP_SRC_PATH2 +fi + +mkdir -p $TMP_PATH + +#Handling of skipping patch +if [ ! "$SKIP_PATCH" == "yes" ]; then + if [ ! -f "$TMP_PATH/$TMP_SRC_PATH2/##patched##" ]; then + + #Copying the actual source files into the temporary directory + cp -r $TMP_SRC_PATH/$TMP_SRC_PATH2 $TMP_PATH + + #Getting the list of files + echo "## Preparing the .diff file list..." + TMP_VAR=`pwd` + cd $DIFF_SRC_PATH/$TMP_SRC_PATH2 + files=`find . -type f` + cd $TMP_VAR + + #Applying the patch or copying the newly created files + echo "## Applying the patch from $DIFF_SRC_PATH/$TMP_SRC_PATH2" | tee "$TMP_PATH/$TMP_SRC_PATH2/##patchlog##" + for file in $files + do + #Copying the files directly into the temporary source directory if is not a .diff file + TMP_VAR=`dirname $file` + if [ "${file##*.}" != "diff" ]; then + #Creating new directories if doesn't exist already and then copying the files + echo "copying $DIFF_SRC_PATH/$TMP_SRC_PATH2/$file $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR" | tee -a "$TMP_PATH/$TMP_SRC_PATH2/##patchlog##" + test -d "$TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR" || mkdir -p "$TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR" && cp $DIFF_SRC_PATH/$TMP_SRC_PATH2/$file $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR + fi + + #Patching the .diff files + TMP_VAR="${file%.*}" + if [ -f "$TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR" ]; then + patch $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR $DIFF_SRC_PATH/$TMP_SRC_PATH2/$file -o $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR.tmp | tee -a "$TMP_PATH/$TMP_SRC_PATH2/##patchlog##" + echo "moving $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR.tmp -> $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR" | tee -a "$TMP_PATH/$TMP_SRC_PATH2/##patchlog##" + cp $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR.tmp $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR + rm $TMP_PATH/$TMP_SRC_PATH2/$TMP_VAR.tmp + fi + done + touch "$TMP_PATH/$TMP_SRC_PATH2/##patched##" + else + echo "## Patching diff files is skipped -- already patched" + fi + if [ "$PATCH" == "yes" ]; then + exit + fi +else + echo "## Patching diff files is skipped -- user input" +fi + diff --git a/src/CLI/renderer/Makefile b/src/CLI/renderer/Makefile new file mode 100644 index 0000000000..7da49f4995 --- /dev/null +++ b/src/CLI/renderer/Makefile @@ -0,0 +1,6 @@ +.PHONY: install +all: + @echo "nothing to install for now" + +clean: + @echo "nothing to clean for now" diff --git a/src/CLI/renderer/scripts/__init__.py b/src/CLI/renderer/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/CLI/renderer/scripts/acl_jinja2.py b/src/CLI/renderer/scripts/acl_jinja2.py new file mode 100755 index 0000000000..6f7b20f35c --- /dev/null +++ b/src/CLI/renderer/scripts/acl_jinja2.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +from jinja2 import Template +import os +acl_out = {'openconfig_aclacl': {'acl_sets': {'acl_set': [{'acl_entries': {'acl_entry': [{'actions': {'config': {'forwarding_action': 'DROP', + 'log_action': None}, + 'state': {'forwarding_action': 'DROP', + 'log_action': None}}, + 'config': {'description': None, + 'sequence_id': 66}, + 'input_interface': None, + 'ipv4': {'config': {'destination_address': '2.2.2.0/24', + 'dscp': None, + 'hop_limit': None, + 'protocol': '6', + 'source_address': '1.1.1.0/24'}, + 'state': {'destination_address': '2.2.2.0/24', + 'dscp': None, + 'hop_limit': None, + 'protocol': '6', + 'source_address': '1.1.1.0/24'}}, + 'ipv6': None, + 'l2': None, + 'sequence_id': 66, + 'state': {'description': None, + 'matched_octets': 0, + 'matched_packets': 0, + 'sequence_id': 66}, + 'transport': {'config': {'destination_port': '200', + 'source_port': '100', + 'tcp_flags': None}, + 'state': {'destination_port': '200', + 'source_port': '100', + 'tcp_flags': None}}}]}, + 'config': {'description': None, + 'name': 'TEST', + 'type': 'ACL_IPV4'}, + 'name': 'TEST', + 'state': {'description': None, + 'name': 'TEST', + 'type': 'ACL_IPV4'}, + 'type': 'ACL_IPV4'}]}, + 'interfaces': None, + 'state': None}} + + + + +#!/usr/bin/env/python + +from jinja2 import Environment, FileSystemLoader + +# Capture our current directory +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + +def acl_show(): + # Create the jinja2 environment. + # Notice the use of trim_blocks, which greatly helps control whitespace. + j2_env = Environment(loader=FileSystemLoader(THIS_DIR), + trim_blocks=True) + print (j2_env.get_template('acl_show.j2').render(acl_out=acl_out)) + +if __name__ == '__main__': + acl_show() diff --git a/src/CLI/renderer/scripts/render_cli.py b/src/CLI/renderer/scripts/render_cli.py new file mode 100755 index 0000000000..b2bc829904 --- /dev/null +++ b/src/CLI/renderer/scripts/render_cli.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +from jinja2 import Template, Environment, FileSystemLoader +import os +import json +import sys +import gc +import select +from rpipe_utils import pipestr + + +# Capture our current directory +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + +global line_count +global ctrl_rfd + +def render_init(fd): + global ctrlc_rfd + + ctrlc_rd_fd_num = int(fd) + try: + ctrlc_rfd = os.fdopen(ctrlc_rd_fd_num, 'r') + except IOError as e: + sys.stdout.write("Received error : " + str(e)) + gc.collect() + return None + +def cli_getch(): + # Disable canonical mode of input stream + # Set min bytes as 1 and read operation as blocking + fd = sys.stdin.fileno() + c = None + + #global ctrc_rfd + #fds = [fd, ctrlc_rfd] + fds = [fd] + try: + read_fds, write_fds, excep_fds = select.select(fds, [], []) + """ + # Return immediately for Ctrl-C interrupt + if ctrlc_rfd in read_fds: + return 'q' + """ + + c = os.read(fd, 1) + except KeyboardInterrupt: + return 'q' + except select.error as e: + if e[0] == 4: # Interrupted system call + return 'q' + else: + sys.stdout.write("Received error : " + str(e)) + return c + +def _write(string, disable_page=False): + """ + This function would take care of complete pagination logic, + like printing --more--, accepting SPACE, ENTER, q, CTRL-C + and act accordingly + """ + global line_count + + page_len_local = 25 + terminal = sys.stdout + # set length as 0 for prints without pagination + if disable_page is True: + page_len_local = 0 + if len(string) != 0: + terminal.write(string + '\n') + if page_len_local == 0: + return False + line_count = line_count + 1 + if line_count == page_len_local: + terminal.write("--more--") + while 1: + terminal.flush() + c = cli_getch() + terminal.flush() + # End of text (ascii value 3) is returned while pressing Ctrl-C + # key when CLISH executes commands from non-TTY + # Example : clish_source plugin + if c == 'q' or ord(c) == 3: + terminal.write('\x1b[2K'+'\x1b[0G') + line_count = 0 + #self.is_stopped = True + return True + elif c == ' ': + line_count = 0 + terminal.write('\x1b[2K'+'\x1b[0G') + break + # Carriage return (\r) is returned while pressing ENTER + # key when CLISH executes commands from non-TTY + # Example : clish_source plugin + elif c == '\n' or c == '\r': + #line_count = page_len_local - 1 + line_count = 0 + terminal.write('\x1b[2K'+'\x1b[0G') + terminal.flush() + break + return False + +def write(t_str): + global line_count + line_count = 0 + q = False + + render_init(0) + if t_str != "": + pipelst = pipestr().read(); + for s_str in t_str.split('\n'): + if pipelst: + if pipelst.process_pipes(s_str): + q = _write(s_str, pipelst.is_page_disabled()) + else: + q = _write(s_str) + if q: + break + + +def show_cli_output(template_file, response): + # Create the jinja2 environment. + # Notice the use of trim_blocks, which greatly helps control whitespace. + + template_path = os.path.abspath(os.path.join(THIS_DIR, "../render-templates")) + + j2_env = Environment(loader=FileSystemLoader(template_path),extensions=['jinja2.ext.do']) + j2_env.trim_blocks = True + j2_env.lstrip_blocks = True + j2_env.rstrip_blocks = True + + if response: + t_str = (j2_env.get_template(template_file).render(json_output=response)) + write(t_str) diff --git a/src/CLI/renderer/scripts/rpipe_utils.py b/src/CLI/renderer/scripts/rpipe_utils.py new file mode 100644 index 0000000000..d2377c536f --- /dev/null +++ b/src/CLI/renderer/scripts/rpipe_utils.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python + +import re +import os +from time import gmtime, strftime + +class pipestr: + """ + pipestr class + For passing the pipestr from the actioner to the renderer + """ + def __init__(self): + pass + + def write(self, argv): + pipe_str = '' + has_pipe = False + for arg in argv: + if has_pipe: + pipe_str += (arg + ' ') + if arg == '|': + has_pipe = True + f = open("pipestr.txt", "w") + if len(pipe_str) > 0: + pipe_str = pipe_str[:-1] + f.write(pipe_str) + f.close() + + def read(self): + pipe_lst = pipelst() + f = open('pipestr.txt', "r") + pipe_str = f.readline() + f.close() + if len(pipe_str) > 0: + if pipe_lst.build_pipes(pipe_str) != 0: + print("error bulding pipe") + return None + return pipe_lst + +class pipelst: + """ + pipelst class + """ + def __init__(self): + # List of pipe objects corresponds to pipe string + self.pipes = [] + self.disable_page = False + + ## + # @brief Preprocess the pipe string and build the pipe objects list + # for later use + # + # @param pipe_str The string following the '|' symbol in the command line + # + # @return None + def build_pipes(self, pipe_str): + """validate pipe string and build pipe objects""" + splitlist = [] + pipe_obj = None + + if pipe_str is None: + return 0 + + # 'save skip-header' is internally triggered for 'show diff' + #if not pipe_str.startswith("save ") and show_batch_obj_g.is_obj_set(): + # if -1 == show_batch_obj_g.pipe_action(pipe_str): + # return -1 + # pipe_str = pipe_str + show_batch_obj_g.get_pipe_str() + + # Check for 'no-more' and disble pagination + if "no-more" in pipe_str: + self.disable_page = True + + splitlist = [x.strip() for x in pipe_str.split(" | ")] + for cmd in splitlist: + tmplist = cmd.split(' ', 1) + if len(tmplist) > 1: + match_str = tmplist[1].lstrip() + match_str = re.sub(r'^"|"$', '', match_str) + else: + continue + + pipe_cmd = tmplist[0].lower() + if pipe_cmd == "grep": + try : + pipe_obj = rpipe_grep(match_str) + except : + return -1 + elif pipe_cmd == "except": + try : + pipe_obj = rpipe_except(match_str) + except : + return -1 + elif pipe_cmd == "find": + try : + pipe_obj = rpipe_find(match_str) + except : + return -1 + elif pipe_cmd == "save": + # Check additional options + skip_header = False + write_mode = 'w' + file_name = match_str + if ' ' in match_str: + match_str_parts = match_str.split(' ') + file_name = match_str_parts[0] + save_option = match_str_parts[1].lower() + # skip-header is used for 'show diff' + if save_option == "skip-header": + skip_header = True + elif save_option == "append": + write_mode = 'a' + try : + pipe_obj = rpipe_save(file_name, write_mode, pipe_str, skip_header) + except : + return -1 + else: + pass + + if pipe_obj != None : + self.pipes.append(pipe_obj) + + return 0 + + ## + # @brief process the pipe objects list against the string + # + # @param string to be processed + # + # @return True/False + def process_pipes(self, string): + """process pipe objects against the string""" + pipe_result = False + print_content = True + + pipe_list = list(self.pipes) + for pipeobj in pipe_list: + pipe_result = pipeobj.pipe_action(string) + if pipe_result == False: + print_content = False + break + # Remove the pipe if needed (for find) + if pipeobj.can_be_removed() == True: + self.pipes.remove(pipeobj) + # Get the status whether can be printed or not + # For save, console print is not necessary + print_content = pipeobj.can_be_printed() + + return print_content + + ## + # @brief destroy the pipe objects + # + # @return None + def destroy_pipes(self): + """destroys pipe objects""" + self.pipes = [] + # enable pagination + self.disable_page = False + return + + ## + # @brief print the pipe objects + # + # @return None + def print_pipes(self): + """dump pipe objects""" + for pipeobj in self.pipes: + print pipeobj + return + + def is_page_disabled(self): + """returns the status of pagination enabled/disabled""" + return self.disable_page + + def __del__(self): + self.destroy_pipes() + +class rpipe_grep: + """ + grep wrapper class + """ + def __init__(self, match_str): + self.remove_pipe = False + self.print_content = True + self.pipe_str = "grep " + match_str + flags = None + if match_str.endswith(" ignore-case"): + flags = re.I + match_str = match_str.rsplit(' ', 1)[0] + try : + if flags is None: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)') + else: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)', flags) + except Exception, error : + print '%Error: ' + str(error) + raise + + def pipe_action(self, string): + if self.regexp.search(string) != None: + return True + else: + return False + + def can_be_removed(self): + return self.remove_pipe + + def can_be_printed(self): + return self.print_content + + def get_pipe_str(self): + return self.pipe_str + + def __del__(self): + self.regexp = "" + +class rpipe_except: + """ + except wrapper class + """ + def __init__(self, match_str): + self.remove_pipe = False + self.print_content = True + self.pipe_str = "except " + match_str + flags = None + if match_str.endswith(" ignore-case"): + flags = re.I + match_str = match_str.rsplit(' ', 1)[0] + try : + if flags is None: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)') + else: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)', flags) + except Exception, error : + print '%Error: ' + str(error) + raise + + def pipe_action(self, string): + if self.regexp.search(string) == None: + return True + else: + return False + + def can_be_removed(self): + return self.remove_pipe + + def can_be_printed(self): + return self.print_content + + def get_pipe_str(self): + return self.pipe_str + + def __del__(self): + self.regexp = "" + +class rpipe_find: + """ + find wrapper class + """ + def __init__(self, match_str): + self.remove_pipe = True + self.print_content = True + self.pipe_str = "find " + match_str + flags = None + if match_str.endswith(" ignore-case"): + flags = re.I + match_str = match_str.rsplit(' ', 1)[0] + try : + if flags is None: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)') + else: + self.regexp = re.compile(r'(.*?)' + match_str + '(.*?)', flags) + except Exception, error : + print '%Error: ' + str(error) + raise + + def pipe_action(self, string): + if self.regexp.search(string) != None: + return True + else: + return False + + def can_be_removed(self): + return self.remove_pipe + + def can_be_printed(self): + return self.print_content + + def get_pipe_str(self): + return self.pipe_str + + def __del__(self): + self.regexp = "" + +class rpipe_save: + """ + save wrapper class + """ + def __init__(self, file_path, file_mode, cmd_str, skip_header=False): + self.remove_pipe = False + self.print_content = False + self.pipe_str = "save " + file_path + self.fd = None + if os.path.isabs(file_path) is True: + if os.path.exists(file_path) is True: + file_dir = os.path.dirname(file_path) + file_name = os.path.basename(file_path) + if file_name != "": + try: + self.fd = open(file_path, file_mode) + except IOError: + print 'Error: cannot create regular file ', \ + '%s : No such file or Directory' % file_path + else: + print "File name not present in %s" % file_path + else: + file_dir = os.path.dirname(file_path) + file_name = os.path.basename(file_path) + if os.path.isdir(file_dir) is True: + try: + self.fd = open(file_path, file_mode) + except IOError: + print 'Error: cannot create regular file ', \ + '%s : No such file or Directory' % file_path + else: + print '%s is not a Valid filepath' % file_path + else: + # For relative path, store the result in user's home + file_path = os.path.expanduser('~') + '/' + file_path + try: + self.fd = open(file_path, file_mode) + except IOError: + print 'Error: cannot create regular file ', \ + '%s : No such file or Directory' % file_path + # Save computed file name for future reference + self.file_path = file_path + # Write header in file + if skip_header is False: + self.write_header(cmd_str) + + def pipe_action(self, string): + # Print error when fd is not valid due to some reasons + if self.fd == None: + return False + + try: + if len(string) != 0: + self.fd.write(string + '\n') + self.fd.flush() + except IOError: + print 'Error: Write into file %s failed' % self.file_path + self.fd.close() + return True + + def can_be_removed(self): + return self.remove_pipe + + def can_be_printed(self): + return self.print_content + + def get_pipe_str(self): + return self.pipe_str + + def write_header(self, cmd_str): + if self.fd != None: + try: + self.fd.write('\n' + "! ===================================" + + "=====================================" + '\n' + + "! Started saving show command output at " + + strftime("%d/%m, %Y, %H:%M:%S", gmtime()) + + " for command:" + '\n! ' + cmd_str + '\n' + + "! ===================================" + + "=====================================" + '\n') + except IOError: + print 'Error: Write into file %s failed' % self.file_path + self.fd.close() + + def __del__(self): + if self.fd != None: + self.fd.close() + diff --git a/src/CLI/renderer/templates/acl_show.j2 b/src/CLI/renderer/templates/acl_show.j2 new file mode 100644 index 0000000000..3a70dedef9 --- /dev/null +++ b/src/CLI/renderer/templates/acl_show.j2 @@ -0,0 +1,4 @@ +{% set acl_sets = acl_out['openconfig_aclacl']['acl_sets']['acl_set'] %} + {% for acl_set in acl_sets %} + Name: {{ acl_set['state']['description'] }} + {% endfor %} diff --git a/src/CLI/renderer/templates/lldp_neighbor_show.j2 b/src/CLI/renderer/templates/lldp_neighbor_show.j2 new file mode 100755 index 0000000000..16c5134783 --- /dev/null +++ b/src/CLI/renderer/templates/lldp_neighbor_show.j2 @@ -0,0 +1,29 @@ +{{'-----------------------------------------------------------'}} +{{'LLDP Neighbors'.ljust(20)}} +{{'-----------------------------------------------------------'}} +{% for neigh in json_output %} +{% set value = neigh['neighbors']['neighbor'][0] %} +{{'Interface:'}}{{' '}}{{value['id']}}{{',via:'}}{{' LLDP'}} +{{' Chassis:'}} +{{' ChassisID: '}}{{value['state']['chassis_id']}} +{{' SysName: '}}{{value['state']['system_name']}} +{% set desc = value['state']['system_description'].split('\r\n')[1:] %} +{{' SysDescr: '}}{{value['state']['system_description'].split('\r\n')[0]}} +{% for v in desc %} +{{' '}}{{v}} +{% endfor %} +{% for cap in value['capabilities']['capability'] %} +{% if cap['state']['enabled'] == true %} +{% set en = 'ON' %} +{% endif %} +{% if cap['state']['enabled'] == false %} +{% set en = 'OFF' %} +{% endif %} +{{' Capability: '}}{{cap['name'].split(':')[1]}}{{', '}}{{en}} +{% endfor %} +{{' Port'}} +{{' PortID: '}}{{value['state']['port_id']}} +{{' PortDescr: '}}{{value['state']['port_description']}} +{{'-----------------------------------------------------------'}} +{% endfor %} + diff --git a/src/CLI/renderer/templates/lldp_show.j2 b/src/CLI/renderer/templates/lldp_show.j2 new file mode 100644 index 0000000000..1f8eaf71d5 --- /dev/null +++ b/src/CLI/renderer/templates/lldp_show.j2 @@ -0,0 +1,18 @@ +{{'------------------------------------------------------------------------------------------------------'}} +{{'LocalPort'.ljust(20)}}{{'RemoteDevice'.ljust(20)}}{{'RemotePortID'.ljust(20)}}{{'Capability'.ljust(20)}} {{'RemotePortDescr'}} +{{'-------------------------------------------------------------------------------------------------------'}} +{% set cap_dict = {'openconfig-lldp-types:REPEATER' : 'O','openconfig-lldp-types:MAC_BRIDGE' : 'B' , 'openconfig-lldp-types:ROUTER' : 'R'} %} +{% for neigh in json_output %} +{% set capabilities = neigh['neighbors']['neighbor'][0]['capabilities']['capability'] %} +{% set cap_list = [] %} +{% for cap in capabilities %} +{% if cap['state']['name'] in cap_dict %} +{% if cap['state']['enabled'] == true %} +{% do cap_list.append(cap_dict[cap['state']['name']]) %} +{% endif %} +{% endif %} +{% endfor %} +{% set value = neigh['neighbors']['neighbor'][0] %} +{{value['id'].ljust(20)}}{{value['state']['system_name'].ljust(20)}}{{value['state']['port_id'].ljust(20)}}{{(cap_list | join() | string).ljust(20)}}{{value['state']['port_description'].ljust(20)}} +{% endfor %} + diff --git a/src/CLI/renderer/templates/platform_show.j2 b/src/CLI/renderer/templates/platform_show.j2 new file mode 100644 index 0000000000..cd18d406ad --- /dev/null +++ b/src/CLI/renderer/templates/platform_show.j2 @@ -0,0 +1,7 @@ +{{'-----------------------------------------------------------'}} +{{'Attribute'.ljust(20)}} {{'Value/State'}} +{{'-----------------------------------------------------------'}} +{% for key,value in json_output.items() %} +{{key.ljust(20)}}:{{value}} +{% endfor %} + diff --git a/src/CLI/renderer/templates/show_access_group.j2 b/src/CLI/renderer/templates/show_access_group.j2 new file mode 100644 index 0000000000..a51bc8aa16 --- /dev/null +++ b/src/CLI/renderer/templates/show_access_group.j2 @@ -0,0 +1,40 @@ +{% if json_output %} +{% for key in json_output %} + {# This condition checks if the JSON response has data from the acl/interface list #} + {% if "interface" in key %} + {% for interface in json_output[key] %} + {% set if_id = interface["id"] %} + {% if interface["ingress_acl_sets"] %} + {% set idirection = "ingress" %} + {% endif %} + {% if interface["egress_acl_sets"] %} + {% set edirection = "egress" %} + {% endif %} + {% if idirection %} + {% set ing_acl_sets = idirection + "_acl_sets" %} + {% set ing_acl_set = idirection + "_acl_set" %} + {% set ing_acl_set_list = interface[ing_acl_sets][ing_acl_set] %} + {% for ing_acl_set in ing_acl_set_list %} + {% set i_acl_name = ing_acl_set["set_name"] %} + {% if idirection == "ingress" %} + {% set idirection = "Ingress" %} + {% endif %} + {{- idirection }} IP access-list {{ i_acl_name }} on {{ if_id }} + {% endfor %} + {% endif %} + {% if edirection %} + {% set eg_acl_sets = edirection + "_acl_sets" %} + {% set eg_acl_set = edirection + "_acl_set" %} + {% set eg_acl_set_list = interface[eg_acl_sets][eg_acl_set] %} + {% for eg_acl_set in eg_acl_set_list %} + {% set e_acl_name = eg_acl_set["set_name"] %} + {% if edirection == "egress" %} + {% set edirection = "Egress" %} + {% endif %} + {{- edirection }} IP access-list {{ e_acl_name }} on {{ if_id }} + {% endfor %} + {% endif %} + {% endfor %} + {% endif %} +{% endfor %} +{% endif %} diff --git a/src/CLI/renderer/templates/show_access_list.j2 b/src/CLI/renderer/templates/show_access_list.j2 new file mode 100644 index 0000000000..46c90199a0 --- /dev/null +++ b/src/CLI/renderer/templates/show_access_list.j2 @@ -0,0 +1,74 @@ +{% macro traverse_acl_entry(acl_entry_list) -%} + {% for seq in acl_entry_list %} + {% set response_list = [] %} + {# Get sequence id #} + {% set seqid = seq["sequence_id"] %} + {% set _list = response_list.append( seqid ) %} + {# Get forwarding action #} + {% set fwd_action = seq["actions"]["config"]["forwarding_action"] %} + {%- if "ACCEPT" in fwd_action %} + {% set fwd_action = "permit" %} + {%- endif %} + {%- if "DROP" in fwd_action %} + {% set fwd_action = "deny" %} + {%- endif %} + {% set _list = response_list.append( fwd_action ) %} + {# Get protocol #} + {% set proto = seq["ipv4"]["state"]["protocol"].split(':')[1].split('_')[1]|lower %} + {% set _list = response_list.append( proto ) %} + {# Get Source IP #} + {% set src_ip = seq["ipv4"]["state"]["source_address"] %} + {% set _list = response_list.append( src_ip ) %} + {# include src port number if available #} + {%- if seq["transport"] %} + {%- if seq["transport"]["config"]["source_port"] %} + {% set src_port = "eq " + seq["transport"]["config"]["source_port"] %} + {% set _list = response_list.append( src_port ) %} + {%- endif %} + {%- endif %} + {# Get Destination IP #} + {% set dstn_ip = seq["ipv4"]["state"]["destination_address"] %} + {% set _list = response_list.append( dstn_ip ) %} + {# include dstn port number if available #} + {%- if seq["transport"] %} + {%- if seq["transport"]["config"]["destination_port"] %} + {% set dstn_port = "eq " + seq["transport"]["config"]["destination_port"] %} + {% set _list = response_list.append( dstn_port ) %} + {%- endif %} + {%- if seq["transport"]["config"]["tcp_flags"] %} + {% for var in seq["transport"]["config"]["tcp_flags"] %} + {% set flag = var.split(':')[1].split('_')[1]|lower %} + {% set _list = response_list.append( flag ) %} + {% endfor %} + {%- endif %} + {%- endif %} + {%- if seq["ipv4"]["state"]["dscp"] %} + {% set _list = response_list.append( "dscp "+seq["ipv4"]["state"]["dscp"]|string ) %} + {%- endif %} + {{- " " }} {{ response_list|join(' ') }} + {% endfor %} +{%- endmacro %} +{% for key in json_output %} + {# This condition checks if the JSON response has data from the acl-entry list #} + {% if "acl_entry" in key -%} + {% set acl_entry = json_output[key] -%} + {{ traverse_acl_entry(acl_entry) }} + {%- endif %} +{% endfor %} +{% for acl_sets in json_output -%} + {% if "acl_set" in acl_sets -%} + {# This condition checks if the JSON response has data from the acl-sets container output -#} + {% for acl_set in json_output[acl_sets] %} + {% if acl_set["state"] -%} + ip access-list {{ acl_set["state"]["name"] }} + {% set acl_entry_list = acl_set["acl_entries"] %} + {% if acl_entry_list -%} + {% for each in acl_entry_list -%} + {% set acl_entry = acl_entry_list[each] -%} + {{ traverse_acl_entry(acl_entry) }} + {%- endfor %} + {%- endif %} + {%- endif %} + {% endfor %} + {%- endif %} +{%- endfor %} diff --git a/src/CLI/renderer/templates/show_interface.j2 b/src/CLI/renderer/templates/show_interface.j2 new file mode 100644 index 0000000000..8e50d6fc74 --- /dev/null +++ b/src/CLI/renderer/templates/show_interface.j2 @@ -0,0 +1,125 @@ +{% set vars = {'ipv4': ""} %} +{% set vars = {'ipv6': ""} %} +{% set vars = {'name': ""} %} +{% set vars = {'admin_state': ""} %} +{% set vars = {'oper_state': ""} %} +{% set vars = {'index': ""} %} +{% set vars = {'description':""} %} +{% set vars = {'mtu': ""} %} +{% set vars = {'ipv4_src_pfx': ""} %} +{% set vars = {'ipv6_src_pfx': ""} %} +{% set vars = {'mode4': "not-set"} %} +{% set vars = {'mode6': "not-set"} %} +{% set vars = {'speed': ""} %} +{% set vars = {'in_pkts':""} %} +{% set vars = {'in_octets':""} %} +{% set vars = {'in_multi':""} %} +{% set vars = {'in_broad':""} %} +{% set vars = {'in_uni':""} %} +{% set vars = {'in_errors':""} %} +{% set vars = {'in_discards':""} %} +{% set vars = {'out_pkts':""} %} +{% set vars = {'out_octets':""} %} +{% set vars = {'out_multi':""} %} +{% set vars = {'out_broad':""} %} +{% set vars = {'out_uni':""} %} +{% set vars = {'out_errors':""} %} +{% set vars = {'out_discards':""} %} +{% if json_output -%} +{% for key_json in json_output %} +{% set interface_list = json_output[key_json]["interface"] %} +{% for interface in interface_list %} + {% for key in interface %} + {% if "ethernet" in key %} + {% if vars.update({'speed':interface[key]["state"]["port-speed"]|replace("openconfig-if-ethernet:SPEED_", "")}) %}{% endif %} + {% endif %} + {% if "state" in key %} + {% if vars.update({'name':interface[key]["name"]}) %}{% endif %} + {% if vars.update({'admin_state':interface[key]["admin-status"]}) %}{% endif %} + {% if vars.update({'oper_state':interface[key]["oper-status"]}) %}{% endif %} + {% if vars.update({'index':interface[key]["ifindex"]}) %}{% endif %} + {% if vars.update({'description':interface[key]["description"]}) %}{% endif %} + {% if vars.update({'mtu':interface[key]["mtu"]}) %}{% endif %} + {% endif %} + {% if "counters" in interface["state"] %} + {% if vars.update({'in_pkts':interface["state"]["counters"]["in-pkts"]}) %}{% endif %} + {% if vars.update({'in_octets':interface["state"]["counters"]["in-octets"]}) %}{% endif %} + {% if vars.update({'in_multi':interface["state"]["counters"]["in-multicast-pkts"]}) %}{% endif %} + {% if vars.update({'in_broad':interface["state"]["counters"]["in-broadcast-pkts"]}) %}{% endif %} + {% if vars.update({'in_uni':interface["state"]["counters"]["in-unicast-pkts"]}) %}{% endif %} + {% if vars.update({'in_errors':interface["state"]["counters"]["in-errors"]}) %}{% endif %} + {% if vars.update({'in_discards':interface["state"]["counters"]["in-discards"]}) %}{% endif %} + {% if vars.update({'out_pkts':interface["state"]["counters"]["out-pkts"]}) %}{% endif %} + {% if vars.update({'out_octets':interface["state"]["counters"]["out-octets"]}) %}{% endif %} + {% if vars.update({'out_multi':interface["state"]["counters"]["out-multicast-pkts"]}) %}{% endif %} + {% if vars.update({'out_broad':interface["state"]["counters"]["out-broadcast-pkts"]}) %}{% endif %} + {% if vars.update({'out_uni':interface["state"]["counters"]["out-unicast-pkts"]}) %}{% endif %} + {% if vars.update({'out_errors':interface["state"]["counters"]["in-errors"]}) %}{% endif %} + {% if vars.update({'out_discards':interface["state"]["counters"]["out-discards"]}) %}{% endif %} + {% endif %} + {% if "subinterfaces" in key %} + {% for subinterface in interface[key] %} + {% set subif_list = interface[key][subinterface] %} + {% for subif in subif_list %} + {% if vars.update({'ipv4':"IPV4"}) %}{% endif %} + {% if vars.update({'ipv6':"IPV6"}) %}{% endif %} + {% if subif["openconfig-if-ip:ipv4"] %} + {% set ip_list = subif["openconfig-if-ip:ipv4"]["addresses"]["address"] %} + {% set ip_all = [] %} + {% for ip in ip_list %} + {% set ipfx = ip["state"]["ip"] + "/" + ip["state"]["prefix-length"]|string() %} + {{ ip_all.append(ipfx)|default("", True)}} + {% if vars.update({'mode4':"MANUAL"}) %}{% endif %} + {% endfor %} + {% if vars.update({'ipv4_src_pfx':ip_all|join(',')}) %}{% endif %} + {% else %} + {% if vars.update({'ipv4_src_pfx':""}) %}{% endif %} + {% if vars.update({'mode4':"not-set"}) %}{% endif %} + {% endif %} + {% if subif["openconfig-if-ip:ipv6"] %} + {% set ip_list = subif["openconfig-if-ip:ipv6"]["addresses"]["address"] %} + {% set ipv6_all = [] %} + {% for ip in ip_list %} + {% set ipfx = ip["state"]["ip"] + "/" + ip["state"]["prefix-length"]|string() %} + {{ ipv6_all.append(ipfx)|default("", True)}} + {% if vars.update({'mode6':"MANUAL"}) %}{% endif %} + {% endfor %} + {% if vars.update({'ipv6_src_pfx':ipv6_all|join(',')}) %}{% endif %} + {% else %} + {% if vars.update({'ipv6_src_pfx':""}) %}{% endif %} + {% if vars.update({'mode6':"not-set"}) %}{% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {% endif %} + {% endfor %} +{% if 'Ethernet' in vars.name %} +{{ vars.name }} is {{ vars.admin_state|lower() }}, line protocol is {{vars.oper_state|lower() }} +Hardware is Eth +Interface index is {{ vars.index }} +{% if vars.description %} +Description: {{ vars.description }} +{% endif %} +{% if vars.ipv4_src_pfx %} +{{ vars.ipv4 }} address is {{ vars.ipv4_src_pfx }} +{% endif %} +Mode of {{ vars.ipv4 }} address assignment: {{ vars.mode4 }} +{% if vars.ipv6_src_pfx %} +{{ vars.ipv6 }} address is {{ vars.ipv6_src_pfx }} +{% endif %} +Mode of {{ vars.ipv6 }} address assignment: {{ vars.mode6 }} +IP MTU {{ vars.mtu }} bytes +LineSpeed {{ vars.speed }}, Auto-negotiation off +Input statistics: + {{vars.in_pkts}} packets, {{vars.in_octets}} octets + {{vars.in_multi}} Multicasts, {{vars.in_broad}} Broadcasts, {{vars.in_uni}} Unicasts + {{vars.in_errors}} error, {{vars.in_discards}} discarded +Output statistics: + {{vars.out_pkts}} packets, {{vars.out_octets}} octets + {{vars.out_multi}} Multicasts, {{vars.out_broad}} Broadcasts, {{vars.out_uni}} Unicasts + {{vars.out_errors}} error, {{vars.out_discards}} discarded + +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} diff --git a/src/CLI/renderer/templates/show_interface_counters.j2 b/src/CLI/renderer/templates/show_interface_counters.j2 new file mode 100644 index 0000000000..0abdbce778 --- /dev/null +++ b/src/CLI/renderer/templates/show_interface_counters.j2 @@ -0,0 +1,39 @@ +{% set vars = {'name': ""} %} +{% set vars = {'oper_state': ""} %} +{% set vars = {'in_packets': ""} %} +{% set vars = {'in_errors': ""} %} +{% set vars = {'in_discards': ""} %} +{% set vars = {'out_packets': ""} %} +{% set vars = {'out_errors': ""} %} +{% set vars = {'out_discards': ""} %} +{% if json_output -%} +------------------------------------------------------------------------------------------------ +{{'%-15s'|format("Interface")}}{{'%-10s'|format("State")}}{{'%-10s'|format("RX_OK")}}{{'%-10s'|format("RX_ERR")}}{{'%-10s'|format("RX_DRP")}}{{'%-10s'|format("TX_OK")}}{{'%-10s'|format("TX_ERR")}}{{'%-10s'|format("TX_DRP")}} +------------------------------------------------------------------------------------------------ +{% for key_json in json_output %} +{% set interface_list = json_output[key_json]["interface"] %} +{% for interface in interface_list %} + {% for key in interface %} + {% if "state" in key %} + {% if vars.update({'name':interface[key]["name"]}) %}{% endif %} + {% if interface[key]["oper-status"] =='DOWN' %} + {% if vars.update({'oper_state':'D'}) %}{% endif %} + {% else %} + {% if vars.update({'oper_state':'U'}) %}{% endif %} + {% endif %} + {% endif %} + {% if "counters" in interface["state"] %} + {% if vars.update({'in_packets':interface["state"]["counters"]["in-pkts"]}) %}{% endif %} + {% if vars.update({'in_errors':interface["state"]["counters"]["in-errors"]}) %}{% endif %} + {% if vars.update({'in_discards':interface["state"]["counters"]["in-discards"]}) %}{% endif %} + {% if vars.update({'out_packets':interface["state"]["counters"]["out-pkts"]}) %}{% endif %} + {% if vars.update({'out_errors':interface["state"]["counters"]["out-errors"]}) %}{% endif %} + {% if vars.update({'out_discards':interface["state"]["counters"]["out-discards"]}) %}{% endif %} + {% endif %} + {% endfor %} +{% if 'Ethernet' in vars.name %} +{{'%-15s'|format(vars.name)}}{{'%-10s'|format(vars.oper_state)}}{{'%-10s'|format(vars.in_packets)}}{{'%-10s'|format(vars.in_errors)}}{{'%-10s'|format(vars.in_discards)}}{{'%-10s'|format(vars.out_packets)}}{{'%-10s'|format(vars.out_errors)}}{{'%-10s'|format(vars.out_discards)}} +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} diff --git a/src/CLI/renderer/templates/show_interface_id.j2 b/src/CLI/renderer/templates/show_interface_id.j2 new file mode 100644 index 0000000000..b4b4b32eed --- /dev/null +++ b/src/CLI/renderer/templates/show_interface_id.j2 @@ -0,0 +1,121 @@ +{% set vars = {'ipv4': ""} %} +{% set vars = {'ipv6': ""} %} +{% set vars = {'name': ""} %} +{% set vars = {'admin_state': ""} %} +{% set vars = {'oper_state': ""} %} +{% set vars = {'index': ""} %} +{% set vars = {'description':""} %} +{% set vars = {'mtu': ""} %} +{% set vars = {'ipv4_src_pfx': ""} %} +{% set vars = {'ipv6_src_pfx': ""} %} +{% set vars = {'mode4': "not-set"} %} +{% set vars = {'mode6': "not-set"} %} +{% set vars = {'speed': ""} %} +{% set vars = {'in_pkts':""} %} +{% set vars = {'in_octets':""} %} +{% set vars = {'in_multi':""} %} +{% set vars = {'in_broad':""} %} +{% set vars = {'in_uni':""} %} +{% set vars = {'in_errors':""} %} +{% set vars = {'in_discards':""} %} +{% set vars = {'out_pkts':""} %} +{% set vars = {'out_octets':""} %} +{% set vars = {'out_multi':""} %} +{% set vars = {'out_broad':""} %} +{% set vars = {'out_uni':""} %} +{% set vars = {'out_errors':""} %} +{% set vars = {'out_discards':""} %} +{% if json_output -%} +{% for interfaces in json_output %} +{% set interface_list = json_output[interfaces] %} +{% for interface in interface_list %} + {% for key in interface %} + {% if "ethernet" in key %} + {% if vars.update({'speed':interface[key]["state"]["port-speed"]|replace("openconfig-if-ethernet:SPEED_", "")}) %}{% endif %} + {% endif %} + {% if "state" in key %} + {% if vars.update({'name':interface[key]["name"]}) %}{% endif %} + {% if vars.update({'admin_state':interface[key]["admin-status"]}) %}{% endif %} + {% if vars.update({'oper_state':interface[key]["oper-status"]}) %}{% endif %} + {% if vars.update({'index':interface[key]["ifindex"]}) %}{% endif %} + {% if vars.update({'description':interface[key]["description"]}) %}{% endif %} + {% if vars.update({'mtu':interface[key]["mtu"]}) %}{% endif %} + {% if vars.update({'in_pkts':interface[key]["counters"]["in-pkts"]}) %}{% endif %} + {% if vars.update({'in_octets':interface[key]["counters"]["in-octets"]}) %}{% endif %} + {% if vars.update({'in_multi':interface[key]["counters"]["in-multicast-pkts"]}) %}{% endif %} + {% if vars.update({'in_broad':interface[key]["counters"]["in-broadcast-pkts"]}) %}{% endif %} + {% if vars.update({'in_uni':interface[key]["counters"]["in-unicast-pkts"]}) %}{% endif %} + {% if vars.update({'in_errors':interface[key]["counters"]["in-errors"]}) %}{% endif %} + {% if vars.update({'in_discards':interface[key]["counters"]["in-discards"]}) %}{% endif %} + {% if vars.update({'out_pkts':interface[key]["counters"]["out-pkts"]}) %}{% endif %} + {% if vars.update({'out_octets':interface[key]["counters"]["out-octets"]}) %}{% endif %} + {% if vars.update({'out_multi':interface[key]["counters"]["out-multicast-pkts"]}) %}{% endif %} + {% if vars.update({'out_broad':interface[key]["counters"]["out-broadcast-pkts"]}) %}{% endif %} + {% if vars.update({'out_uni':interface[key]["counters"]["out-unicast-pkts"]}) %}{% endif %} + {% if vars.update({'out_errors':interface[key]["counters"]["in-errors"]}) %}{% endif %} + {% if vars.update({'out_discards':interface[key]["counters"]["out-discards"]}) %}{% endif %} + {% endif %} + {% if "subinterfaces" in key %} + {% for subinterface in interface[key] %} + {% set subif_list = interface[key][subinterface] %} + {% for subif in subif_list %} + {% if vars.update({'ipv4':"IPV4"}) %}{% endif %} + {% if vars.update({'ipv6':"IPV6"}) %}{% endif %} + {% if subif["openconfig-if-ip:ipv4"] %} + {% set ip_list = subif["openconfig-if-ip:ipv4"]["addresses"]["address"] %} + {% set ip_all = [] %} + {% for ip in ip_list %} + {% set ipfx = ip["state"]["ip"] + "/" + ip["state"]["prefix-length"]|string() %} + {{ ip_all.append(ipfx)|default("", True)}} + {% if vars.update({'mode4':"MANUAL"}) %}{% endif %} + {% endfor %} + {% if vars.update({'ipv4_src_pfx':ip_all|join(',')}) %}{% endif %} + {% else %} + {% if vars.update({'mode4':"not-set"}) %}{% endif %} + {% endif %} + {% if subif["openconfig-if-ip:ipv6"] %} + {% set ip_list = subif["openconfig-if-ip:ipv6"]["addresses"]["address"] %} + {% set ipv6_all = [] %} + {% for ip in ip_list %} + {% set ipfx = ip["state"]["ip"] + "/" + ip["state"]["prefix-length"]|string() %} + {{ ipv6_all.append(ipfx)|default("", True)}} + {% if vars.update({'mode6':"MANUAL"}) %}{% endif %} + {% endfor %} + {% if vars.update({'ipv6_src_pfx':ipv6_all|join(',')}) %}{% endif %} + {% else %} + {% if vars.update({'mode6':"not-set"}) %}{% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {% endif %} + {% endfor %} +{% if vars.name %} +{{ vars.name }} is {{ vars.admin_state|lower() }}, line protocol is {{vars.oper_state|lower() }} +Hardware is Eth + +Interface index is {{ vars.index }} +{% if vars.description %} +Description: {{ vars.description }} +{% endif %} +{% if vars.ipv4_src_pfx %} +{{ vars.ipv4 }} address is {{ vars.ipv4_src_pfx }} +{% endif %} +Mode of {{ vars.ipv4 }} address assignment: {{ vars.mode4 }} +{% if vars.ipv6_src_pfx %} +{{ vars.ipv6 }} address is {{ vars.ipv6_src_pfx }} +{% endif %} +Mode of {{ vars.ipv6 }} address assignment: {{ vars.mode6 }} +IP MTU {{ vars.mtu }} bytes +LineSpeed {{ vars.speed }}, Auto-negotiation off +Input statistics: + {{vars.in_pkts}} packets, {{vars.in_octets}} octets + {{vars.in_multi}} Multicasts, {{vars.in_broad}} Broadcasts, {{vars.in_uni}} Unicasts + {{vars.in_errors}} error, {{vars.in_discards}} discarded +Output statistics: + {{vars.out_pkts}} packets, {{vars.out_octets}} octets + {{vars.out_multi}} Multicasts, {{vars.out_broad}} Broadcasts, {{vars.out_uni}} Unicasts + {{vars.out_errors}} error, {{vars.out_discards}} discarded +{%- endif %} +{% endfor %} +{% endfor %} +{% endif %} diff --git a/src/CLI/renderer/templates/show_interface_status.j2 b/src/CLI/renderer/templates/show_interface_status.j2 new file mode 100644 index 0000000000..c706bd53cd --- /dev/null +++ b/src/CLI/renderer/templates/show_interface_status.j2 @@ -0,0 +1,36 @@ +{% set vars = {'name': ""} %} +{% set vars = {'admin_state': ""} %} +{% set vars = {'oper_state': ""} %} +{% set vars = {'description': ""} %} +{% set vars = {'mtu': ""} %} +{% set vars = {'speed': ""} %} +{% if json_output -%} +------------------------------------------------------------------------------------------ +{{'%-20s'|format("Name")}}{{'%-20s'|format("Description")}}{{'%-15s'|format("Admin")}}{{'%-15s'|format("Oper")}}{{'%-15s'|format("Speed")}}{{'%-15s'|format("MTU")}} +------------------------------------------------------------------------------------------ +{% for key_json in json_output %} +{% set interface_list = json_output[key_json]["interface"] %} +{% for interface in interface_list %} + {% for key in interface %} + {% if "ethernet" in key %} + {% if vars.update({'speed':interface[key]["state"]["port-speed"]|replace("openconfig-if-ethernet:SPEED_", "")}) %}{% endif %} + {% endif %} + {% if "state" in key %} + {% if vars.update({'name':interface[key]["name"]}) %}{% endif %} + {% if vars.update({'admin_state':interface[key]["admin-status"]}) %}{% endif %} + {% if vars.update({'oper_state':interface[key]["oper-status"]}) %}{% endif %} + {% if vars.update({'mtu':interface[key]["mtu"]}) %}{% endif %} + {% if interface[key]["description"] != "" %} + {% if vars.update({'description':interface[key]["description"]}) %}{% endif %} + {%else %} + {% if vars.update({'description':"-"}) %}{% endif %} + {% endif %} + {% endif %} + {% endfor %} +{% if 'Ethernet' in vars.name %} +{{'%-20s'|format(vars.name)}}{{'%-20s'|format(vars.description)}}{{'%-15s'|format(vars.admin_state|lower())}}{{'%-15s'|format(vars.oper_state|lower())}}{{'%-15s'|format(vars.speed)}}{{'%-15s'|format(vars.mtu)}} +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} + diff --git a/src/CLI/renderer/templates/system_cpu_show.j2 b/src/CLI/renderer/templates/system_cpu_show.j2 new file mode 100644 index 0000000000..61028dbd45 --- /dev/null +++ b/src/CLI/renderer/templates/system_cpu_show.j2 @@ -0,0 +1,12 @@ +{{'----------------------------------------------------------------------'}} +{{'CPU'.ljust(20)}}{{'%KERNEL'.ljust(20)}}{{'%USER'.ljust(20)}}{{'%IDLE'}} +{{'----------------------------------------------------------------------'}} +{% for cpu in json_output %} + {% if (cpu['index'] != '0') %} + {% set index = (cpu['index'] | string) %} + {% else %} + {% set index = (('total') | string) %} + {% endif %} +{{('CPU-'+ index).ljust(20)}} {{(cpu['state']['kernel']['instant'] | string).ljust(20)}} {{(cpu['state']['user']['instant'] | string).ljust(20)}} {{(cpu['state']['idle']['instant'] | string)}} +{% endfor %} + diff --git a/src/CLI/renderer/templates/system_processes_show.j2 b/src/CLI/renderer/templates/system_processes_show.j2 new file mode 100755 index 0000000000..c3960df150 --- /dev/null +++ b/src/CLI/renderer/templates/system_processes_show.j2 @@ -0,0 +1,9 @@ +{% set just_var = 10 %} +{{'--------------------------------------------------------------------------'}} +{{'PID'.ljust(just_var)}}{{'%CPU'.ljust(just_var)}}{#{{'CPU-TICKS-USER'.ljust(just_var)}}{{'CPU-TICKS-SYSTEM'.ljust(just_var)}}#}{{'%MEMORY'.ljust(just_var)}}{{'MEM-USAGE(Bytes)'.ljust(just_var)}}{#{{'START-TIME'.ljust(just_var)}}{{'UP-TIME'.ljust(just_var)}}#}{{'NAME'.rjust(just_var)}} +{{'--------------------------------------------------------------------------'}} +{% for process in json_output %} +{%set name = (process['state']['name'] | string).split(' ')%} +{{(process['pid'] | string).ljust(just_var)}} {{(process['state']['cpu_utilization'] | string).ljust(just_var)}} {#{{(process['state']['cpu_usage_user'] | string).ljust(just_var)}} {{(process['state']['cpu_usage_system'] | string).ljust(just_var)}}#} {{(process['state']['memory_utilization'] | string).ljust(just_var)}} {{(process['state']['memory_usage'] | string).ljust(just_var)}} {#{{(process['state']['start_time'] | string).ljust(10)}} {{(process['state']['uptime'] | string).ljust(just_var)}}#} {{name[0]}} +{% endfor %} + diff --git a/src/CLI/renderer/templates/system_show.j2 b/src/CLI/renderer/templates/system_show.j2 new file mode 100755 index 0000000000..cd18d406ad --- /dev/null +++ b/src/CLI/renderer/templates/system_show.j2 @@ -0,0 +1,7 @@ +{{'-----------------------------------------------------------'}} +{{'Attribute'.ljust(20)}} {{'Value/State'}} +{{'-----------------------------------------------------------'}} +{% for key,value in json_output.items() %} +{{key.ljust(20)}}:{{value}} +{% endfor %} + diff --git a/src/cvl/Makefile b/src/cvl/Makefile new file mode 100644 index 0000000000..3a61dd8a4b --- /dev/null +++ b/src/cvl/Makefile @@ -0,0 +1,79 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +all: precheck deps schema tests +GO?=/usr/local/go/bin/go +SRC_FILES=$(shell find . -name '*.go' | grep -v '_test.go' | grep -v '/tests/') +TEST_FILES=$(wildcard *_test.go) +TOP_DIR := $(abspath ../..) +GOFLAGS:= +BUILD_DIR:=build +GO_DOWNLOAD_PATH:=$(BUILD_GOPATH) +CVL_PKG=$(TOP_DIR)/pkg/linux_amd64/cvl.a + +CVL_TEST_DIR = $(TOP_DIR)/build/tests/cvl +CVL_TEST_BIN = $(CVL_TEST_DIR)/cvl.test + +ifdef DEBUG + GOFLAGS += -gcflags="all=-N -l" +endif + +precheck: + $(shell mkdir -p $(BUILD_DIR)) + +deps: $(BUILD_DIR)/.deps $(CVL_PKG) $(CVL_TEST_BIN) + + +$(BUILD_DIR)/.deps: + touch $@ + +$(CVL_PKG): + @echo "Building $@" + GOPATH=$(GOPATH) $(GO) build -v $(GOFLAGS) cvl + GOPATH=$(GOPATH) $(GO) install cvl + +$(CVL_TEST_BIN): $(TEST_FILES) $(SRC_FILES) + GOPATH=$(GOPATH) $(GO) test -c -cover -coverpkg=cvl,cvl/internal/util,cvl/internal/yparser cvl -o $@ + cp -r testdata $(@D)/ + +install: + GOPATH=$(GO_DEP_PATH) $(GO) install + +schema: + make -C schema + +tests: + make -C tests + +gotest: + make -C schema + make -C testdata/schema + cp schema/*.yin testdata/schema + CVL_CFG_FILE=$(abspath .)/conf/cvl_cfg.json CVL_SCHEMA_PATH=$(abspath .)/testdata/schema GOPATH=$(GOPATH) tests/run_test.sh + +clean: + make -C tests clean + rm -rf $(CVL_PKG) + rm -rf $(CVL_TEST_DIR) + +cleanall: + rm -rf $(BUILD_DIR) + rm -rf $(CVL_PKG) + rm -rf $(CVL_TEST_DIR) + diff --git a/src/cvl/README.md b/src/cvl/README.md new file mode 100644 index 0000000000..9445cb22a8 --- /dev/null +++ b/src/cvl/README.md @@ -0,0 +1,62 @@ +1. Install latest version of pyang tool. + +2. Install libyang from https://github.com/CESNET/libyang along with its dependency. + +3. Run 'make' from top level 'cvl' directory. + +4. Refer to top level makefile rules for compiling individual targets. + +5. 'schema' directory should contain all .yin files + +6. On the target the 'schema' directory needs to be present in the same directory where application executable file is present. + + +Debugging Info: +=============== + +Below steps need to be done to enable CVL logging. + +1. Find the CVL json config file in mgmt-framework docker in switch at "/usr/sbin/cvl_cfg.json" . + +2. Change the logging flags from "false" to "true" as below: + + { + "TRACE_CACHE": "true", + "TRACE_LIBYANG": "true", + "TRACE_YPARSER": "true", + "TRACE_CREATE": "true", + "TRACE_UPDATE": "true", + "TRACE_DELETE": "true", + "TRACE_SEMANTIC": "true", + "TRACE_SYNTAX": "true", + "__comment1__": "Set LOGTOSTDER to 'true' to log on standard error", + "LOGTOSTDERR": "true", + "__comment2__": "Display log upto INFO level", + "STDERRTHRESHOLD": "INFO", + "__comment3__": "Display log upto INFO level 8", + "VERBOSITY": "8", + "SKIP_VALIDATION": "false", + "SKIP_SEMANTIC_VALIDATION": "false" + } +3. Below environment variables need to be set at the end in /usr/bin/rest-server.sh in mgmt-framework docker. + + export CVL_DEBUG=1 + export CVL_CFG_FILE=/usr/sbin/cvl_cfg.json + + Note : CVL_CFG_FILE enviroment variable can point to other location also. + +4. CVL Traces can be enabled both with restart and without mgmt-framework docker restart . + + With Restart: + ============ + Restart mgmt-framework docker after which updated cvl_cfg.json file will be read. + + Without Restart: + =============== + Issue SIGUSR2 to rest process(kill -SIGUSR2 , to read changed cvl_cfg.json with logging enabled. + +5. After following above steps, CVL traces can be seen in syslog file in host container at /var/log/syslog. + +6. To disable CVL traces , disable the fields in cvl_cfg.json file and then perform same steps as in Step 4. + + diff --git a/src/cvl/conf/cvl_cfg.json b/src/cvl/conf/cvl_cfg.json new file mode 100644 index 0000000000..1445bf3029 --- /dev/null +++ b/src/cvl/conf/cvl_cfg.json @@ -0,0 +1,20 @@ +{ + "TRACE_CACHE": "false", + "TRACE_LIBYANG": "false", + "TRACE_YPARSER": "false", + "TRACE_CREATE": "false", + "TRACE_UPDATE": "false", + "TRACE_DELETE": "false", + "TRACE_SEMANTIC": "false", + "TRACE_SYNTAX": "false", + "__comment1__": "Log trace data when error occurs", + "TRACE_ONERROR": "true", + "__comment2__": "Set LOGTOSTDER to 'true' to log on standard error", + "LOGTOSTDERR": "false", + "__comment3__": "Display log upto INFO level", + "STDERRTHRESHOLD": "ERROR", + "__comment4__": "Display log upto INFO level 8", + "VERBOSITY": "0", + "SKIP_VALIDATION": "false", + "SKIP_SEMANTIC_VALIDATION": "false" +} diff --git a/src/cvl/cvl.go b/src/cvl/cvl.go new file mode 100644 index 0000000000..375435acaf --- /dev/null +++ b/src/cvl/cvl.go @@ -0,0 +1,1864 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 cvl +import ( + "fmt" + "os" + "strings" + "regexp" + "time" + log "github.com/golang/glog" + "encoding/json" + "github.com/go-redis/redis" + "github.com/antchfx/xmlquery" + "github.com/antchfx/jsonquery" + "cvl/internal/yparser" + . "cvl/internal/util" + "sync" + "flag" + "runtime" +) + +//DB number +const ( + APPL_DB uint8 = 0 + iota + ASIC_DB + COUNTERS_DB + LOGLEVEL_DB + CONFIG_DB + PFC_WD_DB + FLEX_COUNTER_DB = PFC_WD_DB + STATE_DB + SNMP_OVERLAY_DB + INVALID_DB +) + +const DEFAULT_CACHE_DURATION uint16 = 300 /* 300 sec */ +const MAX_BULK_ENTRIES_IN_PIPELINE int = 50 + +var reLeafRef *regexp.Regexp = nil +var reHashRef *regexp.Regexp = nil +var reSelKeyVal *regexp.Regexp = nil +var reLeafInXpath *regexp.Regexp = nil + +var cvlInitialized bool +var dbNameToDbNum map[string]uint8 + +//map of lua script loaded +var luaScripts map[string]*redis.Script + +//var tmpDbCache map[string]interface{} //map of table storing map of key-value pair + //m["PORT_TABLE] = {"key" : {"f1": "v1"}} +//Important schema information to be loaded at bootup time +type modelTableInfo struct { + dbNum uint8 + modelName string + redisTableName string //To which Redis table it belongs to, used for 1 Redis to N Yang List + module *yparser.YParserModule + keys []string + redisKeyDelim string + redisKeyPattern string + mapLeaf []string //for 'mapping list' + leafRef map[string][]string //for storing all leafrefs for a leaf in a table, + //multiple leafref possible for union + mustExp map[string]string + tablesForMustExp map[string]CVLOperation +} + + +/* CVL Error Structure. */ +type CVLErrorInfo struct { + TableName string /* Table having error */ + ErrCode CVLRetCode /* CVL Error return Code. */ + CVLErrDetails string /* CVL Error Message details. */ + Keys []string /* Keys of the Table having error. */ + Value string /* Field Value throwing error */ + Field string /* Field Name throwing error . */ + Msg string /* Detailed error message. */ + ConstraintErrMsg string /* Constraint error message. */ + ErrAppTag string +} + +type CVL struct { + redisClient *redis.Client + yp *yparser.YParser + tmpDbCache map[string]interface{} //map of table storing map of key-value pair + requestCache map[string]map[string][]CVLEditConfigData //Cache of validated data, + //might be used as dependent data in next request + batchLeaf string + chkLeafRefWithOthCache bool +} + +type modelNamespace struct { + prefix string + ns string +} + +type modelDataInfo struct { + modelNs map[string]modelNamespace //model namespace + tableInfo map[string]*modelTableInfo //redis table to model name and keys + redisTableToYangList map[string][]string //Redis table to all YANG lists when it is not 1:1 mapping + allKeyDelims map[string]bool +} + +//Struct for storing global DB cache to store DB which are needed frequently like PORT +type dbCachedData struct { + root *yparser.YParserNode //Root of the cached data + startTime time.Time //When cache started + expiry uint16 //How long cache should be maintained in sec +} + +//Global data cache for redis table +type cvlGlobalSessionType struct { + db map[string]dbCachedData + pubsub *redis.PubSub + stopChan chan int //stop channel to stop notification listener + cv *CVL + mutex *sync.Mutex +} +var cvg cvlGlobalSessionType + +//Single redis client for validation +var redisClient *redis.Client + +//Stores important model info +var modelInfo modelDataInfo + +type keyValuePairStruct struct { + key string + values []string +} + +func TRACE_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + TRACE_LEVEL_LOG(level, tracelevel, fmtStr, args...) +} + +func CVL_LOG(level CVLLogLevel, fmtStr string, args ...interface{}) { + CVL_LEVEL_LOG(level, fmtStr, args...) +} + +//package init function +func init() { + if (os.Getenv("CVL_SCHEMA_PATH") != "") { + CVL_SCHEMA = os.Getenv("CVL_SCHEMA_PATH") + "/" + } + + if (os.Getenv("CVL_DEBUG") != "") { + SetTrace(true) + } + + ConfigFileSyncHandler() + + cvlCfgMap := ReadConfFile() + + if (cvlCfgMap != nil) { + if (strings.Compare(cvlCfgMap["LOGTOSTDERR"], "true") == 0) { + flag.Set("logtostderr", "true") + flag.Set("stderrthreshold", cvlCfgMap["STDERRTHRESHOLD"]) + flag.Set("v", cvlCfgMap["VERBOSITY"]) + } + + CVL_LOG(INFO ,"Current Values of CVL Configuration File %v", cvlCfgMap) + } + + //regular expression for leafref and hashref finding + reLeafRef = regexp.MustCompile(`.*[/]([a-zA-Z]*:)?(.*)[/]([a-zA-Z]*:)?(.*)`) + reHashRef = regexp.MustCompile(`\[(.*)\|(.*)\]`) + reSelKeyVal = regexp.MustCompile("=[ ]*['\"]?([0-9_a-zA-Z]+)['\"]?|(current[(][)])") + reLeafInXpath = regexp.MustCompile("(.*[:/]{1})([a-zA-Z0-9_-]+)([^a-zA-Z0-9_-]*)") + + Initialize() + + cvg.db = make(map[string]dbCachedData) + + //Global session keeps the global cache + cvg.cv, _ = ValidationSessOpen() + //Create buffer channel of length 1 + cvg.stopChan = make(chan int, 1) + //Initialize mutex + cvg.mutex = &sync.Mutex{} + + _, err := redisClient.ConfigSet("notify-keyspace-events", "AKE").Result() + if err != nil { + CVL_LOG(ERROR ,"Could not enable notification error %s", err) + } + + dbCacheSet(false, "PORT", 0) +} + +func Debug(on bool) { + yparser.Debug(on) +} + +//Get attribute value of xml node +func getXmlNodeAttr(node *xmlquery.Node, attrName string) string { + for _, attr := range node.Attr { + if (attrName == attr.Name.Local) { + return attr.Value + } + } + + return "" +} + +//Store useful schema data during initialization +func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such model info can be maintained in C code and fetched from there + f, err := os.Open(CVL_SCHEMA + modelFile) + root, err := xmlquery.Parse(f) + + if err != nil { + return + } + f.Close() + + //model is derived from file name + tokens := strings.Split(modelFile, ".") + modelName := tokens[0] + + //Store namespace + modelNs := modelNamespace{} + + nodes := xmlquery.Find(root, "//module/namespace") + if (nodes != nil) { + modelNs.ns = nodes[0].Attr[0].Value + } + + nodes = xmlquery.Find(root, "//module/prefix") + if (nodes != nil) { + modelNs.prefix = nodes[0].Attr[0].Value + } + + modelInfo.modelNs[modelName] = modelNs + + //Store metadata present in each list. + //Each list represent one Redis table in general. + //However when one Redis table is mapped to multiple + //YANG lists need to store the information in redisTableToYangList map + nodes = xmlquery.Find(root, "//module/container/container/list") + if (nodes == nil) { + return + } + + //number list under one table container i.e. ACL_TABLE container + //has only one ACL_TABLE_LIST list + for _, node := range nodes { + //for each list, remove "_LIST" suffix + tableName := node.Attr[0].Value + if (strings.HasSuffix(tableName, "_LIST")) { + tableName = tableName[0:len(tableName) - len("_LIST")] + } + tableInfo := modelTableInfo{modelName: modelName} + //Store Redis table name + tableInfo.redisTableName = node.Parent.Attr[0].Value + //Store the reference for list node to be used later + listNode := node + node = node.FirstChild + //Default database is CONFIG_DB since CVL works with config db mainly + tableInfo.module = module + tableInfo.dbNum = CONFIG_DB + //default delim '|' + tableInfo.redisKeyDelim = "|" + modelInfo.allKeyDelims[tableInfo.redisKeyDelim] = true + + fieldCount := 0 + + //Check for meta data in schema + for node != nil { + switch node.Data { + case "db-name": + tableInfo.dbNum = dbNameToDbNum[node.Attr[0].Value] + fieldCount++ + case "key": + tableInfo.keys = strings.Split(node.Attr[0].Value," ") + fieldCount++ + keypattern := []string{tableName} + + /* Create the default key pattern of the form Table Name|{key1}|{key2}. */ + for _ , key := range tableInfo.keys { + keypattern = append(keypattern, fmt.Sprintf("{%s}",key)) + } + + tableInfo.redisKeyPattern = strings.Join(keypattern, tableInfo.redisKeyDelim) + + case "key-delim": + tableInfo.redisKeyDelim = node.Attr[0].Value + fieldCount++ + //store all possible key delims + modelInfo.allKeyDelims[tableInfo.redisKeyDelim] = true + case "key-pattern": + tableInfo.redisKeyPattern = node.Attr[0].Value + fieldCount++ + case "map-leaf": + tableInfo.mapLeaf = strings.Split(node.Attr[0].Value," ") + fieldCount++ + } + node = node.NextSibling + } + + //Find and store all leafref under each table + /* + if (listNode == nil) { + //Store the tableInfo in global data + modelInfo.tableInfo[tableName] = tableInfo + + continue + } + */ + + //If container has more than one list, it means one Redis table is mapped to + //multiple lists, store the info in redisTableToYangList + allLists := xmlquery.Find(listNode.Parent, "/list") + if len(allLists) > 1 { + yangList := modelInfo.redisTableToYangList[tableInfo.redisTableName] + yangList = append(yangList, tableName) + //Update the map + modelInfo.redisTableToYangList[tableInfo.redisTableName] = yangList + } + + leafRefNodes := xmlquery.Find(listNode, "//type[@name='leafref']") + if (leafRefNodes == nil) { + //Store the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + + continue + } + + tableInfo.leafRef = make(map[string][]string) + for _, leafRefNode := range leafRefNodes { + if (leafRefNode.Parent == nil || leafRefNode.FirstChild == nil) { + continue + } + + //Get the leaf/leaf-list name holding this leafref + //Note that leaf can have union of leafrefs + leafName := "" + for node := leafRefNode.Parent; node != nil; node = node.Parent { + if (node.Data == "leaf" || node.Data == "leaf-list") { + leafName = getXmlNodeAttr(node, "name") + break + } + } + + //Store the leafref path + if (leafName != "") { + tableInfo.leafRef[leafName] = append(tableInfo.leafRef[leafName], + getXmlNodeAttr(leafRefNode.FirstChild, "value")) + } + } + + //Find all 'must' expression and store the against its parent node + mustExps := xmlquery.Find(listNode, "//must") + if (mustExps == nil) { + //Update the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + continue + } + + tableInfo.mustExp = make(map[string]string) + for _, mustExp := range mustExps { + if (mustExp.Parent == nil) { + continue + } + parentName := "" + for node := mustExp.Parent; node != nil; node = node.Parent { + //assuming must exp is at leaf or list level + if (node.Data == "leaf" || node.Data == "leaf-list" || + node.Data == "list") { + parentName = getXmlNodeAttr(node, "name") + break + } + } + if (parentName != "") { + tableInfo.mustExp[parentName] = getXmlNodeAttr(mustExp, "condition") + } + } + + //Update the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + + } +} + +//Find the tables names in must expression, these tables data need to be fetched +//during semantic validation +func addTableNamesForMustExp() { + + for tblName, tblInfo := range modelInfo.tableInfo { + if (tblInfo.mustExp == nil) { + continue + } + + tblInfo.tablesForMustExp = make(map[string]CVLOperation) + + for _, mustExp := range tblInfo.mustExp { + var op CVLOperation = OP_NONE + //Check if 'must' expression should be executed for a particular operation + if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != CREATE") == true) { + op = op | OP_CREATE + } else if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != UPDATE") == true) { + op = op | OP_UPDATE + } else if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != DELETE") == true) { + op = op | OP_DELETE + } + + //store the current table if aggregate function like count() is used + /*if (strings.Contains(mustExp, "count") == true) { + tblInfo.tablesForMustExp[tblName] = op + }*/ + + //check which table name is present in the must expression + for tblNameSrch, _ := range modelInfo.tableInfo { + if (tblNameSrch == tblName) { + continue + } + //Table name should appear like "../VLAN_MEMBER/tagging_mode' or ' + // "/prt:PORT/prt:ifname" + re := regexp.MustCompile(fmt.Sprintf(".*[/]([a-zA-Z]*:)?%s[\\[/]", tblNameSrch)) + matches := re.FindStringSubmatch(mustExp) + if (len(matches) > 0) { + //stores the table name + tblInfo.tablesForMustExp[tblNameSrch] = op + } + } + } + + //update map + modelInfo.tableInfo[tblName] = tblInfo + } +} + +//Split key into table prefix and key +func splitRedisKey(key string) (string, string) { + + var foundIdx int = -1 + //Check with all key delim + for keyDelim, _ := range modelInfo.allKeyDelims { + foundIdx = strings.Index(key, keyDelim) + if (foundIdx >= 0) { + //Matched with key delim + break + } + } + + if (foundIdx < 0) { + //No matches + return "", "" + } + + tblName := key[:foundIdx] + + if _, exists := modelInfo.tableInfo[tblName]; exists == false { + //Wrong table name + return "", "" + } + + prefixLen := foundIdx + 1 + + return tblName, key[prefixLen:] +} + +//Get the YANG list name from Redis key +//This just returns same YANG list name as Redis table name +//when 1:1 mapping is there. For one Redis table to +//multiple YANG list, it returns appropriate YANG list name +//INTERFACE:Ethernet12 returns ==> INTERFACE +//INTERFACE:Ethernet12:1.1.1.0/32 ==> INTERFACE_IPADDR +func getRedisKeyToYangList(tableName, key string) string { + mapArr, exists := modelInfo.redisTableToYangList[tableName] + + if exists == false { + //1:1 mapping case + return tableName + } + + //As of now determine the mapping based on number of keys + var foundIdx int = -1 + numOfKeys := 1 //Assume only one key initially + for keyDelim, _ := range modelInfo.allKeyDelims { + foundIdx = strings.Index(key, keyDelim) + if (foundIdx >= 0) { + //Matched with key delim + keyComps := strings.Split(key, keyDelim) + numOfKeys = len(keyComps) + break + } + } + + //Check which list has number of keys as 'numOfKeys' + for i := 0; i < len(mapArr); i++ { + tblInfo, exists := modelInfo.tableInfo[mapArr[i]] + if exists == true { + if (len(tblInfo.keys) == numOfKeys) { + //Found the YANG list matching the number of keys + return mapArr[i] + } + } + } + + //No matches + return tableName +} + +//Convert Redis key to Yang keys, if multiple key components are there, +//they are separated based on Yang schema +func getRedisToYangKeys(tableName string, redisKey string)[]keyValuePairStruct{ + keyNames := modelInfo.tableInfo[tableName].keys + //First split all the keys components + keyVals := strings.Split(redisKey, modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator + //Store patterns for each key components by splitting using key delim + keyPatterns := strings.Split(modelInfo.tableInfo[tableName].redisKeyPattern, + modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator + + /* TBD. Workaround for optional keys in INTERFACE Table. + Code will be removed once model is finalized. */ + if ((tableName == "INTERFACE") && (len(keyNames) != len(keyVals))) { + keyVals = append(keyVals, "0.0.0.0/0") + + } else if (len(keyNames) != len(keyVals)) { + return nil //number key names and values does not match + } + + mkeys := []keyValuePairStruct{} + //For each key check the pattern and store key/value pair accordingly + for idx, keyName := range keyNames { + + //check if key-pattern contains specific key pattern + if (keyPatterns[idx+1] == fmt.Sprintf("({%s},)*", keyName)) { // key pattern is "({key},)*" i.e. repeating keys seperated by ',' + repeatedKeys := strings.Split(keyVals[idx], ",") + mkeys = append(mkeys, keyValuePairStruct{keyName, repeatedKeys}) + + } else if (keyPatterns[idx+1] == fmt.Sprintf("{%s}", keyName)) { //no specific key pattern - just "{key}" + + //Store key/value mapping + mkeys = append(mkeys, keyValuePairStruct{keyName, []string{keyVals[idx]}}) + } + } + + return mkeys +} + + +//Add child node to a parent node +func(c *CVL) addChildNode(tableName string, parent *yparser.YParserNode, name string) *yparser.YParserNode { + + //return C.lyd_new(parent, modelInfo.tableInfo[tableName].module, C.CString(name)) + return c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, parent, name) +} + +//Check for path resolution +func (c *CVL) checkPathForTableEntry(tableName string, currentValue string, cfgData *CVLEditConfigData, mustExpStk []string, token string) ([]string, string, CVLRetCode) { + + n := len(mustExpStk) - 1 + xpath := "" + + if (token == ")") { + for n = len(mustExpStk) - 1; mustExpStk[n] != "("; n = len(mustExpStk) - 1 { + //pop until "(" + xpath = mustExpStk[n] + xpath + mustExpStk = mustExpStk[:n] + } + } else if (token == "]") { + //pop until "[" + for n = len(mustExpStk) - 1; mustExpStk[n] != "["; n = len(mustExpStk) - 1 { + xpath = mustExpStk[n] + xpath + mustExpStk = mustExpStk[:n] + } + } + + mustExpStk = mustExpStk[:n] + targetTbl := "" + //Search the table name in xpath + for tblNameSrch, _ := range modelInfo.tableInfo { + if (tblNameSrch == tableName) { + continue + } + //Table name should appear like "../VLAN_MEMBER/tagging_mode' or ' + // "/prt:PORT/prt:ifname" + //re := regexp.MustCompile(fmt.Sprintf(".*[/]([a-zA-Z]*:)?%s[\\[/]", tblNameSrch)) + tblSrchIdx := strings.Index(xpath, fmt.Sprintf("/%s_LIST", tblNameSrch)) //no preifx + if (tblSrchIdx < 0) { + tblSrchIdx = strings.Index(xpath, fmt.Sprintf(":%s_LIST", tblNameSrch)) //with prefix + } + if (tblSrchIdx < 0) { + continue + } + + tblSrchIdxEnd := strings.Index(xpath[tblSrchIdx+len(tblNameSrch)+1:], "[") + if (tblSrchIdxEnd < 0) { + tblSrchIdxEnd = strings.Index(xpath[tblSrchIdx+len(tblNameSrch)+1:], "/") + } + + if (tblSrchIdxEnd >= 0) { //match found + targetTbl = tblNameSrch + break + } + } + + //No match with table found, could be just keys like 'aclname='TestACL1' + //just return the same + if (targetTbl == "") { + return mustExpStk, xpath, CVL_SUCCESS + } + + tableKey := targetTbl + //Add the keys + keyNames := modelInfo.tableInfo[tableKey].keys + + //Form the Redis Key to fetch the entry + for idx, keyName := range keyNames { + //Key value is string/numeric literal, extract the same + keySrchIdx := strings.Index(xpath, keyName) + if (keySrchIdx < 0 ) { + continue + } + + matches := reSelKeyVal.FindStringSubmatch(xpath[keySrchIdx+len(keyName):]) + if (len(matches) > 1) { + if (matches[1] == "current()") { + //replace with current field value + tableKey = tableKey + "*" + modelInfo.tableInfo[tableName].redisKeyDelim + currentValue + } else { + //Use literal + tableKey = tableKey + "*" + modelInfo.tableInfo[tableName].redisKeyDelim + matches[1] + } + + if (idx != len(keyNames) - 1) { + tableKey = tableKey + "|*" + } + } + } + + //Fetch the entries + redisTableKeys, err:= redisClient.Keys(tableKey).Result() + + if (err !=nil || len (redisTableKeys) > 1) { //more than one entry is returned, can't proceed further + //Just add all the entries for caching + for _, redisTableKey := range redisTableKeys { + c.addTableEntryToCache(splitRedisKey(redisTableKey)) + } + + return mustExpStk, "", CVL_SUCCESS + } + + for _, redisTableKey := range redisTableKeys { + + var entry map[string]string + + if tmpEntry, mergeNeeded := c.fetchDataFromRequestCache(splitRedisKey(redisTableKey)); (tmpEntry == nil || mergeNeeded == true) { + //If data is not available in validated cache fetch from Redis DB + entry, err = redisClient.HGetAll(redisTableKey).Result() + + if (mergeNeeded) { + mergeMap(entry, tmpEntry) + } + } + + //Get the entry fields from Redis + if (entry != nil) { + //Just add all the entries for caching + c.addTableEntryToCache(splitRedisKey(redisTableKey)) + + leafInPath := "" + index := strings.LastIndex(xpath, "/") + if (index >= 0) { + + matches := reLeafInXpath.FindStringSubmatch(xpath[index:]) + if (len(matches) > 2) { //should return atleasts two subgroup and entire match + leafInPath = matches[2] + } else { + //No leaf requested in xpath selection + return mustExpStk, "", CVL_SUCCESS + } + + index = strings.Index(xpath, "=") + tblIndex := strings.Index(xpath, targetTbl) + if (index >= 0 && tblIndex >=0) { + + if leafVal, existing := entry[leafInPath + "@"]; existing == true { + //Get the field value referred in the xpath + if (index < tblIndex) { + // case like - [ifname=../../ACL_TABLE[aclname=current()] + return mustExpStk, xpath[:index+1] + leafVal, CVL_SUCCESS + } else { + // case like - [ifname=current()] + return mustExpStk, leafVal, CVL_SUCCESS + } + } else { + + if (index < tblIndex) { + return mustExpStk, xpath[:index+1] + entry[leafInPath], CVL_SUCCESS + } else { + return mustExpStk, entry[leafInPath], CVL_SUCCESS + } + } + } + } + } + } + + return mustExpStk, "", CVL_FAILURE +} + +//Add specific entries by looking at must expression +//Must expression may need single or multiple entries +//It can be within same table or across multiple tables +//Node-set function such count() can be quite expensive and +//should be avoided through this function +func (c *CVL) addTableEntryForMustExp(cfgData *CVLEditConfigData, tableName string) CVLRetCode { + if (modelInfo.tableInfo[tableName].mustExp == nil) { + return CVL_SUCCESS + } + + for fieldName, mustExp := range modelInfo.tableInfo[tableName].mustExp { + + currentValue := "" // Current value for current() function + + //Get the current() field value from the entry being created/updated/deleted + keyValuePair := getRedisToYangKeys(tableName, cfgData.Key[len(tableName)+1:]) + + //Try to get the current() from the 'key' provided + if (keyValuePair != nil) { + for _, keyValItem := range keyValuePair { + if (keyValItem.key == fieldName) { + currentValue = keyValItem.values[0] + } + } + } + + //current() value needs to be fetched from other field + if (currentValue == "") { + if (cfgData.VOp == OP_CREATE) { + if (tableName != fieldName) { //must expression is not at list level + currentValue = cfgData.Data[fieldName] + if (currentValue == "") { + currentValue = cfgData.Data[fieldName + "@"] + } + } + } else if (cfgData.VOp == OP_UPDATE || cfgData.VOp == OP_DELETE) { + //fetch the entry to get current() value + c.clearTmpDbCache() + entryKey := cfgData.Key[len(tableName)+1:] + c.tmpDbCache[tableName] = map[string]interface{}{entryKey: nil} + + if (c.fetchTableDataToTmpCache(tableName, + map[string]interface{}{entryKey: nil}) > 0) { + mapTable := c.tmpDbCache[tableName] + if fields, existing := mapTable.(map[string]interface{})[entryKey]; existing == true { + currentValue = fmt.Sprintf("%v", fields.(map[string]interface{})[fieldName]) + } + } + } + } + + mustExpStk := []string{} //Use the string slice as stack + mustExpStr := "(" + mustExp + ")" + strLen := len(mustExpStr) + strTmp := "" + //Parse the xpath expression and fetch Redis entry by looking at xpath, + // any xpath function call is ignored except current(). + for i := 0; i < strLen; i++ { + switch mustExpStr[i] { + case '(': + if (mustExpStr[i+1] == ')') { + strTmp = strTmp + "()" + if index := strings.Index(strTmp, "current()"); index >= 0 { + strTmp = strTmp[:index] + currentValue + } + i = i + 1 + continue + } + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + mustExpStk = append(mustExpStk, "(") + strTmp = "" + case ')': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + strTmp = "" + //Check Path - pop until ')' + mustExpStk, evalPath,_ := c.checkPathForTableEntry(tableName, currentValue, cfgData, + mustExpStk, ")") + if (evalPath != "") { + mustExpStk = append(mustExpStk, evalPath) + } + mustExpStk = append(mustExpStk, ")") + case '[': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + mustExpStk = append(mustExpStk, "[") + strTmp = "" + case ']': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + //Check Path - pop until = or '[' + mustExpStk, evalPath,_ := c.checkPathForTableEntry(tableName, currentValue, cfgData, + mustExpStk, "]") + if (evalPath != "") { + mustExpStk = append(mustExpStk, "[" + evalPath + "]") + } + strTmp = "" + default: + strTmp = fmt.Sprintf("%s%c", strTmp, mustExpStr[i]) + } + } + + //Get the redis data for accumulated keys and add them to session cache + depData := c.fetchDataToTmpCache() //fetch data to temp cache for temporary validation + + if (depData != nil) { + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_CACHE, "Adding entries for 'must' expression : %s", c.yp.NodeDump(depData)) + } + } else { + //Could not fetch any entry from Redis after xpath evaluation + return CVL_FAILURE + } + + if errObj := c.yp.CacheSubtree(false, depData); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_FAILURE + } + + } //for each must expression + + return CVL_SUCCESS +} + +//Add all other table data for validating all 'must' exp for tableName +func (c *CVL) addTableDataForMustExp(op CVLOperation, tableName string) CVLRetCode { + if (modelInfo.tableInfo[tableName].mustExp == nil) { + return CVL_SUCCESS + } + + for mustTblName, mustOp := range modelInfo.tableInfo[tableName].tablesForMustExp { + //First check if must expression should be executed for the given operation + if (mustOp != OP_NONE) && ((mustOp & op) == OP_NONE) { + //must to be excuted for particular operation, but current operation + //is not the same one + continue + } + + //Check in global cache first and merge to session cache + if topNode, _ := dbCacheGet(mustTblName); topNode != nil { + var errObj yparser.YParserError + //If global cache has the table, add to the session validation + TRACE_LOG(INFO_API, TRACE_CACHE, "Adding global cache to session cache for table %s", tableName) + if errObj = c.yp.CacheSubtree(true, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_SYNTAX_ERROR + } + } else { //Put the must table in global table and add to session cache + cvg.cv.chkLeafRefWithOthCache = true + dbCacheSet(false, mustTblName, 100*DEFAULT_CACHE_DURATION) //Keep the cache for default duration + cvg.cv.chkLeafRefWithOthCache = false + + if topNode, ret := dbCacheGet(mustTblName); topNode != nil { + var errObj yparser.YParserError + //If global cache has the table, add to the session validation + TRACE_LOG(INFO_API, TRACE_CACHE, "Global cache created, add the data to session cache for table %s", tableName) + if errObj = c.yp.CacheSubtree(true, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_SYNTAX_ERROR + } + } else if (ret == CVL_SUCCESS) { + TRACE_LOG(INFO_API, TRACE_CACHE, "Global cache empty, no data in Redis for table %s", tableName) + return CVL_SUCCESS + } else { + CVL_LOG(ERROR ,"Could not create global cache for table %s", mustTblName) + return CVL_ERROR + } + + + /* + tableKeys, err:= redisClient.Keys(mustTblName + + modelInfo.tableInfo[mustTblName].redisKeyDelim + "*").Result() + + if (err != nil) { + continue + } + + for _, tableKey := range tableKeys { + tableKey = tableKey[len(mustTblName+ modelInfo.tableInfo[mustTblName].redisKeyDelim):] //remove table prefix + if (c.tmpDbCache[mustTblName] == nil) { + c.tmpDbCache[mustTblName] = map[string]interface{}{tableKey: nil} + } else { + tblMap := c.tmpDbCache[mustTblName] + tblMap.(map[string]interface{})[tableKey] =nil + c.tmpDbCache[mustTblName] = tblMap + } + } + */ + } + } + + return CVL_SUCCESS +} + +func (c *CVL) addTableEntryToCache(tableName string, redisKey string) { + if (tableName == "" || redisKey == "") { + return + } + + if (c.tmpDbCache[tableName] == nil) { + c.tmpDbCache[tableName] = map[string]interface{}{redisKey: nil} + } else { + tblMap := c.tmpDbCache[tableName] + tblMap.(map[string]interface{})[redisKey] =nil + c.tmpDbCache[tableName] = tblMap + } +} + +//Check delete constraint for leafref if key/field is deleted +func (c *CVL) checkDeleteConstraint(cfgData []CVLEditConfigData, + tableName, keyVal, field string) CVLRetCode { + var leafRefs []tblFieldPair + if (field != "") { + //Leaf or field is getting deleted + leafRefs = c.findUsedAsLeafRef(tableName, field) + } else { + //Entire entry is getting deleted + leafRefs = c.findUsedAsLeafRef(tableName, modelInfo.tableInfo[tableName].keys[0]) + } + + //The entry getting deleted might have been referred from multiple tables + //Return failure if at-least one table is using this entry + for _, leafRef := range leafRefs { + TRACE_LOG(INFO_API, (TRACE_DELETE | TRACE_SEMANTIC), "Checking delete constraint for leafRef %s/%s", leafRef.tableName, leafRef.field) + //Check in dependent data first, if the referred entry is already deleted + leafRefDeleted := false + for _, cfgDataItem := range cfgData { + if (cfgDataItem.VType == VALIDATE_NONE) && + (cfgDataItem.VOp == OP_DELETE ) && + (strings.HasPrefix(cfgDataItem.Key, (leafRef.tableName + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim + keyVal + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim))) { + //Currently, checking for one entry is being deleted in same session + //We should check for all entries + leafRefDeleted = true + break + } + } + + if (leafRefDeleted == true) { + continue //check next leafref + } + + //Else, check if any referred enrty is present in DB + var nokey []string + refKeyVal, err := luaScripts["find_key"].Run(redisClient, nokey, leafRef.tableName, + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim, leafRef.field, keyVal).Result() + if (err == nil && refKeyVal != "") { + CVL_LOG(ERROR, "Delete will violate the constraint as entry %s is referred in %s", tableName, refKeyVal) + + return CVL_SEMANTIC_ERROR + } + } + + + return CVL_SUCCESS +} + +//Add the data which are referring this key +func (c *CVL) updateDeleteDataToCache(tableName string, redisKey string) { + if _, existing := c.tmpDbCache[tableName]; existing == false { + return + } else { + tblMap := c.tmpDbCache[tableName] + if _, existing := tblMap.(map[string]interface{})[redisKey]; existing == true { + delete(tblMap.(map[string]interface{}), redisKey) + c.tmpDbCache[tableName] = tblMap + } + } +} + +//Find which all tables (and which field) is using given (tableName/field) +// as leafref +//Use LUA script to find if table has any entry for this leafref + +type tblFieldPair struct { + tableName string + field string +} + +func (c *CVL) findUsedAsLeafRef(tableName, field string) []tblFieldPair { + + var tblFieldPairArr []tblFieldPair + + for tblName, tblInfo := range modelInfo.tableInfo { + if (tableName == tblName) { + continue + } + if (len(tblInfo.leafRef) == 0) { + continue + } + + for fieldName, leafRefs := range tblInfo.leafRef { + found := false + //Find leafref by searching table and field name + for _, leafRef := range leafRefs { + if ((strings.Contains(leafRef, tableName) == true) && + (strings.Contains(leafRef, field) == true)) { + tblFieldPairArr = append(tblFieldPairArr, + tblFieldPair{tblName, fieldName}) + //Found as leafref, no need to search further + found = true + break + } + } + + if (found == true) { + break + } + } + } + + return tblFieldPairArr +} + +//Add leafref entry for caching +//It has to be recursive in nature, as there can be chained leafref +func (c *CVL) addLeafRef(config bool, tableName string, name string, value string) { + + if (config == false) { + return + } + + //Check if leafRef entry is there for this field + if (len(modelInfo.tableInfo[tableName].leafRef[name]) > 0) { //array of leafrefs for a leaf + for _, leafRef := range modelInfo.tableInfo[tableName].leafRef[name] { + + //Get reference table name from the path and the leaf name + matches := reLeafRef.FindStringSubmatch(leafRef) + + //We have the leafref table name and the leaf name as well + if (matches != nil && len(matches) == 5) { //whole + 4 sub matches + refTableName := matches[2] + redisKey := value + + //Check if leafref dependency can also be met from 'must' table + if (c.chkLeafRefWithOthCache == true) { + found := false + for mustTbl, _ := range modelInfo.tableInfo[tableName].tablesForMustExp { + if mustTbl == refTableName { + found = true + break + } + } + if (found == true) { + //Leafref data will be available from must table dep data, skip this leafref entry + continue + } + } + + //only key is there, value wil be fetched and stored here, + //if value can't fetched this entry will be deleted that time + //Strip "_LIST" suffix + refRedisTableName := refTableName[0:len(refTableName) - len("_LIST")] + if (c.tmpDbCache[refRedisTableName] == nil) { + c.tmpDbCache[refRedisTableName] = map[string]interface{}{redisKey: nil} + } else { + tblMap := c.tmpDbCache[refRedisTableName] + _, exist := tblMap.(map[string]interface{})[redisKey] + if (exist == false) { + tblMap.(map[string]interface{})[redisKey] = nil + c.tmpDbCache[refRedisTableName] = tblMap + } + } + } + } + } +} + + +func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParserNode, name string, value string) { + + /* If there is no value then assign default space string. */ + if len(value) == 0 { + value = " " + } + + //Batch leaf creation + c.batchLeaf = c.batchLeaf + name + "#" + value + "#" + //Check if this leaf has leafref, + //If so add the add redis key to its table so that those + // details can be fetched for dependency validation + + c.addLeafRef(config, tableName, name, value) +} + + +func (c *CVL) checkFieldMap(fieldMap *map[string]string) map[string]interface{} { + fieldMapNew := map[string]interface{}{} + + for field, value := range *fieldMap { + if (field == "NULL") { + continue + } else if (field[len(field)-1:] == "@") { + //last char @ means it is a leaf-list/array of fields + field = field[:len(field)-1] //strip @ + //split the values seprated using ',' + strArr := strings.Split(value, ",") + //fieldMapNew[field] = strings.Split(value, ",") + arrMap := make([]interface{}, 0)//len(strArr)) + for _, strArrItem := range strArr { + arrMap = append(arrMap, strArrItem) + } + fieldMapNew[field] = arrMap//make([]interface{}, len(strArr)) + } else { + fieldMapNew[field] = value + } + } + + return fieldMapNew +} + +//Merge 'src' map to 'dest' map of map[string]string type +func mergeMap(dest map[string]string, src map[string]string) { + for key, data := range src { + dest[key] = data + } +} + +// Fetch dependent data from validated data cache, +// Returns the data and flag to indicate that if requested data +// is found in update request, the data should be merged with Redis data +func (c *CVL) fetchDataFromRequestCache(tableName string, key string) (map[string]string, bool) { + cfgDataArr := c.requestCache[tableName][key] + if (cfgDataArr != nil) { + for _, cfgReqData := range cfgDataArr { + //Delete request doesn't have depedent data + if (cfgReqData.VOp == OP_CREATE) { + return cfgReqData.Data, false + } else if (cfgReqData.VOp == OP_UPDATE) { + return cfgReqData.Data, true + } + } + } + + return nil, false +} + +//Fetch given table entries using pipeline +func (c *CVL) fetchTableDataToTmpCache(tableName string, dbKeys map[string]interface{}) int { + + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Entered fetchTableDataToTmpCache", time.Now()) + + totalCount := len(dbKeys) + if (totalCount == 0) { + //No entry to be fetched + return 0 + } + + entryFetched := 0 + bulkCount := 0 + bulkKeys := []string{} + for dbKey, val := range dbKeys { //for all keys + + if (val != nil) { //skip entry already fetched + mapTable := c.tmpDbCache[tableName] + delete(mapTable.(map[string]interface{}), dbKey) //delete entry already fetched + totalCount = totalCount - 1 + if(bulkCount != totalCount) { + //If some entries are remaining go back to 'for' loop + continue + } + } else { + //Accumulate entries to be fetched + bulkKeys = append(bulkKeys, dbKey) + bulkCount = bulkCount + 1 + } + + if(bulkCount != totalCount) && ((bulkCount % MAX_BULK_ENTRIES_IN_PIPELINE) != 0) { + //If some entries are remaining and bulk bucket is not filled, + //go back to 'for' loop + continue + } + + mCmd := map[string]*redis.StringStringMapCmd{} + + pipe := redisClient.Pipeline() + + for _, dbKey := range bulkKeys { + + redisKey := tableName + modelInfo.tableInfo[tableName].redisKeyDelim + dbKey + //Check in validated cache first and add as dependent data + if entry, mergeNeeded := c.fetchDataFromRequestCache(tableName, dbKey); (entry != nil) { + c.tmpDbCache[tableName].(map[string]interface{})[dbKey] = entry + entryFetched = entryFetched + 1 + //Entry found in validated cache, so skip fetching from Redis + //if merging is not required with Redis DB + if (mergeNeeded == false) { + continue + } + } + + //Otherwise fetch it from Redis + mCmd[dbKey] = pipe.HGetAll(redisKey) //write into pipeline + if mCmd[dbKey] == nil { + CVL_LOG(ERROR, "Failed pipe.HGetAll('%s')", redisKey) + } + } + + _, err := pipe.Exec() + if err != nil { + CVL_LOG(ERROR, "Failed to fetch details for table %s", tableName) + return 0 + } + pipe.Close() + bulkKeys = nil + + mapTable := c.tmpDbCache[tableName] + + for key, val := range mCmd { + res, err := val.Result() + if (err != nil || len(res) == 0) { + //no data found, don't keep blank entry + delete(mapTable.(map[string]interface{}), key) + continue + } + //exclude table name and delim + keyOnly := key + + if (mapTable.(map[string]interface{})[keyOnly] != nil) { + tmpFieldMap := (mapTable.(map[string]interface{})[keyOnly]).(map[string]string) + //merge with validated cache data + mergeMap(res, tmpFieldMap) + fieldMap := c.checkFieldMap(&res) + mapTable.(map[string]interface{})[keyOnly] = fieldMap + } else { + fieldMap := c.checkFieldMap(&res) + mapTable.(map[string]interface{})[keyOnly] = fieldMap + } + + entryFetched = entryFetched + 1 + } + + runtime.Gosched() + } + + TRACE_LOG(INFO_API, TRACE_CACHE,"\n%v, Exiting fetchTableDataToTmpCache", time.Now()) + + return entryFetched +} + +//populate redis data to cache +func (c *CVL) fetchDataToTmpCache() *yparser.YParserNode { + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Entered fetchToTmpCache", time.Now()) + + entryToFetch := 0 + var root *yparser.YParserNode = nil + var errObj yparser.YParserError + + for entryToFetch = 1; entryToFetch > 0; { //Force to enter the loop for first time + //Repeat until all entries are fetched + entryToFetch = 0 + for tableName, dbKeys := range c.tmpDbCache { //for each table + entryToFetch = entryToFetch + c.fetchTableDataToTmpCache(tableName, dbKeys.(map[string]interface{})) + } //for each table + + //If no table entry delete the table itself + for tableName, dbKeys := range c.tmpDbCache { //for each table + if (len(dbKeys.(map[string]interface{})) == 0) { + delete(c.tmpDbCache, tableName) + continue + } + } + + if (entryToFetch == 0) { + //No more entry to fetch + break + } + + if (Tracing == true) { + jsonDataBytes, _ := json.Marshal(c.tmpDbCache) + jsonData := string(jsonDataBytes) + TRACE_LOG(INFO_API, TRACE_CACHE, "Top Node=%v\n", jsonData) + } + + data, err := jsonquery.ParseJsonMap(&c.tmpDbCache) + + if (err != nil) { + return nil + } + + //Build yang tree for each table and cache it + for jsonNode := data.FirstChild; jsonNode != nil; jsonNode=jsonNode.NextSibling { + TRACE_LOG(INFO_API, TRACE_CACHE, "Top Node=%v\n", jsonNode.Data) + //Visit each top level list in a loop for creating table data + topNode, _ := c.generateTableData(true, jsonNode) + if (root == nil) { + root = topNode + } else { + if root, errObj = c.yp.MergeSubtree(root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return nil + } + } + } + } // until all dependent data is fetched + + if root != nil && Tracing == true { + dumpStr := c.yp.NodeDump(root) + TRACE_LOG(INFO_DETAIL, TRACE_CACHE, "Dependent Data = %v\n", dumpStr) + } + + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Exiting fetchToTmpCache", time.Now()) + return root +} + + +func (c *CVL) clearTmpDbCache() { + for key, _ := range c.tmpDbCache { + delete(c.tmpDbCache, key) + } +} + +func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *jsonquery.Node, +parent *yparser.YParserNode) CVLRetCode { + + //Traverse fields + for jsonFieldNode := jsonNode.FirstChild; jsonFieldNode!= nil; + jsonFieldNode = jsonFieldNode.NextSibling { + //Add fields as leaf to the list + if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.TextNode) { + + if (len(modelInfo.tableInfo[tableName].mapLeaf) == 2) {//mapping should have two leaf always + //Values should be stored inside another list as map table + listNode := c.addChildNode(tableName, parent, tableName) //Add the list to the top node + c.addChildLeaf(config, tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[0], + jsonFieldNode.Data) + + c.addChildLeaf(config, tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[1], + jsonFieldNode.FirstChild.Data) + + } else { + //check if it is hash-ref, then need to add only key from "TABLE|k1" + hashRefMatch := reHashRef.FindStringSubmatch(jsonFieldNode.FirstChild.Data) + + if (hashRefMatch != nil && len(hashRefMatch) == 3) { + /*if (strings.HasPrefix(jsonFieldNode.FirstChild.Data, "[")) && + (strings.HasSuffix(jsonFieldNode.FirstChild.Data, "]")) && + (strings.Index(jsonFieldNode.FirstChild.Data, "|") > 0) {*/ + + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + hashRefMatch[2]) //take hashref key value + } else { + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + jsonFieldNode.FirstChild.Data) + } + } + + } else if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.ElementNode) { + //Array data e.g. VLAN members + for arrayNode:=jsonFieldNode.FirstChild; arrayNode != nil; + + arrayNode = arrayNode.NextSibling { + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + arrayNode.FirstChild.Data) + } + } + } + + return CVL_SUCCESS +} + +func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser.YParserNode, CVLErrorInfo) { + var cvlErrObj CVLErrorInfo + + tableName := fmt.Sprintf("%s",jsonNode.Data) + c.batchLeaf = "" + + //Every Redis table is mapped as list within a container, + //E.g. ACL_RULE is mapped as + // container ACL_RULE { list ACL_RULE_LIST {} } + var topNode *yparser.YParserNode + + // Add top most conatiner e.g. 'container sonic-acl {...}' + if _, exists := modelInfo.tableInfo[tableName]; exists == false { + return nil, cvlErrObj + } + topNode = c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + nil, modelInfo.tableInfo[tableName].modelName) + + //Add the container node for each list + //e.g. 'container ACL_TABLE { list ACL_TABLE_LIST ...} + listConatinerNode := c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + topNode, tableName) + + //Traverse each key instance + for jsonNode = jsonNode.FirstChild; jsonNode != nil; jsonNode = jsonNode.NextSibling { + + //For each field check if is key + //If it is key, create list as child of top container + // Get all key name/value pairs + if yangListName := getRedisKeyToYangList(tableName, jsonNode.Data); yangListName!= "" { + tableName = yangListName + } + keyValuePair := getRedisToYangKeys(tableName, jsonNode.Data) + keyCompCount := len(keyValuePair) + totalKeyComb := 1 + var keyIndices []int + + //Find number of all key combinations + //Each key can have one or more key values, which results in nk1 * nk2 * nk2 combinations + idx := 0 + for i,_ := range keyValuePair { + totalKeyComb = totalKeyComb * len(keyValuePair[i].values) + keyIndices = append(keyIndices, 0) + } + + for ; totalKeyComb > 0 ; totalKeyComb-- { + //Get the YANG list name from Redis table name + //Ideally they are same except when one Redis table is split + //into multiple YANG lists + + //Add table i.e. create list element + listNode := c.addChildNode(tableName, listConatinerNode, tableName + "_LIST") //Add the list to the top node + + //For each key combination + //Add keys as leaf to the list + for idx = 0; idx < keyCompCount; idx++ { + c.addChildLeaf(config, tableName, + listNode, keyValuePair[idx].key, + keyValuePair[idx].values[keyIndices[idx]]) + } + + //Get all fields under the key field and add them as children of the list + c.generateTableFieldsData(config, tableName, jsonNode, listNode) + + //Check which key elements left after current key element + var next int = keyCompCount - 1 + for ((next > 0) && ((keyIndices[next] +1) >= len(keyValuePair[next].values))) { + next-- + } + //No more combination possible + if (next < 0) { + break + } + + keyIndices[next]++ + + //Reset indices for all other key elements + for idx = next+1; idx < keyCompCount; idx++ { + keyIndices[idx] = 0 + } + + TRACE_LOG(INFO_API, TRACE_CACHE, "Starting batch leaf creation - %s\n", c.batchLeaf) + //process batch leaf creation + if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, c.batchLeaf); errObj.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = CreateCVLErrObj(errObj) + return nil, cvlErrObj + } + c.batchLeaf = "" + } + } + + return topNode, cvlErrObj +} + +func (c *CVL) translateToYang(jsonMap *map[string]interface{}) (*yparser.YParserNode, CVLErrorInfo) { + + var cvlErrObj CVLErrorInfo + //Parse the map data to json tree + data, _ := jsonquery.ParseJsonMap(jsonMap) + var root *yparser.YParserNode + root = nil + var errObj yparser.YParserError + + for jsonNode := data.FirstChild; jsonNode != nil; jsonNode=jsonNode.NextSibling { + TRACE_LOG(INFO_API, TRACE_LIBYANG, "Top Node=%v\n", jsonNode.Data) + //Visit each top level list in a loop for creating table data + topNode, cvlErrObj := c.generateTableData(true, jsonNode) + + if topNode == nil { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + return nil, cvlErrObj + } + + if (root == nil) { + root = topNode + } else { + if root, errObj = c.yp.MergeSubtree(root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return nil, cvlErrObj + } + } + } + + return root, cvlErrObj +} + +//Validate config - syntax and semantics +func (c *CVL) validate (data *yparser.YParserNode) CVLRetCode { + + depData := c.fetchDataToTmpCache() + /* + if (depData != nil) { + if (0 != C.lyd_merge_to_ctx(&data, depData, C.LYD_OPT_DESTRUCT, ctx)) { + TRACE_LOG(1, "Failed to merge status data\n") + } + } + + if (0 != C.lyd_data_validate(&data, C.LYD_OPT_CONFIG, ctx)) { + fmt.Println("Validation failed\n") + return CVL_SYNTAX_ERROR + }*/ + + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "\nValidate1 data=%v\n", c.yp.NodeDump(data)) + errObj := c.yp.ValidateData(data, depData) + if yparser.YP_SUCCESS != errObj.ErrCode { + return CVL_FAILURE + } + + return CVL_SUCCESS +} + +func CreateCVLErrObj(errObj yparser.YParserError) CVLErrorInfo { + + cvlErrObj := CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[CVLRetCode(errObj.ErrCode)], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + return cvlErrObj + +} + +//Perform syntax checks +func (c *CVL) validateSyntax(data *yparser.YParserNode) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "Validating syntax \n....") + + if errObj := c.yp.ValidateSyntax(data); errObj.ErrCode != yparser.YP_SUCCESS { + + retCode := CVLRetCode(errObj.ErrCode) + + cvlErrObj = CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[retCode], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + + return cvlErrObj, retCode + } + + return cvlErrObj, CVL_SUCCESS +} + +//Perform semantic checks +func (c *CVL) validateSemantics(data *yparser.YParserNode, appDepData *yparser.YParserNode) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + + if (SkipSemanticValidation() == true) { + return cvlErrObj, CVL_SUCCESS + } + + //Get dependent data from + depData := c.fetchDataToTmpCache() //fetch data to temp cache for temporary validation + + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_SEMANTIC, "Validating semantics data=%s\n depData =%s\n, appDepData=%s\n....", c.yp.NodeDump(data), c.yp.NodeDump(depData), c.yp.NodeDump(appDepData)) + } + + if errObj := c.yp.ValidateSemantics(data, depData, appDepData); errObj.ErrCode != yparser.YP_SUCCESS { + + retCode := CVLRetCode(errObj.ErrCode) + + cvlErrObj = CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[retCode], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + + return cvlErrObj, retCode + } + + return cvlErrObj ,CVL_SUCCESS +} + +//Add config data item to accumulate per table +func (c *CVL) addCfgDataItem(configData *map[string]interface{}, + cfgDataItem CVLEditConfigData) (string, string){ + var cfgData map[string]interface{} + cfgData = *configData + + tblName, key := splitRedisKey(cfgDataItem.Key) + if (tblName == "" || key == "") { + //Bad redis key + return "", "" + } + + if (cfgDataItem.VOp == OP_DELETE) { + //Don't add data it is delete operation + return tblName, key + } + + if _, existing := cfgData[tblName]; existing { + fieldsMap := cfgData[tblName].(map[string]interface{}) + fieldsMap[key] = c.checkFieldMap(&cfgDataItem.Data) + } else { + fieldsMap := make(map[string]interface{}) + fieldsMap[key] = c.checkFieldMap(&cfgDataItem.Data) + cfgData[tblName] = fieldsMap + } + + return tblName, key +} + +//Get table entry from cache for redis key +func dbCacheEntryGet(tableName, key string) (*yparser.YParserNode, CVLRetCode) { + //First check if the table is cached + topNode, _ := dbCacheGet(tableName) + + + if (topNode != nil) { + //Convert to Yang keys + keyValuePair := getRedisToYangKeys(tableName, key) + + //Find if the entry is cached + keyCompStr := "" + for _, keyValItem := range keyValuePair { + keyCompStr = keyCompStr + fmt.Sprintf("[%s='%s']", + keyValItem.key, keyValItem.values[0]) + } + + entryNode := yparser.FindNode(topNode, fmt.Sprintf("//%s:%s/%s%s", + modelInfo.tableInfo[tableName].modelName, + modelInfo.tableInfo[tableName].modelName, + tableName, keyCompStr)) + + if (entryNode != nil) { + return entryNode, CVL_SUCCESS + } + } + + return nil, CVL_ERROR +} + +//Get the data from global cache +func dbCacheGet(tableName string) (*yparser.YParserNode, CVLRetCode) { + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Updating global cache for table %s", tableName) + dbCacheTmp, existing := cvg.db[tableName] + + if (existing == false) { + return nil, CVL_FAILURE //not even empty cache present + } + + if (dbCacheTmp.root != nil) { + if (dbCacheTmp.expiry != 0) { + //If cache is destroyable (i.e. expiry != 0), check if it has already expired. + //If not expired update the time stamp + if (time.Now().After(dbCacheTmp.startTime.Add(time.Second * time.Duration(dbCacheTmp.expiry)))) { + //Cache expired, clear the cache + dbCacheClear(tableName) + + return nil, CVL_ERROR + } + + //Since the cache is used actively, update the timestamp + dbCacheTmp.startTime = time.Now() + cvg.db[tableName] = dbCacheTmp + } + + return dbCacheTmp.root, CVL_SUCCESS + } else { + return nil, CVL_SUCCESS // return success for no entry in Redis db and hencec empty cache + } +} + +//Get the table data from redis and cache it in yang node format +//expiry =0 never expire the cache +func dbCacheSet(update bool, tableName string, expiry uint16) CVLRetCode { + + cvg.mutex.Lock() + + //Get the data from redis and save it + tableKeys, err:= redisClient.Keys(tableName + + modelInfo.tableInfo[tableName].redisKeyDelim + "*").Result() + + if (err != nil) { + cvg.mutex.Unlock() + return CVL_FAILURE + } + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Building global cache for table %s", tableName) + + tablePrefixLen := len(tableName + modelInfo.tableInfo[tableName].redisKeyDelim) + for _, tableKey := range tableKeys { + tableKey = tableKey[tablePrefixLen:] //remove table prefix + if (cvg.cv.tmpDbCache[tableName] == nil) { + cvg.cv.tmpDbCache[tableName] = map[string]interface{}{tableKey: nil} + } else { + tblMap := cvg.cv.tmpDbCache[tableName] + tblMap.(map[string]interface{})[tableKey] =nil + cvg.cv.tmpDbCache[tableName] = tblMap + } + } + + cvg.db[tableName] = dbCachedData{startTime:time.Now(), expiry: expiry, + root: cvg.cv.fetchDataToTmpCache()} + + if (Tracing == true) { + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Cached Data = %v\n", cvg.cv.yp.NodeDump(cvg.db[tableName].root)) + } + + cvg.mutex.Unlock() + + //install keyspace notification for updating the cache + if (update == false) { + installDbChgNotif() + } + + + return CVL_SUCCESS +} + +//Receive all updates for all tables on a single channel +func installDbChgNotif() { + if (len(cvg.db) > 1) { //notif running for at least one table added previously + cvg.stopChan <- 1 //stop active notification + } + + subList := make([]string, 0) + for tableName, _ := range cvg.db { + subList = append(subList, + fmt.Sprintf("__keyspace@%d__:%s%s*", modelInfo.tableInfo[tableName].dbNum, + tableName, modelInfo.tableInfo[tableName].redisKeyDelim)) + + } + + //Listen on multiple channels + cvg.pubsub = redisClient.PSubscribe(subList...) + + go func() { + keySpacePrefixLen := len("__keyspace@4__:") + + notifCh := cvg.pubsub.Channel() + for { + select { + case <-cvg.stopChan: + //stop this routine + return + case msg:= <-notifCh: + //Handle update + tbl, key := splitRedisKey(msg.Channel[keySpacePrefixLen:]) + if (tbl != "" && key != "") { + dbCacheUpdate(tbl, key, msg.Payload) + } + } + } + }() +} + +func dbCacheUpdate(tableName, key, op string) CVLRetCode { + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Updating global cache for table %s with key %s", tableName, key) + + //Find the node + //Delete the entry in yang tree + + cvg.mutex.Lock() + + node, _:= dbCacheEntryGet(tableName, key) + if (node != nil) { + //unlink and free the node + cvg.cv.yp.FreeNode(node) + } + + //Clear json map cache if any + cvg.cv.clearTmpDbCache() + + tableKeys := []string {key} + switch op { + case "hset", "hmset", "hdel": + //Get the entry from DB + for _, tableKey := range tableKeys { + cvg.cv.tmpDbCache[tableName] = map[string]interface{}{tableKey: nil} + } + + //Get the translated Yang tree + topNode := cvg.cv.fetchDataToTmpCache() + + //Merge the subtree with existing yang tree + var errObj yparser.YParserError + if (cvg.db[tableName].root != nil) { + if topNode, errObj = cvg.cv.yp.MergeSubtree(cvg.db[tableName].root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + cvg.mutex.Unlock() + return CVL_ERROR + } + } + + //Update DB map + db := cvg.db[tableName] + db.root = topNode + cvg.db[tableName] = db + + case "del": + //NOP, already deleted the entry + } + + cvg.mutex.Unlock() + + return CVL_SUCCESS +} + +//Clear cache data for given table +func dbCacheClear(tableName string) CVLRetCode { + cvg.cv.yp.FreeNode(cvg.db[tableName].root) + delete(cvg.db, tableName) + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Clearing global cache for table %s", tableName) + + return CVL_SUCCESS +} + diff --git a/src/cvl/cvl_api.go b/src/cvl/cvl_api.go new file mode 100644 index 0000000000..52b2316396 --- /dev/null +++ b/src/cvl/cvl_api.go @@ -0,0 +1,514 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 cvl + +import ( + "fmt" + "encoding/json" + "github.com/go-redis/redis" + "path/filepath" + "cvl/internal/yparser" + . "cvl/internal/util" +) + +type CVLValidateType uint +const ( + VALIDATE_NONE CVLValidateType = iota //Data is used as dependent data + VALIDATE_SYNTAX //Syntax is checked and data is used as dependent data + VALIDATE_SEMANTICS //Semantics is checked + VALIDATE_ALL //Syntax and Semantics are checked +) + +type CVLOperation uint +const ( + OP_NONE CVLOperation = 0 //Used to just validate the config without any operation + OP_CREATE = 1 << 0//For Create operation + OP_UPDATE = 1 << 1//For Update operation + OP_DELETE = 1 << 2//For Delete operation +) + +var cvlErrorMap = map[CVLRetCode]string { + CVL_SUCCESS : "Config Validation Success", + CVL_SYNTAX_ERROR : "Config Validation Syntax Error", + CVL_SEMANTIC_ERROR : "Config Validation Semantic Error", + CVL_SYNTAX_MISSING_FIELD : "Required Field is Missing", + CVL_SYNTAX_INVALID_FIELD : "Invalid Field Received", + CVL_SYNTAX_INVALID_INPUT_DATA : "Invalid Input Data Received", + CVL_SYNTAX_MULTIPLE_INSTANCE : "Multiple Field Instances Received", + CVL_SYNTAX_DUPLICATE : "Duplicate Instances Received", + CVL_SYNTAX_ENUM_INVALID : "Invalid Enum Value Received", + CVL_SYNTAX_ENUM_INVALID_NAME : "Invalid Enum Value Received", + CVL_SYNTAX_ENUM_WHITESPACE : "Enum name with leading/trailing whitespaces Received", + CVL_SYNTAX_OUT_OF_RANGE : "Value out of range/length/pattern (data)", + CVL_SYNTAX_MINIMUM_INVALID : "min-elements constraint not honored", + CVL_SYNTAX_MAXIMUM_INVALID : "max-elements constraint not honored", + CVL_SEMANTIC_DEPENDENT_DATA_MISSING : "Dependent Data is missing", + CVL_SEMANTIC_MANDATORY_DATA_MISSING : "Mandatory Data is missing", + CVL_SEMANTIC_KEY_ALREADY_EXIST : "Key already existing.", + CVL_SEMANTIC_KEY_NOT_EXIST : "Key does not exist.", + CVL_SEMANTIC_KEY_DUPLICATE : "Duplicate key received", + CVL_SEMANTIC_KEY_INVALID : "Invalid Key Received", + CVL_INTERNAL_UNKNOWN : "Internal Unknown Error", + CVL_ERROR : "Generic Error", + CVL_NOT_IMPLEMENTED : "Error Not Implemented", + CVL_FAILURE : "Generic Failure", +} + +//Error code +type CVLRetCode int +const ( + CVL_SUCCESS CVLRetCode = iota + CVL_ERROR + CVL_NOT_IMPLEMENTED + CVL_INTERNAL_UNKNOWN + CVL_FAILURE + CVL_SYNTAX_ERROR = CVLRetCode(yparser.YP_SYNTAX_ERROR) + CVL_SEMANTIC_ERROR = CVLRetCode(yparser.YP_SEMANTIC_ERROR) + CVL_SYNTAX_MISSING_FIELD = CVLRetCode(yparser.YP_SYNTAX_MISSING_FIELD) + CVL_SYNTAX_INVALID_FIELD = CVLRetCode(yparser.YP_SYNTAX_INVALID_FIELD) /* Invalid Field */ + CVL_SYNTAX_INVALID_INPUT_DATA = CVLRetCode(yparser.YP_SYNTAX_INVALID_INPUT_DATA) /*Invalid Input Data */ + CVL_SYNTAX_MULTIPLE_INSTANCE = CVLRetCode(yparser.YP_SYNTAX_MULTIPLE_INSTANCE) /* Multiple Field Instances */ + CVL_SYNTAX_DUPLICATE = CVLRetCode(yparser.YP_SYNTAX_DUPLICATE) /* Duplicate Fields */ + CVL_SYNTAX_ENUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_ENUM_INVALID) /* Invalid enum value */ + CVL_SYNTAX_ENUM_INVALID_NAME = CVLRetCode(yparser.YP_SYNTAX_ENUM_INVALID_NAME) /* Invalid enum name */ + CVL_SYNTAX_ENUM_WHITESPACE = CVLRetCode(yparser.YP_SYNTAX_ENUM_WHITESPACE) /* Enum name with leading/trailing whitespaces */ + CVL_SYNTAX_OUT_OF_RANGE = CVLRetCode(yparser.YP_SYNTAX_OUT_OF_RANGE) /* Value out of range/length/pattern (data) */ + CVL_SYNTAX_MINIMUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_MINIMUM_INVALID) /* min-elements constraint not honored */ + CVL_SYNTAX_MAXIMUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_MAXIMUM_INVALID) /* max-elements constraint not honored */ + CVL_SEMANTIC_DEPENDENT_DATA_MISSING = CVLRetCode(yparser.YP_SEMANTIC_DEPENDENT_DATA_MISSING) /* Dependent Data is missing */ + CVL_SEMANTIC_MANDATORY_DATA_MISSING = CVLRetCode(yparser.YP_SEMANTIC_MANDATORY_DATA_MISSING) /* Mandatory Data is missing */ + CVL_SEMANTIC_KEY_ALREADY_EXIST = CVLRetCode(yparser.YP_SEMANTIC_KEY_ALREADY_EXIST) /* Key already existing. */ + CVL_SEMANTIC_KEY_NOT_EXIST = CVLRetCode(yparser.YP_SEMANTIC_KEY_NOT_EXIST) /* Key is missing. */ + CVL_SEMANTIC_KEY_DUPLICATE = CVLRetCode(yparser.YP_SEMANTIC_KEY_DUPLICATE) /* Duplicate key. */ + CVL_SEMANTIC_KEY_INVALID = CVLRetCode(yparser.YP_SEMANTIC_KEY_INVALID) +) + +//Strcture for key and data in API +type CVLEditConfigData struct { + VType CVLValidateType //Validation type + VOp CVLOperation //Operation type + Key string //Key format : "PORT|Ethernet4" + Data map[string]string //Value : {"alias": "40GE0/28", "mtu" : 9100, "admin_status": down} +} + +func Initialize() CVLRetCode { + if (cvlInitialized == true) { + //CVL has already been initialized + return CVL_SUCCESS + } + + //Scan schema directory to get all schema files + modelFiles, err := filepath.Glob(CVL_SCHEMA + "/*.yin") + if err != nil { + CVL_LOG(FATAL ,"Could not read schema %v", err) + } + + yparser.Initialize() + + modelInfo.modelNs = make(map[string]modelNamespace) //redis table to model name + modelInfo.tableInfo = make(map[string]*modelTableInfo) //model namespace + modelInfo.allKeyDelims = make(map[string]bool) //all key delimiter + modelInfo.redisTableToYangList = make(map[string][]string) //Redis table to Yang list map + dbNameToDbNum = map[string]uint8{"APPL_DB": APPL_DB, "CONFIG_DB": CONFIG_DB} + + /* schema */ + for _, modelFilePath := range modelFiles { + _, modelFile := filepath.Split(modelFilePath) + + TRACE_LOG(INFO_DEBUG, TRACE_LIBYANG, "Parsing schema file %s ...\n", modelFilePath) + var module *yparser.YParserModule + if module, _ = yparser.ParseSchemaFile(modelFilePath); module == nil { + + CVL_LOG(FATAL,fmt.Sprintf("Unable to parse schema file %s", modelFile)) + return CVL_ERROR + } + + storeModelInfo(modelFile, module) + } + + //Add all table names to be fetched to validate 'must' expression + addTableNamesForMustExp() + + //Initialize redis Client + + redisClient = redis.NewClient(&redis.Options{ + Addr: ":6379", + Password: "", // no password set + DB: int(CONFIG_DB), // use APP DB + }) + + if (redisClient == nil) { + CVL_LOG(FATAL, "Unable to connect with Redis Config DB") + return CVL_ERROR + } + + //Load lua script into redis + loadLuaScript() + + cvlInitialized = true + + return CVL_SUCCESS +} + +func Finish() { + yparser.Finish() +} + +func ValidationSessOpen() (*CVL, CVLRetCode) { + cvl := &CVL{} + cvl.tmpDbCache = make(map[string]interface{}) + cvl.requestCache = make(map[string]map[string][]CVLEditConfigData) + cvl.yp = &yparser.YParser{} + + if (cvl == nil || cvl.yp == nil) { + return nil, CVL_FAILURE + } + + return cvl, CVL_SUCCESS +} + +func ValidationSessClose(c *CVL) CVLRetCode { + c.yp.DestroyCache() + c = nil + + return CVL_SUCCESS +} + +func (c *CVL) ValidateStartupConfig(jsonData string) CVLRetCode { + //Check config data syntax + //Finally validate + return CVL_NOT_IMPLEMENTED +} + +func (c *CVL) ValidateIncrementalConfig(jsonData string) CVLRetCode { + //Check config data syntax + //Fetch the depedent data + //Merge config and dependent data + //Finally validate + c.clearTmpDbCache() + var v interface{} + + b := []byte(jsonData) + if err := json.Unmarshal(b, &v); err != nil { + return CVL_SYNTAX_ERROR + } + + var dataMap map[string]interface{} = v.(map[string]interface{}) + + root, _ := c.translateToYang(&dataMap) + if root == nil { + return CVL_SYNTAX_ERROR + + } + + //Add and fetch entries if already exists in Redis + for tableName, data := range dataMap { + for key, _ := range data.(map[string]interface{}) { + c.addTableEntryToCache(tableName, key) + } + } + + existingData := c.fetchDataToTmpCache() + + //Merge existing data for update syntax or checking duplicate entries + var errObj yparser.YParserError + if (existingData != nil) { + if root, errObj = c.yp.MergeSubtree(root, existingData); + errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_ERROR + } + } + + //Clear cache + c.clearTmpDbCache() + + //Add tables for 'must' expression + for tableName, _ := range dataMap { + c.addTableDataForMustExp(OP_NONE, tableName) + } + + //Perform validation + if _, cvlRetCode := c.validateSemantics(root, nil); cvlRetCode != CVL_SUCCESS { + return cvlRetCode + } + + return CVL_SUCCESS +} + +//Validate data for operation +func (c *CVL) ValidateConfig(jsonData string) CVLRetCode { + c.clearTmpDbCache() + var v interface{} + + b := []byte(jsonData) + if err := json.Unmarshal(b, &v); err == nil { + var value map[string]interface{} = v.(map[string]interface{}) + root, _ := c.translateToYang(&value) + //if ret == CVL_SUCCESS && root != nil { + if root == nil { + return CVL_FAILURE + + } + + if (c.validate(root) != CVL_SUCCESS) { + return CVL_FAILURE + } + + } + + return CVL_SUCCESS +} + +//Validate config data based on edit operation - no marshalling in between +func (c *CVL) ValidateEditConfig(cfgData []CVLEditConfigData) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + + if (SkipValidation() == true) { + + return cvlErrObj, CVL_SUCCESS + } + + c.clearTmpDbCache() + + //Step 1: Get requested dat first + //add all dependent data to be fetched from Redis + requestedData := make(map[string]interface{}) + + for _, cfgDataItem := range cfgData { + if (VALIDATE_ALL != cfgDataItem.VType) { + continue + } + + //Add config data item to be validated + tbl,key := c.addCfgDataItem(&requestedData, cfgDataItem) + + //Add to request cache + reqTbl, exists := c.requestCache[tbl] + if (exists == false) { + //Create new table key data + reqTbl = make(map[string][]CVLEditConfigData) + } + cfgDataItemArr, _ := reqTbl[key] + cfgDataItemArr = append(cfgDataItemArr, cfgDataItem) + reqTbl[key] = cfgDataItemArr + c.requestCache[tbl] = reqTbl + + //Invalid table name or invalid key separator + if key == "" { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SYNTAX_ERROR + } + + switch cfgDataItem.VOp { + case OP_CREATE: + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + + case OP_UPDATE: + //Get the existing data from Redis to cache, so that final validation can be done after merging this dependent data + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + c.addTableEntryToCache(tbl, key) + + case OP_DELETE: + if (len(cfgDataItem.Data) > 0) { + //Delete a single field + if (len(cfgDataItem.Data) != 1) { + CVL_LOG(ERROR, "Only single field is allowed for field deletion") + } else { + for field, _ := range cfgDataItem.Data { + if (c.checkDeleteConstraint(cfgData, tbl, key, field) != CVL_SUCCESS) { + cvlErrObj.ErrCode = CVL_SEMANTIC_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_ERROR + } + break //only one field there + } + } + } else { + //Entire entry to be deleted + if (c.checkDeleteConstraint(cfgData, tbl, key, "") != CVL_SUCCESS) { + cvlErrObj.ErrCode = CVL_SEMANTIC_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_ERROR + } + //TBD : Can we do this ? + //No entry has depedency on this key, + //remove from requestCache, we don't need any more as depedent data + //delete(c.requestCache[tbl], key) + } + + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + + c.addTableEntryToCache(tbl, key) + } + } + + //Only for tracing + if (IsTraceSet()) { + jsonData := "" + + jsonDataBytes, err := json.Marshal(requestedData) + if (err == nil) { + jsonData = string(jsonDataBytes) + } else { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SYNTAX_ERROR + } + + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "Requested JSON Data = [%s]\n", jsonData) + } + + //Step 2 : Perform syntax validation only + yang, errN := c.translateToYang(&requestedData) + if (errN.ErrCode == CVL_SUCCESS) { + if cvlErrObj, cvlRetCode := c.validateSyntax(yang); cvlRetCode != CVL_SUCCESS { + return cvlErrObj, cvlRetCode + } + } else { + return errN,errN.ErrCode + } + + //Step 3 : Check keys and update dependent data + dependentData := make(map[string]interface{}) + + for _, cfgDataItem := range cfgData { + + if (cfgDataItem.VType == VALIDATE_ALL || cfgDataItem.VType == VALIDATE_SEMANTICS) { + //Step 3.1 : Check keys + switch cfgDataItem.VOp { + case OP_CREATE: + //Check key should not already exist + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 == nil && n > 0) { + //Check if key deleted and CREATE done in same session, + //allow to create the entry + tbl, key := splitRedisKey(cfgDataItem.Key) + deletedInSameSession := false + if tbl != "" && key != "" { + for _, cachedCfgData := range c.requestCache[tbl][key] { + if cachedCfgData.VOp == OP_DELETE { + deletedInSameSession = true + break + } + } + } + + if deletedInSameSession == false { + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s already exists", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_ALREADY_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_ALREADY_EXIST + + } else { + TRACE_LOG(INFO_API, TRACE_CREATE, "\nKey %s is deleted in same session, skipping key existence check for OP_CREATE operation", cfgDataItem.Key) + } + } + + c.yp.SetOperation("CREATE") + + case OP_UPDATE: + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 != nil || n == 0) { //key must exists + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s does not exist", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_ALREADY_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_NOT_EXIST + } + + c.yp.SetOperation("UPDATE") + + case OP_DELETE: + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 != nil || n == 0) { //key must exists + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s does not exist", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_NOT_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_NOT_EXIST + } + + c.yp.SetOperation("DELETE") + //store deleted keys + } + + }/* else if (cfgDataItem.VType == VALIDATE_NONE) { + //Step 3.2 : Get dependent data + + switch cfgDataItem.VOp { + case OP_CREATE: + //NOP + case OP_UPDATE: + //NOP + case OP_DELETE: + tbl,key := c.addCfgDataItem(&dependentData, cfgDataItem) + //update cache by removing deleted entry + c.updateDeleteDataToCache(tbl, key) + //store deleted keys + } + }*/ + } + + var depYang *yparser.YParserNode = nil + if (len(dependentData) > 0) { + depYang, errN = c.translateToYang(&dependentData) + } + //Step 4 : Perform validation + if cvlErrObj, cvlRetCode1 := c.validateSemantics(yang, depYang); cvlRetCode1 != CVL_SUCCESS { + return cvlErrObj, cvlRetCode1 + } + + //Cache validated data + /* + if errObj := c.yp.CacheSubtree(false, yang); errObj.ErrCode != yparser.YP_SUCCESS { + TRACE_LOG(INFO_API, TRACE_CACHE, "Could not cache validated data") + } + */ + + c.yp.DestroyCache() + return cvlErrObj, CVL_SUCCESS +} + +/* Fetch the Error Message from CVL Return Code. */ +func GetErrorString(retCode CVLRetCode) string{ + + return cvlErrorMap[retCode] + +} + +//Validate key only +func (c *CVL) ValidateKeys(key []string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} + +//Validate key and data +func (c *CVL) ValidateKeyData(key string, data string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} + +//Validate key, field and value +func (c *CVL) ValidateFields(key string, field string, value string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} diff --git a/src/cvl/cvl_luascript.go b/src/cvl/cvl_luascript.go new file mode 100644 index 0000000000..fd4c863ad1 --- /dev/null +++ b/src/cvl/cvl_luascript.go @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 cvl +import ( + "github.com/go-redis/redis" +) + +//Redis server side script +func loadLuaScript() { + luaScripts = make(map[string]*redis.Script) + + // Find entry which has given fieldName and value + luaScripts["find_key"] = redis.NewScript(` + local tableName=ARGV[1] + local sep=ARGV[2] + local fieldName=ARGV[3] + local fieldValue=ARGV[4] + + -- Check if field value is part of key + local entries=redis.call('KEYS', tableName..sep.."*"..fieldValue.."*") + + if (entries[1] ~= nil) + then + return entries[1] + else + + -- Search through all keys for fieldName and fieldValue + local entries=redis.call('KEYS', tableName..sep.."*") + + local idx = 1 + while(entries[idx] ~= nil) + do + local val = redis.call("HGET", entries[idx], fieldName) + if (val == fieldValue) + then + -- Return the key + return entries[idx] + end + + idx = idx + 1 + end + end + + -- Could not find the key + return "" + `) + +} diff --git a/src/cvl/cvl_test.go b/src/cvl/cvl_test.go new file mode 100644 index 0000000000..d4a4205261 --- /dev/null +++ b/src/cvl/cvl_test.go @@ -0,0 +1,3491 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 cvl_test + +import ( + "cvl" + "encoding/json" + "fmt" + "github.com/go-redis/redis" + "io/ioutil" + "os" + "os/exec" + "strings" + "syscall" + "testing" + "runtime" + . "cvl/internal/util" + "cvl/internal/yparser" +) + +type testEditCfgData struct { + filedescription string + cfgData string + depData string + retCode cvl.CVLRetCode +} + +var rclient *redis.Client +var port_map map[string]interface{} +var filehandle *os.File + +/* Dependent port channel configuration. */ +var depDataMap = map[string]interface{} { + "PORTCHANNEL" : map[string]interface{} { + "PortChannel001": map[string] interface{} { + "admin_status": "up", + "mtu": "9100", + }, + "PortChannel002": map[string] interface{} { + "admin_status": "up", + "mtu": "9100", + }, + }, + "PORTCHANNEL_MEMBER": map[string]interface{} { + "PortChannel001|Ethernet4": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel001|Ethernet8": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel001|Ethernet12": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel002|Ethernet20": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel002|Ethernet24": map[string] interface{} { + "NULL": "NULL", + }, + }, +} + +/* Converts JSON Data in a File to Map. */ +func convertJsonFileToMap(t *testing.T, fileName string) map[string]string { + var mapstr map[string]string + + jsonData := convertJsonFileToString(t, fileName) + byteData := []byte(jsonData) + + err := json.Unmarshal(byteData, &mapstr) + + if err != nil { + fmt.Println("Failed to convert Json File to map:", err) + } + + return mapstr + +} + +/* Converts JSON Data in a File to Map. */ +func convertDataStringToMap(t *testing.T, dataString string) map[string]string { + var mapstr map[string]string + + byteData := []byte(dataString) + + err := json.Unmarshal(byteData, &mapstr) + + if err != nil { + fmt.Println("Failed to convert Json Data String to map:", err) + } + + return mapstr + +} + +/* Converts JSON Data in a File to String. */ +func convertJsonFileToString(t *testing.T, fileName string) string { + var jsonData string + + data, err := ioutil.ReadFile(fileName) + + if err != nil { + fmt.Printf("\nFailed to read data file : %v\n", err) + } else { + jsonData = string(data) + } + + return jsonData +} + +/* Converts JSON config to map which can be loaded to Redis */ +func loadConfig(key string, in []byte) map[string]interface{} { + var fvp map[string]interface{} + + err := json.Unmarshal(in, &fvp) + if err != nil { + fmt.Printf("Failed to Unmarshal %v err: %v", in, err) + } + if key != "" { + kv := map[string]interface{}{} + kv[key] = fvp + return kv + } + return fvp +} + +/* Separator for keys. */ +func getSeparator() string { + return "|" +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, mpi map[string]interface{}) { + for key, fv := range mpi { + switch fv.(type) { + case map[string]interface{}: + for subKey, subValue := range fv.(map[string]interface{}) { + newKey := key + getSeparator() + subKey + _, err := rclient.Del(newKey).Result() + + if err != nil { + fmt.Printf("Invalid data for db: %v : %v %v", newKey, subValue, err) + } + + } + default: + fmt.Printf("Invalid data for db: %v : %v", key, fv) + } + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, mpi map[string]interface{}) { + for key, fv := range mpi { + switch fv.(type) { + case map[string]interface{}: + for subKey, subValue := range fv.(map[string]interface{}) { + newKey := key + getSeparator() + subKey + _, err := rclient.HMSet(newKey, subValue.(map[string]interface{})).Result() + + if err != nil { + fmt.Printf("Invalid data for db: %v : %v %v", newKey, subValue, err) + } + + } + default: + fmt.Printf("Invalid data for db: %v : %v", key, fv) + } + } +} + +func compareErrorDetails(cvlErr cvl.CVLErrorInfo, expCode cvl.CVLRetCode, errAppTag string, constraintmsg string) bool { + + if ((cvlErr.ErrCode == expCode) && ((cvlErr.ErrAppTag == errAppTag) || (cvlErr.ConstraintErrMsg == constraintmsg))) { + return true + } + + return false +} + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Network: "tcp", + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Prepares the database in Redis Server. */ +func prepareDb() { + rclient = getConfigDbClient() + + fileName := "testdata/port_table.json" + PortsMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + port_map = loadConfig("", PortsMapByte) + + loadConfigDB(rclient, port_map) + loadConfigDB(rclient, depDataMap) +} + +func WriteToFile(message string) { + pc := make([]uintptr, 10) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + + message = f.Name()+ "\n" + message + + if _, err := filehandle.Write([]byte(message)); err != nil { + fmt.Println("Unable to write to cvl test log file") + } + + message = "\n-------------------------------------------------\n" + + + if _, err := filehandle.Write([]byte(message)); err != nil { + fmt.Println("Unable to write to cvl test log file") + } +} + +/* Setup before starting of test. */ +func TestMain(m *testing.M) { + + redisAlreadyRunning := false + pidOfRedis, err := exec.Command("/bin/pidof", "redis-server").Output() + if err == nil && string(pidOfRedis) != "\n" { + redisAlreadyRunning = true + } + + if (redisAlreadyRunning == false) { + //Redis not running, lets start it + _, err := exec.Command("/bin/sh", "-c", "sudo /etc/init.d/redis-server start").Output() + if err != nil { + fmt.Println(err.Error()) + } + + } + + os.Remove("testdata/cvl_test_details.log") + + filehandle, err = os.OpenFile("testdata/cvl_test_details.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if err != nil { + fmt.Println("Could not open the log file for writing.") + } + + + /* Prepare the Redis database. */ + prepareDb() + SetTrace(true) + cvl.Debug(true) + code := m.Run() + //os.Exit(m.Run()) + + unloadConfigDB(rclient, port_map) + unloadConfigDB(rclient, depDataMap) + cvl.Finish() + rclient.Close() + rclient.FlushDB() + + if err := filehandle.Close(); err != nil { + //log.Fatal(err) + } + + if (redisAlreadyRunning == false) { + //If Redis was not already running, close the instance that we ran + _, err := exec.Command("/bin/sh", "-c", "sudo /etc/init.d/redis-server stop").Output() + if err != nil { + fmt.Println(err.Error()) + } + + } + + os.Exit(code) + +} + +//Test Initialize() API +func TestInitialize(t *testing.T) { + ret := cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + ret = cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl re-initialization should not fail") + } +} + +//Test Initialize() API +func TestFinish(t *testing.T) { + ret := cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + cvl.Finish() + + //Initialize again for other test cases to run + cvl.Initialize() +} + +/* ValidateEditConfig with user input in file . */ +func TestValidateEditConfig_CfgFile(t *testing.T) { + + tests := []struct { + filedescription string + cfgDataFile string + depDataFile string + retCode cvl.CVLRetCode + }{ + {filedescription: "ACL_DATA", cfgDataFile: "testdata/aclrule.json", depDataFile: "testdata/acltable.json", retCode: cvl.CVL_SUCCESS}, + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + + t.Run(tc.filedescription, func(t *testing.T) { + + jsonEditCfg_Create_DependentMap := convertJsonFileToMap(t, tc.depDataFile) + jsonEditCfg_Create_ConfigMap := convertJsonFileToMap(t, tc.cfgDataFile) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", jsonEditCfg_Create_DependentMap}, + } + + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_RULE|TestACL1|Rule1", jsonEditCfg_Create_ConfigMap}, + } + + + cvlErrObj, err = cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + }) + } + + cvl.ValidationSessClose(cvSess) +} + +/* ValidateEditConfig with user input inline. */ +func TestValidateEditConfig_CfgStrBuffer(t *testing.T) { + + type testStruct struct { + filedescription string + cfgData string + depData string + retCode cvl.CVLRetCode + } + + cvSess, _ := cvl.ValidationSessOpen() + + tests := []testStruct{} + + /* Iterate through data present is separate file. */ + for index, _ := range json_edit_config_create_acl_table_dependent_data { + tests = append(tests, testStruct{filedescription: "ACL_DATA", cfgData: json_edit_config_create_acl_rule_config_data[index], + depData: json_edit_config_create_acl_table_dependent_data[index], retCode: cvl.CVL_SUCCESS}) + } + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(tc.filedescription, func(t *testing.T) { + jsonEditCfg_Create_DependentMap := convertDataStringToMap(t, tc.depData) + jsonEditCfg_Create_ConfigMap := convertDataStringToMap(t, tc.cfgData) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", jsonEditCfg_Create_DependentMap}, + } + + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_RULE|TestACL1|Rule1", jsonEditCfg_Create_ConfigMap}, + } + + + cvlErrObj, err = cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + }) + } + + cvl.ValidationSessClose(cvSess) +} +/* API when config is given as string buffer. */ +func TestValidateConfig_CfgStrBuffer(t *testing.T) { + type testStruct struct { + filedescription string + jsonString string + retCode cvl.CVLRetCode + } + + tests := []testStruct{} + + for index, _ := range json_validate_config_data { + // Fetch the modelName. + result := strings.Split(json_validate_config_data[index], "{") + modelName := strings.Trim(strings.Replace(strings.TrimSpace(result[1]), "\"", "", -1), ":") + + tests = append(tests, testStruct{filedescription: modelName, jsonString: json_validate_config_data[index], retCode: cvl.CVL_SUCCESS}) + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(fmt.Sprintf("%s [%d]", tc.filedescription, index+1), func(t *testing.T) { + err := cvSess.ValidateConfig(tc.jsonString) + + + if err != tc.retCode { + t.Errorf("Config Validation failed.") + } + + }) + } + + cvl.ValidationSessClose(cvSess) + +} + + +/* API when config is given as json file. */ +func TestValidateConfig_CfgFile(t *testing.T) { + + /* Structure containing file information. */ + tests := []struct { + filedescription string + fileName string + retCode cvl.CVLRetCode + }{ + {filedescription: "Config File - VLAN,ACL,PORTCHANNEL", fileName: "testdata/config_db1.json", retCode: cvl.CVL_SUCCESS}, + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(tc.filedescription, func(t *testing.T) { + jsonString := convertJsonFileToString(t, tc.fileName) + err := cvSess.ValidateConfig(jsonString) + + + if err != tc.retCode { + t.Errorf("Config Validation failed.") + } + + }) + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Delete_Must_Check_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@": "Ethernet3,Ethernet5", + }, + "TestACL2": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE" : map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + "TestACL2|Rule2": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL2|Rule2", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* +func TestValidateEditConfig_Delete_Must_Check_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@": "Ethernet3,Ethernet5", + }, + }, + "ACL_RULE" : map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + unloadConfigDB(rclient, depDataMap) +} +*/ + +//Validate invalid json data +func TestValidateConfig_Negative(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + jsonData := `{ + "VLANjunk": { + "Vlan100": { + "members": [ + "Ethernet4", + "Ethernet8" + ], + "vlanid": "100" + } + } + }` + + err := cvSess.ValidateConfig(jsonData) + + if err == cvl.CVL_SUCCESS { //Should return failure + t.Errorf("Config Validation failed.") + } + + cvl.ValidationSessClose(cvSess) +} + +/* Delete Existing Key.*/ +/* +func TestValidateEditConfig_Delete_Semantic_ACLTableReference_Positive(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1005": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE": map[string]interface{} { + "TestACL1005|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1005|Rule1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} +*/ + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Valid_FieldValue(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with invalid field value. */ +func TestValidateEditConfig_Create_Syntax_CableLength(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "CABLE_LENGTH|AZURE", + map[string]string{ + "Ethernet8": "5m", + "Ethernet12": "5m", + "Ethernet16": "5m", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* API to test edit config with invalid field value. */ +func TestValidateEditConfig_Create_Syntax_Invalid_FieldValue(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", map[string]string{ + "stage": "INGRESS", + "type": "junk", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Invalid_PacketAction_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD777", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Invalid_SrcPrefix_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/3288888", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvl.ValidationSessClose(cvSess) + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidIPAddress_Negative(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1a.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_OutofBound_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "19099090909090", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidProtocol_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "10388888", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* API to test edit config with valid syntax. */ +//Note: Syntax check is done first before dependency check +//hence ACL_TABLE is not required here +func TestValidateEditConfig_Create_Syntax_InvalidRange_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "777779000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidCharNEw_Negative(t *testing.T) { + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1jjjj|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_SpecialChar_Positive(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule_1-2", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSessNew, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSessNew.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSessNew) + + if err != cvl.CVL_SUCCESS { //Should succeed + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Create_Syntax_InvalidKeyName_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "AC&&***L_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Semantic_AdditionalInvalidNode_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + "extra": "shhs", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_Semantic_MissingMandatoryNode_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan101", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Invalid_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULERule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_IncompleteKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* +func TestValidateEditConfig_Update_Syntax_DependentData_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "MIRROR_SESSION|everflow", + map[string]string{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string{ + "MIRROR_ACTION": "everflow", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrObj)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrObj) + } + +} + +func TestValidateEditConfig_Create_Syntax_DependentData_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch1", + map[string]string{ + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch2", + map[string]string{ + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet4", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet8", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet12", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet16", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet20", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "102", + "members@": "Ethernet24,ch1,Ethernet8", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} +*/ + +func TestValidateEditConfig_Delete_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Delete_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1:Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrObj)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrObj) + } + +} + +func TestValidateEditConfig_Update_Semantic_Invalid_Key_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103uuuu", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Delete_Semantic_Positive(t *testing.T) { + depDataMap := map[string]interface{}{ + "MIRROR_SESSION": map[string]interface{}{ + "everflow": map[string]interface{}{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Delete_Semantic_KeyNotExisting_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow0", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Semantic_MissingKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL177|Rule1", + map[string]string{ + "MIRROR_ACTION": "everflow", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Duplicate_Key_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL100": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + //Load same key in DB + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL100", + map[string]string{ + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Update_Semantic_Positive(t *testing.T) { + + // Create ACL Table. + fileName := "testdata/create_acl_table.json" + aclTableMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_map := loadConfig("", aclTableMapByte) + loadConfigDB(rclient, mpi_acl_table_map) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, mpi_acl_table_map) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateConfig_Update_Semantic_Vlan_Negative(t *testing.T) { + + cvSess, _ := cvl.ValidationSessOpen() + + jsonData := `{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "107" + } + } + }` + + err := cvSess.ValidateConfig(jsonData) + + if err == cvl.CVL_SUCCESS { //Expected semantic failure + t.Errorf("Config Validation failed -- error details.") + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Redis_Positive(t *testing.T) { + + // Create ACL Table. + fileName := "testdata/create_acl_table13.json" + aclTableMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_map := loadConfig("", aclTableMapByte) + loadConfigDB(rclient, mpi_acl_table_map) + + // Create ACL Rule. + fileName = "testdata/acl_rule.json" + aclTableMapRule, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_rule := loadConfig("", aclTableMapRule) + loadConfigDB(rclient, mpi_acl_table_rule) + + depDataMap := map[string]interface{}{ + /* Use MIRROR session once supported --- + "MIRROR_SESSION": map[string]interface{}{ + "everflow2": map[string]interface{}{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + */ + } + + loadConfigDB(rclient, depDataMap) + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL13|Rule1", + map[string]string{ + /* Use Mirror session when supported + "MIRROR_ACTION": "everflow2", + */ + "PACKET_ACTION" : "FORWARD", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, mpi_acl_table_map) + unloadConfigDB(rclient, mpi_acl_table_rule) + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Invalid_Op_Seq(t *testing.T) { + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "DROP", + "L4_SRC_PORT": "781", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //Validation should fail + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Redis_Negative(t *testing.T) { + + /* ACL does not exist.*/ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "MIRROR_ACTION": "everflow0", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* Create with User provided dependent data. */ +func TestValidateEditConfig_Create_Syntax_DependentData_Redis_Positive(t *testing.T) { + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL22", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL22|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err = cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +/* Delete Non-Existing Key.*/ +func TestValidateEditConfig_Delete_Semantic_ACLTableReference_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Dependent_CacheData(t *testing.T) { + + cvSess, _ := cvl.ValidationSessOpen() + + //Create ACL rule + cfgDataAcl := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL14", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err1 := cvSess.ValidateEditConfig(cfgDataAcl) + + //Create ACL rule + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL14|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err2 := cvSess.ValidateEditConfig(cfgDataRule) + + if err1 != cvl.CVL_SUCCESS || err2 != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Create_DepData_In_MultiSess(t *testing.T) { + + //Create ACL rule - Session 1 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataAcl := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL16", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err1 := cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + //Create ACL rule - Session 2, validation should fail + cvSess, _ = cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL16|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + _, err2 := cvSess.ValidateEditConfig(cfgDataRule) + + + cvl.ValidationSessClose(cvSess) + + if err1 != cvl.CVL_SUCCESS || err2 == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_DepData_From_Redis_Negative11(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL188|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + + cvl.ValidationSessClose(cvSess) + + if (err != cvl.CVL_SEMANTIC_DEPENDENT_DATA_MISSING) { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_DepData_From_Redis(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Range_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan701", + map[string]string{ + "vlanid": "7001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "vlanid-invalid", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Length_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + "policy_desc": "A12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "policy-desc-invalid-length", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Pattern_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan5001", + map[string]string{ + "vlanid": "102", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "vlan-name-invalid", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_ErrAppTag_In_Must_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "102", + "members@": "Ethernet24,Ethernet8", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + 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 { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InValid_FieldValue(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +//EditConfig(Create) with dependent data from redis +func TestValidateEditConfig_Create_DepData_From_Redis_Negative(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgDataRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL2|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation should fail.") + } + + unloadConfigDB(rclient, depDataMap) +} + +// EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Create_Chained_Leafref_DepData_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan100": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "100", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + "Ethernet2" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "2", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan100|Ethernet1", + map[string]string { + "tagging_mode" : "tagged", + }, + }, + } + + _, err := cvSess.ValidateEditConfig(cfgDataVlan) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + _, err = cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +//EditConfig(Delete) deleting entry already used by other table as leafref +func TestValidateEditConfig_Delete_Dep_Leafref_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE": map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|TestACL1", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataVlan) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { //should be semantic failure + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +//EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Create_Chained_Leafref_DepData_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + _, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} +func TestValidateEditConfig_Create_Syntax_InvalidVlanRange_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan5002", + map[string]string{ + "vlanid": "6002", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed with details %v.", cvlErrInfo) + } + +} + +//Test Initialize() API +func TestLogging(t *testing.T) { + ret := cvl.Initialize() + str := "Testing" + cvl.CVL_LOG(INFO ,"This is Info Log %s", str) + cvl.CVL_LOG(WARNING,"This is Warning Log %s", str) + cvl.CVL_LOG(ERROR ,"This is Error Log %s", str) + cvl.CVL_LOG(INFO_API ,"This is Info API %s", str) + cvl.CVL_LOG(INFO_TRACE ,"This is Info Trace %s", str) + cvl.CVL_LOG(INFO_DEBUG ,"This is Info Debug %s", str) + cvl.CVL_LOG(INFO_DATA ,"This is Info Data %s", str) + cvl.CVL_LOG(INFO_DETAIL ,"This is Info Detail %s", str) + cvl.CVL_LOG(INFO_ALL ,"This is Info all %s", str) + + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + cvl.Finish() + + //Initialize again for other test cases to run + cvl.Initialize() +} + +func TestValidateEditConfig_DepData_Through_Cache(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + //Modify entry + depDataMap = map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "mtu": "9200", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet3,Ethernet5", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + _, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +/* Delete field for an existing key.*/ +func TestValidateEditConfig_Delete_Single_Field_Positive(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "policy_desc":"Test ACL desc", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|TestACL1", + map[string]string{ + "policy_desc":"Test ACL desc", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateConfig_Repeated_Keys_Positive(t *testing.T) { + jsonData := `{ + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + } + }` + + cvSess, _ := cvl.ValidationSessOpen() + err := cvSess.ValidateConfig(jsonData) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details.") + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Delete_Entry_Then_Dep_Leafref_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan20": map[string] interface{} { + "vlanid": "20", + }, + }, + "VLAN_MEMBER": map[string]interface{} { + "Vlan20|Ethernet4": map[string] interface{} { + "tagging_mode": "tagged", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAcl := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN_MEMBER|Vlan20|Ethernet4", + map[string]string { + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataAcl) + + cfgDataAcl = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_DELETE, + "VLAN_MEMBER|Vlan20|Ethernet4", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN|Vlan20", + map[string]string { + }, + }, + } + + cvlErrInfo, err = cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { //should be success + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestBadSchema(t *testing.T) { + env := os.Environ() + env[0] = env[0] + " " + + if _, err := os.Stat("/usr/sbin/schema"); os.IsNotExist(err) { + //Corrupt some schema file + exec.Command("/bin/sh", "-c", "/bin/cp testdata/schema/sonic-port.yin testdata/schema/sonic-port.yin.bad" + + " && /bin/sed -i '1 a ' testdata/schema/sonic-port.yin.bad").Output() + + //Parse bad schema file + if module, _ := yparser.ParseSchemaFile("testdata/schema/sonic-port.yin.bad"); module != nil { //should fail + t.Errorf("Bad schema parsing should fail.") + } + + //Revert to + exec.Command("/bin/sh", "-c", "/bin/rm testdata/schema/sonic-port.yin.bad").Output() + } else { + //Corrupt some schema file + exec.Command("/bin/sh", "-c", "/bin/cp /usr/sbin/schema/sonic-port.yin /usr/sbin/schema/sonic-port.yin.bad" + + " && /bin/sed -i '1 a ' /usr/sbin/schema/sonic-port.yin.bad").Output() + + //Parse bad schema file + if module, _ := yparser.ParseSchemaFile("/usr/sbin/schema/sonic-port.yin.bad"); module != nil { //should fail + t.Errorf("Bad schema parsing should fail.") + } + + //Revert to + exec.Command("/bin/sh", "-c", "/bin/rm /usr/sbin/schema/sonic-port.yin.bad").Output() + } + +} + + +func TestServicability_Debug_Trace(t *testing.T) { + + cvl.Debug(false) + SetTrace(false) + + //Reload the config file by sending SIGUSR2 to ourself + p, err := os.FindProcess(os.Getpid()) + if (err == nil) { + p.Signal(syscall.SIGUSR2) + } + + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + cvSess.ValidateEditConfig(cfgDataRule) + + unloadConfigDB(rclient, depDataMap) + + SetTrace(true) + cvl.Debug(true) + + cvl.ValidationSessClose(cvSess) + + //Reload the bad config file by sending SIGUSR2 to ourself + exec.Command("/bin/sh", "-c", "/bin/cp conf/cvl_cfg.json conf/cvl_cfg.json.orig" + + " && /bin/echo 'junk' >> conf/cvl_cfg.json").Output() + p, err = os.FindProcess(os.Getpid()) + if (err == nil) { + p.Signal(syscall.SIGUSR2) + } + exec.Command("/bin/sh", "-c", "/bin/mv conf/cvl_cfg.json.orig conf/cvl_cfg.json").Output() +} + +// EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Delete_Create_Same_Entry_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan100": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "100", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN|Vlan100", + map[string]string { + }, + }, + } + + _, err1 := cvSess.ValidateEditConfig(cfgDataVlan) + + //Same entry getting created again + cfgDataVlan = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan100", + map[string]string { + "vlanid": "100", + }, + }, + } + + _, err2 := cvSess.ValidateEditConfig(cfgDataVlan) + + if err1 != cvl.CVL_SUCCESS || err2 != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } + + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateStartupConfig_Positive(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateStartupConfig("") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateIncrementalConfig_Positive(t *testing.T) { + existingDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan800": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "800", + }, + "Vlan801": map[string]interface{} { + "members@": "Ethernet2", + "vlanid": "801", + }, + }, + "VLAN_MEMBER": map[string]interface{} { + "Vlan800|Ethernet1": map[string] interface{} { + "tagging_mode": "tagged", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + "Ethernet2" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, existingDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + jsonData := `{ + "VLAN": { + "Vlan800": { + "members": [ + "Ethernet1", + "Ethernet2" + ], + "vlanid": "800" + } + }, + "VLAN_MEMBER": { + "Vlan800|Ethernet1": { + "tagging_mode": "untagged" + }, + "Vlan801|Ethernet2": { + "tagging_mode": "tagged" + } + } + }` + + ret := cvSess.ValidateIncrementalConfig(jsonData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, existingDataMap) + + if ret != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } +} + +//Validate key only +func TestValidateKeys(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateKeys([]string{}) { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +//Validate key and data +func TestValidateKeyData(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateKeyData("", "") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +//Validate key, field and value +func TestValidateFields(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateFields("", "", "") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Two_Updates_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAcl := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string { + "policy_desc": "Test ACL", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string { + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { //should be success + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) + +} +func TestValidateEditConfig_Create_Syntax_DependentData_PositivePortChannel(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet28,PortChannel002", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + + +func TestValidateEditConfig_Create_Syntax_DependentData_PositivePortChannelIfName(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet24,PortChannel001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_DependentData_NegativePortChannelEthernet(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "PortChannel001,Ethernet4", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_DependentData_NegativePortChannelNew(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet12,PortChannel001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Use_Updated_Data_As_Create_DependentData_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan201": map[string] interface{} { + "vlanid": "201", + "members@": "Ethernet8", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "VLAN|Vlan201", + map[string]string{ + "members@": "Ethernet8,Ethernet12", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + unloadConfigDB(rclient, depDataMap) + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + return + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan201|Ethernet8", + map[string]string{ + "tagging_mode": "tagged", + }, + }, + } + + cvlErrInfo, _ = cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Use_Updated_Data_As_Create_DependentData_Single_Call_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan201": map[string] interface{} { + "vlanid": "201", + "members@": "Ethernet8", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "VLAN|Vlan201", + map[string]string{ + "members@": "Ethernet8,Ethernet12", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan201|Ethernet8", + map[string]string{ + "tagging_mode": "tagged", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_AllKeys_Positive(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|Ethernet24|10.0.0.0/31", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_OptionalKey_Positive(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|Ethernet24", + map[string]string{ + /*"vrf-name": "Vrf1", -- Enable once VRF YANG implemented */ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_IncorrectKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|10.0.0.0/31", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_EmptyNode_Positive(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "PORT|Ethernet0", + map[string]string{ + "description": "", + "index": "3", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} diff --git a/src/cvl/internal/util/util.go b/src/cvl/internal/util/util.go new file mode 100644 index 0000000000..7404500d04 --- /dev/null +++ b/src/cvl/internal/util/util.go @@ -0,0 +1,255 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 util + +import ( + "os" + "fmt" + "runtime" + "encoding/json" + "io/ioutil" + "os/signal" + "syscall" + "strings" + "flag" + log "github.com/golang/glog" +) + +var CVL_SCHEMA string = "schema/" +var CVL_CFG_FILE string = "/usr/sbin/cvl_cfg.json" + +//package init function +func init() { + if (os.Getenv("CVL_SCHEMA_PATH") != "") { + CVL_SCHEMA = os.Getenv("CVL_SCHEMA_PATH") + "/" + } + + if (os.Getenv("CVL_CFG_FILE") != "") { + CVL_CFG_FILE = os.Getenv("CVL_CFG_FILE") + } +} + +var cvlCfgMap map[string]string + +/* Logging Level for CVL global logging. */ +type CVLLogLevel uint8 +const ( + INFO = 0 + iota + WARNING + ERROR + FATAL + INFO_API + INFO_TRACE + INFO_DEBUG + INFO_DATA + INFO_DETAIL + INFO_ALL +) + +var cvlTraceFlags uint32 + +/* Logging levels for CVL Tracing. */ +type CVLTraceLevel uint32 +const ( + TRACE_MIN = 0 + TRACE_MAX = 8 + TRACE_CACHE = 1 << TRACE_MIN + TRACE_LIBYANG = 1 << 1 + TRACE_YPARSER = 1 << 2 + TRACE_CREATE = 1 << 3 + TRACE_UPDATE = 1 << 4 + TRACE_DELETE = 1 << 5 + TRACE_SEMANTIC = 1 << 6 + TRACE_ONERROR = 1 << 7 + TRACE_SYNTAX = 1 << TRACE_MAX + +) + + +var traceLevelMap = map[int]string { + /* Caching operation traces */ + TRACE_CACHE : "TRACE_CACHE", + /* Libyang library traces. */ + TRACE_LIBYANG: "TRACE_LIBYANG", + /* Yang Parser traces. */ + TRACE_YPARSER : "TRACE_YPARSER", + /* Create operation traces. */ + TRACE_CREATE : "TRACE_CREATE", + /* Update operation traces. */ + TRACE_UPDATE : "TRACE_UPDATE", + /* Delete operation traces. */ + TRACE_DELETE : "TRACE_DELETE", + /* Semantic Validation traces. */ + TRACE_SEMANTIC : "TRACE_SEMANTIC", + /* Syntax Validation traces. */ + TRACE_SYNTAX : "TRACE_SYNTAX", + /* Trace on Error. */ + TRACE_ONERROR : "TRACE_ONERROR", +} + +var Tracing bool = false + +var traceFlags uint16 = 0 + +func SetTrace(on bool) { + if (on == true) { + Tracing = true + traceFlags = 1 + } else { + Tracing = false + traceFlags = 0 + } +} + +func IsTraceSet() bool { + if (traceFlags == 0) { + return false + } else { + return true + } +} + +func TRACE_LEVEL_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + + if (IsTraceSet() == false) { + return + } + + level = (level - INFO_API) + 1; + + traceEnabled := false + if ((cvlTraceFlags & (uint32)(tracelevel)) != 0) { + traceEnabled = true + } + + if IsTraceSet() == true && traceEnabled == true { + pc := make([]uintptr, 10) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + file, line := f.FileLine(pc[0]) + + fmt.Printf("%s:%d %s(): ", file, line, f.Name()) + fmt.Printf(fmtStr+"\n", args...) + } else { + if (traceEnabled == true) { + log.V(level).Infof(fmtStr, args...) + } + } +} + +func CVL_LEVEL_LOG(level CVLLogLevel, format string, args ...interface{}) { + + switch level { + case INFO: + log.Infof(format, args...) + case WARNING: + log.Warningf(format, args...) + case ERROR: + log.Errorf(format, args...) + case FATAL: + log.Fatalf(format, args...) + case INFO_API: + log.V(1).Infof(format, args...) + case INFO_TRACE: + log.V(2).Infof(format, args...) + case INFO_DEBUG: + log.V(3).Infof(format, args...) + case INFO_DATA: + log.V(4).Infof(format, args...) + case INFO_DETAIL: + log.V(5).Infof(format, args...) + case INFO_ALL: + log.V(6).Infof(format, args...) + } + +} + + +func ConfigFileSyncHandler() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGUSR2) + go func() { + for { + <-sigs + cvlCfgMap := ReadConfFile() + + if cvlCfgMap == nil { + return + } + + CVL_LEVEL_LOG(INFO ,"Received SIGUSR2. Changed configuration values are %v", cvlCfgMap) + + + if (strings.Compare(cvlCfgMap["LOGTOSTDERR"], "true") == 0) { + SetTrace(true) + flag.Set("logtostderr", "true") + flag.Set("stderrthreshold", cvlCfgMap["STDERRTHRESHOLD"]) + flag.Set("v", cvlCfgMap["VERBOSITY"]) + } + } + }() + +} + +func ReadConfFile() map[string]string{ + + /* Return if CVL configuration file is not present. */ + if _, err := os.Stat(CVL_CFG_FILE); os.IsNotExist(err) { + return nil + } + + data, err := ioutil.ReadFile(CVL_CFG_FILE) + + err = json.Unmarshal(data, &cvlCfgMap) + + if err != nil { + CVL_LEVEL_LOG(INFO ,"Error in reading cvl configuration file %v", err) + return nil + } + + CVL_LEVEL_LOG(INFO ,"Current Values of CVL Configuration File %v", cvlCfgMap) + var index uint32 + + for index = TRACE_MIN ; index < TRACE_MAX ; index++ { + if (strings.Compare(cvlCfgMap[traceLevelMap[1 << index]], "true") == 0) { + cvlTraceFlags = cvlTraceFlags | (1 << index) + } + } + + return cvlCfgMap +} + +func SkipValidation() bool { + val, existing := cvlCfgMap["SKIP_VALIDATION"] + if (existing == true) && (val == "true") { + return true + } + + return false +} + +func SkipSemanticValidation() bool { + val, existing := cvlCfgMap["SKIP_SEMANTIC_VALIDATION"] + if (existing == true) && (val == "true") { + return true + } + + return false +} diff --git a/src/cvl/internal/yparser/yparser.go b/src/cvl/internal/yparser/yparser.go new file mode 100644 index 0000000000..55c3f6d367 --- /dev/null +++ b/src/cvl/internal/yparser/yparser.go @@ -0,0 +1,699 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 yparser + +/* Yang parser using libyang library */ + +import ( + "os" + "strings" + log "github.com/golang/glog" + . "cvl/internal/util" +) + +/* +#cgo LDFLAGS: -lyang +#include +#include +#include +#include +#include + +struct lyd_node* lyd_parse_data_path(struct ly_ctx *ctx, const char *path, LYD_FORMAT format, int options) { + return lyd_parse_path(ctx, path, format, options); +} + +struct lyd_node *lyd_parse_data_mem(struct ly_ctx *ctx, const char *data, LYD_FORMAT format, int options) +{ + return lyd_parse_mem(ctx, data, format, options); +} + +int lyd_data_validate(struct lyd_node **node, int options, struct ly_ctx *ctx) +{ + return lyd_validate(node, options, ctx); +} + +int lyd_data_validate_all(const char *data, const char *depData, const char *othDepData, int options, struct ly_ctx *ctx) +{ + struct lyd_node *pData; + struct lyd_node *pDepData; + struct lyd_node *pOthDepData; + + if ((data == NULL) || (data[0] == '\0')) + { + return -1; + } + + pData = lyd_parse_mem(ctx, data, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS); + if (pData == NULL) + { + return -1; + } + + if ((depData != NULL) && (depData[0] != '\0')) + { + if (NULL != (pDepData = lyd_parse_mem(ctx, depData, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS))) + { + if (0 != lyd_merge_to_ctx(&pData, pDepData, LYD_OPT_DESTRUCT, ctx)) + { + return -1; + } + } + } + + if ((othDepData != NULL) && (othDepData[0] != '\0')) + { + if (NULL != (pOthDepData = lyd_parse_mem(ctx, othDepData, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS))) + { + if (0 != lyd_merge_to_ctx(&pData, pOthDepData, LYD_OPT_DESTRUCT, ctx)) + { + return -1; + } + } + } + + return lyd_validate(&pData, LYD_OPT_CONFIG, ctx); +} + +int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, const char *leafVal) +{ + char s[4048]; + char *name, *val, *saveptr; + + strcpy(s, leafVal); + + name = strtok_r(s, "#", &saveptr); + + while (name != NULL) + { + val = strtok_r(NULL, "#", &saveptr); + if (val != NULL) + { + if (NULL == lyd_new_leaf(parent, module, name, val)) + { + return -1; + } + } + + name = strtok_r(NULL, "#", &saveptr); + } + + return 0; +} + +struct lyd_node *lyd_find_node(struct lyd_node *root, const char *xpath) +{ + struct ly_set *set = NULL; + struct lyd_node *node = NULL; + + if (root == NULL) + { + return NULL; + } + + set = lyd_find_path(root, xpath); + if (set == NULL || set->number == 0) { + return NULL; + } + + node = set->set.d[0]; + ly_set_free(set); + + return node; +} + +int lyd_change_leaf_data(struct lyd_node *leaf, const char *val_str) +{ + return lyd_change_leaf((struct lyd_node_leaf_list *)leaf, val_str); +} + +*/ +import "C" + +type YParserCtx C.struct_ly_ctx +type YParserNode C.struct_lyd_node +type YParserModule C.struct_lys_module + +var ypCtx *YParserCtx +var ypOpModule *YParserModule +var ypOpRoot *YParserNode //Operation root +var ypOpNode *YParserNode //Operation node + + +type YParser struct { + ctx *YParserCtx //Parser context + root *YParserNode //Top evel root for validation + operation string //Edit operation +} + +/* YParser Error Structure */ +type YParserError struct { + ErrCode YParserRetCode /* Error Code describing type of error. */ + Msg string /* Detailed error message. */ + ErrTxt string /* High level error message. */ + TableName string /* List/Table having error */ + Keys []string /* Keys of the Table having error. */ + Field string /* Field Name throwing error . */ + Value string /* Field Value throwing error */ + ErrAppTag string /* Error App Tag. */ +} + +type YParserRetCode int +const ( + YP_SUCCESS YParserRetCode = 1000 + iota + YP_SYNTAX_ERROR + YP_SEMANTIC_ERROR + YP_SYNTAX_MISSING_FIELD + YP_SYNTAX_INVALID_FIELD /* Invalid Field */ + YP_SYNTAX_INVALID_INPUT_DATA /*Invalid Input Data */ + YP_SYNTAX_MULTIPLE_INSTANCE /* Multiple Field Instances */ + YP_SYNTAX_DUPLICATE /* Duplicate Fields */ + YP_SYNTAX_ENUM_INVALID /* Invalid enum value */ + YP_SYNTAX_ENUM_INVALID_NAME /* Invalid enum name */ + YP_SYNTAX_ENUM_WHITESPACE /* Enum name with leading/trailing whitespaces */ + YP_SYNTAX_OUT_OF_RANGE /* Value out of range/length/pattern (data) */ + YP_SYNTAX_MINIMUM_INVALID /* min-elements constraint not honored */ + YP_SYNTAX_MAXIMUM_INVALID /* max-elements constraint not honored */ + YP_SEMANTIC_DEPENDENT_DATA_MISSING /* Dependent Data is missing */ + YP_SEMANTIC_MANDATORY_DATA_MISSING /* Mandatory Data is missing */ + YP_SEMANTIC_KEY_ALREADY_EXIST /* Key already existing */ + YP_SEMANTIC_KEY_NOT_EXIST /* Key is missing */ + YP_SEMANTIC_KEY_DUPLICATE /* Duplicate key */ + YP_SEMANTIC_KEY_INVALID /* Invalid key */ + YP_INTERNAL_UNKNOWN +) + +var yparserInitialized bool = false + +func TRACE_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + TRACE_LEVEL_LOG(level, tracelevel , fmtStr, args...) +} + +func CVL_LOG(level CVLLogLevel, fmtStr string, args ...interface{}) { + CVL_LEVEL_LOG(level, fmtStr, args...) +} + +//package init function +func init() { + if (os.Getenv("CVL_DEBUG") != "") { + Debug(true) + } +} + +func Debug(on bool) { + if (on == true) { + C.ly_verb(C.LY_LLDBG) + } else { + C.ly_verb(C.LY_LLERR) + } +} + +func Initialize() { + if (yparserInitialized != true) { + ypCtx = (*YParserCtx)(C.ly_ctx_new(C.CString(CVL_SCHEMA), 0)) + C.ly_verb(C.LY_LLERR) + // yparserInitialized = true + } +} + +func Finish() { + if (yparserInitialized == true) { + C.ly_ctx_destroy((*C.struct_ly_ctx)(ypCtx), nil) + // yparserInitialized = false + } +} + +//Parse YIN schema file +func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { + /* schema */ + TRACE_LOG(INFO_DEBUG, TRACE_YPARSER, "Parsing schema file %s ...\n", modelFile) + + module := C.lys_parse_path((*C.struct_ly_ctx)(ypCtx), C.CString(modelFile), C.LYS_IN_YIN) + if module == nil { + return nil, getErrorDetails() + } + + if (strings.Contains(modelFile, "sonic-common.yin") == true) { + ypOpModule = (*YParserModule)(module) + ypOpRoot = (*YParserNode)(C.lyd_new(nil, (*C.struct_lys_module)(ypOpModule), C.CString("operation"))) + ypOpNode = (*YParserNode)(C.lyd_new_leaf((*C.struct_lyd_node)(ypOpRoot), (*C.struct_lys_module)(ypOpModule), C.CString("operation"), C.CString("NOP"))) + } + + return (*YParserModule)(module), YParserError {ErrCode : YP_SUCCESS,} +} + +//Add child node to a parent node +func(yp *YParser) AddChildNode(module *YParserModule, parent *YParserNode, name string) *YParserNode { + + ret := (*YParserNode)(C.lyd_new((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(name))) + if (ret == nil) { + TRACE_LOG(INFO_DEBUG, TRACE_YPARSER, "Failed parsing node %s\n", name) + } + + return ret +} + +//Add child node to a parent node +func(yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, multiLeaf string) YParserError { + if (0 != C.lyd_multi_new_leaf((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(multiLeaf))) { + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to create Multi Leaf Data = %v", multiLeaf) + } + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} + +} + +//Return entire subtree in XML format in string +func (yp *YParser) NodeDump(root *YParserNode) string { + if (root == nil) { + return "" + } else { + var outBuf *C.char + C.lyd_print_mem(&outBuf, (*C.struct_lyd_node)(root), C.LYD_XML, C.LYP_WITHSIBLINGS) + return C.GoString(outBuf) + } +} + +//Merge source with destination +func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserError) { + rootTmp := (*C.struct_lyd_node)(root) + + if (root == nil || node == nil) { + return root, YParserError {ErrCode: YP_SUCCESS} + } + + if (Tracing == true) { + rootdumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Root subtree = %v\n", rootdumpStr) + } + + if (0 != C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_OPT_DESTRUCT, + (*C.struct_ly_ctx)(ypCtx))) { + return (*YParserNode)(rootTmp), getErrorDetails() + } + + if (Tracing == true) { + dumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Merged subtree = %v\n", dumpStr) + } + + return (*YParserNode)(rootTmp), YParserError {ErrCode : YP_SUCCESS,} +} + +//Cache subtree +func (yp *YParser) CacheSubtree(dupSrc bool, node *YParserNode) YParserError { + rootTmp := (*C.struct_lyd_node)(yp.root) + var dup *C.struct_lyd_node + + if (node == nil) { + //nothing to merge + return YParserError {ErrCode : YP_SUCCESS,} + } + + if (dupSrc == true) { + dup = C.lyd_dup_withsiblings((*C.struct_lyd_node)(node), C.LYD_DUP_OPT_RECURSIVE | C.LYD_DUP_OPT_NO_ATTR) + } else { + dup = (*C.struct_lyd_node)(node) + } + + if (yp.root != nil) { + if (0 != C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(dup), C.LYD_OPT_DESTRUCT, + (*C.struct_ly_ctx)(ypCtx))) { + return getErrorDetails() + } + } else { + yp.root = (*YParserNode)(dup) + } + + if (Tracing == true) { + dumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Cached subtree = %v\n", dumpStr) + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +func (yp *YParser) DestroyCache() YParserError { + + if (yp.root != nil) { + C.lyd_free_withsiblings((*C.struct_lyd_node)(yp.root)) + yp.root = nil + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Set operation +func (yp *YParser) SetOperation(op string) YParserError { + if (ypOpNode == nil) { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } + + if (0 != C.lyd_change_leaf_data((*C.struct_lyd_node)(ypOpNode), C.CString(op))) { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } + + yp.operation = op + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Validate config - syntax and semantics +func (yp *YParser) ValidateData(data, depData *YParserNode) YParserError { + + var dataRoot *YParserNode + + if (depData != nil) { + if dataRoot, _ = yp.MergeSubtree(data, depData); dataRoot == nil { + CVL_LOG(ERROR, "Failed to merge dependent data\n") + return getErrorDetails() + } + } + + dataRootTmp := (*C.struct_lyd_node)(dataRoot) + + if (0 != C.lyd_data_validate(&dataRootTmp, C.LYD_OPT_CONFIG, (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataRootTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate data = %v", strData) + } + + CVL_LOG(ERROR, "Validation failed\n") + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Perform syntax checks +func (yp *YParser) ValidateSyntax(data *YParserNode) YParserError { + dataTmp := (*C.struct_lyd_node)(data) + + //Just validate syntax + if (0 != C.lyd_data_validate(&dataTmp, C.LYD_OPT_EDIT | C.LYD_OPT_NOEXTDEPS, + (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate Syntax, data = %v", strData) + } + return getErrorDetails() + } + //fmt.Printf("Error Code from libyang is %d\n", C.ly_errno) + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Perform semantic checks +func (yp *YParser) ValidateSemantics(data, depData, appDepData *YParserNode) YParserError { + + var dataTmp *C.struct_lyd_node + + if (data != nil) { + dataTmp = (*C.struct_lyd_node)(data) + } else if (depData != nil) { + dataTmp = (*C.struct_lyd_node)(depData) + } else if (yp.root != nil) { + dataTmp = (*C.struct_lyd_node)(yp.root) + } else { + if (yp.operation == "CREATE") || (yp.operation == "UPDATE") { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } else { + return YParserError {ErrCode : YP_SUCCESS,} + } + } + + //parse dependent data + if (data != nil && depData != nil) { + + //merge input data and dependent data for semantic validation + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(depData), + C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge dependent data\n") + return getErrorDetails() + } + } + + //Merge cached data + if ((data != nil || depData != nil) && yp.root != nil) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(yp.root), + 0, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge cached dependent data\n") + return getErrorDetails() + } + } + + //Merge appDepData + if (appDepData != nil) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(appDepData), + C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge other dependent data\n") + return getErrorDetails() + } + } + + //Add operation for constraint check + if (ypOpRoot != nil) { + //if (0 != C.lyd_insert_sibling(&dataTmp, (*C.struct_lyd_node)(ypOpRoot))) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(ypOpRoot), + 0, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to insert operation node") + return getErrorDetails() + } + } + + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Semantics data = %v", strData) + } + + //Check semantic validation + if (0 != C.lyd_data_validate(&dataTmp, C.LYD_OPT_CONFIG, (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData1 := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate Semantics, data = %v", strData1) + } + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +func (yp *YParser) FreeNode(node *YParserNode) YParserError { + + C.lyd_free_withsiblings((*C.struct_lyd_node)(node)) + + return YParserError {ErrCode : YP_SUCCESS,} +} + +/* This function translates LIBYANG error code to valid YPARSER error code. */ +func translateLYErrToYParserErr(LYErrcode int) YParserRetCode { + var ypErrCode YParserRetCode + + switch LYErrcode { + case C.LYVE_SUCCESS: /**< no error */ + ypErrCode = YP_SUCCESS + case C.LYVE_XML_MISS, C.LYVE_INARG, C.LYVE_MISSELEM: /**< missing XML object */ + ypErrCode = YP_SYNTAX_MISSING_FIELD + case C.LYVE_XML_INVAL, C.LYVE_XML_INCHAR, C.LYVE_INMOD, C.LYVE_INELEM , C.LYVE_INVAL, C.LYVE_MCASEDATA:/**< invalid XML object */ + ypErrCode = YP_SYNTAX_INVALID_FIELD + case C.LYVE_EOF, C.LYVE_INSTMT, C.LYVE_INPAR, C.LYVE_INID, C.LYVE_MISSSTMT, C.LYVE_MISSARG: /**< invalid statement (schema) */ + ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA + case C.LYVE_TOOMANY: /**< too many instances of some object */ + ypErrCode = YP_SYNTAX_MULTIPLE_INSTANCE + case C.LYVE_DUPID, C.LYVE_DUPLEAFLIST, C.LYVE_DUPLIST, C.LYVE_NOUNIQ:/**< duplicated identifier (schema) */ + ypErrCode = YP_SYNTAX_DUPLICATE + case C.LYVE_ENUM_INVAL: /**< invalid enum value (schema) */ + ypErrCode = YP_SYNTAX_ENUM_INVALID + case C.LYVE_ENUM_INNAME: /**< invalid enum name (schema) */ + ypErrCode = YP_SYNTAX_ENUM_INVALID_NAME + case C.LYVE_ENUM_WS: /**< enum name with leading/trailing whitespaces (schema) */ + ypErrCode = YP_SYNTAX_ENUM_WHITESPACE + case C.LYVE_KEY_NLEAF, C.LYVE_KEY_CONFIG, C.LYVE_KEY_TYPE : /**< list key is not a leaf (schema) */ + ypErrCode = YP_SEMANTIC_KEY_INVALID + case C.LYVE_KEY_MISS, C.LYVE_PATH_MISSKEY: /**< list key not found (schema) */ + ypErrCode = YP_SEMANTIC_KEY_NOT_EXIST + case C.LYVE_KEY_DUP: /**< duplicated key identifier (schema) */ + ypErrCode = YP_SEMANTIC_KEY_DUPLICATE + case C.LYVE_NOMIN:/**< min-elements constraint not honored (data) */ + ypErrCode = YP_SYNTAX_MINIMUM_INVALID + case C.LYVE_NOMAX:/**< max-elements constraint not honored (data) */ + ypErrCode = YP_SYNTAX_MAXIMUM_INVALID + case C.LYVE_NOMUST, C.LYVE_NOWHEN, C.LYVE_INWHEN, C.LYVE_NOLEAFREF : /**< unsatisfied must condition (data) */ + ypErrCode = YP_SEMANTIC_DEPENDENT_DATA_MISSING + case C.LYVE_NOMANDCHOICE:/**< max-elements constraint not honored (data) */ + ypErrCode = YP_SEMANTIC_MANDATORY_DATA_MISSING + case C.LYVE_PATH_EXISTS: /**< target node already exists (path) */ + ypErrCode = YP_SEMANTIC_KEY_ALREADY_EXIST + default: + ypErrCode = YP_INTERNAL_UNKNOWN + + } + return ypErrCode +} + +/* This function performs parsing and processing of LIBYANG error messages. */ +func getErrorDetails() YParserError { + var key []string + var errtableName string + var ElemVal string + var errMessage string + var ElemName string + var errText string + var msg string + var ypErrCode YParserRetCode = YP_INTERNAL_UNKNOWN + var errMsg, errPath, errAppTag string + + ctx := (*C.struct_ly_ctx)(ypCtx) + ypErrFirst := C.ly_err_first(ctx); + + + if (ypErrFirst == nil) { + return YParserError { + TableName : errtableName, + ErrCode : ypErrCode, + Keys : key, + Value : ElemVal, + Field : ElemName, + Msg : errMessage, + ErrTxt: errText, + ErrAppTag: errAppTag, + } + } + + + if ((ypErrFirst != nil) && ypErrFirst.prev.no == C.LY_SUCCESS) { + return YParserError { + ErrCode : YP_SUCCESS, + } + } + + if (ypErrFirst != nil) { + errMsg = C.GoString(ypErrFirst.prev.msg) + errPath = C.GoString(ypErrFirst.prev.path) + errAppTag = C.GoString(ypErrFirst.prev.apptag) + } + + + /* Example error messages. + 1. Leafref "/sonic-port:sonic-port/sonic-port:PORT/sonic-port:ifname" of value "Ethernet668" points to a non-existing leaf. + (path: /sonic-interface:sonic-interface/INTERFACE[portname='Ethernet668'][ip_prefix='10.0.0.0/31']/portname) + 2. A vlan interface member cannot be part of portchannel which is already a vlan member + (path: /sonic-vlan:sonic-vlan/VLAN[name='Vlan1001']/members[.='Ethernet8']) + 3. Value "ch1" does not satisfy the constraint "Ethernet([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])" (range, length, or pattern). + (path: /sonic-vlan:sonic-vlan/VLAN[name='Vlan1001']/members[.='ch1'])*/ + + + /* Fetch the TABLE Name which are in CAPS. */ + resultTable := strings.SplitN(errPath, "[", 2) + if (len(resultTable) >= 2) { + resultTab := strings.Split(resultTable[0], "/") + errtableName = resultTab[len(resultTab) -1] + + /* Fetch the Error Elem Name. */ + resultElem := strings.Split(resultTable[1], "/") + ElemName = resultElem[len(resultElem) -1] + } + + /* Fetch the invalid field name. */ + result := strings.Split(errMsg, "\"") + if (len(result) > 1) { + for i := range result { + if (strings.Contains(result[i], "value")) || + (strings.Contains(result[i], "Value")) { + ElemVal = result[i+1] + } + } + } else if (len(result) == 1) { + /* Custom contraint error message like in must statement. + This can be used by App to display to user. + */ + errText = errMsg + } + + // Find key elements + resultKey := strings.Split(errPath, "=") + for i := range resultKey { + if (strings.Contains(resultKey[i], "]")) { + newRes := strings.Split(resultKey[i], "]") + key = append(key, newRes[0]) + } + } + + /* Form the error message. */ + msg = "[" + for _, elem := range key { + msg = msg + elem + " " + } + msg = msg + "]" + + /* For non-constraint related errors , print below error message. */ + if (len(result) > 1) { + errMessage = errtableName + " with keys" + msg + " has field " + + ElemName + " with invalid value " + ElemVal + }else { + /* Dependent data validation error. */ + errMessage = "Dependent data validation failed for table " + + errtableName + " with keys" + msg + } + + + if (C.ly_errno == C.LY_EVALID) { //Validation failure + ypErrCode = translateLYErrToYParserErr(int(ypErrFirst.prev.vecode)) + + } else { + switch (C.ly_errno) { + case C.LY_EMEM: + errText = "Memory allocation failure" + + case C.LY_ESYS: + errText = "System call failure" + + case C.LY_EINVAL: + errText = "Invalid value" + + case C.LY_EINT: + errText = "Internal error" + + case C.LY_EPLUGIN: + errText = "Error reported by a plugin" + } + } + + errObj := YParserError { + TableName : errtableName, + ErrCode : ypErrCode, + Keys : key, + Value : ElemVal, + Field : ElemName, + Msg : errMessage, + ErrTxt: errText, + ErrAppTag: errAppTag, + } + + TRACE_LOG(INFO_API, TRACE_YPARSER, "YParser error details: %v...", errObj) + + return errObj +} + +func FindNode(root *YParserNode, xpath string) *YParserNode { + return (*YParserNode)(C.lyd_find_node((*C.struct_lyd_node)(root), C.CString(xpath))) +} diff --git a/src/cvl/jsondata_test.go b/src/cvl/jsondata_test.go new file mode 100644 index 0000000000..87fe059e27 --- /dev/null +++ b/src/cvl/jsondata_test.go @@ -0,0 +1,70 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 cvl_test + +var json_edit_config_create_acl_table_dependent_data = []string{`{ + "stage": "INGRESS", + "type": "L3" + }`} + +var json_edit_config_create_acl_rule_config_data = []string{ + `{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + + + }`} + +var json_validate_config_data = []string{`{ + "INTERFACE": { + "Ethernet8|10.0.0.0/31": {}, + "Ethernet12|10.0.0.2/31": {}, + "Ethernet16|10.0.0.4/31": {} + } + }`, + `{ + "DEVICE_METADATA": { + "localhost": { + "hwsku": "Force10-S6100", + "default_bgp_status": "up", + "docker_routing_config_mode": "unified", + "hostname": "sonic-s6100-01", + "platform": "x86_64-dell_s6100_c2538-r0", + "mac": "4c:76:25:f4:70:82", + "default_pfcwd_status": "disable", + "deployment_id": "1", + "type": "ToRRouter" + } + } + }`, + `{ + "CABLE_LENGTH": { + "AZURE": { + "Ethernet8": "5m", + "Ethernet12": "5m", + "Ethernet16": "5m", + } + } + }`} diff --git a/src/cvl/schema/Makefile b/src/cvl/schema/Makefile new file mode 100644 index 0000000000..e1132130a2 --- /dev/null +++ b/src/cvl/schema/Makefile @@ -0,0 +1,66 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR=../../.. +YANGDIR=$(TOPDIR)/models/yang +sonic_yang=$(YANGDIR)/sonic +std_yang_common=$(YANGDIR)/common/ +sonic_yang_common=$(sonic_yang)/common +pyang_plugin_dir=$(TOPDIR)/tools/pyang/pyang_plugins + +src_files=$(wildcard $(sonic_yang)/*.yang) +src_files += $(wildcard $(sonic_yang_common)/*.yang) +out=$(patsubst %.yang, %.yin, $(shell ls -1 $(sonic_yang)/*.yang | cut -d'/' -f7)) +out_common=$(patsubst %.yang, %.yin, $(shell ls -1 $(sonic_yang_common)/*.yang | cut -d'/' -f8)) +out_tree=$(patsubst %.yang, %.tree, $(src_files)) +search_path=$(std_yang_common):$(sonic_yang):$(sonic_yang_common) + +all: schema + +schema: $(out) $(out_common) + + +schema-tree: $(out_tree) + +#Build YANG models +%.yin:$(sonic_yang)/%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $@ | cut -d . -f1`-deviation.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) --plugindir $(pyang_plugin_dir) \ + -f yin-cvl $$devOpt $< -o `basename $@` + + +#Build common YANG models +%.yin:$(sonic_yang_common)/%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $@ | cut -d . -f1`-deviation.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) --plugindir $(pyang_plugin_dir) \ + -f yin-cvl $$devOpt $< -o `basename $@` + +%.tree:%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $< | cut -d . -f1`-dev.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) -f tree $$devOpt $< -o `basename $@` + +clean: + @echo "Removing files ..." + rm -rf *.yin *.tree diff --git a/src/cvl/testdata/acl_rule.json b/src/cvl/testdata/acl_rule.json new file mode 100644 index 0000000000..46c5a3745a --- /dev/null +++ b/src/cvl/testdata/acl_rule.json @@ -0,0 +1,10 @@ +{ +"ACL_RULE": { + "TestACL13|Rule1": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "IPV4", + "L4_SRC_PORT": "0" + } + } +} diff --git a/src/cvl/testdata/aclrule.json b/src/cvl/testdata/aclrule.json new file mode 100644 index 0000000000..41a768225a --- /dev/null +++ b/src/cvl/testdata/aclrule.json @@ -0,0 +1,9 @@ +{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "IP_TYPE" : "IPV4", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" +} diff --git a/src/cvl/testdata/acltable.json b/src/cvl/testdata/acltable.json new file mode 100644 index 0000000000..810caee0db --- /dev/null +++ b/src/cvl/testdata/acltable.json @@ -0,0 +1,4 @@ +{ +"stage": "INGRESS", +"type": "L3" +} diff --git a/src/cvl/testdata/config_db.json b/src/cvl/testdata/config_db.json new file mode 100644 index 0000000000..ad2d952f28 --- /dev/null +++ b/src/cvl/testdata/config_db.json @@ -0,0 +1,107 @@ +{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan100|Ethernet44": { + "tagging_mode": "tagged" + }, + "Vlan1200|Ethernet8": { + "tagging_mode": "tagged" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} diff --git a/src/cvl/testdata/config_db1.json b/src/cvl/testdata/config_db1.json new file mode 100644 index 0000000000..ec6ce5e734 --- /dev/null +++ b/src/cvl/testdata/config_db1.json @@ -0,0 +1,133 @@ +{ + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104" + } + } +} + diff --git a/src/cvl/testdata/config_db2.json b/src/cvl/testdata/config_db2.json new file mode 100644 index 0000000000..e6be83ac4e --- /dev/null +++ b/src/cvl/testdata/config_db2.json @@ -0,0 +1,3437 @@ +{ + "VLAN_MEMBER": { + "Vlan51|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan51|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan97|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet108": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet124": { + "tagging_mode": "untagged" + }, + "Vlan101|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan199|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "VLAN": { + "Vlan51": { + "members": [ + "Ethernet108", + "Ethernet124" + ], + "vlanid": "51" + }, + "Vlan97": { + "members": [ + "Ethernet4" + ], + "vlanid": "97" + }, + "Vlan99": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "99" + }, + "Vlan101": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "101" + }, + "Vlan102": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "102" + }, + "Vlan103": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "103" + }, + "Vlan104": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "104" + }, + "Vlan105": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "105" + }, + "Vlan106": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "106" + }, + "Vlan107": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "107" + }, + "Vlan108": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "108" + }, + "Vlan109": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "109" + }, + "Vlan110": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "110" + }, + "Vlan111": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "111" + }, + "Vlan112": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "112" + }, + "Vlan113": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "113" + }, + "Vlan114": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "114" + }, + "Vlan115": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "115" + }, + "Vlan116": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "116" + }, + "Vlan117": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "117" + }, + "Vlan118": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "118" + }, + "Vlan119": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "119" + }, + "Vlan120": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "120" + }, + "Vlan121": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "121" + }, + "Vlan122": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "122" + }, + "Vlan123": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "123" + }, + "Vlan124": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "124" + }, + "Vlan125": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "125" + }, + "Vlan126": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "126" + }, + "Vlan127": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "127" + }, + "Vlan128": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "128" + }, + "Vlan129": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "129" + }, + "Vlan130": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "130" + }, + "Vlan131": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "131" + }, + "Vlan132": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "132" + }, + "Vlan133": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "133" + }, + "Vlan134": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "134" + }, + "Vlan135": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "135" + }, + "Vlan136": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "136" + }, + "Vlan137": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "137" + }, + "Vlan138": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "138" + }, + "Vlan139": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "139" + }, + "Vlan140": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "140" + }, + "Vlan141": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "141" + }, + "Vlan142": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "142" + }, + "Vlan143": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "143" + }, + "Vlan144": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "144" + }, + "Vlan145": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "145" + }, + "Vlan146": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "146" + }, + "Vlan147": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "147" + }, + "Vlan148": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "148" + }, + "Vlan149": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "149" + }, + "Vlan150": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "150" + }, + "Vlan151": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "151" + }, + "Vlan152": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "152" + }, + "Vlan153": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "153" + }, + "Vlan154": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "154" + }, + "Vlan155": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "155" + }, + "Vlan156": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "156" + }, + "Vlan157": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "157" + }, + "Vlan158": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "158" + }, + "Vlan159": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "159" + }, + "Vlan160": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "160" + }, + "Vlan161": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "161" + }, + "Vlan162": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "162" + }, + "Vlan163": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "163" + }, + "Vlan164": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "164" + }, + "Vlan165": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "165" + }, + "Vlan166": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "166" + }, + "Vlan167": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "167" + }, + "Vlan168": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "168" + }, + "Vlan169": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "169" + }, + "Vlan170": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "170" + }, + "Vlan171": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "171" + }, + "Vlan172": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "172" + }, + "Vlan173": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "173" + }, + "Vlan174": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "174" + }, + "Vlan175": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "175" + }, + "Vlan176": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "176" + }, + "Vlan177": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "177" + }, + "Vlan178": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "178" + }, + "Vlan179": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "179" + }, + "Vlan180": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "180" + }, + "Vlan181": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "181" + }, + "Vlan182": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "182" + }, + "Vlan183": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "183" + }, + "Vlan184": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "184" + }, + "Vlan185": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "185" + }, + "Vlan186": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "186" + }, + "Vlan187": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "187" + }, + "Vlan188": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "188" + }, + "Vlan189": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "189" + }, + "Vlan190": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "190" + }, + "Vlan191": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "191" + }, + "Vlan192": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "192" + }, + "Vlan193": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "193" + }, + "Vlan194": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "194" + }, + "Vlan195": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "195" + }, + "Vlan196": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "196" + }, + "Vlan197": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "197" + }, + "Vlan198": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "198" + }, + "Vlan199": { + "members": [ + "Ethernet64" + ], + "vlanid": "199" + }, + "Vlan200": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "200" + }, + "Vlan201": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "201" + }, + "Vlan202": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "202" + }, + "Vlan203": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "203" + }, + "Vlan204": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "204" + }, + "Vlan205": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "205" + }, + "Vlan206": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "206" + }, + "Vlan207": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "207" + }, + "Vlan208": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "208" + }, + "Vlan209": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "209" + }, + "Vlan210": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "210" + }, + "Vlan211": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "211" + }, + "Vlan212": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "212" + }, + "Vlan213": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "213" + }, + "Vlan214": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "214" + }, + "Vlan215": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "215" + }, + "Vlan216": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "216" + }, + "Vlan217": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "217" + }, + "Vlan218": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "218" + }, + "Vlan219": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "219" + }, + "Vlan220": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "220" + }, + "Vlan221": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "221" + }, + "Vlan222": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "222" + }, + "Vlan223": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "223" + }, + "Vlan224": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "224" + }, + "Vlan225": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "225" + }, + "Vlan226": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "226" + }, + "Vlan227": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "227" + }, + "Vlan228": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "228" + }, + "Vlan229": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "229" + }, + "Vlan230": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "230" + }, + "Vlan231": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "231" + }, + "Vlan232": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "232" + }, + "Vlan233": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "233" + }, + "Vlan234": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "234" + }, + "Vlan235": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "235" + }, + "Vlan236": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "236" + }, + "Vlan237": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "237" + }, + "Vlan238": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "238" + }, + "Vlan239": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "239" + }, + "Vlan240": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "240" + }, + "Vlan241": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "241" + }, + "Vlan242": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "242" + }, + "Vlan243": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "243" + }, + "Vlan244": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "244" + }, + "Vlan245": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "245" + }, + "Vlan246": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "246" + }, + "Vlan247": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "247" + }, + "Vlan248": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "248" + }, + "Vlan249": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "249" + }, + "Vlan250": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "250" + }, + "Vlan251": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "251" + }, + "Vlan252": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "252" + }, + "Vlan253": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "253" + }, + "Vlan254": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "254" + }, + "Vlan255": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "255" + }, + "Vlan256": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "256" + }, + "Vlan257": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "257" + }, + "Vlan258": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "258" + }, + "Vlan259": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "259" + }, + "Vlan260": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "260" + }, + "Vlan261": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "261" + }, + "Vlan262": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "262" + }, + "Vlan263": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "263" + }, + "Vlan264": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "264" + }, + "Vlan265": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "265" + }, + "Vlan266": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "266" + }, + "Vlan267": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "267" + }, + "Vlan268": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "268" + }, + "Vlan269": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "269" + }, + "Vlan270": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "270" + }, + "Vlan271": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "271" + }, + "Vlan272": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "272" + }, + "Vlan273": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "273" + }, + "Vlan274": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "274" + }, + "Vlan275": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "275" + }, + "Vlan276": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "276" + }, + "Vlan277": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "277" + }, + "Vlan278": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "278" + }, + "Vlan279": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "279" + }, + "Vlan280": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "280" + }, + "Vlan281": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "281" + }, + "Vlan282": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "282" + }, + "Vlan283": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "283" + }, + "Vlan284": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "284" + }, + "Vlan285": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "285" + }, + "Vlan286": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "286" + }, + "Vlan287": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "287" + }, + "Vlan288": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "288" + }, + "Vlan289": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "289" + }, + "Vlan290": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "290" + }, + "Vlan291": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "291" + }, + "Vlan292": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "292" + }, + "Vlan293": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "293" + }, + "Vlan294": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "294" + }, + "Vlan295": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "295" + }, + "Vlan296": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "296" + }, + "Vlan297": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "297" + }, + "Vlan298": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "298" + }, + "Vlan299": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "299" + }, + "Vlan300": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "300" + } + } +} diff --git a/src/cvl/testdata/create_acl_table.json b/src/cvl/testdata/create_acl_table.json new file mode 100644 index 0000000000..604be2e2d2 --- /dev/null +++ b/src/cvl/testdata/create_acl_table.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/src/cvl/testdata/create_acl_table12.json b/src/cvl/testdata/create_acl_table12.json new file mode 100644 index 0000000000..83f74d6f18 --- /dev/null +++ b/src/cvl/testdata/create_acl_table12.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL13": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/src/cvl/testdata/create_acl_table13.json b/src/cvl/testdata/create_acl_table13.json new file mode 100644 index 0000000000..83f74d6f18 --- /dev/null +++ b/src/cvl/testdata/create_acl_table13.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL13": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/src/cvl/testdata/port_table.json b/src/cvl/testdata/port_table.json new file mode 100644 index 0000000000..266731a6ed --- /dev/null +++ b/src/cvl/testdata/port_table.json @@ -0,0 +1,165 @@ +{ + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32", + "index": "0" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28", + "index": "1" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40", + "index": "2" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36", + "index": "3" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44", + "index": "4" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48", + "index": "5" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8", + "index": "6" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4", + "index": "7" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12", + "index": "8" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16", + "index": "9" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24", + "index": "10" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20", + "index": "11" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52", + "index": "12" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56", + "index": "13" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64", + "index": "14" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60", + "index": "15" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68", + "index": "16" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72", + "index": "17" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80", + "index": "18" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76", + "index": "19" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108", + "index": "20" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112", + "index": "21" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120", + "index": "22" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116", + "index": "23" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124", + "index": "24" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128", + "index": "25" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88", + "index": "26" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84", + "index": "27" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92", + "index": "28" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96", + "index": "29" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100", + "index": "30" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104", + "index": "31" + } + } +} + diff --git a/src/cvl/testdata/schema/Makefile b/src/cvl/testdata/schema/Makefile new file mode 100644 index 0000000000..7f69d689d1 --- /dev/null +++ b/src/cvl/testdata/schema/Makefile @@ -0,0 +1,53 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR=../../../.. +YANGDIR=$(TOPDIR)/models/yang +sonic_yang=$(YANGDIR)/sonic +std_yang_common=$(YANGDIR)/common/ +sonic_yang_common=$(sonic_yang)/common +pyang_plugin_dir=$(TOPDIR)/tools/pyang/pyang_plugins +src_files=$(wildcard *.yang) +out=$(patsubst %.yang, %.yin, $(src_files)) +out_ext=$(patsubst %.yang, %.tree, $(src_files)) +search_path=$(std_yang_common):$(sonic_yang):$(sonic_yang_common) + +all:schema + +schema: $(out) + +schema-tree: $(out_ext) + +%.yin:%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $< | cut -d . -f1`-dev.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) \ + --plugindir $(pyang_plugin_dir) -f yin-cvl $$devOpt $< -o `basename $@` + +%.tree:%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $< | cut -d . -f1`-dev.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) \ + -f tree $$devOpt $< -o `basename $@` + +clean: + @echo "Removing files ..." + rm -rf *.yin *.tree diff --git a/src/cvl/testdata/schema/sonic-acl-deviation.yang b/src/cvl/testdata/schema/sonic-acl-deviation.yang new file mode 100644 index 0000000000..c1d701c29d --- /dev/null +++ b/src/cvl/testdata/schema/sonic-acl-deviation.yang @@ -0,0 +1,43 @@ +module sonic-acl-deviation { + namespace "http://github.com/Azure/sonic-acl-deviation"; + prefix acld; + yang-version 1.1; + + import sonic-acl { + prefix sacl; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC ACL Deviations"; + + revision 2019-05-15 { + description + "Initial revision."; + } +/* + deviation /sacl:sonic-acl/sacl:ACL_TABLE/sacl:type { + deviate replace { + type enumeration { + enum MIRROR; + enum L2; + enum L3; + } + } + } + + deviation /sacl:sonic-acl/sacl:ACL_RULE/sacl:PACKET_ACTION { + deviate replace { + type enumeration { + enum FORWARD; + enum DROP; + } + } + } + */ +} diff --git a/src/cvl/testdata/schema/sonic-bgp-neighbor.yang b/src/cvl/testdata/schema/sonic-bgp-neighbor.yang new file mode 100644 index 0000000000..14504d6f9b --- /dev/null +++ b/src/cvl/testdata/schema/sonic-bgp-neighbor.yang @@ -0,0 +1,84 @@ +module sonic-bgp-neighbor { + namespace "http://github.com/Azure/sonic-bgp-neighbor"; + prefix sbn; + + import ietf-inet-types { + prefix inet; + } + + import sonic-common { + prefix scommon; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BGP NEIGHBOR"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-bgp-neighbor { + + container BGP_NEIGHBOR { + + list BGP_NEIGHBOR_LIST { + key "ipaddress"; + + leaf ipaddress{ + type inet:ip-address; + } + + leaf rrclient { + type uint8 { + range "0..255"; + } + } + + leaf admin_status{ + type scommon:admin-status; + } + + leaf peer_addr{ + type inet:ip-address; + } + + leaf name { + type string; + } + + leaf local_addr { + type inet:ipv4-address; + } + + leaf nhopself { + type uint8 { + range "0..255"; + } + } + + leaf holdtime { + type uint8 { + range "0..255"; + } + } + + leaf asn { + type uint64; + } + + leaf keepalive { + type uint8 { + range "0..255"; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-buffer-pg.yang b/src/cvl/testdata/schema/sonic-buffer-pg.yang new file mode 100644 index 0000000000..27918081e8 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-buffer-pg.yang @@ -0,0 +1,66 @@ +module sonic-buffer-pg { + namespace "http://github.com/Azure/sonic-buffer-pg"; + prefix bpg; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-buffer-profile { + prefix bpf; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER PG"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-buffer-pg { + + container BUFFER_PG { + + list BUFFER_PG_LIST { + key "ifname pg_num"; + sonic-ext:key-pattern "BUFFER_PG|({ifname},)*|{pg_num}"; //special pattern used for extracting keys from + //redis-key and fill populate the yang instance + // Total list instance = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf pg_num { + type string { + pattern "[0-7]((-)[0-7])?" { + error-message "Invalid Buffer PG number"; + error-app-tag pg-num-invalid; + } + } + } + + leaf profile { //Hash reference key + type leafref { + path "/bpf:sonic-buffer-profile/bpf:BUFFER_PROFILE/bpf:BUFFER_PROFILE_LIST/bpf:name"; + } + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-buffer-pool.yang b/src/cvl/testdata/schema/sonic-buffer-pool.yang new file mode 100644 index 0000000000..5f935b3f94 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-buffer-pool.yang @@ -0,0 +1,51 @@ +module sonic-buffer-pool { + namespace "http://github.com/Azure/sonic-buffer-pool"; + prefix bpl; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER POOL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-buffer-pool { + + container BUFFER_POOL { + + list BUFFER_POOL_LIST { + key "name"; + + leaf name { + type string; + } + + leaf type { + type enumeration { + enum ingress; + enum egress; + } + } + + leaf mode { + type enumeration { + enum static; + enum dynamic; + } + } + + leaf size { + type uint64; + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-buffer-profile.yang b/src/cvl/testdata/schema/sonic-buffer-profile.yang new file mode 100644 index 0000000000..71f077654d --- /dev/null +++ b/src/cvl/testdata/schema/sonic-buffer-profile.yang @@ -0,0 +1,67 @@ +module sonic-buffer-profile { + namespace "http://github.com/Azure/sonic-buffer-profile"; + prefix bpf; + + import sonic-buffer-pool { + prefix bpl; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER PROFILE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-buffer-profile { + + container BUFFER_PROFILE { + + list BUFFER_PROFILE_LIST { + key "name"; + + leaf name { + type string; + } + + leaf static_th { + type uint64; + } + + leaf dynamic_th { + type int64; + } + + leaf size { + type uint64; + } + + leaf pool { + type leafref { + path "/bpl:sonic-buffer-pool/bpl:BUFFER_POOL/bpl:BUFFER_POOL_LIST/bpl:name"; + } + } + + leaf xon_offset { + type uint64; + } + + leaf xon { + type uint64; + } + + leaf xoff { + type uint64; + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-cablelength.yang b/src/cvl/testdata/schema/sonic-cablelength.yang new file mode 100644 index 0000000000..af4746211b --- /dev/null +++ b/src/cvl/testdata/schema/sonic-cablelength.yang @@ -0,0 +1,60 @@ +module sonic-cablelength { + namespace "http://github.com/Azure/sonic-cablelength"; + prefix scl; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC CABLELENGTH"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-cablelength { + + container CABLE_LENGTH { + + list CABLE_LENGTH_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "port length"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list CABLE_LENGTH { //this is list inside list for storing mapping between two fields + key "port length"; + + leaf port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + + } + + leaf length { + type string { + pattern "[0-9]?[0-9]m"; + } + } + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-device-metadata.yang b/src/cvl/testdata/schema/sonic-device-metadata.yang new file mode 100644 index 0000000000..014f88cd47 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-device-metadata.yang @@ -0,0 +1,97 @@ +module sonic-device-metadata { + namespace "http://github.com/Azure/sonic-device-metadata"; + prefix sdm; + + import ietf-yang-types { + prefix yang; + } + + import sonic-common { + prefix scommon; + } + + import sonic-bgp-neighbor { + prefix sbn; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DEVICE METADATA"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-device-metadata { + + container DEVICE_METADATA { + + list DEVICE_METADATA_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf hwsku { + type string; + } + + leaf hostname { + type string; + } + + leaf platform { + type string; + } + + leaf mac { + type yang:mac-address; + } + + leaf bgp_asn { + type leafref { + path "/sbn:sonic-bgp-neighbor/sbn:BGP_NEIGHBOR/sbn:BGP_NEIGHBOR_LIST/sbn:asn"; + } + } + + leaf default_pfcwd_status { + type enumeration { + enum enable; + enum disable; + } + } + + leaf default_bgp_status { + type scommon:admin-status; + } + + leaf docker_routing_config_mode { + type enumeration { + enum unified; + enum separated; + } + } + + leaf deployment_id { + type uint8 { + range "0..255"; + } + } + + leaf type { + type enumeration { + enum ToRRouter; + enum LeafRouter; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-device-neighbor.yang b/src/cvl/testdata/schema/sonic-device-neighbor.yang new file mode 100644 index 0000000000..a91302ac24 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-device-neighbor.yang @@ -0,0 +1,72 @@ +module sonic-device-neighbor { + namespace "http://github.com/Azure/sonic-device-neighbor"; + prefix sdn; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DEVICE NEIGHBOR"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-device-neighbor { + + container DEVICE_NEIGHBOR { + + list DEVICE_NEIGHBOR_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf mgmt_addr{ + type inet:ip-address; + } + + leaf hwsku { + type string; + } + + leaf lo_addr { + type inet:ip-address; + } + + leaf local_port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf type { + type enumeration { + enum ToRRouter; + enum LeafRouter; + } + } + + leaf port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-dscp-tc-map.yang b/src/cvl/testdata/schema/sonic-dscp-tc-map.yang new file mode 100644 index 0000000000..14dccddd3d --- /dev/null +++ b/src/cvl/testdata/schema/sonic-dscp-tc-map.yang @@ -0,0 +1,58 @@ +module sonic-dscp-tc-map { + namespace "http://github.com/Azure/sonic-dscp-tc-map"; + prefix dtm; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DSCP_TO_TC_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-dscp-tc-map { + + container DSCP_TO_TC_MAP { + + list DSCP_TO_TC_MAP_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "dscp tc_num"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list DSCP_TO_TC_MAP { //this is list inside list for storing mapping between two fields + key "dscp tc_num"; + + leaf tc_num { + type string { + pattern "[0-9]?"{ + error-message "Invalid Traffic Class number"; + error-app-tag tc-num-invalid; + } + } + } + + leaf dscp { + type string { + pattern "[1-9][0-9]?|[0-9]?"; + } + } + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-mirror-session.yang b/src/cvl/testdata/schema/sonic-mirror-session.yang new file mode 100644 index 0000000000..f9e22691dd --- /dev/null +++ b/src/cvl/testdata/schema/sonic-mirror-session.yang @@ -0,0 +1,60 @@ +module sonic-mirror-session { + namespace "http://github.com/Azure/sonic-mirror-session"; + prefix sms; + + import ietf-inet-types { + prefix inet; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONiC MIRROR SESSION"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-mirror-session { + + container MIRROR_SESSION { + + list MIRROR_SESSION_LIST { + key "name"; + + leaf name { + type string; + } + + leaf src_ip { + type inet:ipv4-address; + } + + leaf dst_ip { + type inet:ipv4-address; + } + + leaf gre_type { + type string; + } + + leaf dscp { + type uint8; + } + + leaf ttl { + type uint8; + } + + leaf queue { + type uint8; + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-pf-limits.yang b/src/cvl/testdata/schema/sonic-pf-limits.yang new file mode 100644 index 0000000000..658a6e9a8d --- /dev/null +++ b/src/cvl/testdata/schema/sonic-pf-limits.yang @@ -0,0 +1,44 @@ +module sonic-pf-limits { + namespace "http://github.com/Azure/sonic-pf-limits"; + prefix spf; + yang-version 1.1; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC Platform constrainst"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-pf-limits { + sonic-ext:db-name "APPL_DB"; + + container acl { + leaf MAX_ACL_RULES { + type uint16; + } + leaf MAX_PRIORITY { + type uint16 { + range "1..65535"; + } + } + + } + container vlan { + leaf MAX_VLANS { + type uint16; + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang b/src/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang new file mode 100644 index 0000000000..ad51874d69 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang @@ -0,0 +1,55 @@ +module sonic-pfc-priority-queue-map { + namespace "http://github.com/Azure/sonic-pfc-priority-queue-map"; + prefix ppq; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC MAP_PFC_PRIORITY_TO_QUEUE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-pfc-priority-queue-map { + + container MAP_PFC_PRIORITY_TO_QUEUE { + + list MAP_PFC_PRIORITY_TO_QUEUE_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "pfc_priority qindex"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list MAP_PFC_PRIORITY_TO_QUEUE { //this is list inside list for storing mapping between two fields + key "pfc_priority qindex"; + + leaf pfc_priority { + type string { + pattern "[0-9]?"; + } + } + + leaf qindex { + type string { + pattern "[0-9]?"; + } + } + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-port-qos-map.yang b/src/cvl/testdata/schema/sonic-port-qos-map.yang new file mode 100644 index 0000000000..f9785cb46a --- /dev/null +++ b/src/cvl/testdata/schema/sonic-port-qos-map.yang @@ -0,0 +1,87 @@ +module sonic-port-qos-map { + namespace "http://github.com/Azure/sonic-port-qos-map"; + prefix pqm; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-tc-priority-group-map { + prefix tpg; + } + + import sonic-tc-queue-map { + prefix tqm; + } + + import sonic-pfc-priority-queue-map { + prefix ppq; + } + + import sonic-dscp-tc-map { + prefix dtm; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORT_QOS_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-port-qos-map { + + list PORT_QOS_MAP { + key "ifname"; + sonic-ext:key-pattern "PORT_QOS_MAP|({ifname},)*"; //special pattern used for extracting keys from redis-key and fill populate the yang instance + // Total list instance = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf tc_to_pg_map { + type leafref { + path "/tpg:sonic-tc-priority-group-map/tpg:TC_TO_PRIORITY_GROUP_MAP/tpg:TC_TO_PRIORITY_GROUP_MAP_LIST/tpg:name"; + } + } + + leaf tc_to_queue_map { + type leafref { + path "/tqm:sonic-tc-queue-map/tqm:TC_TO_QUEUE_MAP/tqm:TC_TO_QUEUE_MAP_LIST/tqm:name"; + } + } + + leaf pfc-enable { + type string { + pattern "[0-9](,[0-9])?"; + } + } + + leaf pfc_to_queue_map { + type leafref { + path "/ppq:sonic-pfc-priority-queue-map/ppq:MAP_PFC_PRIORITY_TO_QUEUE/ppq:MAP_PFC_PRIORITY_TO_QUEUE_LIST/ppq:name"; + } + } + + leaf dscp_to_tc_map { + type leafref { + path "/dtm:sonic-dscp-tc-map/dtm:DSCP_TO_TC_MAP/dtm:DSCP_TO_TC_MAP_LIST/dtm:name"; + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-portchannel-interface.yang b/src/cvl/testdata/schema/sonic-portchannel-interface.yang new file mode 100644 index 0000000000..45d92787d2 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-portchannel-interface.yang @@ -0,0 +1,48 @@ +module sonic-portchannel-interface { + namespace "http://github.com/Azure/sonic-portchannel-interface"; + prefix spchint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-portchannel { + prefix spc; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORTCHANNEL INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-portchannel-interface { + + container PORTCHANNEL_INTERFACE { + + list PORTCHANNEL_INTERFACE_LIST { + key "pch_name ip_prefix"; + + leaf pch_name{ + type leafref { + path "/spc:sonic-portchannel/spc:PORTCHANNEL/spc:PORTCHANNEL_LIST/spc:name"; + } + } + + leaf ip_prefix { + mandatory true; + type inet:ip-prefix; + + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-portchannel.yang b/src/cvl/testdata/schema/sonic-portchannel.yang new file mode 100644 index 0000000000..afe308f4e5 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-portchannel.yang @@ -0,0 +1,77 @@ +module sonic-portchannel { + namespace "http://github.com/Azure/sonic-portchannel"; + prefix spc; + + import sonic-common { + prefix scommon; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORTCHANNEL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-portchannel { + + container PORTCHANNEL { + + list PORTCHANNEL_LIST { + key "name"; + + max-elements 3; + + leaf name { + type string; + } + + leaf admin_status { + type scommon:admin-status; + } + + leaf mtu { + type uint16; + } + + leaf min_links { + type uint8; + } + + leaf fallback { + type boolean; + } + } + } + + container PORTCHANNEL_MEMBER { + + list PORTCHANNEL_MEMBER_LIST { + key "name ifname"; + + leaf name { + type leafref { + path "../../../PORTCHANNEL/PORTCHANNEL_LIST/name"; + } + } + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-queue.yang b/src/cvl/testdata/schema/sonic-queue.yang new file mode 100644 index 0000000000..92a7f4c96f --- /dev/null +++ b/src/cvl/testdata/schema/sonic-queue.yang @@ -0,0 +1,74 @@ +module sonic-queue { + namespace "http://github.com/Azure/sonic-queue"; + prefix squeue; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-scheduler { + prefix sch; + } + + import sonic-wred-profile { + prefix wrd; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC QUEUE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-queue { + + container QUEUE { + + list QUEUE_LIST { + key "ifname qindex"; + sonic-ext:key-pattern "QUEUE|({ifname},)*|{qindex}"; //special pattern used for extracting keys from redis-key and populate the yang instance + // Total list instances = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf qindex { + type string { + pattern "[0-8]((-)[0-8])?"{ + error-message "Invalid Q-index"; + error-app-tag qindex-invalid; + } + } + } + + leaf scheduler { + type leafref { + path "/sch:sonic-scheduler/sch:SCHEDULER/sch:SCHEDULER_LIST/sch:name"; + } + } + + leaf wred_profile { + type leafref { + path "/wrd:sonic-wred-profile/wrd:WRED_PROFILE/wrd:WRED_PROFILE_LIST/wrd:name"; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-scheduler.yang b/src/cvl/testdata/schema/sonic-scheduler.yang new file mode 100644 index 0000000000..2fc7997f71 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-scheduler.yang @@ -0,0 +1,52 @@ +module sonic-scheduler { + namespace "http://github.com/Azure/sonic-scheduler"; + prefix sch; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC SCHEDULER"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-scheduler { + + container SCHEDULER { + + list SCHEDULER_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf type { + type enumeration { + enum DWRR; + enum WRR; + enum PRIORITY; + } + } + + leaf weight { + type uint8 { + range "0..255"; + } + } + + leaf priority { + type uint8 { + range "0..9"; + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-tc-priority-group-map.yang b/src/cvl/testdata/schema/sonic-tc-priority-group-map.yang new file mode 100644 index 0000000000..ed89a281ec --- /dev/null +++ b/src/cvl/testdata/schema/sonic-tc-priority-group-map.yang @@ -0,0 +1,54 @@ +module sonic-tc-priority-group-map { + namespace "http://github.com/Azure/sonic-tc-priority-group-map"; + prefix tpg; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC TC_TO_PRIORITY_GROUP_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-tc-priority-group-map { + + container TC_TO_PRIORITY_GROUP_MAP { + + list TC_TO_PRIORITY_GROUP_MAP_LIST { + key "name"; + sonic-ext:map-list "true"; //special conversion for map tables + sonic-ext:map-leaf "tc_num pg_num"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list TC_TO_PRIORITY_GROUP_MAP { //this is list inside list for storing mapping between two fields + key "tc_num pg_num"; + + leaf tc_num { + type string { + pattern "[0-9]?"; + } + } + + leaf pg_num { + type string { + pattern "[0-7]?"; + } + } + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-tc-queue-map.yang b/src/cvl/testdata/schema/sonic-tc-queue-map.yang new file mode 100644 index 0000000000..37ab1c8113 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-tc-queue-map.yang @@ -0,0 +1,55 @@ +module sonic-tc-queue-map { + namespace "http://github.com/Azure/sonic-tc-queue-map"; + prefix tqm; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC TC_TO_QUEUE_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-tc-queue-map { + + container TC_TO_QUEUE_MAP { + + list TC_TO_QUEUE_MAP_LIST { + key "name"; + sonic-ext:map-list "true"; //special conversion for map tables + sonic-ext:map-leaf "tc_num qindex"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, qindex=7 + + leaf name { + type string; + } + + list TC_TO_QUEUE_MAP { //this is list inside list for storing mapping between two fields + key "tc_num qindex"; + + leaf tc_num { + type string { + pattern "[0-9]?"; + } + } + + leaf qindex { + type string { + pattern "[0-9]?"; + } + } + } + + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-vlan-deviation.yang b/src/cvl/testdata/schema/sonic-vlan-deviation.yang new file mode 100644 index 0000000000..ff426fb30c --- /dev/null +++ b/src/cvl/testdata/schema/sonic-vlan-deviation.yang @@ -0,0 +1,36 @@ +module sonic-vlan-deviation { + namespace "http://github.com/Azure/sonic-vlan-deviation"; + prefix svd; + yang-version 1.1; + + /* + import sonic-vlan { + prefix svlan; + } + */ + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN deviation file"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + /* + deviation /svlan:sonic-vlan/svlan:VLAN/svlan:name { + deviate replace { + type string { + // Supports 3K VLANs + pattern "Vlan([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])"; + } + } + } + */ +} diff --git a/src/cvl/testdata/schema/sonic-vlan-interface.yang b/src/cvl/testdata/schema/sonic-vlan-interface.yang new file mode 100644 index 0000000000..554feb1f58 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-vlan-interface.yang @@ -0,0 +1,48 @@ +module sonic-vlan-interface { + namespace "http://github.com/Azure/sonic-vlan-interface"; + prefix svint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-vlan { + prefix svlan; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-vlan-interface { + + container VLAN_INTERFACE { + + list VLAN_INTERFACE_LIST { + key "portname ip_prefix"; + + leaf portname{ + type leafref { + path "/svlan:sonic-vlan/svlan:VLAN/svlan:VLAN_LIST/svlan:name"; + } + } + + leaf ip_prefix { + mandatory true; + type inet:ip-prefix; + + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-vlan.yang b/src/cvl/testdata/schema/sonic-vlan.yang new file mode 100644 index 0000000000..1170960df1 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-vlan.yang @@ -0,0 +1,107 @@ +module sonic-vlan { + namespace "http://github.com/Azure/sonic-vlan"; + prefix svlan; + yang-version 1.1; + + import sonic-common { + prefix scommon; + } + + import sonic-port { + prefix prt; + } + + import sonic-portchannel { + prefix spc; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-vlan { + + container VLAN { + + list 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-list members { + must "count(../members[text()=/spc:sonic-portchannel/spc:PORTCHANNEL_MEMBER/" + + "spc:PORTCHANNEL_MEMBER_LIST[spc:ifname=current()]/spc:name]) = 0 and " + + "count(../members[text()=/spc:sonic-portchannel/spc:PORTCHANNEL_MEMBER/" + + "spc:PORTCHANNEL_MEMBER_LIST[spc:name=current()]/spc:ifname]) = 0 " { + error-message "A vlan interface member cannot be part of portchannel which is already a vlan member"; + } + + + type union { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + type leafref { + path "/spc:sonic-portchannel/spc:PORTCHANNEL/spc:PORTCHANNEL_LIST/spc:name"; + } + } + } + } + } + + container VLAN_MEMBER { + + list VLAN_MEMBER_LIST { + key "name ifname"; + + leaf name { + type leafref { + path "../../../VLAN/VLAN_LIST/name"; + } + } + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf tagging_mode { + type scommon:tagging_mode; + default tagged; + } + } + } + } +} diff --git a/src/cvl/testdata/schema/sonic-wred-profile.yang b/src/cvl/testdata/schema/sonic-wred-profile.yang new file mode 100644 index 0000000000..e3c5abe363 --- /dev/null +++ b/src/cvl/testdata/schema/sonic-wred-profile.yang @@ -0,0 +1,80 @@ +module sonic-wred-profile { + namespace "http://github.com/Azure/sonic-wred-profile"; + prefix wrd; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC WRED_PROFILE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-wred-profile { + + container WRED_PROFILE { + + list WRED_PROFILE_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf yellow_min_threshold { + type uint64; + } + + leaf green_min_threshold { + type uint64; + } + + leaf red_min_threshold { + type uint64; + } + leaf yellow_max_threshold { + type uint64; + } + + leaf green_max_threshold { + type uint64; + } + + leaf red_max_threshold { + type uint64; + } + + leaf ecn { + type enumeration { + enum ecn_none; + enum ecn_green; + enum ecn_yellow; + enum ecn_red; + enum ecn_green_yellow; + enum ecn_green_red; + enum ecn_yellow_red; + enum ecn_all; + } + } + + leaf wred_green_enable { + type boolean; + } + + leaf wred_yellow_enable { + type boolean; + } + + leaf wred_red_enable { + type boolean; + } + } + } + } +} diff --git a/src/cvl/tests/Makefile b/src/cvl/tests/Makefile new file mode 100644 index 0000000000..ce4dc21f0f --- /dev/null +++ b/src/cvl/tests/Makefile @@ -0,0 +1,37 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +SRC_FILES=$(wildcard *.go) +OUT=$(patsubst %.go, %, $(SRC_FILES)) +TOPDIR := $(abspath ../../..) +GO=/usr/local/go/bin/go +GOPATH = $(TOPDIR):$(shell go env GOPATH) + +all:tests + +tests: $(OUT) + +%:%.go + make -C ../testdata/schema + @echo "Building $@ ..." + GOPATH=$(GOPATH) $(GO) build -gcflags="all=-N -l" $< + +clean: + @echo "Removing files ..." + rm -rf $(OUT) diff --git a/src/cvl/tests/acl_rule.json b/src/cvl/tests/acl_rule.json new file mode 100644 index 0000000000..c26cdee0e5 --- /dev/null +++ b/src/cvl/tests/acl_rule.json @@ -0,0 +1,22 @@ +{ +"ACL_TABLE": { + "TestACL11": { + "type": "L3", + "ports": "Ethernet0" + } + }, +"ACL_RULE": { + "TestACL11|Rule1": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "ANY", + "L4_SRC_PORT": "0" + }, + "TestACL11|Rule2": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "ANY", + "L4_SRC_PORT": "1" + } + } +} diff --git a/src/cvl/tests/cfg_validator.go b/src/cvl/tests/cfg_validator.go new file mode 100644 index 0000000000..f5a532da95 --- /dev/null +++ b/src/cvl/tests/cfg_validator.go @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + "os" + "time" + "io/ioutil" + "cvl" +) + +func main() { + jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + } + }` + /* + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "l3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "packet_action": "forward", + "src_ip": "10.1.1.1/32", + "l4_src_port": "ABC", + "ip_protocol": "ip", + "dst_ip": "20.2.2.2/32", + "l4_dst_port_range": "9000-12000", + "mirror_action" : "mirror1" + } + }*/ + +/*jsonData := `{ + "DEVICE_METADATA": { + "localhost": { + "hwsku": "Force10-S6100", + "default_bgp_status": "up", + "docker_routing_config_mode": "unified", + "hostname": "sonic-s6100-01", + "platform": "x86_64-dell_s6100_c2538-r0", + "mac": "4c:76:25:f4:70:82", + "default_pfcwd_status": "disable", + "deployment_id": "1", + "type": "ToRRouter" + } + } + }`*/ +/*jsonData := `{ + "DEVICE_NEIGHBOR": { + "ARISTA04T1": { + "mgmt_addr": "10.20.0.163", + "hwsku": "Arista", + "lo_addr": "2.2.2.2", + "local_port": "Ethernet124", + "type": "LeafRouter", + "port": "Ethernet68" + } + } + }`*/ +/*jsonData := `{ + "BGP_NEIGHBOR": { + "10.0.0.61": { + "local_addr": "10.0.0.60", + "asn": 64015, + "name": "ARISTA15T0" + } + } + }`*/ + +/* jsonData := `{ + "INTERFACE": { + "Ethernet68|10.0.0.0/31": {}, + "Ethernet24|10.0.0.2/31": {}, + "Ethernet112|10.0.0.4/31": {} + } + }`*/ + +/*jsonData := `{ + "INTERFACE": { + "Ethernet68|10.0.0.0/31": {}, + "Ethernet24|10.0.0.2/31": {}, + "Ethernet112|10.0.0.4/31": {} + } + }`*/ +/*jsonData := `{ + "PORTCHANNEL_INTERFACE": { + "PortChannel01|10.0.0.56/31": {}, + "PortChannel01|FC00::71/126": {}, + "PortChannel02|10.0.0.58/31": {}, + "PortChannel02|FC00::75/126": {} + } + + }`*/ +/*jsonData := `{ + "VLAN_INTERFACE": { + "Vlan1000|192.168.0.1/27": {} + } + }`*/ + start := time.Now() + + dataFile := "" + if (len(os.Args) >= 2) { + if (os.Args[1] == "debug") { + cvl.Debug(true) + } else { + dataFile = os.Args[1] + } + } + if (len(os.Args) == 3) { + dataFile = os.Args[2] + } + + //cvl.Initialize() + + b, e := ioutil.ReadFile(dataFile) + if e != nil { + fmt.Printf("\nFailed to read data file : %v\n", e) + } else { + jsonData = string(b) + } + + + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("NewDB: Could not create CVL session") + return + } + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + keyData := make([]cvl.CVLEditConfigData, 4) + keyData[0].VType = cvl.VALIDATE_NONE + keyData[0].VOp = cvl.OP_NONE + keyData[0].Key = "ACL_TABLE|MyACL55_ACL_IPV4" + keyData[0].Data = make(map[string]string) + keyData[0].Data["stage"] = "INGRESS" + keyData[0].Data["type"] = "l3" + + keyData[1].VType = cvl.VALIDATE_NONE + keyData[1].VOp = cvl.OP_NONE + keyData[1].Key = "ACL_RULE|MyACL55_ACL_IPV4|RULE_1" + keyData[1].Data = make(map[string]string) + keyData[1].Data["packet_action"] = "forward" + keyData[1].Data["ip_protocol"] = "ip" + keyData[1].Data["src_ip"] = "10.1.1.1/32" + keyData[1].Data["dst_ip"] = "20.2.2.2/32" + + keyData[2].VType = cvl.VALIDATE_NONE + keyData[2].VOp = cvl.OP_NONE + keyData[2].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData[2].Data = make(map[string]string) + keyData[2].Data["stage"] = "INGRESS" + + keyData[3].VType = cvl.VALIDATE_ALL + keyData[3].VOp = cvl.OP_CREATE + keyData[3].Key = "VLAN|Vlan901" + keyData[3].Data = make(map[string]string) + keyData[3].Data["members"] = "Ethernet8" + keyData[3].Data["vlanid"] = "901" + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + keyData1 := make([]cvl.CVLEditConfigData, 3) + keyData1[0].VType = cvl.VALIDATE_NONE + keyData1[0].VOp = cvl.OP_NONE + keyData1[0].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData1[0].Data = make(map[string]string) + keyData1[0].Data["stage"] = "INGRESS" + keyData1[0].Data["type"] = "l3" + + keyData1[1].VType = cvl.VALIDATE_NONE + keyData1[1].VOp = cvl.OP_NONE + keyData1[1].Key = "ACL_RULE|MyACL11_ACL_IPV4|RULE_1" + keyData1[1].Data = make(map[string]string) + keyData1[1].Data["packet_action"] = "forward" + keyData1[1].Data["ip_protocol"] = "ip" + keyData1[1].Data["src_ip"] = "10.1.1.1/32" + keyData1[1].Data["dst_ip"] = "20.2.2.2/32" + + keyData1[2].VType = cvl.VALIDATE_ALL + keyData1[2].VOp = cvl.OP_UPDATE + keyData1[2].Key = "ACL_TABLE|MyACL33_ACL_IPV4" + keyData1[2].Data = make(map[string]string) + keyData1[2].Data["stage"] = "INGRESS" + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + + keyData2 := make([]cvl.CVLEditConfigData, 3) + keyData2[0].VType = cvl.VALIDATE_ALL + keyData2[0].VOp = cvl.OP_DELETE + keyData2[0].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData2[0].Data = make(map[string]string) + + keyData2[1].VType = cvl.VALIDATE_ALL + keyData2[1].VOp = cvl.OP_DELETE + keyData2[1].Key = "ACL_RULE|MyACL11_ACL_IPV4|RULE_1" + keyData2[1].Data = make(map[string]string) + + keyData2[2].VType = cvl.VALIDATE_ALL + keyData2[2].VOp = cvl.OP_DELETE + keyData2[2].Key = "ACL_TABLE|MyACL33_ACL_IPV4" + keyData2[2].Data = make(map[string]string) + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + + cvl.ValidationSessClose(cv) + cvl.Finish() + fmt.Printf("\n\n\n Time taken = %v\n", time.Since(start)) + + stopChan := make(chan int, 1) + for { + select { + case <- stopChan: + } + } + + +} diff --git a/src/cvl/tests/config_db.json b/src/cvl/tests/config_db.json new file mode 100644 index 0000000000..381412ad6b --- /dev/null +++ b/src/cvl/tests/config_db.json @@ -0,0 +1,107 @@ +{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet924": { + "tagging_mode": "tagged" + }, + "Vlan100|Ethernet28": { + "tagging_mode": "tagged" + }, + "Vlan1200|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} diff --git a/src/cvl/tests/config_db1.json b/src/cvl/tests/config_db1.json new file mode 100644 index 0000000000..b565af7cb1 --- /dev/null +++ b/src/cvl/tests/config_db1.json @@ -0,0 +1,313 @@ +{ + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "BUFFER_PG": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0-1": { + "profile": "[BUFFER_PROFILE|ingress_lossy_profile]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "profile": "[BUFFER_PROFILE|ingress_lossless_profile]" + } + }, + "BUFFER_PROFILE": { + "egress_lossless_profile": { + "static_th": "12766208", + "pool": "[BUFFER_POOL|egress_lossless_pool]", + "size": "0" + }, + "egress_lossy_profile": { + "dynamic_th": "3", + "pool": "[BUFFER_POOL|egress_lossy_pool]", + "size": "1518" + }, + "ingress_lossless_profile": { + "xon_offset": "2496", + "dynamic_th": "-4", + "xon": "18432", + "xoff": "40560", + "pool": "[BUFFER_POOL|ingress_lossless_pool]", + "size": "41808" + }, + "ingress_lossy_profile": { + "dynamic_th": "3", + "pool": "[BUFFER_POOL|ingress_lossless_pool]", + "size": "0" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + }, + "DSCP_TO_TC_MAP": { + "AZURE": { + "56": "0", + "54": "0", + "28": "0", + "48": "0", + "29": "0", + "60": "0", + "61": "0", + "62": "0", + "63": "0", + "49": "0", + "34": "0", + "24": "0", + "25": "0", + "26": "0", + "27": "0", + "20": "0", + "21": "0", + "22": "0", + "23": "0", + "46": "0", + "47": "0", + "44": "0", + "45": "0", + "42": "0", + "43": "0", + "40": "0", + "41": "0", + "1": "0", + "0": "0", + "3": "3", + "2": "0", + "5": "0", + "4": "4", + "7": "0", + "6": "0", + "9": "0", + "8": "1", + "35": "0", + "13": "0", + "12": "0", + "15": "0", + "58": "0", + "11": "0", + "10": "0", + "39": "0", + "38": "0", + "59": "0", + "14": "0", + "17": "0", + "16": "0", + "19": "0", + "18": "0", + "31": "0", + "30": "0", + "51": "0", + "36": "0", + "53": "0", + "52": "0", + "33": "0", + "55": "0", + "37": "0", + "32": "0", + "57": "0", + "50": "0" + } + }, + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} + diff --git a/src/cvl/tests/config_db2.json b/src/cvl/tests/config_db2.json new file mode 100644 index 0000000000..e6be83ac4e --- /dev/null +++ b/src/cvl/tests/config_db2.json @@ -0,0 +1,3437 @@ +{ + "VLAN_MEMBER": { + "Vlan51|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan51|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan97|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet108": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet124": { + "tagging_mode": "untagged" + }, + "Vlan101|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan199|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "VLAN": { + "Vlan51": { + "members": [ + "Ethernet108", + "Ethernet124" + ], + "vlanid": "51" + }, + "Vlan97": { + "members": [ + "Ethernet4" + ], + "vlanid": "97" + }, + "Vlan99": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "99" + }, + "Vlan101": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "101" + }, + "Vlan102": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "102" + }, + "Vlan103": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "103" + }, + "Vlan104": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "104" + }, + "Vlan105": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "105" + }, + "Vlan106": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "106" + }, + "Vlan107": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "107" + }, + "Vlan108": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "108" + }, + "Vlan109": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "109" + }, + "Vlan110": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "110" + }, + "Vlan111": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "111" + }, + "Vlan112": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "112" + }, + "Vlan113": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "113" + }, + "Vlan114": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "114" + }, + "Vlan115": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "115" + }, + "Vlan116": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "116" + }, + "Vlan117": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "117" + }, + "Vlan118": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "118" + }, + "Vlan119": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "119" + }, + "Vlan120": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "120" + }, + "Vlan121": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "121" + }, + "Vlan122": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "122" + }, + "Vlan123": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "123" + }, + "Vlan124": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "124" + }, + "Vlan125": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "125" + }, + "Vlan126": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "126" + }, + "Vlan127": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "127" + }, + "Vlan128": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "128" + }, + "Vlan129": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "129" + }, + "Vlan130": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "130" + }, + "Vlan131": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "131" + }, + "Vlan132": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "132" + }, + "Vlan133": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "133" + }, + "Vlan134": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "134" + }, + "Vlan135": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "135" + }, + "Vlan136": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "136" + }, + "Vlan137": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "137" + }, + "Vlan138": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "138" + }, + "Vlan139": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "139" + }, + "Vlan140": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "140" + }, + "Vlan141": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "141" + }, + "Vlan142": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "142" + }, + "Vlan143": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "143" + }, + "Vlan144": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "144" + }, + "Vlan145": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "145" + }, + "Vlan146": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "146" + }, + "Vlan147": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "147" + }, + "Vlan148": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "148" + }, + "Vlan149": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "149" + }, + "Vlan150": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "150" + }, + "Vlan151": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "151" + }, + "Vlan152": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "152" + }, + "Vlan153": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "153" + }, + "Vlan154": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "154" + }, + "Vlan155": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "155" + }, + "Vlan156": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "156" + }, + "Vlan157": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "157" + }, + "Vlan158": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "158" + }, + "Vlan159": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "159" + }, + "Vlan160": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "160" + }, + "Vlan161": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "161" + }, + "Vlan162": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "162" + }, + "Vlan163": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "163" + }, + "Vlan164": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "164" + }, + "Vlan165": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "165" + }, + "Vlan166": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "166" + }, + "Vlan167": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "167" + }, + "Vlan168": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "168" + }, + "Vlan169": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "169" + }, + "Vlan170": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "170" + }, + "Vlan171": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "171" + }, + "Vlan172": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "172" + }, + "Vlan173": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "173" + }, + "Vlan174": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "174" + }, + "Vlan175": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "175" + }, + "Vlan176": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "176" + }, + "Vlan177": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "177" + }, + "Vlan178": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "178" + }, + "Vlan179": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "179" + }, + "Vlan180": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "180" + }, + "Vlan181": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "181" + }, + "Vlan182": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "182" + }, + "Vlan183": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "183" + }, + "Vlan184": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "184" + }, + "Vlan185": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "185" + }, + "Vlan186": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "186" + }, + "Vlan187": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "187" + }, + "Vlan188": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "188" + }, + "Vlan189": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "189" + }, + "Vlan190": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "190" + }, + "Vlan191": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "191" + }, + "Vlan192": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "192" + }, + "Vlan193": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "193" + }, + "Vlan194": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "194" + }, + "Vlan195": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "195" + }, + "Vlan196": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "196" + }, + "Vlan197": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "197" + }, + "Vlan198": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "198" + }, + "Vlan199": { + "members": [ + "Ethernet64" + ], + "vlanid": "199" + }, + "Vlan200": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "200" + }, + "Vlan201": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "201" + }, + "Vlan202": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "202" + }, + "Vlan203": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "203" + }, + "Vlan204": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "204" + }, + "Vlan205": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "205" + }, + "Vlan206": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "206" + }, + "Vlan207": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "207" + }, + "Vlan208": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "208" + }, + "Vlan209": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "209" + }, + "Vlan210": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "210" + }, + "Vlan211": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "211" + }, + "Vlan212": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "212" + }, + "Vlan213": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "213" + }, + "Vlan214": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "214" + }, + "Vlan215": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "215" + }, + "Vlan216": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "216" + }, + "Vlan217": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "217" + }, + "Vlan218": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "218" + }, + "Vlan219": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "219" + }, + "Vlan220": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "220" + }, + "Vlan221": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "221" + }, + "Vlan222": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "222" + }, + "Vlan223": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "223" + }, + "Vlan224": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "224" + }, + "Vlan225": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "225" + }, + "Vlan226": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "226" + }, + "Vlan227": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "227" + }, + "Vlan228": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "228" + }, + "Vlan229": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "229" + }, + "Vlan230": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "230" + }, + "Vlan231": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "231" + }, + "Vlan232": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "232" + }, + "Vlan233": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "233" + }, + "Vlan234": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "234" + }, + "Vlan235": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "235" + }, + "Vlan236": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "236" + }, + "Vlan237": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "237" + }, + "Vlan238": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "238" + }, + "Vlan239": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "239" + }, + "Vlan240": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "240" + }, + "Vlan241": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "241" + }, + "Vlan242": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "242" + }, + "Vlan243": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "243" + }, + "Vlan244": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "244" + }, + "Vlan245": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "245" + }, + "Vlan246": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "246" + }, + "Vlan247": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "247" + }, + "Vlan248": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "248" + }, + "Vlan249": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "249" + }, + "Vlan250": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "250" + }, + "Vlan251": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "251" + }, + "Vlan252": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "252" + }, + "Vlan253": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "253" + }, + "Vlan254": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "254" + }, + "Vlan255": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "255" + }, + "Vlan256": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "256" + }, + "Vlan257": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "257" + }, + "Vlan258": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "258" + }, + "Vlan259": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "259" + }, + "Vlan260": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "260" + }, + "Vlan261": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "261" + }, + "Vlan262": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "262" + }, + "Vlan263": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "263" + }, + "Vlan264": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "264" + }, + "Vlan265": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "265" + }, + "Vlan266": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "266" + }, + "Vlan267": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "267" + }, + "Vlan268": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "268" + }, + "Vlan269": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "269" + }, + "Vlan270": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "270" + }, + "Vlan271": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "271" + }, + "Vlan272": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "272" + }, + "Vlan273": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "273" + }, + "Vlan274": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "274" + }, + "Vlan275": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "275" + }, + "Vlan276": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "276" + }, + "Vlan277": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "277" + }, + "Vlan278": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "278" + }, + "Vlan279": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "279" + }, + "Vlan280": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "280" + }, + "Vlan281": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "281" + }, + "Vlan282": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "282" + }, + "Vlan283": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "283" + }, + "Vlan284": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "284" + }, + "Vlan285": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "285" + }, + "Vlan286": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "286" + }, + "Vlan287": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "287" + }, + "Vlan288": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "288" + }, + "Vlan289": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "289" + }, + "Vlan290": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "290" + }, + "Vlan291": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "291" + }, + "Vlan292": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "292" + }, + "Vlan293": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "293" + }, + "Vlan294": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "294" + }, + "Vlan295": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "295" + }, + "Vlan296": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "296" + }, + "Vlan297": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "297" + }, + "Vlan298": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "298" + }, + "Vlan299": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "299" + }, + "Vlan300": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "300" + } + } +} diff --git a/src/cvl/tests/create_acl_table.json b/src/cvl/tests/create_acl_table.json new file mode 100644 index 0000000000..604be2e2d2 --- /dev/null +++ b/src/cvl/tests/create_acl_table.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/src/cvl/tests/cv_acl.go b/src/cvl/tests/cv_acl.go new file mode 100644 index 0000000000..cb12c0109b --- /dev/null +++ b/src/cvl/tests/cv_acl.go @@ -0,0 +1,443 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + "time" + "os" + "cvl" + "github.com/go-redis/redis" + "strconv" +) + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, key string, data map[string]string) { + _, err := rclient.Del(key).Result() + + if err != nil { + fmt.Printf("Failed to delete for key %s, data %v, err %v", key, data, err) + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, key string, data map[string]string) { + + dataTmp := make(map[string]interface{}) + + for k, v := range data { + dataTmp[k] = v + } + + _, err := rclient.HMSet(key, dataTmp).Result() + + if err != nil { + fmt.Printf("Failed to add for key %s, data %v, err %v", key, data, err) + } + +} + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + rclient := getConfigDbClient() + + if ((len(os.Args) > 1) && (os.Args[1] == "add")) { + + //Add ACL + aclNoStart, _ := strconv.Atoi(os.Args[2]) + aclNoEnd, _ := strconv.Atoi(os.Args[3]) + for aclNum:= aclNoStart ;aclNum <= aclNoEnd; aclNum++ { + aclNo := fmt.Sprintf("%d", aclNum) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("ACL_TABLE|TestACL%s", aclNo), + map[string]string { + "stage": "INGRESS", + "type": "L3", + //"ports@": "Ethernet0", + }, + }, + } + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[0].VType = cvl.VALIDATE_NONE + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("ACL_RULE|TestACL%s|Rule%d", aclNo, i+1), + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": fmt.Sprintf("%d", 201 + i), + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT": fmt.Sprintf("%d", 701 + i), + }, + }) + + _, ret1 := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret1 != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[1 + i].VType = cvl.VALIDATE_NONE + } + + //Write to DB + for _, cfgDataItem := range cfgDataAclRule { + loadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } else if ((len(os.Args) > 1) && (os.Args[1] == "del")) { + aclNoStart, _ := strconv.Atoi(os.Args[2]) + aclNoEnd, _ := strconv.Atoi(os.Args[3]) + for aclNum:= aclNoStart ;aclNum <= aclNoEnd; aclNum++ { + aclNo := fmt.Sprintf("%d", aclNum) + cvSess,_ := cvl.ValidationSessOpen() + + //Delete ACL + + cfgDataAclRule := []cvl.CVLEditConfigData{} + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("ACL_RULE|TestACL%s|Rule%d", aclNo, i+1), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[i].VType = cvl.VALIDATE_NONE + } + + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("ACL_TABLE|TestACL%s", aclNo), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + //Write to DB + for _, cfgDataItem := range cfgDataAclRule { + unloadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } + + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Could not create CVL session") + return + } + + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + }, + "TestACL2": { + "stage": "EGRESS", + "ports": "Ethernet4" + } + } + }` + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL2": { + "stage": "EGRESS", + "ports": "Ethernet804" + } + } + }` + + + fmt.Printf("\nValidating data for external dependency check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "type": "L3" + } + } + }` + + + fmt.Printf("\nValidating data for mandatory element misssing = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103", + "SRC_IP": "10.1.1.1/32", + "DST_IP": "20.2.2.2/32" + } + } + }` + + + + fmt.Printf("\nValidating data for internal dependency check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103" + } + } + }` + + + + fmt.Printf("\nValidating data for mandatory element check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32" + } + } + }` + + + + fmt.Printf("\nValidating data for mandatory element check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": 8080, + "ETHER_TYPE":"0x0800", + "IP_PROTOCOL": "1", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + } + } + }` + + + + fmt.Printf("\nValidating data for pattern check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "ABC", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + } + } + }` + + + + fmt.Printf("\nValidating data for type check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/src/cvl/tests/cv_edit_op.go b/src/cvl/tests/cv_edit_op.go new file mode 100644 index 0000000000..a9035bcf51 --- /dev/null +++ b/src/cvl/tests/cv_edit_op.go @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + "time" + "os" + "cvl" +) + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + cv, _ := cvl.ValidationSessOpen() + + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "ACL_TABLE|TestACL1", + map[string]string { + "stage": "INGRESS", + "type": "L3", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + fmt.Printf("\n\n%d. Validating create data = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|MyACL11_ACL_IPV4", + map[string]string { + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + fmt.Printf("\n\n%d. Validating update data = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "MIRROR_SESSION|everflow", + map[string]string { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + } + + fmt.Printf("\n\n%d. Validating create data = %v\n\n", count, cfgData); + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + count++ + cfgData = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "MIRROR_SESSION|everflow", + map[string]string { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string { + "MIRROR_ACTION": "everflow", + }, + }, + } + + fmt.Printf("\n\n%d. Validating data for update = %v\n\n", count, cfgData); + + _, err = cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string { + }, + }, + } + + fmt.Printf("\n\n%d. Validating data for delete = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/src/cvl/tests/cv_vlan.go b/src/cvl/tests/cv_vlan.go new file mode 100644 index 0000000000..b230e86b5a --- /dev/null +++ b/src/cvl/tests/cv_vlan.go @@ -0,0 +1,448 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + + +import ( + "fmt" + "os" + "time" + "cvl" + "github.com/go-redis/redis" + "strconv" +) + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, key string, data map[string]string) { + _, err := rclient.Del(key).Result() + + if err != nil { + fmt.Printf("Failed to delete for key %s, data %v, err %v", key, data, err) + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, key string, data map[string]string) { + + dataTmp := make(map[string]interface{}) + + for k, v := range data { + dataTmp[k] = v + } + + _, err := rclient.HMSet(key, dataTmp).Result() + + if err != nil { + fmt.Printf("Failed to add for key %s, data %v, err %v", key, data, err) + } + +} + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + rclient := getConfigDbClient() + + if ((len(os.Args) > 1) && (os.Args[1] == "add")) { + + //Add ACL + vlanNoStart, _ := strconv.Atoi(os.Args[2]) + vlanNoEnd, _ := strconv.Atoi(os.Args[3]) + for vlanNum:= vlanNoStart ;vlanNum <= vlanNoEnd; vlanNum++ { + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("VLAN|Vlan%d", vlanNum), + map[string]string { + "vlanid": fmt.Sprintf("%d", vlanNum), + "members@": "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28", + }, + }, + } + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataVlan[0].VType = cvl.VALIDATE_NONE + + for i:=0; i<7; i++ { + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("VLAN_MEMBER|Vlan%d|Ethernet%d", vlanNum, i * 4), + map[string]string { + "tagging_mode" : "tagged", + }, + }) + + _, ret1 := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret1 != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataVlan[1 + i].VType = cvl.VALIDATE_NONE + } + + //Write to DB + for _, cfgDataItem := range cfgDataVlan { + loadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } else if ((len(os.Args) > 1) && (os.Args[1] == "del")) { + vlanNoStart, _ := strconv.Atoi(os.Args[2]) + vlanNoEnd, _ := strconv.Atoi(os.Args[3]) + for vlanNum:= vlanNoStart ;vlanNum <= vlanNoEnd; vlanNum++ { + cvSess,_ := cvl.ValidationSessOpen() + + //Delete ACL + + cfgDataVlan := []cvl.CVLEditConfigData{} + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("VLAN_MEMBER|Vlan%d|Ethernet%d", vlanNum, i * 4), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + //return + } + + cfgDataVlan[i].VType = cvl.VALIDATE_NONE + } + + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("VLAN|Vlan%d", vlanNum), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + //return + } + + //Write to DB + for _, cfgDataItem := range cfgDataVlan { + unloadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + return + } + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("NewDB: Could not create CVL session") + return + } + + { + count++ + keyData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch1", + map[string]string { + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch2", + map[string]string { + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet4", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet8", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet12", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet16", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet20", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string { + "vlanid": "1001", + "members@": "Ethernet24,ch1,Ethernet8", + }, + }, + } + + fmt.Printf("\nValidating data for must = %v\n\n", keyData); + + _, err := cv.ValidateEditConfig(keyData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + } + + { + keyData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|MyACL1_ACL_IPV4", + map[string]string { + "type": "L3", + }, + }, + } + + _, err := cv.ValidateEditConfig(keyData) + + fmt.Printf("\nValidating field delete...\n\n"); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vln100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for key syntax = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan4096": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for range check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan201": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for internal dependency check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + /*jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet964" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet1008" + ], + "vlanid": "1200" + } + } + }`*/ + jsonData :=`{ + "VLAN": { + "Vlan4095": { + "vlanid": "4995" + } + } + }` + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for external dependency check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/src/cvl/tests/run_test.sh b/src/cvl/tests/run_test.sh new file mode 100755 index 0000000000..e786ac0144 --- /dev/null +++ b/src/cvl/tests/run_test.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +profiling="" +testcase="" +coverpkgs="-coverpkg=cvl,cvl/internal/util,cvl/internal/yparser" + +if [ "${BUILD}:" != ":" ] ; then + go test -v -c -gcflags="all=-N -l" +fi + +if [ "${TESTCASE}:" != ":" ] ; then + testcase="-run ${TESTCASE}" +fi + +if [ "${PROFILE}:" != ":" ] ; then + profiling="-bench=. -benchmem -cpuprofile profile.out" +fi + +#Run test and display report +if [ "${NOREPORT}:" != ":" ] ; then + go test -v -cover ${coverpkgs} ${testcase} +elif [ "${COVERAGE}:" != ":" ] ; then + go test -v -cover -coverprofile coverage.out ${coverpkgs} ${testcase} + go tool cover -html=coverage.out +else + go test -v -cover -json ${profiling} ${testcase} | tparse -smallscreen -all +fi + +#With profiling +#go test -v -cover -json -bench=. -benchmem -cpuprofile profile.out | tparse -smallscreen -all + diff --git a/src/rest/Makefile b/src/rest/Makefile new file mode 100644 index 0000000000..f4f4d335d7 --- /dev/null +++ b/src/rest/Makefile @@ -0,0 +1,84 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR ?= ../.. +ABS_TOPDIR ?= $(abspath $(TOPDIR)) +BUILD_DIR ?= $(TOPDIR)/build + +GO ?= go +GOPATH ?= $(ABS_TOPDIR)/gopkgs:$(ABS_TOPDIR):$(shell $(GO) env GOPATH) +GOROOT ?= $(shell $(GO) env GOROOT) + + +REST_BUILD_DIR := $(BUILD_DIR)/rest_server +REST_BIN := $(REST_BUILD_DIR)/main +REST_TEST_BIN := $(BUILD_DIR)/tests/rest/server.test + +ALL_GO_SRCS = $(shell find $(TOPDIR)/src -name '*.go' | grep -v '_test.go' | grep -v '/tests\?/') +REST_TEST_SRCS = $(shell find . -name '*_test.go') + +# Source files affecting REST server +REST_SRCS := $(ALL_GO_SRCS) \ + $(shell find $(TOPDIR)/models/yang -name '*.yang' | sort) \ + $(shell find $(TOPDIR)/models/openapi -name '*.yaml' | sort) + +REST_GOPATH := $(GOPATH):$(abspath $(REST_BUILD_DIR)/dist) + +# Certificate generator tool for generating temp certificates. +# Compiled from standard crypto/tls/generate_cert.go +CERTGEN_BIN := $(REST_BUILD_DIR)/generate_cert + + +# Default target +all: $(REST_BIN) $(CERTGEN_BIN) $(REST_TEST_BIN) + +$(REST_BUILD_DIR)/: + mkdir -p $@ + +# REST Server binary +# Invokes yang and model make to generate swagger artifcats. +$(REST_BIN): $(REST_SRCS) | $(REST_BUILD_DIR)/ + $(MAKE) -C $(TOPDIR)/models/yang + $(MAKE) -C $(TOPDIR)/models/yang/sonic + $(MAKE) -C $(TOPDIR)/models +ifeq ($(SONIC_COVERAGE_ON),y) + GOPATH=$(REST_GOPATH) $(GO) test -coverpkg=".././..." -c -o $@ main/main.go main/main_test.go +else + GOPATH=$(REST_GOPATH) $(GO) build -gcflags="all=-N -l" -v -o $@ main/main.go +endif + +# Gotest binary for REST Server +$(REST_TEST_BIN): $(REST_TEST_SRCS) $(ALL_GO_SRCS) | $(REST_BUILD_DIR)/ + GOPATH=$(REST_GOPATH) $(GO) test -cover -c rest/server -o $@ + +# Compile a certificate generator temporary certificates from +# standard crypto/tls/generate_cert.go file. Source file will be +# available in GOROOT/src. +$(CERTGEN_BIN): | $(REST_BUILD_DIR)/ + $(GO) build -o $@ $(GOROOT)/src/crypto/tls/generate_cert.go + +test: + GOPATH=$(REST_GOPATH) $(GO) test -cover -v rest/server + +clean: + $(MAKE) -C $(TOPDIR)/models clean + $(MAKE) -C $(TOPDIR)/models/yang clean + rm -f $(REST_BIN) $(CERTGEN_BIN) + rm -f $(REST_TEST_BIN) + diff --git a/src/rest/main/main.go b/src/rest/main/main.go new file mode 100644 index 0000000000..e44056c733 --- /dev/null +++ b/src/rest/main/main.go @@ -0,0 +1,206 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/signal" + "rest/server" + "swagger" + "syscall" + + "github.com/golang/glog" + "github.com/pkg/profile" +) + +// Command line parameters +var ( + port int // Server port + uiDir string // SwaggerUI directory + certFile string // Server certificate file path + keyFile string // Server private key file path + caFile string // Client CA certificate file path + clientAuth string // Client auth mode +) + +func init() { + // Parse command line + flag.IntVar(&port, "port", 443, "Listen port") + flag.StringVar(&uiDir, "ui", "/rest_ui", "UI directory") + flag.StringVar(&certFile, "cert", "", "Server certificate file path") + flag.StringVar(&keyFile, "key", "", "Server private key file path") + flag.StringVar(&caFile, "cacert", "", "CA certificate for client certificate validation") + flag.StringVar(&clientAuth, "client_auth", "none", "Client auth mode - none|cert|user") + flag.Parse() + // Suppress warning messages related to logging before flag parse + flag.CommandLine.Parse([]string{}) +} + +// Start REST server +func main() { + + /* Enable profiling by default. Send SIGUSR1 signal to rest_server to + * stop profiling and save data to /tmp/profile/cpu.pprof file. + * Copy over the cpu.pprof file and rest_server to a Linux host and run + * any of the following commands to generate a report in needed format. + * go tool pprof --txt ./rest_server ./cpu.pprof > report.txt + * go tool pprof --pdf ./rest_server ./cpu.pprof > report.pdf + * Note: install graphviz to generate the graph on a pdf format + */ + prof := profile.Start() + defer prof.Stop() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGUSR1) + go func() { + <-sigs + prof.Stop() + }() + + swagger.Load() + + server.SetUIDirectory(uiDir) + + if clientAuth == "user" { + server.SetUserAuthEnable(true) + } + + router := server.NewRouter() + + address := fmt.Sprintf(":%d", port) + + // Prepare TLSConfig from the parameters + tlsConfig := tls.Config{ + ClientAuth: getTLSClientAuthType(), + Certificates: prepareServerCertificate(), + ClientCAs: prepareCACertificates(), + MinVersion: tls.VersionTLS12, + CurvePreferences: getPreferredCurveIDs(), + PreferServerCipherSuites: true, + CipherSuites: getPreferredCipherSuites(), + } + + // Prepare HTTPS server + restServer := &http.Server{ + Addr: address, + Handler: router, + TLSConfig: &tlsConfig, + } + + glog.Infof("Server started on %v", address) + glog.Infof("UI directory is %v", uiDir) + + // Start HTTPS server + glog.Fatal(restServer.ListenAndServeTLS("", "")) +} + +// prepareServerCertificate function parses --cert and --key parameter +// values. Both cert and private key PEM files are loaded into a +// tls.Certificate objects. Exits the process if files are not +// specified or not found or corrupted. +func prepareServerCertificate() []tls.Certificate { + if certFile == "" { + glog.Fatal("Server certificate file not specified") + } + + if keyFile == "" { + glog.Fatal("Server private key file not specified") + } + + glog.Infof("Server certificate file: %s", certFile) + glog.Infof("Server private key file: %s", keyFile) + + certificate, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + glog.Fatal("Failed to load server cert/key -- ", err) + } + + return []tls.Certificate{certificate} +} + +// prepareCACertificates function parses --ca parameter, which is the +// path to CA certificate file. Loads file contents to a x509.CertPool +// object. Returns nil if file name is empty (not specified). Exists +// the process if file path is invalid or file is corrupted. +func prepareCACertificates() *x509.CertPool { + if caFile == "" { // no CA file.. + return nil + } + + glog.Infof("Client CA certificate file: %s", caFile) + + caCert, err := ioutil.ReadFile(caFile) + if err != nil { + glog.Fatal("Failed to load CA certificate file -- ", err) + } + + caPool := x509.NewCertPool() + ok := caPool.AppendCertsFromPEM(caCert) + if !ok { + glog.Fatal("Invalid CA certificate") + } + + return caPool +} + +// getTLSClientAuthType function parses the --client_auth parameter. +// Returns corresponding tls.ClientAuthType value. Exits the process +// if value is not valid ('none', 'cert' or 'auth') +func getTLSClientAuthType() tls.ClientAuthType { + switch clientAuth { + case "none": + return tls.RequestClientCert + case "user": + return tls.RequestClientCert + case "cert": + if caFile == "" { + glog.Fatal("--cacert option is mandatory when --client_auth is 'cert'") + } + return tls.RequireAndVerifyClientCert + default: + glog.Fatalf("Invalid '--client_auth' value '%s'. "+ + "Expecting one of 'none', 'cert' or 'user'", clientAuth) + return tls.RequireAndVerifyClientCert // dummy + } +} + +func getPreferredCurveIDs() []tls.CurveID { + return []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + } +} + +func getPreferredCipherSuites() []uint16 { + return []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + } +} diff --git a/src/rest/main/main_test.go b/src/rest/main/main_test.go new file mode 100644 index 0000000000..50ee1010da --- /dev/null +++ b/src/rest/main/main_test.go @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "testing" +) + +func TestMain(t *testing.T) { + go main() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGUSR1) + fmt.Println("Listening on sig kill from TestMain") + <-sigs + fmt.Println("Returning from TestMain on sig kill") +} + diff --git a/src/rest/server/context.go b/src/rest/server/context.go new file mode 100644 index 0000000000..883679e343 --- /dev/null +++ b/src/rest/server/context.go @@ -0,0 +1,190 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "context" + "fmt" + "mime" + "net/http" + "regexp" + "sync/atomic" +) + +// RequestContext holds metadata about REST request. +type RequestContext struct { + + // Unique reqiest id + ID string + + // Name represents the operationId from OpenAPI spec + Name string + + // "consumes" and "produces" data from OpenAPI spec + Consumes MediaTypes + Produces MediaTypes + + // Model holds pointer to the OpenAPI data model object for + // the body. When set, the request handler can validate the + // request payload by loading the body into this model object. + Model interface{} +} + +type contextkey int + +const requestContextKey contextkey = 0 + +// Request Id generator +var requestCounter uint64 + +// GetContext function returns the RequestContext object for a +// HTTP request. RequestContext is maintained as a context value of +// the request. Creates a new RequestContext object is not already +// available; in which case this function also creates a copy of +// the HTTP request object with new context. +func GetContext(r *http.Request) (*RequestContext, *http.Request) { + cv := r.Context().Value(requestContextKey) + if cv != nil { + return cv.(*RequestContext), r + } + + rc := new(RequestContext) + rc.ID = fmt.Sprintf("REST-%v", atomic.AddUint64(&requestCounter, 1)) + + r = r.WithContext(context.WithValue(r.Context(), requestContextKey, rc)) + return rc, r +} + +/////////// + +// MediaType represents the parsed media type value. Includes +// a MIME type string and optional parameters. +type MediaType struct { + Type string + Params map[string]string + + TypePrefix string + TypeSuffix string + TypeMiddle string +} + +// mediaTypeExpr is the regex to extract parts from media type string. +var mediaTypeExpr = regexp.MustCompile(`([^/]+)(?:/(?:([^+]+)\+)?(.+))?`) + +// Parse function parses a full media type value with parameters +// into this MediaType object. +func parseMediaType(value string) (*MediaType, error) { + if value == "" { + return nil, nil + } + + mtype, params, err := mime.ParseMediaType(value) + if err != nil { + return nil, err + } + + // Extract parts from the mime type + parts := mediaTypeExpr.FindStringSubmatch(mtype) + if parts[3] == "*" && parts[2] == "" { // for patterns like "text/*" + parts[2] = "*" + } + + return &MediaType{Type: mtype, Params: params, + TypePrefix: parts[1], TypeMiddle: parts[2], TypeSuffix: parts[3]}, nil +} + +// Format function returns the full media type string - including +// MIME type and parameters. +func (m *MediaType) Format() string { + return mime.FormatMediaType(m.Type, m.Params) +} + +// Matches verifies if this Mediatype matches the another MediaType. +func (m *MediaType) Matches(other *MediaType) bool { + return m.Type == other.Type || + (matchPart(m.TypePrefix, other.TypePrefix) && + matchPart(m.TypeMiddle, other.TypeMiddle) && + matchPart(m.TypeSuffix, other.TypeSuffix)) +} + +// isJSON function checks if this media type represents a json +// content. Uses the suffix part of media type string. +func (m *MediaType) isJSON() bool { + return m.TypeSuffix == "json" +} + +func matchPart(x, y string) bool { + return x == y || x == "*" || y == "*" +} + +////////// + +// MediaTypes is a collection of parsed media type values +type MediaTypes []MediaType + +// Add function parses and adds a media type to the MediaTypes +// object. Any parameters in the media type value are ignored. +func (m *MediaTypes) Add(mimeType string) error { + mtype, err := parseMediaType(mimeType) + if err == nil { + *m = append(*m, *mtype) + } + + return err +} + +// Contains function checks if a given media type value is +// present in the ContentTypes. Ignores the media type parameters. +func (m *MediaTypes) Contains(mimeType string) bool { + t, _, _ := mime.ParseMediaType(mimeType) + for _, entry := range *m { + if entry.Type == t { + return true + } + } + + return false +} + +// GetMatching returns registered full content type value +// matching a given hint. +func (m *MediaTypes) GetMatching(mimeType string) MediaTypes { + mtype, err := parseMediaType(mimeType) + if err != nil { + return nil // TODO return error + } + + var matchList MediaTypes + for _, entry := range *m { + if entry.Matches(mtype) { + matchList = append(matchList, entry) + } + } + + return matchList +} + +func (m MediaTypes) String() string { + types := make([]string, 0, len(m)) + for _, entry := range m { + types = append(types, entry.Type) + } + return fmt.Sprintf("%v", types) +} diff --git a/src/rest/server/context_test.go b/src/rest/server/context_test.go new file mode 100644 index 0000000000..1df7fc3857 --- /dev/null +++ b/src/rest/server/context_test.go @@ -0,0 +1,228 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func init() { + fmt.Println("+++++ init context_test +++++") +} + +func TestGetContext(t *testing.T) { + r, err := http.NewRequest("GET", "/index.html", nil) + if err != nil { + t.Fatalf("Unexpected error; %v", err) + } + + idSufix := fmt.Sprintf("-%v", requestCounter+1) + + rc1, r := GetContext(r) + rc2, r := GetContext(r) + rc3, r := GetContext(r) + + if rc1 != rc2 || rc1 != rc3 { + t.Fatalf("Got duplicate contexts!!") + } + + if !strings.HasSuffix(rc1.ID, idSufix) { + t.Fatalf("Unexpected id '%s'; expected suffix '%s", rc1.ID, idSufix) + } +} + +func TestParseEmptyMtype(t *testing.T) { + m, e := parseMediaType("") + if m != nil || e != nil { + t.Errorf("Unexpected return values; m=%v, e=%v", m, e) + } +} + +func TestParseMtype(t *testing.T) { + t.Run("X/Y", testParseMtype("application/json", + "application/json", "application", "", "json", mkmap())) + + t.Run("X/Y+Z", testParseMtype("application/yang-data+json", + "application/yang-data+json", "application", "yang-data", "json", mkmap())) + + t.Run("X/Z; A=B", testParseMtype("application/xml; q=5", + "application/xml", "application", "", "xml", mkmap("q", "5"))) + + t.Run("X/Z; A=B; C=D", testParseMtype("application/xml; q=5; ver=0.1", + "application/xml", "application", "", "xml", mkmap("q", "5", "ver", "0.1"))) + + t.Run("*/*", testParseMtype("*/*", + "*/*", "*", "*", "*", mkmap())) + + t.Run("text/*", testParseMtype("text/*", + "text/*", "text", "*", "*", mkmap())) + + t.Run("*/xml", testParseMtype("*/xml", + "*/xml", "*", "", "xml", mkmap())) + + t.Run("text/*+xml", testParseMtype("text/*+xml", + "text/*+xml", "text", "*", "xml", mkmap())) + + // invalid media types + t.Run("Partial", testBadMtype("application/")) + t.Run("WithSpace", testBadMtype("app lication/json")) + t.Run("WithSpace2", testBadMtype("application/ json")) + t.Run("BadParam", testBadMtype("application/json;q=10 x=20")) +} + +func testParseMtype(v, mimeType, prefix, middle, suffix string, params map[string]string) func(*testing.T) { + return func(t *testing.T) { + m := toMtype(t, v) + if m.Type != mimeType || m.TypePrefix != prefix || + m.TypeMiddle != middle || m.TypeSuffix != suffix || + reflect.DeepEqual(m.Params, params) == false { + t.Fatalf("\"%s\" did not tokenize into mime=\"%s\", prefix=\"%s\", middle=\"%s\", suffix=\"%s\", params=%v", + v, mimeType, prefix, middle, suffix, params) + } + if m.Format() != v { + t.Logf("Could not reconstruct \"%s\" from %v", v, m) + } + } +} + +func testBadMtype(v string) func(*testing.T) { + return func(t *testing.T) { + _, e := parseMediaType(v) + if e == nil { + t.Errorf("Invalid media type \"%s\" not flagged", v) + } + } +} + +func toMtype(t *testing.T, v string) *MediaType { + m, e := parseMediaType(v) + if e != nil { + t.Fatalf("Bad media type \"%s\"; err=%v", v, e) + } + return m +} + +func mkmap(args ...string) map[string]string { + m := make(map[string]string) + for i := 0; i < len(args); i += 2 { + m[args[i]] = args[i+1] + } + return m +} + +func TestMtypeMatch(t *testing.T) { + t.Run("A/B=~A/B", testMtypeMatch("text/json", "text/json", true)) + t.Run("A/B!~A/C", testMtypeMatch("text/json", "text/xml", false)) + t.Run("A/B!~C/B", testMtypeMatch("text/json", "new/json", false)) + t.Run("A/B=~*/*", testMtypeMatch("text/json", "*/*", true)) + t.Run("A/B=~A/*", testMtypeMatch("text/json", "text/*", true)) + t.Run("A/B=~*/B", testMtypeMatch("text/json", "*/json", true)) + t.Run("A/B!~*/C+B", testMtypeMatch("text/json", "*/new+json", false)) + t.Run("A/B!~*/B+*", testMtypeMatch("text/json", "*/json+*", false)) + t.Run("A/B=~A/*+B", testMtypeMatch("text/json", "text/*+json", true)) + t.Run("A/B=~A/*+*", testMtypeMatch("text/json", "text/*+*", true)) + t.Run("A/V+B=~A/V+B", testMtypeMatch("text/v1+json", "text/v1+json", true)) + t.Run("A/V+B=~A/V+*", testMtypeMatch("text/v1+json", "text/v1+*", true)) + t.Run("A/V+B=~A/*+B", testMtypeMatch("text/v1+json", "text/*+json", true)) + t.Run("A/V+B=~A/*", testMtypeMatch("text/v1+json", "text/*", true)) + t.Run("A/V+B=~*/*", testMtypeMatch("text/v1+json", "*/*", true)) + t.Run("A/V+B!~A/B", testMtypeMatch("text/v1+json", "text/json", false)) +} + +func testMtypeMatch(lhs, rhs string, exp bool) func(*testing.T) { + return func(t *testing.T) { + x := toMtype(t, lhs) + y := toMtype(t, rhs) + if x.Matches(y) != exp { + t.Fatalf("condition failed: \"%s\" match \"%s\" == %v", lhs, rhs, exp) + } + } +} + +func TestIsJSON(t *testing.T) { + t.Run("A/json", testIsJSON("text/json", true)) + t.Run("A/V+json", testIsJSON("text/v1+json", true)) + t.Run("A/json+V", testIsJSON("text/json+V", false)) + t.Run("A/xml", testIsJSON("text/xml", false)) + t.Run("json/A", testIsJSON("json/text", false)) +} + +func testIsJSON(v string, exp bool) func(*testing.T) { + return func(t *testing.T) { + m := toMtype(t, v) + if m.isJSON() != exp { + t.Fatalf("condition failed: isJson(\"%s\") == %v", v, exp) + } + } +} + +func TestMtypes(t *testing.T) { + var m MediaTypes + + // add 3 values to MediaTypes + m.Add("text/xml; q=5") + m.Add("text/json; q=2; r=3") + m.Add("text/v1+json") + + // check length + if len(m) != 3 { + t.Fatalf("Expected 3 items; found %d", len(m)) + } + + // check String() + expStrValue := "[text/xml text/json text/v1+json]" + if m.String() != expStrValue { + t.Fatalf("String() check failed.. expected \"%s\"; found \"%s\"", expStrValue, m) + } + + // check Contains() + t.Run("Contains#1", testMtypesContains(m, "text/xml", true)) + t.Run("Contains#2", testMtypesContains(m, "text/json", true)) + t.Run("Contains#3", testMtypesContains(m, "text/v1+json", true)) + t.Run("NotContains", testMtypesContains(m, "text/plain", false)) + + // check GetMatching() + t.Run("Match 1", testMtypesGetMatching(m, "text/xml", "[text/xml]")) + t.Run("Match 2", testMtypesGetMatching(m, "text/*+json", "[text/json text/v1+json]")) + t.Run("Match 0", testMtypesGetMatching(m, "text/plain", "[]")) + t.Run("Match Err", testMtypesGetMatching(m, "text plain", "[]")) +} + +func testMtypesContains(m MediaTypes, v string, exp bool) func(*testing.T) { + return func(t *testing.T) { + if m.Contains(v) != exp { + t.Fatalf("condition failed: %v.Contains(\"%s\") == %v", m, v, exp) + } + } +} + +func testMtypesGetMatching(m MediaTypes, v, exp string) func(*testing.T) { + return func(t *testing.T) { + m1 := m.GetMatching(v) + if m1.String() != exp { + t.Logf("Items matching \"%s\" from %s are %s", v, m, m1) + t.Fatalf("Expected %s", exp) + } + } +} diff --git a/src/rest/server/error.go b/src/rest/server/error.go new file mode 100644 index 0000000000..0335e70eac --- /dev/null +++ b/src/rest/server/error.go @@ -0,0 +1,202 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "encoding/json" + "fmt" + "net/http" + + "cvl" + "translib/tlerr" +) + +// errorResponse defines the RESTCONF compliant error response +// payload. It includes a list of errorEntry object. +type errorResponse struct { + Err struct { + Arr []errorEntry `json:"error"` + } `json:"ietf-restconf:errors"` +} + +// errorEntry defines the RESTCONF compilant error information +// payload. +type errorEntry struct { + Type errtype `json:"error-type"` + Tag errtag `json:"error-tag"` + AppTag string `json:"error-app-tag,omitempty"` + Path string `json:"error-path,omitempty"` + Message string `json:"error-message,omitempty"` +} + +type errtype string +type errtag string + +const ( + // error-type values + errtypeProtocol errtype = "protocol" + errtypeApplication errtype = "application" + + // error-tag values + errtagInvalidValue errtag = "invalid-value" + errtagOperationFailed errtag = "operation-failed" + errtagOperationNotSupported errtag = "operation-not-supported" + errtagAccessDenied errtag = "access-denied" + errtagResourceDenied errtag = "resource-denied" + errtagInUse errtag = "in-use" + errtagMalformedMessage errtag = "malformed-message" +) + +// httpErrorType is an error structure for indicating HTTP protocol +// errors. Includes HTTP status code and user displayable message. +type httpErrorType struct { + status int + message string +} + +func (e httpErrorType) Error() string { + return e.message +} + +func httpError(status int, msg string, args ...interface{}) error { + return httpErrorType{ + status: status, + message: fmt.Sprintf(msg, args...)} +} + +func httpBadRequest(msg string, args ...interface{}) error { + return httpError(http.StatusBadRequest, msg, args...) +} + +func httpServerError(msg string, args ...interface{}) error { + return httpError(http.StatusInternalServerError, msg, args...) +} + +// prepareErrorResponse returns HTTP status code and response payload +// for an error object. Response payalod is formatted as per RESTCONF +// specification (RFC8040, section 7.1). Uses json encoding. +func prepareErrorResponse(err error, r *http.Request) (status int, data []byte, mimeType string) { + status, entry := toErrorEntry(err, r) + var resp errorResponse + resp.Err.Arr = append(resp.Err.Arr, entry) + data, _ = json.Marshal(&resp) + mimeType = "application/yang-data+json" + return +} + +// toErrorEntry translates an error object into HTTP status and an +// errorEntry object. +func toErrorEntry(err error, r *http.Request) (status int, errInfo errorEntry) { + // By default everything is 500 Internal Server Error + status = http.StatusInternalServerError + errInfo.Type = errtypeApplication + errInfo.Tag = errtagOperationFailed + + switch e := err.(type) { + case httpErrorType: + status = e.status + errInfo.Type = errtypeProtocol + errInfo.Message = e.message + + // Guess error app tag from http status code + switch status { + case http.StatusBadRequest: // 400 + errInfo.Tag = errtagInvalidValue + case http.StatusUnauthorized: // 401 + errInfo.Tag = errtagAccessDenied + case http.StatusForbidden: // 403 + errInfo.Tag = errtagAccessDenied + case http.StatusNotFound: // 404 + errInfo.Tag = errtagInvalidValue + case http.StatusMethodNotAllowed: // 405 + errInfo.Tag = errtagOperationNotSupported + case http.StatusUnsupportedMediaType: + errInfo.Tag = errtagInvalidValue + default: // 5xx and others + errInfo.Tag = errtagOperationFailed + } + + case tlerr.TranslibSyntaxValidationError: + status = http.StatusBadRequest + errInfo.Type = errtypeProtocol + errInfo.Tag = errtagInvalidValue + errInfo.Message = e.ErrorStr.Error() + + case tlerr.TranslibRedisClientEntryNotExist: + status = http.StatusNotFound + errInfo.Tag = errtagInvalidValue + errInfo.Message = "Entry not found" + + case tlerr.TranslibCVLFailure: + status = http.StatusInternalServerError + errInfo.Tag = errtagInvalidValue + errInfo.Message = e.CVLErrorInfo.ConstraintErrMsg + errInfo.AppTag = e.CVLErrorInfo.ErrAppTag + + switch cvl.CVLRetCode(e.Code) { + case cvl.CVL_SEMANTIC_KEY_ALREADY_EXIST, cvl.CVL_SEMANTIC_KEY_DUPLICATE: + status = http.StatusConflict + errInfo.Tag = errtagResourceDenied + errInfo.Message = "Entry already exists" + + case cvl.CVL_SEMANTIC_KEY_NOT_EXIST: + status = http.StatusNotFound + errInfo.Tag = errtagInvalidValue + errInfo.Message = "Entry not found" + } + + case tlerr.TranslibTransactionFail: + status = http.StatusConflict + errInfo.Type = errtypeProtocol + errInfo.Tag = errtagInUse + errInfo.Message = "Transaction failed. Please try again." + + case tlerr.InternalError: + errInfo.Message = e.Error() + errInfo.Path = e.Path + + case tlerr.NotSupportedError: + status = http.StatusMethodNotAllowed + errInfo.Tag = errtagOperationNotSupported + errInfo.Message = e.Error() + errInfo.Path = e.Path + + case tlerr.InvalidArgsError: + status = http.StatusBadRequest + errInfo.Tag = errtagInvalidValue + errInfo.Message = e.Error() + errInfo.Path = e.Path + + case tlerr.NotFoundError: + status = http.StatusNotFound + errInfo.Tag = errtagInvalidValue + errInfo.Message = e.Error() + errInfo.Path = e.Path + + case tlerr.AlreadyExistsError: + status = http.StatusConflict + errInfo.Tag = errtagResourceDenied + errInfo.Message = e.Error() + errInfo.Path = e.Path + + } + + return +} diff --git a/src/rest/server/error_test.go b/src/rest/server/error_test.go new file mode 100644 index 0000000000..7dfdfce3df --- /dev/null +++ b/src/rest/server/error_test.go @@ -0,0 +1,229 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "errors" + "fmt" + "strings" + "testing" + + "cvl" + "translib/tlerr" +) + +func init() { + fmt.Println("+++++ init error_test +++++") +} + +func TestProtoError(t *testing.T) { + t.Run("400", testProtoError( + httpBadRequest("Bad %s", "json"), 400, "Bad json")) + + t.Run("500", testProtoError( + httpServerError("Failed"), 500, "Failed")) + + t.Run("XXX", testProtoError( + httpError(401, "Invalid user"), 401, "Invalid user")) +} + +func testProtoError(err error, status int, msg string) func(*testing.T) { + return func(t *testing.T) { + e, ok := err.(httpErrorType) + if !ok { + t.Error("not a httpErrorType") + } else if e.status != status || chkmsg(e.message, msg) == false { + t.Errorf("expecting %d/'%s'; found %d/'%s'", + status, msg, e.status, e.message) + } + } +} + +func TestErrorEntry(t *testing.T) { + + // errorEntry mapping for server errors + + t.Run("RequestReadError", testErrorEntry( + httpBadRequest("hii"), + 400, "protocol", "invalid-value", "", "hii")) + + t.Run("GenericServerError", testErrorEntry( + httpServerError("hii"), + 500, "protocol", "operation-failed", "", "hii")) + + t.Run("AuthenticationFailed", testErrorEntry( + httpError(401, "hii"), + 401, "protocol", "access-denied", "", "hii")) + + t.Run("AuthorizationFailed", testErrorEntry( + httpError(403, "hii"), + 403, "protocol", "access-denied", "", "hii")) + + t.Run("NotFound", testErrorEntry( + httpError(404, "404 NotFound."), + 404, "protocol", "invalid-value", "", "404 NotFound.")) + + t.Run("NotSupported", testErrorEntry( + httpError(405, "405 NotSupported."), + 405, "protocol", "operation-not-supported", "", "405 NotSupported.")) + + t.Run("UnknownMediaType", testErrorEntry( + httpError(415, "hii"), + 415, "protocol", "invalid-value", "", "hii")) + + // errorEntry mapping for unknown errors + + t.Run("UnknownError", testErrorEntry( + errors.New("hii"), + 500, "application", "operation-failed", "", "")) + + // errorEntry mapping for app errors + + t.Run("InvalidArgs", testErrorEntry( + tlerr.InvalidArgsError{Format: "hii", Path: "xyz"}, + 400, "application", "invalid-value", "xyz", "hii")) + + t.Run("ResourceNotFound", testErrorEntry( + tlerr.NotFoundError{Format: "hii", Path: "xyz"}, + 404, "application", "invalid-value", "xyz", "hii")) + + t.Run("AlreadyExists", testErrorEntry( + tlerr.AlreadyExistsError{Format: "hii", Path: "xyz"}, + 409, "application", "resource-denied", "xyz", "hii")) + + t.Run("UnsupportedOper", testErrorEntry( + tlerr.NotSupportedError{Format: "hii", Path: "xyz"}, + 405, "application", "operation-not-supported", "xyz", "hii")) + + t.Run("AppGenericErr", testErrorEntry( + tlerr.InternalError{Format: "hii", Path: "xyz"}, + 500, "application", "operation-failed", "xyz", "hii")) + + // errorEntry mapping for DB errors + + t.Run("DB_EntryNotExist", testErrorEntry( + tlerr.TranslibRedisClientEntryNotExist{}, + 404, "application", "invalid-value", "", "Entry not found")) + + t.Run("TransactionFailed", testErrorEntry( + tlerr.TranslibTransactionFail{}, + 409, "protocol", "in-use", "", "*")) + + t.Run("DB_CannotOpen", testErrorEntry( + tlerr.TranslibDBCannotOpen{}, + 500, "application", "operation-failed", "", "")) + + t.Run("DB_NotInit", testErrorEntry( + tlerr.TranslibDBNotInit{}, + 500, "application", "operation-failed", "", "")) + + t.Run("DB_SubscribeFailed", testErrorEntry( + tlerr.TranslibDBSubscribeFail{}, + 500, "application", "operation-failed", "", "")) + + // errorEntry mapping for CVL errors + + t.Run("CVL_KeyNotExists", testErrorEntry( + cvlError(cvl.CVL_SEMANTIC_KEY_NOT_EXIST, "hii"), + 404, "application", "invalid-value", "", "Entry not found")) + + t.Run("CVL_KeyExists", testErrorEntry( + cvlError(cvl.CVL_SEMANTIC_KEY_ALREADY_EXIST, "hii"), + 409, "application", "resource-denied", "", "Entry already exists")) + + t.Run("CVL_KeyDup", testErrorEntry( + cvlError(cvl.CVL_SEMANTIC_KEY_DUPLICATE, "hii"), + 409, "application", "resource-denied", "", "Entry already exists")) + + t.Run("CVL_SemanticErr", testErrorEntry( + cvlError(cvl.CVL_SEMANTIC_ERROR, "hii"), + 500, "application", "invalid-value", "", "hii")) + + // errorEntry mapping for YGOT errors + t.Run("YGOT_400", testErrorEntry( + tlerr.TranslibSyntaxValidationError{ErrorStr: errors.New("ygot")}, + 400, "protocol", "invalid-value", "", "ygot")) + +} + +func testErrorEntry(err error, + expStatus int, expType, expTag, expPath, expMessage string) func(*testing.T) { + return func(t *testing.T) { + status, entry := toErrorEntry(err, nil) + if status != expStatus || string(entry.Type) != expType || + string(entry.Tag) != expTag || entry.Path != expPath || + chkmsg(entry.Message, expMessage) == false { + t.Errorf("%T: expecting %d/%s/%s/\"%s\"/\"%s\"; found %d/%s/%s/\"%s\"/\"%s\"", + err, expStatus, expType, expTag, expPath, expMessage, + status, entry.Type, entry.Tag, entry.Path, entry.Message) + } + } +} + +func TestErrorResponse(t *testing.T) { + t.Run("WithMsg", testErrorResponse( + tlerr.NotFoundError{Format: "hii", Path: "xyz"}, + 404, "{\"ietf-restconf:errors\":{\"error\":[{"+ + "\"error-type\":\"application\",\"error-tag\":\"invalid-value\","+ + "\"error-path\":\"xyz\",\"error-message\":\"hii\"}]}}")) + + t.Run("NoMsg", testErrorResponse( + errors.New("hii"), + 500, "{\"ietf-restconf:errors\":{\"error\":[{"+ + "\"error-type\":\"application\",\"error-tag\":\"operation-failed\"}]}}")) +} + +func testErrorResponse(err error, expStatus int, expData string) func(*testing.T) { + return func(t *testing.T) { + status, data, ctype := prepareErrorResponse(err, nil) + + if status != expStatus { + t.Errorf("FAIL: bad status %d; expected %d", status, expStatus) + } else if ctype != "application/yang-data+json" { + t.Errorf("FAIL: bad content-type '%s'", ctype) + } else if string(data) != expData { + t.Errorf("FAIL: bad data %s", data) + t.Errorf("expected %s", expData) + } + } +} + +func chkmsg(actual, expected string) bool { + if expected == "*" { + return true + } + if strings.HasPrefix(expected, "!") { + return actual != expected[1:] + } + return actual == expected +} + +func cvlError(code cvl.CVLRetCode, msg string) error { + return tlerr.TranslibCVLFailure{ + Code: int(code), + CVLErrorInfo: cvl.CVLErrorInfo{ + ErrCode: code, + TableName: "unknown", + CVLErrDetails: "blah blah blah", + Msg: "ignore me", + ConstraintErrMsg: msg, + }, + } +} diff --git a/src/rest/server/handler.go b/src/rest/server/handler.go new file mode 100644 index 0000000000..5596c310f9 --- /dev/null +++ b/src/rest/server/handler.go @@ -0,0 +1,258 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "translib" + + "github.com/golang/glog" + "github.com/gorilla/mux" +) + +// Process function is the common landing place for all REST requests. +// Swagger code-gen should be configured to invoke this function +// from all generated stub functions. +func Process(w http.ResponseWriter, r *http.Request) { + rc, r := GetContext(r) + reqID := rc.ID + path := r.URL.Path + + var status int + var data []byte + var rtype string + + glog.Infof("[%s] %s %s; content-len=%d", reqID, r.Method, path, r.ContentLength) + _, body, err := getRequestBody(r, rc) + if err != nil { + status, data, rtype = prepareErrorResponse(err, r) + goto write_resp + } + + path = getPathForTranslib(r) + glog.Infof("[%s] Translated path = %s", reqID, path) + + status, data, err = invokeTranslib(reqID, r.Method, path, body) + if err != nil { + glog.Errorf("[%s] Translib error %T - %v", reqID, err, err) + status, data, rtype = prepareErrorResponse(err, r) + goto write_resp + } + + rtype, err = resolveResponseContentType(data, r, rc) + if err != nil { + glog.Errorf("[%s] Failed to resolve response content-type, err=%v", rc.ID, err) + status, data, rtype = prepareErrorResponse(err, r) + goto write_resp + } + +write_resp: + glog.Infof("[%s] Sending response %d, type=%s, data=%s", reqID, status, rtype, data) + + // Write http response.. Following strict order should be + // maintained to form proper response. + // 1. Set custom headers via w.Header().Set("N", "V") + // 2. Set status code via w.WriteHeader(code) + // 3. Finally, write response body via w.Write(bytes) + if len(data) != 0 { + w.Header().Set("Content-Type", rtype) + w.WriteHeader(status) + w.Write([]byte(data)) + } else { + // No data, status only + w.WriteHeader(status) + } +} + +// getRequestBody returns the validated request body +func getRequestBody(r *http.Request, rc *RequestContext) (*MediaType, []byte, error) { + if r.ContentLength == 0 { + glog.Infof("[%s] No body", rc.ID) + return nil, nil, nil + } + + // read body + body, err := ioutil.ReadAll(r.Body) + if err != nil { + glog.Errorf("[%s] Failed to read body; err=%v", rc.ID, err) + return nil, nil, httpError(http.StatusInternalServerError, "") + } + + // Parse content-type header value + ctype := r.Header.Get("Content-Type") + + // Guess the contet type if client did not provide it + if ctype == "" { + glog.Infof("[%s] Content-type not provided in request. Guessing it...", rc.ID) + ctype = http.DetectContentType(body) + } + + ct, err := parseMediaType(ctype) + if err != nil { + glog.Errorf("[%s] Bad content-type '%s'; err=%v", + rc.ID, r.Header.Get("Content-Type"), err) + return nil, nil, httpBadRequest("Bad content-type") + } + + // Check if content type is one of the acceptable types specified + // in "consumes" section in OpenAPI spec. + if !rc.Consumes.Contains(ct.Type) { + glog.Errorf("[%s] Content-type '%s' not supported. Valid types %v", rc.ID, ct.Type, rc.Consumes) + return nil, nil, httpError(http.StatusUnsupportedMediaType, "Unsupported content-type") + } + + // Do payload validation if model info is set in the context. + if rc.Model != nil { + body, err = RequestValidate(body, ct, rc) + if err != nil { + return nil, nil, err + } + } + + glog.Infof("[%s] Content-type=%s; data=%s", rc.ID, ctype, body) + return ct, body, nil +} + +// resolveResponseContentType +func resolveResponseContentType(data []byte, r *http.Request, rc *RequestContext) (string, error) { + if len(data) == 0 { + return "", nil + } + + // If OpenAPI spec has only one "produces" option, assume that + // app module will return that exact type data!! + if len(rc.Produces) == 1 { + return rc.Produces[0].Format(), nil + } + + //TODO validate against Accept header + + return http.DetectContentType(data), nil +} + +// getPathForTranslib converts REST URIs into GNMI paths +func getPathForTranslib(r *http.Request) string { + // Return the URL path if no variables in the template.. + vars := mux.Vars(r) + if len(vars) == 0 { + return trimRestconfPrefix(r.URL.Path) + } + + path, err := mux.CurrentRoute(r).GetPathTemplate() + if err != nil { + glog.Infof("No path template for this route") + return trimRestconfPrefix(r.URL.Path) + } + + // Path is a template.. Convert it into GNMI style path + // WARNING: does not handle duplicate key attribute names + // + // Template = /openconfig-acl:acl/acl-sets/acl-set={name},{type} + // REST style = /openconfig-acl:acl/acl-sets/acl-set=TEST,ACL_IPV4 + // GNMI style = /openconfig-acl:acl/acl-sets/acl-set[name=TEST][type=ACL_IPV4] + path = trimRestconfPrefix(path) + path = strings.Replace(path, "={", "{", -1) + path = strings.Replace(path, "},{", "}{", -1) + + for k, v := range vars { + restStyle := fmt.Sprintf("{%v}", k) + gnmiStyle := fmt.Sprintf("[%v=%v]", k, v) + path = strings.Replace(path, restStyle, gnmiStyle, 1) + } + + return path +} + +// trimRestconfPrefix removes "/restconf/data" prefix from the path. +func trimRestconfPrefix(path string) string { + pattern := "/restconf/data/" + k := strings.Index(path, pattern) + if k >= 0 { + path = path[k+len(pattern)-1:] + } + + return path +} + +// 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) { + var status = 400 + var content []byte + var err error + + switch method { + case "GET": + req := translib.GetRequest{Path: path} + resp, err1 := translib.Get(req) + if err1 == nil { + status = 200 + content = []byte(resp.Payload) + } else { + err = err1 + } + + case "POST": + //TODO return 200 for operations request + status = 201 + req := translib.SetRequest{Path: path, Payload: payload} + _, err = translib.Create(req) + + case "PUT": + //TODO send 201 if PUT resulted in creation + status = 204 + req := translib.SetRequest{Path: path, Payload: payload} + _, err = translib.Replace(req) + + case "PATCH": + status = 204 + req := translib.SetRequest{Path: path, Payload: payload} + _, err = translib.Update(req) + + case "DELETE": + status = 204 + req := translib.SetRequest{Path: path} + _, err = translib.Delete(req) + + default: + glog.Errorf("[%s] Unknown method '%v'", reqID, method) + err = httpBadRequest("Invalid method") + } + + return status, content, err +} + +// hostMetadataHandler function handles "GET /.well-known/host-meta" +// request as per RFC6415. RESTCONF specification requires this for +// advertising the RESTCONF root path ("/restconf" in our case). +func hostMetadataHandler(w http.ResponseWriter, r *http.Request) { + var data bytes.Buffer + data.WriteString("") + data.WriteString("") + data.WriteString("") + + w.Header().Set("Content-Type", "application/xrd+xml") + w.Write(data.Bytes()) +} diff --git a/src/rest/server/handler_test.go b/src/rest/server/handler_test.go new file mode 100644 index 0000000000..16e1ca21f5 --- /dev/null +++ b/src/rest/server/handler_test.go @@ -0,0 +1,524 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "encoding/xml" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gorilla/mux" +) + +func init() { + fmt.Println("+++++ init handler_test +++++") +} + +var testRouter *mux.Router + +// Basic mux.Router tests +func TestRoutes(t *testing.T) { + initCount := countRoutes(NewRouter()) + + // Add couple of test handlers + + AddRoute("one", "GET", "/test/1", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(1) + }) + + AddRoute("two", "GET", "/test/2", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(2) + }) + + SetUIDirectory("/tmp/ui") // !!? + testRouter = NewRouter() + newCount := countRoutes(testRouter) + + if newCount != initCount+2 { + t.Fatalf("Expected route count %d; found %d", initCount+2, newCount) + } + + // Try the test URLs and an unknown URL. The unknonw path + // should return 404 + t.Run("Get1", testGet("/test/1", 1)) + t.Run("Get2", testGet("/test/2", 2)) + t.Run("GetUnknown", testGet("/test/unknown", 404)) + t.Run("Meta", testGet("/.well-known/host-meta", 200)) + + // Try the test URLs with authentication enabled.. This should + // fail the requests with 401 error. Unknown path should still + // return 404. + SetUserAuthEnable(true) + testRouter = NewRouter() + t.Run("Get1_auth", testGet("/test/1", 401)) + t.Run("Get2_auth", testGet("/test/2", 401)) + t.Run("GetUnknown_auth", testGet("/test/unknown", 404)) + + // Meta handler should not be affected by user auth + t.Run("Meta_auth", testGet("/.well-known/host-meta", 200)) + + // Cleanup for next tests + SetUserAuthEnable(false) + testRouter = nil +} + +// countRoutes counts the registered routes in a mux.Router +// object by walking it +func countRoutes(r *mux.Router) int { + var count int + r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + count++ + return nil + }) + + return count +} + +// Try the url and check response code +func testGet(url string, expStatus int) func(*testing.T) { + return func(t *testing.T) { + w := httptest.NewRecorder() + testRouter.ServeHTTP(w, httptest.NewRequest("GET", url, nil)) + if w.Code != expStatus { + t.Fatalf("Expected response code %d; found %d", expStatus, w.Code) + } + } +} + +func TestMetadataHandler(t *testing.T) { + r := httptest.NewRequest("GET", "/.well-known/host-meta", nil) + w := httptest.NewRecorder() + + NewRouter().ServeHTTP(w, r) + + if w.Code != 200 { + t.Fatalf("Request failed with status %d", w.Code) + } + + ct, _ := parseMediaType(w.Header().Get("content-type")) + if ct == nil || ct.Type != "application/xrd+xml" { + t.Fatalf("Unexpected content-type '%s'", w.Header().Get("content-type")) + } + + data := w.Body.Bytes() + if len(data) == 0 { + t.Fatalf("No response body") + } + + var payload struct { + XMLName xml.Name `xml:"XRD"` + Links []struct { + Rel string `xml:"rel,attr"` + Href string `xml:"href,attr"` + } `xml:"Link"` + } + + err := xml.Unmarshal(data, &payload) + if err != nil { + t.Fatalf("Response parsing failed; err=%v", err) + } + + if payload.XMLName.Local != "XRD" || + payload.XMLName.Space != "http://docs.oasis-open.org/ns/xri/xrd-1.0" { + t.Fatalf("Invalid response '%s'", data) + } + + var rcRoot string + for _, x := range payload.Links { + if x.Rel == "restconf" { + rcRoot = x.Href + } + } + + t.Logf("Restconf root = '%s'", rcRoot) + if rcRoot != "/restconf" { + t.Fatalf("Invalid restconf root; expected '/restconf'") + } +} + +// Test REST to Translib path conversions +func TestPathConv(t *testing.T) { + + t.Run("novar", testPathConv( + "/simple/url/with/no/vars", + "/simple/url/with/no/vars", + "/simple/url/with/no/vars")) + + t.Run("1var", testPathConv( + "/sample/id={name}", + "/sample/id=TEST1", + "/sample/id[name=TEST1]")) + + t.Run("1var_no=", testPathConv( + "/sample/{name}", + "/sample/TEST1", + "/sample/[name=TEST1]")) + + t.Run("1var_middle", testPathConv( + "/sample/id={name}/test/suffix", + "/sample/id=TEST1/test/suffix", + "/sample/id[name=TEST1]/test/suffix")) + + t.Run("2vars", testPathConv( + "/sample/id={name},{type}", + "/sample/id=TEST2,NEW", + "/sample/id[name=TEST2][type=NEW]")) + + t.Run("2vars_middle", testPathConv( + "/sample/id={name},{type}/hey", + "/sample/id=TEST2,NEW/hey", + "/sample/id[name=TEST2][type=NEW]/hey")) + + t.Run("5vars", testPathConv( + "/sample/key={name},{type},{subtype},{color},{ver}", + "/sample/key=TEST2,NEW,LATEST,RED,1.0", + "/sample/key[name=TEST2][type=NEW][subtype=LATEST][color=RED][ver=1.0]")) + + t.Run("5vars_no=", testPathConv( + "/sample/{name},{type},{subtype},{color},{ver}", + "/sample/TEST2,NEW,LATEST,RED,1.0", + "/sample/[name=TEST2][type=NEW][subtype=LATEST][color=RED][ver=1.0]")) + + t.Run("multi", testPathConv( + "/sample/id={name},{type},{subtype}/data/color={colorname},{rgb}/{ver}", + "/sample/id=TEST2,NEW,LATEST/data/color=RED,ff0000/1.0", + "/sample/id[name=TEST2][type=NEW][subtype=LATEST]/data/color[colorname=RED][rgb=ff0000]/[ver=1.0]")) + + t.Run("rcdata_novar", testPathConv( + "/restconf/data/no/vars", + "/restconf/data/no/vars", + "/no/vars")) + + t.Run("xrcdata_novar", testPathConv( + "/myroot/restconf/data/no/vars", + "/myroot/restconf/data/no/vars", + "/no/vars")) + + t.Run("rcdata_1var", testPathConv( + "/restconf/data/id={name}", + "/restconf/data/id=TEST1", + "/id[name=TEST1]")) + + t.Run("xrcdata_1var", testPathConv( + "/myroot/restconf/data/id={name}", + "/myroot/restconf/data/id=TEST1", + "/id[name=TEST1]")) + + t.Run("no_template", testPathConv( + "*", + "/test/id=NOTEMPLATE", + "/test/id=NOTEMPLATE")) +} + +// test handler to invoke getPathForTranslib and write the conveted +// path into response. Conversion logic depends on context values +// managed by mux router. Hence should be called from a handler. +var pathConvHandler = func(w http.ResponseWriter, r *http.Request) { + // t, err := mux.CurrentRoute(r).GetPathTemplate() + // fmt.Printf("Patt : %v (err=%v)\n", t, err) + // fmt.Printf("Vars : %v\n", mux.Vars(r)) + + w.Write([]byte(getPathForTranslib(r))) +} + +func testPathConv(template, path, expPath string) func(*testing.T) { + return func(t *testing.T) { + router := mux.NewRouter() + if template == "*" { + t.Logf("No template...") + router.Methods("GET").HandlerFunc(pathConvHandler) + } else { + router.HandleFunc(template, pathConvHandler) + } + + w := httptest.NewRecorder() + router.ServeHTTP(w, httptest.NewRequest("GET", path, nil)) + + convPath := w.Body.String() + if convPath != expPath { + t.Logf("Conversion for template '%s' failed", template) + t.Logf("Input path '%s'", path) + t.Logf("Converted '%s'", convPath) + t.Logf("Expected '%s'", expPath) + t.FailNow() + } + } +} + +type errReader string + +func (er errReader) Read(p []byte) (n int, err error) { + return 0, errors.New(string(er)) +} + +func TestReqData_NoBody(t *testing.T) { + r := httptest.NewRequest("GET", "/test", nil) + rc := &RequestContext{ID: t.Name()} + + ct, data, err := getRequestBody(r, rc) + if ct != nil || data != nil || err != nil { + t.Fatalf("Expected nil response; found ct=%v, data=%v, err=%v", ct, data, err) + } +} + +func TestReqData_ReadFailure(t *testing.T) { + r := httptest.NewRequest("PUT", "/test", errReader("e-r-r-o-r")) + rc := &RequestContext{ID: t.Name()} + + testReqError(t, r, rc, 500) +} + +func TestReqData_Unknown(t *testing.T) { + r := httptest.NewRequest("PUT", "/test", strings.NewReader("Hello, world!")) + rc := &RequestContext{ID: t.Name()} + + testReqError(t, r, rc, 415) +} + +func TestReqData_Unknown2(t *testing.T) { + r := httptest.NewRequest("PUT", "/test", strings.NewReader("Hello, world!")) + rc := &RequestContext{ID: t.Name()} + rc.Consumes.Add("text/html") + + testReqError(t, r, rc, 415) +} + +func TestReqData_BadMime(t *testing.T) { + r := httptest.NewRequest("PUT", "/test", strings.NewReader("Hello, world!")) + r.Header.Set("content-type", "b a d") + rc := &RequestContext{ID: t.Name()} + rc.Consumes.Add("b a d") + + testReqError(t, r, rc, 400) +} + +func TestReqData_Text(t *testing.T) { + r := httptest.NewRequest("PUT", "/test", strings.NewReader("Hello, world!")) + rc := &RequestContext{ID: t.Name()} + rc.Consumes.Add("text/plain") + + testReqSuccess(t, r, rc, "text/plain", "Hello, world!") +} + +func TestReqData_Json(t *testing.T) { + input := "{\"one\":1}" + r := httptest.NewRequest("PUT", "/test", strings.NewReader(input)) + r.Header.Set("content-type", "application/json") + + rc := &RequestContext{ID: t.Name()} + rc.Consumes.Add("text/html") + rc.Consumes.Add("text/plain") + rc.Consumes.Add("application/json") + + testReqSuccess(t, r, rc, "application/json", input) +} + +func TestReqData_BadJsonNoValidation(t *testing.T) { + input := "{\"one:1}" + r := httptest.NewRequest("PUT", "/test", strings.NewReader(input)) + r.Header.Set("content-type", "application/json") + + rc := &RequestContext{ID: t.Name()} + rc.Consumes.Add("application/json") + + testReqSuccess(t, r, rc, "application/json", input) +} + +func TestReqData_BadJsonWithValidation(t *testing.T) { + input := "{\"one:1}" + r := httptest.NewRequest("PUT", "/test", strings.NewReader(input)) + r.Header.Set("content-type", "application/json") + + model := make(map[string]int) + rc := &RequestContext{ID: t.Name(), Model: &model} + rc.Consumes.Add("application/json") + + testReqError(t, r, rc, 400) +} + +func testReqSuccess(t *testing.T, r *http.Request, rc *RequestContext, expType, expData string) { + ct, data, err := getRequestBody(r, rc) + + if ct == nil || ct.Type != expType { + t.Fatalf("Expected %s; found %s", expType, ct.Type) + } + if data == nil || string(data) != expData { + t.Fatalf("Expected data \"%s\"; found \"%s\"", expData, data) + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func testReqError(t *testing.T, r *http.Request, rc *RequestContext, expCode int) { + ct, data, err := getRequestBody(r, rc) + + if ct != nil { + t.Fatalf("Expected nil content-type; found %s", ct.Type) + } + if data != nil { + t.Fatalf("Expected nil data; found \"%s\"", data) + } + + he, ok := err.(httpErrorType) + if !ok { + t.Fatalf("Expecting httpErrorType; got %T", err) + } + if he.status != expCode { + t.Fatalf("Expecting http status %d; got %d", expCode, he.status) + } +} + +func TestRespData_NoContent(t *testing.T) { + rc := &RequestContext{ID: t.Name()} + t.Run("nil", testRespData(nil, rc, nil, "")) + t.Run("empty", testRespData(nil, rc, []byte(""), "")) +} + +func TestRespData_NoProduces(t *testing.T) { + rc := &RequestContext{ID: t.Name()} + t.Run("txt", testRespData(nil, rc, []byte("Hello, world!"), "text/plain")) + t.Run("bin", testRespData(nil, rc, make([]byte, 5), "application/octet-stream")) +} + +func TestRespData_1Produces(t *testing.T) { + rc := &RequestContext{ID: t.Name()} + rc.Produces.Add("application/json") + + t.Run("jsn", testRespData(nil, rc, []byte("{}"), "application/json")) + t.Run("bin", testRespData(nil, rc, make([]byte, 5), "application/json")) +} + +func TestRespData_nProduces(t *testing.T) { + rc := &RequestContext{ID: t.Name()} + rc.Produces.Add("application/json") + rc.Produces.Add("application/xml") + rc.Produces.Add("text/plain") + + t.Run("jsn", testRespData(nil, rc, []byte("{}"), "text/plain")) + t.Run("bin", testRespData(nil, rc, make([]byte, 5), "application/octet-stream")) +} + +func testRespData(r *http.Request, rc *RequestContext, data []byte, expType string) func(*testing.T) { + return func(t *testing.T) { + if r == nil { + r = httptest.NewRequest("GET", "/get", nil) + } + + ctype, err := resolveResponseContentType(data, r, rc) + ct, err := parseMediaType(ctype) + + if (expType == "" && ctype != "") || (ct != nil && ct.Type != expType) { + t.Fatalf("Expected resp content-type \"%s\"; got \"%s\"", expType, ctype) + } + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + } +} + +func TestProcessGET(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "GET", "/test", "")) + verifyResponse(t, w, 500) +} + +func TestProcessGET_ACL(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "GET", "/openconfig-acl:acl", "")) + verifyResponse(t, w, 200) +} + +func TestProcessPUT(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "PUT", "/test", "{}")) + verifyResponse(t, w, 500) +} + +func TestProcessPOST(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "POST", "/test", "{}")) + verifyResponse(t, w, 500) +} + +func TestProcessPATCH(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "PATCH", "/test", "{}")) + verifyResponse(t, w, 500) +} + +func TestProcessDELETE(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "DELETE", "/test", "{}")) + verifyResponse(t, w, 500) +} + +func TestProcessBadMethod(t *testing.T) { + w := httptest.NewRecorder() + Process(w, prepareRequest(t, "TEST", "/test", "{}")) + verifyResponse(t, w, 400) +} + +func TestProcessBadContent(t *testing.T) { + w := httptest.NewRecorder() + r := prepareRequest(t, "PUT", "/test", "{}") + r.Header.Set("content-type", "bad/content") + + Process(w, r) + verifyResponse(t, w, 415) +} + +func TestProcessReadError(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("PUT", "/test", errReader("simulated error")) + r.Header.Set("content-type", "application/json") + + rc, r := GetContext(r) + rc.ID = t.Name() + rc.Consumes.Add("application/json") + + Process(w, r) + verifyResponse(t, w, 500) +} + +func prepareRequest(t *testing.T, method, path, data string) *http.Request { + r := httptest.NewRequest(method, path, strings.NewReader(data)) + rc, r := GetContext(r) + rc.ID = t.Name() + + if data != "" { + r.Header.Set("content-type", "application/json") + rc.Consumes.Add("application/json") + } else { + rc.Produces.Add("application/json") + } + + return r +} + +func verifyResponse(t *testing.T, w *httptest.ResponseRecorder, expCode int) { + if w.Code != expCode { + t.Fatalf("Expecting response status %d; got %d", expCode, w.Code) + } +} diff --git a/src/rest/server/pamAuth.go b/src/rest/server/pamAuth.go new file mode 100644 index 0000000000..4ad9f0180e --- /dev/null +++ b/src/rest/server/pamAuth.go @@ -0,0 +1,165 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "net/http" + "os/user" + + "github.com/golang/glog" + //"github.com/msteinert/pam" + "golang.org/x/crypto/ssh" +) + +/* +type UserCredential struct { + Username string + Password string +} + +//PAM conversation handler. +func (u UserCredential) PAMConvHandler(s pam.Style, msg string) (string, error) { + + switch s { + case pam.PromptEchoOff: + return u.Password, nil + case pam.PromptEchoOn: + return u.Password, nil + case pam.ErrorMsg: + return "", nil + case pam.TextInfo: + return "", nil + default: + return "", errors.New("unrecognized conversation message style") + } +} + +// PAMAuthenticate performs PAM authentication for the user credentials provided +func (u UserCredential) PAMAuthenticate() error { + tx, err := pam.StartFunc("login", u.Username, u.PAMConvHandler) + if err != nil { + return err + } + return tx.Authenticate(0) +} + +func PAMAuthUser(u string, p string) error { + + cred := UserCredential{u, p} + err := cred.PAMAuthenticate() + return err +} +*/ + +func IsAdminGroup(username string) bool { + + usr, err := user.Lookup(username) + if err != nil { + return false + } + gids, err := usr.GroupIds() + if err != nil { + return false + } + glog.V(2).Infof("User:%s, groups=%s", username, gids) + admin, err := user.Lookup("admin") + if err != nil { + return false + } + for _, x := range gids { + if x == admin.Gid { + return true + } + } + return false +} + +func PAMAuthenAndAuthor(r *http.Request, rc *RequestContext) error { + + username, passwd, authOK := r.BasicAuth() + if authOK == false { + glog.Errorf("[%s] User info not present", rc.ID) + return httpError(http.StatusUnauthorized, "") + } + + glog.Infof("[%s] Received user=%s", rc.ID, username) + + /* + * mgmt-framework container does not have access to /etc/passwd, /etc/group, + * /etc/shadow and /etc/tacplus_conf files of host. One option is to share + * /etc of host with /etc of container. For now disable this and use ssh + * for authentication. + */ + /* err := PAMAuthUser(username, passwd) + if err != nil { + log.Printf("Authentication failed. user=%s, error:%s", username, err.Error()) + return err + }*/ + + //Use ssh for authentication. + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(passwd), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + _, err := ssh.Dial("tcp", "127.0.0.1:22", config) + if err != nil { + glog.Infof("[%s] Failed to authenticate; %v", rc.ID, err) + return httpError(http.StatusUnauthorized, "") + } + + glog.Infof("[%s] Authentication passed. user=%s ", rc.ID, username) + + //Allow SET request only if user belong to admin group + if isWriteOperation(r) && IsAdminGroup(username) == false { + glog.Errorf("[%s] Not an admin; cannot allow %s", rc.ID, r.Method) + return httpError(http.StatusForbidden, "Not an admin user") + } + + glog.Infof("[%s] Authorization passed", rc.ID) + return nil +} + +// isWriteOperation checks if the HTTP request is a write operation +func isWriteOperation(r *http.Request) bool { + m := r.Method + return m == "POST" || m == "PUT" || m == "PATCH" || m == "DELETE" +} + +// authMiddleware function creates a middleware for request +// authentication and authorization. This middleware will return +// 401 response if authentication fails and 403 if authorization +// fails. +func authMiddleware(inner http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rc, r := GetContext(r) + err := PAMAuthenAndAuthor(r, rc) + if err != nil { + status, data, ctype := prepareErrorResponse(err, r) + w.Header().Set("Content-Type", ctype) + w.WriteHeader(status) + w.Write(data) + } else { + inner.ServeHTTP(w, r) + } + }) +} diff --git a/src/rest/server/pamAuth_test.go b/src/rest/server/pamAuth_test.go new file mode 100644 index 0000000000..d3015ea09d --- /dev/null +++ b/src/rest/server/pamAuth_test.go @@ -0,0 +1,195 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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. // +// // +//////////////////////////////////////////////////////////////////////////////// +// +// Test cases for REST Server PAM Authentication module. +// +// Runs various combinations with local and TACACS+ user credentials. +// Test users should be already configured in the system. Below table +// lists various default user name and passwords and corresponding +// command line parameters to override them. +// +// Test user type User name Password Command line param +// ---------------------- ------------- ---------- ------------------- +// Local admin user testadmin password -ladmname -ladmpass +// Local non-admin user testuser password -lusrname -lusrpass +// TACACS+ admin user tactestadmin password -tadmname -tadmpass +// TACACS+ non-admin user tactestuser password -tusrname -tusrpass +// +// By default all test cases are skipped!! This is to avoid seeing test +// failures if target system is not ready. Command line param -authtest +// should be passed to enable the test cases. Valid values are "local" +// or "tacacs" or comma separated list of them. +// +// -authtest=local ==> Tests with only local user credentials +// -authtest=tacacs ==> Tests with only TACACS+ user credentials +// -authtest=local,tacacs ==> Tests with both local and TACACS+ users +// +/////////////////////////////////////////////////////////////////////// + +package server + +import ( + "flag" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +var authTest map[string]bool +var lusrName = flag.String("lusrname", "testuser", "Local non-admin username") +var lusrPass = flag.String("lusrpass", "password", "Local non-admin password") +var ladmName = flag.String("ladmname", "testadmin", "Local admin username") +var ladmPass = flag.String("ladmpass", "password", "Local admin password") +var tusrName = flag.String("tusrname", "tactestuser", "TACACS+ non-admin username") +var tusrPass = flag.String("tusrpass", "password", "TACACS+ non-admin password") +var tadmName = flag.String("tadmname", "tactestadmin", "TACACS+ admin username") +var tadmPass = flag.String("tadmpass", "password", "TACACS+ admin password") + +func init() { + fmt.Println("+++++ pamAuth_test +++++") +} + +func TestMain(m *testing.M) { + + t := flag.String("authtest", "", "Comma separated auth types to test (local tacacs)") + flag.Parse() + + var tlist []string + if *t != "" { + authTest = make(map[string]bool) + for _, x := range strings.Split(*t, ",") { + v := strings.ToLower(strings.TrimSpace(x)) + if v == "local" || v == "tacacs" { + authTest[v] = true + tlist = append(tlist, v) + } + } + if len(authTest) != 0 { + authTest[""] = true // Special key for any auth + } + } + + fmt.Println("+++++ Enabled auth test types", tlist) + + os.Exit(m.Run()) +} + +// Dummy test handler which returns 200 on success; 401 on +// authentication failure and 403 on authorization failure +var authTestHandler = authMiddleware(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + })) + +func TestLocalUser_Get(t *testing.T) { + ensureAuthTestEnabled(t, "local") + testAuthGet(t, *lusrName, *lusrPass, 200) +} + +func TestLocalUser_Set(t *testing.T) { + ensureAuthTestEnabled(t, "local") + testAuthSet(t, *lusrName, *lusrPass, 403) +} + +func TestLocalAdmin_Get(t *testing.T) { + ensureAuthTestEnabled(t, "local") + testAuthGet(t, *ladmName, *ladmPass, 200) +} + +func TestLocalAdmin_Set(t *testing.T) { + ensureAuthTestEnabled(t, "local") + testAuthSet(t, *ladmName, *ladmPass, 200) +} + +func TestTacacsUser_Get(t *testing.T) { + ensureAuthTestEnabled(t, "tacacs") + testAuthGet(t, *tusrName, *tusrPass, 200) +} + +func TestTacacslUser_Set(t *testing.T) { + ensureAuthTestEnabled(t, "tacacs") + testAuthSet(t, *tusrName, *tusrPass, 403) +} + +func TestTacacsAdmin_Get(t *testing.T) { + ensureAuthTestEnabled(t, "tacacs") + testAuthGet(t, *tadmName, *tadmPass, 200) +} + +func TestTacacsAdmin_Set(t *testing.T) { + ensureAuthTestEnabled(t, "tacacs") + testAuthSet(t, *tadmName, *tadmPass, 200) +} + +func TestAuth_NoUser(t *testing.T) { + ensureAuthTestEnabled(t, "") + testAuthGet(t, "", "", 401) + testAuthSet(t, "", "", 401) +} + +func TestAuth_BadUser(t *testing.T) { + ensureAuthTestEnabled(t, "") + testAuthGet(t, "baduserbaduserbaduser", "password", 401) + testAuthSet(t, "baduserbaduserbaduser", "password", 401) +} + +func TestAuth_BadPass(t *testing.T) { + ensureAuthTestEnabled(t, "") + testAuthGet(t, *lusrName, "Hello,world!", 401) + testAuthSet(t, *ladmName, "Hello,world!", 401) +} + +func ensureAuthTestEnabled(t *testing.T, authtype string) { + if _, ok := authTest[authtype]; !ok { + t.Skipf("%s auth tests not enabled.. Rerun with -authtest flag", authtype) + } +} + +func testAuthGet(t *testing.T, username, password string, expStatus int) { + t.Run("GET", testAuth("GET", username, password, expStatus)) + t.Run("HEAD", testAuth("HEAD", username, password, expStatus)) + t.Run("OPTIONS", testAuth("OPTIONS", username, password, expStatus)) +} + +func testAuthSet(t *testing.T, username, password string, expStatus int) { + t.Run("PUT", testAuth("PUT", username, password, expStatus)) + t.Run("POST", testAuth("POST", username, password, expStatus)) + t.Run("PATCH", testAuth("PATCH", username, password, expStatus)) + t.Run("DELETE", testAuth("DELETE", username, password, expStatus)) +} + +func testAuth(method, username, password string, expStatus int) func(*testing.T) { + return func(t *testing.T) { + r := httptest.NewRequest(method, "/auth", nil) + w := httptest.NewRecorder() + + if username != "" { + r.SetBasicAuth(username, password) + } + + authTestHandler.ServeHTTP(w, r) + + if w.Code != expStatus { + t.Fatalf("Expected response %d; got %d", expStatus, w.Code) + } + } +} diff --git a/src/rest/server/req_validate.go b/src/rest/server/req_validate.go new file mode 100644 index 0000000000..4571d8a100 --- /dev/null +++ b/src/rest/server/req_validate.go @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "encoding/json" + "reflect" + + "github.com/golang/glog" + "gopkg.in/go-playground/validator.v9" +) + +func isSkipValidation(t reflect.Type) bool { + if t == reflect.TypeOf([]int32{}) { + return true + } + + return false +} + +// RequestValidate performas payload validation of request body. +func RequestValidate(payload []byte, ctype *MediaType, rc *RequestContext) ([]byte, error) { + if ctype.isJSON() { + return validateRequestJSON(payload, rc) + } + + glog.Infof("[%s] Skipping payload validation for content-type '%v'", rc.ID, ctype.Type) + return payload, nil +} + +// validateRequestJSON performs payload validation for JSON data +func validateRequestJSON(jsn []byte, rc *RequestContext) ([]byte, error) { + var err error + v := rc.Model + glog.Infof("[%s] Unmarshalling %d bytes into %T", rc.ID, len(jsn), v) + + err = json.Unmarshal(jsn, v) + if err != nil { + glog.Errorf("[%s] json decoding error; %v", rc.ID, err) + return nil, httpBadRequest("Invalid json") + } + + //log.Printf("Received data: %s\n", jsn) + //log.Printf("Type is: %T, Value is:%v\n", v, v) + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if !isSkipValidation(val.Type()) { + glog.Infof("[%s] Going to validate request", rc.ID) + validate := validator.New() + if val.Kind() == reflect.Slice { + //log.Println("Validate using Var") + err = validate.Var(v, "dive") + } else { + //log.Println("Validate using Struct") + err = validate.Struct(v) + } + if err != nil { + glog.Errorf("[%s] validation failed: %v", rc.ID, err) + return nil, httpBadRequest("Content not as per schema") + } + } else { + glog.Infof("[%s] Skipping payload validation for dataType %v", rc.ID, val.Type()) + } + + // Get sanitized json by marshalling validated body. Removes + // extra fields if any.. + newBody, err := json.Marshal(v) + if err != nil { + glog.Errorf("[%s] Failed to marshall; %v", rc.ID, err) + return nil, httpServerError("Internal error") + } + + return newBody, nil +} diff --git a/src/rest/server/router.go b/src/rest/server/router.go new file mode 100644 index 0000000000..aa01d193e5 --- /dev/null +++ b/src/rest/server/router.go @@ -0,0 +1,140 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 server + +import ( + "net/http" + "strings" + "time" + + "github.com/golang/glog" + "github.com/gorilla/mux" +) + +// Root directory for UI files +var swaggerUIDir = "./ui" +var isUserAuthEnabled = false + +// SetUIDirectory functions sets directiry where Swagger UI +// resources are maintained. +func SetUIDirectory(directory string) { + swaggerUIDir = directory +} + +// SetUserAuthEnable function enables/disables the PAM based user +// authentication for REST requests. By default user uthentication +// is disabled. When enabled, the server expects clients to pass +// user credentials as per HTTP Basic Autnetication method. +func SetUserAuthEnable(val bool) { + isUserAuthEnabled = val +} + +// Route registration information +type Route struct { + Name string + Method string + Pattern string + Handler http.HandlerFunc +} + +// Collection of all routes +var allRoutes []Route + +// AddRoute appends specified routes to the routes collection. +// Called by init functions of swagger generated router.go files. +func AddRoute(name, method, pattern string, handler http.HandlerFunc) { + route := Route{ + Name: name, + Method: strings.ToUpper(method), + Pattern: pattern, + Handler: handler, + } + + allRoutes = append(allRoutes, route) +} + +// NewRouter function returns a new http router instance. Collects +// route information from swagger-codegen generated code and makes a +// github.com/gorilla/mux router object. +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + + glog.Infof("Server has %d paths", len(allRoutes)) + + // Collect swagger generated route information + for _, route := range allRoutes { + handler := withMiddleware(route.Handler, route.Name) + + glog.V(2).Infof( + "Adding %s, %s %s", + route.Name, route.Method, route.Pattern) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + + // Documentation and test UI + uiHandler := http.StripPrefix("/ui/", http.FileServer(http.Dir(swaggerUIDir))) + router.Methods("GET").PathPrefix("/ui/").Handler(uiHandler) + + // Redirect "/ui" to "/ui/index.html" + router.Methods("GET").Path("/ui"). + Handler(http.RedirectHandler("/ui/index.html", 301)) + + //router.Methods("GET").Path("/model"). + // Handler(http.RedirectHandler("/ui/model.html", 301)) + + // Metadata discovery handler + metadataHandler := http.HandlerFunc(hostMetadataHandler) + router.Methods("GET").Path("/.well-known/host-meta"). + Handler(loggingMiddleware(metadataHandler, "hostMetadataHandler")) + + return router +} + +// loggingMiddleware returns a handler which times and logs the request. +// It should be the top handler in the middleware chain. +func loggingMiddleware(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rc, r := GetContext(r) + rc.Name = name + + glog.Infof("[%s] Recevied %s request from %s", rc.ID, name, r.RemoteAddr) + + start := time.Now() + + inner.ServeHTTP(w, r) + + glog.Infof("[%s] %s took %s", rc.ID, name, time.Since(start)) + }) +} + +// withMiddleware function prepares the default middleware chain for +// REST APIs. +func withMiddleware(h http.Handler, name string) http.Handler { + if isUserAuthEnabled { + h = authMiddleware(h) + } + + return loggingMiddleware(h, name) +} diff --git a/src/translib/Makefile b/src/translib/Makefile new file mode 100644 index 0000000000..69eec34521 --- /dev/null +++ b/src/translib/Makefile @@ -0,0 +1,60 @@ +####################################################################### +# +# Copyright 2019 Broadcom. All rights reserved. +# The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +# +####################################################################### + +ifeq ($(TOPDIR),) +TOPDIR := ../.. +endif + +ifeq ($(BUILD_DIR),) +BUILD_DIR := $(TOPDIR)/build +endif + +ifeq ($(GO),) +GO = /usr/local/go/bin/go +endif + +ifeq ($(GOPATH),) +GOPATH = $(shell $(GO) env GOPATH) +endif + +ifeq ($(GOROOT),) +GOROOT = $(shell $(GO) env GOROOT) +endif + +TRANSLIB_PKG = $(TOPDIR)/pkg/linux_amd64/translib.a + +TRANSLIB_MAIN_SRCS = $(shell find . -name '*.go' | grep -v '_test.go' | grep -v '/test/') +TRANSLIB_TEST_SRCS = $(shell find . -maxdepth 1 -name '*_test.go') +TRANSL_DB_ALL_SRCS = $(shell find db/ -name '*.go' | grep -v '/test/') + +TRANSLIB_TEST_DIR = $(BUILD_DIR)/tests/translib +TRANSLIB_TEST_BIN = $(TRANSLIB_TEST_DIR)/translib.test +TRANSL_DB_TEST_BIN = $(TRANSLIB_TEST_DIR)/db.test + +YANG_FILES = $(shell find $(TOPDIR)/models/yang -name '*.yang') +YGOT_BINDS = ocbinds/ocbinds.go + +all: $(TRANSLIB_PKG) $(TRANSLIB_TEST_BIN) $(TRANSL_DB_TEST_BIN) + +$(TRANSLIB_PKG): $(TRANSLIB_MAIN_SRCS) $(YGOT_BINDS) + $(GO) build -gcflags="all=-N -l" -v translib + $(GO) install translib + +$(TRANSLIB_TEST_BIN): $(TRANSLIB_MAIN_SRCS) $(TRANSLIB_TEST_SRCS) $(YGOT_BINDS) + $(GO) test -cover -coverpkg=translib,translib/tlerr -c translib -o $@ + +$(TRANSL_DB_TEST_BIN) : $(TRANSL_DB_ALL_SRCS) + $(GO) test -cover -c translib/db -o $@ + +$(YGOT_BINDS): $(YANG_FILES) + cd ocbinds && $(GO) generate + +clean: + rm -f $(YGOT_BINDS) + rm -f $(TRANSLIB_PKG) + rm -rf $(TRANSLIB_TEST_DIR) + diff --git a/src/translib/acl_app.go b/src/translib/acl_app.go new file mode 100644 index 0000000000..0678cde7aa --- /dev/null +++ b/src/translib/acl_app.go @@ -0,0 +1,1712 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" + "translib/db" + "translib/ocbinds" + "translib/tlerr" + + log "github.com/golang/glog" + "github.com/openconfig/ygot/util" + "github.com/openconfig/ygot/ygot" +) + +const ( + TABLE_SEPARATOR = "|" + KEY_SEPARATOR = "|" + ACL_TABLE = "ACL_TABLE" + RULE_TABLE = "ACL_RULE" + ACL_TYPE = "type" + ACL_DESCRIPTION = "policy_desc" + SONIC_ACL_TYPE_L2 = "L2" + SONIC_ACL_TYPE_IPV4 = "L3" + SONIC_ACL_TYPE_IPV6 = "L3V6" + OPENCONFIG_ACL_TYPE_IPV4 = "ACL_IPV4" + OPENCONFIG_ACL_TYPE_IPV6 = "ACL_IPV6" + OPENCONFIG_ACL_TYPE_L2 = "ACL_L2" + OC_ACL_APP_MODULE_NAME = "/openconfig-acl:acl" + OC_ACL_YANG_PATH_PREFIX = "/device/acl" + + MIN_PRIORITY = 1 + MAX_PRIORITY = 65536 +) + +var IP_PROTOCOL_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]uint8{ + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_ICMP: 1, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_IGMP: 2, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_TCP: 6, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_UDP: 17, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_RSVP: 46, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_GRE: 47, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_AUTH: 51, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_PIM: 103, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_L2TP: 115, +} + +var ETHERTYPE_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_ETHERTYPE]uint32{ + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_LLDP: 0x88CC, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_VLAN: 0x8100, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ROCE: 0x8915, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ARP: 0x0806, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4: 0x0800, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6: 0x86DD, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_MPLS: 0x8847, +} + +type AclApp struct { + pathInfo *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + + aclTs *db.TableSpec + ruleTs *db.TableSpec + + aclTableMap map[string]db.Value + ruleTableMap map[string]map[string]db.Value +} + +func init() { + + err := register("/openconfig-acl:acl", + &appInfo{appType: reflect.TypeOf(AclApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + isNative: false, + tablesToWatch: []*db.TableSpec{&db.TableSpec{Name: ACL_TABLE}, &db.TableSpec{Name: RULE_TABLE}}}) + + if err != nil { + log.Fatal("Register ACL app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-acl", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *AclApp) initialize(data appData) { + log.Info("initialize:acl:path =", data.path) + pathInfo := NewPathInfo(data.path) + *app = AclApp{pathInfo: pathInfo, ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + + app.aclTs = &db.TableSpec{Name: ACL_TABLE} + app.ruleTs = &db.TableSpec{Name: RULE_TABLE} + + app.aclTableMap = make(map[string]db.Value) + app.ruleTableMap = make(map[string]map[string]db.Value) +} + +func (app *AclApp) getAppRootObject() *ocbinds.OpenconfigAcl_Acl { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Acl +} + +func (app *AclApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, CREATE) + return keys, err +} + +func (app *AclApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, UPDATE) + return keys, err +} + +func (app *AclApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, REPLACE) + return keys, err +} + +func (app *AclApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:acl:path =", app.pathInfo.Template) + + return keys, err +} + +func (app *AclApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:acl:path =", app.pathInfo.Template) + return err +} + +func (app *AclApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ConfigDB} + notSupported := tlerr.NotSupportedError{ + Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/acl-sets") { + // Subscribing to top level ACL record is not supported. It requires listening + // to 2 tables (ACL and ACL_RULE); TransLib does not support it yet + if pathInfo.HasSuffix("/acl-sets") || + pathInfo.HasSuffix("/acl-set") || + pathInfo.HasSuffix("/acl-set{}{}") { + log.Errorf("Subscribe not supported for top level ACL %s", pathInfo.Template) + return nil, nil, notSupported + } + + t, err := getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + return nil, nil, err + } + + aclkey := getAclKeyStrFromOCKey(pathInfo.Var("name"), t) + + if strings.Contains(pathInfo.Template, "/acl-entry{}") { + // Subscribe for one rule + rulekey := "RULE_" + pathInfo.Var("sequence-id") + notifInfo.table = db.TableSpec{Name: RULE_TABLE} + notifInfo.key = asKey(aclkey, rulekey) + notifInfo.needCache = !pathInfo.HasSuffix("/acl-entry{}") + + } else if pathInfo.HasSuffix("/acl-entries") || pathInfo.HasSuffix("/acl-entry") { + // Subscribe for all rules of an ACL + notifInfo.table = db.TableSpec{Name: RULE_TABLE} + notifInfo.key = asKey(aclkey, "*") + + } else { + // Subscibe for ACL fields only + notifInfo.table = db.TableSpec{Name: ACL_TABLE} + notifInfo.key = asKey(aclkey) + notifInfo.needCache = true + } + + } else if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/interfaces") { + // Right now interface binding config is maintained within ACL + // table itself. Multiple ACLs can be bound to one intf; one + // inname can occur in multiple ACL entries. So we cannot map + // interface binding xpaths to specific ACL table entry keys. + // For now subscribe for full ACL table!! + notifInfo.table = db.TableSpec{Name: ACL_TABLE} + notifInfo.key = asKey("*") + notifInfo.needCache = true + + } else { + log.Errorf("Unknown path %s", pathInfo.Template) + return nil, nil, notSupported + } + + return nil, ¬ifInfo, nil +} + +func (app *AclApp) 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 *AclApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, UPDATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, REPLACE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) 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 *AclApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + + 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 *AclApp) translateCRUCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCRUCommon:acl:path =", app.pathInfo.Template) + + app.convertOCAclsToInternal() + app.convertOCAclRulesToInternal(d) + app.convertOCAclBindingsToInternal() + + return keys, err +} + +func (app *AclApp) processCommon(d *db.DB, opcode int) error { + var err error + var topmostPath bool = false + acl := 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() == "OpenconfigAcl_Acl" { + topmostPath = true + } + } + + targetUriPath, _ := getYangPathFromUri(app.pathInfo.Path) + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets") { + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}") { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}/acl-entries/acl-entry{}") { + // Subtree of one Rule + for seqId, _ := range aclSet.AclEntries.AclEntry { + ruleKey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := aclSet.AclEntries.AclEntry[seqId] + + ruleNodeYangPath := getYangPathFromYgotStruct(entrySet, OC_ACL_YANG_PATH_PREFIX, OC_ACL_APP_MODULE_NAME) + isRuleNodeSubtree := len(targetUriPath) > len(ruleNodeYangPath) + switch opcode { + case CREATE: + if isRuleNodeSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + } else if *app.ygotTarget == entrySet { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else { + log.Errorf("processCommon: Given CREATE path %s not handled", targetUriPath) + } + case REPLACE: + err = d.SetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + case UPDATE: + err = d.ModEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + case DELETE: + if *app.ygotTarget == entrySet { + err = d.DeleteEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}) + } else if isRuleNodeSubtree { + err = app.handleRuleFieldsDeletion(d, aclKey, ruleKey) + if err != nil { + return err + } + //err = d.SetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + } else { + log.Errorf("processCommon: Given DELETE path %s not handled", targetUriPath) + } + case GET: + err = app.convertDBAclRulesToInternal(d, aclKey, int64(seqId), db.Key{}) + ygot.BuildEmptyTree(entrySet) + app.convertInternalToOCAclRule(aclKey, aclSetKey.Type, int64(seqId), nil, entrySet) + } + } + } else { + isAclEntriesSubtree := isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}/acl-entries") + switch opcode { + case CREATE: + if *app.ygotTarget == aclSet { + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else if isAclEntriesSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else { + err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case REPLACE: + if *app.ygotTarget == aclSet || isAclEntriesSubtree { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "RULE_*"}}) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + if err != nil { + return err + } + } + if !isAclEntriesSubtree { + err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case UPDATE: + if !isAclEntriesSubtree { + err = app.setAclDataInConfigDb(d, app.aclTableMap, false) + //err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + if err != nil { + return err + } + } + if *app.ygotTarget == aclSet || isAclEntriesSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + } + case DELETE: + if *app.ygotTarget == aclSet { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "*"}}) + if err != nil { + return err + } + err = d.DeleteEntry(app.aclTs, db.Key{Comp: []string{aclKey}}) + } else if isAclEntriesSubtree { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "RULE_*"}}) + } else { + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + if nodeInfo != nil && nodeInfo.IsLeaf() && nodeInfo.Name == "description" { + err = d.DeleteEntryFields(app.aclTs, asKey(aclKey), createEmptyDbValue(ACL_DESCRIPTION)) + } + //err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case GET: + err = app.convertDBAclToInternal(d, db.Key{Comp: []string{aclKey}}) + if err != nil { + return err + } + ygot.BuildEmptyTree(aclSet) + app.convertInternalToOCAcl(aclKey, acl.AclSets, aclSet) + } + } + } + } else { + // All Acls and their rules + err = app.processCommonToplevelPath(d, acl, opcode, false) + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces") { + switch opcode { + case CREATE, REPLACE, UPDATE: + err = app.setAclBindDataInConfigDb(d, app.aclTableMap, opcode) + case DELETE: + err = app.handleBindingsDeletion(d) + case GET: + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + for intfId := range acl.Interfaces.Interface { + intfData := acl.Interfaces.Interface[intfId] + ygot.BuildEmptyTree(intfData) + if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/ingress-acl-sets") { + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + } else if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/egress-acl-sets") { + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } else { + // Direction unknown. Check ACL Table for binding information. + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + if err != nil { + return err + } + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } + } + } else { + err = app.getAllBindingsInfo(d) + } + } + } else { + err = app.processCommonToplevelPath(d, acl, opcode, true) + } + + if !topmostPath && !isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/acl-sets") && !isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces") { + err = tlerr.NotSupported("URL %s is not supported", app.pathInfo.Template) + } + + return err +} + +func (app *AclApp) processCommonToplevelPath(d *db.DB, acl *ocbinds.OpenconfigAcl_Acl, opcode int, isTopmostPath bool) error { + var err error + switch opcode { + case CREATE: + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + case REPLACE: + err = d.DeleteTable(app.aclTs) + if err != nil { + return err + } + err = d.DeleteTable(app.ruleTs) + if err != nil { + return err + } + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + case UPDATE: + err = app.setAclDataInConfigDb(d, app.aclTableMap, false) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + case DELETE: + err = d.DeleteTable(app.ruleTs) + if err != nil { + return err + } + err = d.DeleteTable(app.aclTs) + case GET: + ygot.BuildEmptyTree(acl) + err = app.convertDBAclToInternal(d, db.Key{}) + if err != nil { + return err + } + app.convertInternalToOCAcl("", acl.AclSets, nil) + if isTopmostPath { + err = app.getAllBindingsInfo(d) + } + } + return err +} + +/*********** These are Translation Helper Function ***********/ +func (app *AclApp) convertDBAclRulesToInternal(dbCl *db.DB, aclName string, seqId int64, ruleKey db.Key) error { + var err error + if seqId != -1 { + ruleKey.Comp = []string{aclName, "RULE_" + strconv.FormatInt(int64(seqId), 10)} + } + if ruleKey.Len() > 1 { + ruleName := ruleKey.Get(1) + if ruleName != "DEFAULT_RULE" { + ruleData, err := dbCl.GetEntry(app.ruleTs, ruleKey) + if err != nil { + return err + } + if app.ruleTableMap[aclName] == nil { + app.ruleTableMap[aclName] = make(map[string]db.Value) + } + app.ruleTableMap[aclName][ruleName] = ruleData + } + } else { + ruleKeys, err := dbCl.GetKeys(app.ruleTs) + if err != nil { + return err + } + for i, _ := range ruleKeys { + if aclName == ruleKeys[i].Get(0) { + app.convertDBAclRulesToInternal(dbCl, aclName, -1, ruleKeys[i]) + } + } + } + return err +} + +func (app *AclApp) convertDBAclToInternal(dbCl *db.DB, aclkey db.Key) error { + var err error + if aclkey.Len() > 0 { + // Get one particular ACL + entry, err := dbCl.GetEntry(app.aclTs, aclkey) + if err != nil { + return err + } + if entry.IsPopulated() { + app.aclTableMap[aclkey.Get(0)] = entry + app.ruleTableMap[aclkey.Get(0)] = make(map[string]db.Value) + err = app.convertDBAclRulesToInternal(dbCl, aclkey.Get(0), -1, db.Key{}) + if err != nil { + return err + } + } else { + return tlerr.NotFound("Acl %s is not configured", aclkey.Get(0)) + } + } else { + // Get all ACLs + tbl, err := dbCl.GetTable(app.aclTs) + if err != nil { + return err + } + keys, _ := tbl.GetKeys() + for i, _ := range keys { + app.convertDBAclToInternal(dbCl, keys[i]) + } + } + return err +} + +func (app *AclApp) convertInternalToOCAcl(aclName string, aclSets *ocbinds.OpenconfigAcl_Acl_AclSets, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet) { + if len(aclName) > 0 { + aclData := app.aclTableMap[aclName] + if aclSet != nil { + aclSet.Config.Name = aclSet.Name + aclSet.Config.Type = aclSet.Type + aclSet.State.Name = aclSet.Name + aclSet.State.Type = aclSet.Type + + for k := range aclData.Field { + if ACL_DESCRIPTION == k { + descr := aclData.Get(k) + aclSet.Config.Description = &descr + aclSet.State.Description = &descr + } else if "ports@" == k { + continue + } + } + + app.convertInternalToOCAclRule(aclName, aclSet.Type, -1, aclSet, nil) + } + } else { + for acln := range app.aclTableMap { + acldata := app.aclTableMap[acln] + var aclNameStr string + var aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_IPV4 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_IPV6 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_L2 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + aclSetPtr, aclErr := aclSets.NewAclSet(aclNameStr, aclType) + if aclErr != nil { + fmt.Println("Error handling: ", aclErr) + } + ygot.BuildEmptyTree(aclSetPtr) + app.convertInternalToOCAcl(acln, nil, aclSetPtr) + } + } +} + +func (app *AclApp) convertInternalToOCAclRule(aclName string, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, seqId int64, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet, entrySet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if seqId != -1 { + ruleName := "RULE_" + strconv.FormatInt(int64(seqId), 10) + app.convertInternalToOCAclRuleProperties(app.ruleTableMap[aclName][ruleName], aclType, nil, entrySet) + } else { + for ruleName := range app.ruleTableMap[aclName] { + app.convertInternalToOCAclRuleProperties(app.ruleTableMap[aclName][ruleName], aclType, aclSet, nil) + } + } +} + +func (app *AclApp) convertInternalToOCAclRuleProperties(ruleData db.Value, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet, entrySet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + priority, _ := strconv.ParseInt(ruleData.Get("PRIORITY"), 10, 32) + seqId := uint32(MAX_PRIORITY - priority) + //ruleDescr := ruleData.Get("RULE_DESCRIPTION") + + if entrySet == nil { + if aclSet != nil { + entrySet_, _ := aclSet.AclEntries.NewAclEntry(seqId) + entrySet = entrySet_ + ygot.BuildEmptyTree(entrySet) + } + } + + entrySet.Config.SequenceId = &seqId + //entrySet.Config.Description = &ruleDescr + entrySet.State.SequenceId = &seqId + //entrySet.State.Description = &ruleDescr + + var num uint64 + num = 0 + entrySet.State.MatchedOctets = &num + entrySet.State.MatchedPackets = &num + + ygot.BuildEmptyTree(entrySet.Transport) + ygot.BuildEmptyTree(entrySet.Actions) + + for ruleKey := range ruleData.Field { + if "L4_SRC_PORT" == ruleKey || "L4_SRC_PORT_RANGE" == ruleKey { + port := ruleData.Get(ruleKey) + srcPort := getTransportSrcDestPorts(port, "src") + entrySet.Transport.Config.SourcePort, _ = entrySet.Transport.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union(srcPort) + entrySet.Transport.State.SourcePort, _ = entrySet.Transport.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_State_SourcePort_Union(srcPort) + } else if "L4_DST_PORT" == ruleKey || "L4_DST_PORT_RANGE" == ruleKey { + port := ruleData.Get(ruleKey) + destPort := getTransportSrcDestPorts(port, "dest") + entrySet.Transport.Config.DestinationPort, _ = entrySet.Transport.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union(destPort) + entrySet.Transport.State.DestinationPort, _ = entrySet.Transport.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_State_DestinationPort_Union(destPort) + } else if "TCP_FLAGS" == ruleKey { + tcpFlags := ruleData.Get(ruleKey) + entrySet.Transport.Config.TcpFlags = getTransportConfigTcpFlags(tcpFlags) + entrySet.Transport.State.TcpFlags = getTransportConfigTcpFlags(tcpFlags) + } else if "PACKET_ACTION" == ruleKey { + if "FORWARD" == ruleData.Get(ruleKey) { + entrySet.Actions.Config.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT + entrySet.Actions.State.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT + } else { + entrySet.Actions.Config.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP + entrySet.Actions.State.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP + } + } + } + + if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 { + ygot.BuildEmptyTree(entrySet.Ipv4) + for ruleKey := range ruleData.Field { + if "IP_PROTOCOL" == ruleKey { + ipProto, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + protocolVal := getIpProtocol(ipProto) + entrySet.Ipv4.Config.Protocol, _ = entrySet.Ipv4.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union(protocolVal) + entrySet.Ipv4.State.Protocol, _ = entrySet.Ipv4.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_State_Protocol_Union(protocolVal) + } else if "DSCP" == ruleKey { + var dscp uint8 + dscpData, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + dscp = uint8(dscpData) + entrySet.Ipv4.Config.Dscp = &dscp + entrySet.Ipv4.State.Dscp = &dscp + } else if "SRC_IP" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv4.Config.SourceAddress = &addr + entrySet.Ipv4.State.SourceAddress = &addr + } else if "DST_IP" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv4.Config.DestinationAddress = &addr + entrySet.Ipv4.State.DestinationAddress = &addr + } + } + } else if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 { + ygot.BuildEmptyTree(entrySet.Ipv6) + for ruleKey := range ruleData.Field { + if "IP_PROTOCOL" == ruleKey { + ipProto, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + protocolVal := getIpProtocol(ipProto) + entrySet.Ipv6.Config.Protocol, _ = entrySet.Ipv6.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union(protocolVal) + entrySet.Ipv6.State.Protocol, _ = entrySet.Ipv6.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_State_Protocol_Union(protocolVal) + } else if "DSCP" == ruleKey { + var dscp uint8 + dscpData, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + dscp = uint8(dscpData) + entrySet.Ipv6.Config.Dscp = &dscp + entrySet.Ipv6.State.Dscp = &dscp + } else if "SRC_IPV6" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv6.Config.SourceAddress = &addr + entrySet.Ipv6.State.SourceAddress = &addr + } else if "DST_IPV6" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv6.Config.DestinationAddress = &addr + entrySet.Ipv6.State.DestinationAddress = &addr + } + } + } else if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 { + ygot.BuildEmptyTree(entrySet.L2) + for ruleKey := range ruleData.Field { + if "ETHER_TYPE" == ruleKey { + ethType, _ := strconv.ParseUint(strings.Replace(ruleData.Get(ruleKey), "0x", "", -1), 16, 32) + ethertype := getL2EtherType(ethType) + entrySet.L2.Config.Ethertype, _ = entrySet.L2.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union(ethertype) + entrySet.L2.State.Ethertype, _ = entrySet.L2.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_State_Ethertype_Union(ethertype) + } + } + } +} + +func convertInternalToOCAclRuleBinding(d *db.DB, priority uint32, seqId int64, direction string, aclSet ygot.GoStruct, entrySet ygot.GoStruct) { + if seqId == -1 { + seqId = int64(MAX_PRIORITY - priority) + } + + var num uint64 + num = 0 + var ruleId uint32 = uint32(seqId) + + if direction == "INGRESS" { + var ingressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + ingressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet) + if ingressEntrySet, ok = ingressAclSet.AclEntries.AclEntry[ruleId]; !ok { + ingressEntrySet, _ = ingressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + ingressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry) + } + if ingressEntrySet != nil { + ygot.BuildEmptyTree(ingressEntrySet) + ingressEntrySet.State.SequenceId = &ruleId + ingressEntrySet.State.MatchedPackets = &num + ingressEntrySet.State.MatchedOctets = &num + } + } else if direction == "EGRESS" { + var egressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + egressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet) + if egressEntrySet, ok = egressAclSet.AclEntries.AclEntry[ruleId]; !ok { + egressEntrySet, _ = egressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + egressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry) + } + if egressEntrySet != nil { + ygot.BuildEmptyTree(egressEntrySet) + egressEntrySet.State.SequenceId = &ruleId + egressEntrySet.State.MatchedPackets = &num + egressEntrySet.State.MatchedOctets = &num + } + } +} + +func (app *AclApp) convertInternalToOCAclBinding(d *db.DB, aclName string, intfId string, direction string, intfAclSet ygot.GoStruct) error { + var err error + if _, ok := app.aclTableMap[aclName]; !ok { + aclEntry, err1 := d.GetEntry(app.aclTs, db.Key{Comp: []string{aclName}}) + if err1 != nil { + return err1 + } + if !contains(aclEntry.GetList("ports"), intfId) { + return tlerr.InvalidArgs("Acl %s not binded with %s", aclName, intfId) + } + } + + if _, ok := app.ruleTableMap[aclName]; !ok { + ruleKeys, _ := d.GetKeys(app.ruleTs) + for i, _ := range ruleKeys { + rulekey := ruleKeys[i] + // Rulekey has two keys, first aclkey and second rulename + if rulekey.Get(0) == aclName && rulekey.Get(1) != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(rulekey.Get(1), "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, intfAclSet, nil) + } + } + } else { + for ruleName := range app.ruleTableMap[aclName] { + if ruleName != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(ruleName, "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, intfAclSet, nil) + } + } + } + + return err +} + +func (app *AclApp) getAllBindingsInfo(d *db.DB) error { + var err error + acl := app.getAppRootObject() + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[(aclKeys[i]).Get(0)] = aclEntry + } + } + var interfaces []string + for aclName := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + + for _, intfId := range interfaces { + var intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface + intfData, ok := acl.Interfaces.Interface[intfId] + if !ok { + intfData, _ = acl.Interfaces.NewInterface(intfId) + } + ygot.BuildEmptyTree(intfData) + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } + return err +} + +func (app *AclApp) getAclBindingInfoForInterfaceData(d *db.DB, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface, intfId string, direction string) error { + var err error + if intfData != nil { + intfData.Config.Id = intfData.Id + intfData.State.Id = intfData.Id + } + if direction == "INGRESS" { + if intfData.IngressAclSets != nil && len(intfData.IngressAclSets.IngressAclSet) > 0 { + for ingressAclSetKey, _ := range intfData.IngressAclSets.IngressAclSet { + aclName := strings.Replace(strings.Replace(ingressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := ingressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(ingressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + ingressAclSet := intfData.IngressAclSets.IngressAclSet[ingressAclSetKey] + if ingressAclSet != nil && ingressAclSet.AclEntries != nil && len(ingressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range ingressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := ingressAclSet.AclEntries.AclEntry[seqId] + _, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, rulekey}}) + if err != nil { + return err + } + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclName, Type: ingressAclSetKey.Type} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclName, Type: ingressAclSetKey.Type} + err = app.convertInternalToOCAclBinding(d, aclKey, intfId, direction, ingressAclSet) + } + } + } else { + err = app.findAndGetAclBindingInfoForInterfaceData(d, intfId, direction, intfData) + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil && len(intfData.EgressAclSets.EgressAclSet) > 0 { + for egressAclSetKey, _ := range intfData.EgressAclSets.EgressAclSet { + aclName := strings.Replace(strings.Replace(egressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := egressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(egressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + egressAclSet := intfData.EgressAclSets.EgressAclSet[egressAclSetKey] + if egressAclSet != nil && egressAclSet.AclEntries != nil && len(egressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range egressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := egressAclSet.AclEntries.AclEntry[seqId] + _, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, rulekey}}) + if err != nil { + return err + } + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclName, Type: egressAclSetKey.Type} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclName, Type: egressAclSetKey.Type} + err = app.convertInternalToOCAclBinding(d, aclKey, intfId, direction, egressAclSet) + } + } + } else { + err = app.findAndGetAclBindingInfoForInterfaceData(d, intfId, direction, intfData) + } + } else { + log.Error("Unknown direction") + } + return err +} + +func (app *AclApp) findAndGetAclBindingInfoForInterfaceData(d *db.DB, intfId string, direction string, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface) error { + var err error + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[aclKeys[i].Get(0)] = aclEntry + } + } + + for aclName, _ := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + aclIntfs := aclData.GetList("ports") + aclType := aclData.Get(ACL_TYPE) + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + if contains(aclIntfs, intfId) && direction == aclData.Get("stage") { + if direction == "INGRESS" { + if intfData.IngressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + ingressAclSet, ok := intfData.IngressAclSets.IngressAclSet[aclSetKey] + if !ok { + ingressAclSet, _ = intfData.IngressAclSets.NewIngressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = app.convertInternalToOCAclBinding(d, aclName, intfId, direction, ingressAclSet) + if err != nil { + return err + } + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + egressAclSet, ok := intfData.EgressAclSets.EgressAclSet[aclSetKey] + if !ok { + egressAclSet, _ = intfData.EgressAclSets.NewEgressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = app.convertInternalToOCAclBinding(d, aclName, intfId, direction, egressAclSet) + if err != nil { + return err + } + } + } + } + } + return err +} + +/*func (app *AclApp) isInterfaceBindWithACL(d *db.DB, intfId string) bool { + var isFound bool = false + + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[(aclKeys[i]).Get(0)] = aclEntry + } + } + + var interfaces []string + for aclName := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + + isFound = contains(interfaces, intfId) + return isFound +}*/ + +func (app *AclApp) handleBindingsDeletion(d *db.DB) error { + var err error + + acl := app.getAppRootObject() + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + var isRequestedAclFound = false + if len(aclEntry.GetList("ports")) > 0 { + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + direction := aclEntry.Get("stage") + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/ingress-acl-sets") && direction != "INGRESS" { + return tlerr.InvalidArgs("Acl %s is not Ingress", aclKeys[i].Get(0)) + } + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/egress-acl-sets") && direction != "EGRESS" { + return tlerr.InvalidArgs("Acl %s is not Egress", aclKeys[i].Get(0)) + } + for intfId := range acl.Interfaces.Interface { + aclname, acltype := getAclKeysFromStrKey(aclKeys[i].Get(0), aclEntry.Get("type")) + intfData := acl.Interfaces.Interface[intfId] + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/ingress-acl-sets/ingress-acl-set{}{}") { + for k := range intfData.IngressAclSets.IngressAclSet { + if aclname == k.SetName { + if acltype == k.Type { + isRequestedAclFound = true + } else { + return tlerr.InvalidArgs("Acl Type is not matching") + } + } else { + goto SkipDBProcessing + } + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/egress-acl-sets/egress-acl-set{}{}") { + for k := range intfData.EgressAclSets.EgressAclSet { + if aclname == k.SetName { + if acltype == k.Type { + isRequestedAclFound = true + } else { + return tlerr.InvalidArgs("Acl Type is not matching") + } + } else { + goto SkipDBProcessing + } + } + } + intfs := aclEntry.GetList("ports") + intfs = removeElement(intfs, intfId) + aclEntry.SetList("ports", intfs) + err = d.SetEntry(app.aclTs, aclKeys[i], aclEntry) + if err != nil { + return err + } + // If last interface removed, then remove stage field also + if len(intfs) == 0 { + aclEntry.Remove("stage") + } + } + SkipDBProcessing: + } else { + aclEntry.Remove("stage") + aclEntry.SetList("ports", []string{}) + err = d.SetEntry(app.aclTs, aclKeys[i], aclEntry) + if err != nil { + return err + } + } + } + if isRequestedAclFound { + break + } + } + + return err +} + +/******************** CREATE related *******************************/ +func (app *AclApp) convertOCAclsToInternal() { + acl := app.getAppRootObject() + if acl != nil { + app.aclTableMap = make(map[string]db.Value) + if acl.AclSets != nil && len(acl.AclSets.AclSet) > 0 { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + app.aclTableMap[aclKey] = db.Value{Field: map[string]string{}} + + if aclSet.Config != nil { + if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_IPV4 + } else if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_IPV6 + } else if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_L2 + } + + if aclSet.Config.Description != nil && len(*aclSet.Config.Description) > 0 { + app.aclTableMap[aclKey].Field[ACL_DESCRIPTION] = *aclSet.Config.Description + } + } + } + } + } +} + +func (app *AclApp) convertOCAclRulesToInternal(d *db.DB) { + acl := app.getAppRootObject() + if acl != nil { + app.ruleTableMap = make(map[string]map[string]db.Value) + if acl.AclSets != nil && len(acl.AclSets.AclSet) > 0 { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + app.ruleTableMap[aclKey] = make(map[string]db.Value) + + if aclSet.AclEntries != nil { + for seqId, _ := range aclSet.AclEntries.AclEntry { + entrySet := aclSet.AclEntries.AclEntry[seqId] + ruleName := "RULE_" + strconv.Itoa(int(seqId)) + app.ruleTableMap[aclKey][ruleName] = db.Value{Field: map[string]string{}} + convertOCAclRuleToInternalAclRule(app.ruleTableMap[aclKey][ruleName], seqId, aclKey, aclSet.Type, entrySet) + } + } + + yangPathStr, _ := getYangPathFromUri(app.pathInfo.Path) + if yangPathStr != "/openconfig-acl:acl/acl-sets/acl-set/acl-entries" && yangPathStr != "/openconfig-acl:acl/acl-sets/acl-set/acl-entries/acl-entry" { + app.createDefaultDenyAclRule(d, aclKey, app.ruleTableMap[aclKey]) + } + } + } + } +} + +func (app *AclApp) convertOCAclBindingsToInternal() { + aclObj := app.getAppRootObject() + + if aclObj.Interfaces != nil && len(aclObj.Interfaces.Interface) > 0 { + aclInterfacesMap := make(map[string][]string) + // Below code assumes that an ACL can be either INGRESS or EGRESS but not both. + for intfId, _ := range aclObj.Interfaces.Interface { + intf := aclObj.Interfaces.Interface[intfId] + if intf != nil { + if intf.IngressAclSets != nil && len(intf.IngressAclSets.IngressAclSet) > 0 { + for inAclKey, _ := range intf.IngressAclSets.IngressAclSet { + aclName := getAclKeyStrFromOCKey(inAclKey.SetName, inAclKey.Type) + // TODO: Need to handle Subinterface also + if intf.InterfaceRef != nil && intf.InterfaceRef.Config.Interface != nil { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.InterfaceRef.Config.Interface) + } else { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + } + if len(app.aclTableMap) == 0 { + app.aclTableMap[aclName] = db.Value{Field: map[string]string{}} + } + app.aclTableMap[aclName].Field["stage"] = "INGRESS" + } + } + + if intf.EgressAclSets != nil && len(intf.EgressAclSets.EgressAclSet) > 0 { + for outAclKey, _ := range intf.EgressAclSets.EgressAclSet { + aclName := getAclKeyStrFromOCKey(outAclKey.SetName, outAclKey.Type) + if intf.InterfaceRef != nil && intf.InterfaceRef.Config.Interface != nil { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.InterfaceRef.Config.Interface) + } else { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + } + if len(app.aclTableMap) == 0 { + app.aclTableMap[aclName] = db.Value{Field: map[string]string{}} + } + app.aclTableMap[aclName].Field["stage"] = "EGRESS" + } + } + } + } + for k, _ := range aclInterfacesMap { + val := app.aclTableMap[k] + (&val).SetList("ports", aclInterfacesMap[k]) + } + } +} + +func (app *AclApp) createDefaultDenyAclRule(d *db.DB, aclName string, rulesInfo map[string]db.Value) { + existingRuleEntry, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclName, "DEFAULT_RULE"}}) + // If Default Rule already exists, Do not add new Default Rule + if existingRuleEntry.IsPopulated() && err == nil { + return + } + m := make(map[string]string) + rulesInfo["DEFAULT_RULE"] = db.Value{Field: m} + rulesInfo["DEFAULT_RULE"].Field["PRIORITY"] = strconv.FormatInt(int64(MIN_PRIORITY), 10) + rulesInfo["DEFAULT_RULE"].Field["PACKET_ACTION"] = "DROP" + rulesInfo["DEFAULT_RULE"].Field["IP_TYPE"] = "ANY" +} + +func convertOCAclRuleToInternalAclRule(ruleData db.Value, seqId uint32, aclName string, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + ruleIndex := seqId + ruleData.Field["PRIORITY"] = strconv.FormatInt(int64(MAX_PRIORITY-ruleIndex), 10) + // Rule Description is not supported in Sonic. So commenting this out. + /* + if rule.Config != nil && rule.Config.Description != nil { + ruleData.Field["RULE_DESCRIPTION"] = *rule.Config.Description + } + */ + + if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 == aclType { + convertOCToInternalIPv4(ruleData, aclName, ruleIndex, rule) + } else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 == aclType { + convertOCToInternalIPv6(ruleData, aclName, ruleIndex, rule) + } else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 == aclType { + convertOCToInternalL2(ruleData, aclName, ruleIndex, rule) + } /*else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_MIXED == aclType { + } */ + + convertOCToInternalTransport(ruleData, aclName, ruleIndex, rule) + convertOCToInternalInputInterface(ruleData, aclName, ruleIndex, rule) + convertOCToInternalInputAction(ruleData, aclName, ruleIndex, rule) +} + +func convertOCToInternalL2(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.L2 == nil { + return + } + if rule.L2.Config.Ethertype != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.L2.Config.Ethertype)) { + ethertypeType := reflect.TypeOf(rule.L2.Config.Ethertype).Elem() + var b bytes.Buffer + switch ethertypeType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE{}): + v := (rule.L2.Config.Ethertype).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE) + //ruleData["ETHER_TYPE"] = v.E_OpenconfigPacketMatchTypes_ETHERTYPE.ΛMap()["E_OpenconfigPacketMatchTypes_ETHERTYPE"][int64(v.E_OpenconfigPacketMatchTypes_ETHERTYPE)].Name + fmt.Fprintf(&b, "0x%0.4x", ETHERTYPE_MAP[v.E_OpenconfigPacketMatchTypes_ETHERTYPE]) + ruleData.Field["ETHER_TYPE"] = b.String() + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16{}): + v := (rule.L2.Config.Ethertype).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16) + fmt.Fprintf(&b, "0x%0.4x", v.Uint16) + ruleData.Field["ETHER_TYPE"] = b.String() + } + } +} + +func convertOCToInternalIPv4(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Ipv4.Config.Protocol != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Ipv4.Config.Protocol)) { + protocolType := reflect.TypeOf(rule.Ipv4.Config.Protocol).Elem() + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (rule.Ipv4.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + //ruleData["IP_PROTOCOL"] = v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL.ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL)].Name + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(IP_PROTOCOL_MAP[v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]), 10) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8{}): + v := (rule.Ipv4.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8) + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + } + } + + if rule.Ipv4.Config.Dscp != nil { + ruleData.Field["DSCP"] = strconv.FormatInt(int64(*rule.Ipv4.Config.Dscp), 10) + } + if rule.Ipv4.Config.SourceAddress != nil { + ruleData.Field["SRC_IP"] = *rule.Ipv4.Config.SourceAddress + } + if rule.Ipv4.Config.DestinationAddress != nil { + ruleData.Field["DST_IP"] = *rule.Ipv4.Config.DestinationAddress + } +} + +func convertOCToInternalIPv6(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Ipv6.Config.Protocol != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Ipv6.Config.Protocol)) { + protocolType := reflect.TypeOf(rule.Ipv6.Config.Protocol).Elem() + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (rule.Ipv6.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + //ruleData["IP_PROTOCOL"] = v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL.ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL)].Name + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(IP_PROTOCOL_MAP[v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]), 10) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_Uint8{}): + v := (rule.Ipv6.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_Uint8) + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + } + } + + if rule.Ipv6.Config.Dscp != nil { + ruleData.Field["DSCP"] = strconv.FormatInt(int64(*rule.Ipv6.Config.Dscp), 10) + } + if rule.Ipv6.Config.SourceAddress != nil { + ruleData.Field["SRC_IPV6"] = *rule.Ipv6.Config.SourceAddress + } + if rule.Ipv6.Config.DestinationAddress != nil { + ruleData.Field["DST_IPV6"] = *rule.Ipv6.Config.DestinationAddress + } + if rule.Ipv6.Config.SourceFlowLabel != nil { + ruleData.Field["SRC_FLOWLABEL"] = strconv.FormatInt(int64(*rule.Ipv6.Config.SourceFlowLabel), 10) + } + if rule.Ipv6.Config.DestinationFlowLabel != nil { + ruleData.Field["DST_FLOWLABEL"] = strconv.FormatInt(int64(*rule.Ipv6.Config.DestinationFlowLabel), 10) + } +} + +func convertOCToInternalTransport(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Transport == nil { + return + } + if rule.Transport.Config.SourcePort != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Transport.Config.SourcePort)) { + sourceportType := reflect.TypeOf(rule.Transport.Config.SourcePort).Elem() + switch sourceportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort) + ruleData.Field["L4_SRC_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort)].Name + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String) + ruleData.Field["L4_SRC_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16) + ruleData.Field["L4_SRC_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + } + } + + if rule.Transport.Config.DestinationPort != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Transport.Config.DestinationPort)) { + destportType := reflect.TypeOf(rule.Transport.Config.DestinationPort).Elem() + switch destportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort) + ruleData.Field["L4_DST_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort)].Name + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String) + ruleData.Field["L4_DST_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16) + ruleData.Field["L4_DST_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + } + } + + var tcpFlags uint32 = 0x00 + if len(rule.Transport.Config.TcpFlags) > 0 { + for _, flag := range rule.Transport.Config.TcpFlags { + switch flag { + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN: + tcpFlags |= 0x01 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN: + tcpFlags |= 0x02 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST: + tcpFlags |= 0x04 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH: + tcpFlags |= 0x08 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK: + tcpFlags |= 0x10 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG: + tcpFlags |= 0x20 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE: + tcpFlags |= 0x40 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR: + tcpFlags |= 0x80 + } + } + var b bytes.Buffer + fmt.Fprintf(&b, "0x%0.2x/0x%0.2x", tcpFlags, tcpFlags) + ruleData.Field["TCP_FLAGS"] = b.String() + } +} + +func convertOCToInternalInputInterface(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.InputInterface != nil && rule.InputInterface.InterfaceRef != nil { + ruleData.Field["IN_PORTS"] = *rule.InputInterface.InterfaceRef.Config.Interface + } +} + +func convertOCToInternalInputAction(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Actions != nil && rule.Actions.Config != nil { + switch rule.Actions.Config.ForwardingAction { + case ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT: + ruleData.Field["PACKET_ACTION"] = "FORWARD" + case ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP, ocbinds.OpenconfigAcl_FORWARDING_ACTION_REJECT: + ruleData.Field["PACKET_ACTION"] = "DROP" + default: + } + } +} + +func (app *AclApp) handleRuleFieldsDeletion(d *db.DB, aclKey string, ruleKey string) error { + var err error + + ruleEntry, err := d.GetEntry(app.ruleTs, asKey(aclKey, ruleKey)) + if err != nil { + return err + } + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + if nodeInfo.IsLeaf() { + switch nodeInfo.Name { + case "description": + (&ruleEntry).Remove("RULE_DESCRIPTION") + // L2 + case "ethertype": + (&ruleEntry).Remove("ETHER_TYPE") + // IPv4/IPv6 + case "source-address": + if strings.Contains(app.pathInfo.Path, "ipv4/config") { + (&ruleEntry).Remove("SRC_IP") + } else if strings.Contains(app.pathInfo.Path, "ipv6/config") { + (&ruleEntry).Remove("SRC_IPV6") + } + case "destination-address": + if strings.Contains(app.pathInfo.Path, "ipv4/config") { + (&ruleEntry).Remove("DST_IP") + } else if strings.Contains(app.pathInfo.Path, "ipv6/config") { + (&ruleEntry).Remove("DST_IPV6") + } + case "dscp": + (&ruleEntry).Remove("DSCP") + case "protocol": + (&ruleEntry).Remove("IP_PROTOCOL") + // transport + case "source-port": + (&ruleEntry).Remove("L4_SRC_PORT") + (&ruleEntry).Remove("L4_SRC_PORT_RANGE") + case "destination-port": + (&ruleEntry).Remove("L4_DST_PORT") + (&ruleEntry).Remove("L4_DST_PORT_RANGE") + // actions + case "forwarding-action": + (&ruleEntry).Remove("PACKET_ACTION") + //input-interface + case "interface": + (&ruleEntry).Remove("IN_PORTS") + //case "subinterface": + } + } else if nodeInfo.IsContainer() { + targetType := reflect.TypeOf(*app.ygotTarget) + switch targetType.Elem().Name() { + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config": + (&ruleEntry).Remove("ETHER_TYPE") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config": + (&ruleEntry).Remove("IP_PROTOCOL") + (&ruleEntry).Remove("SRC_IP") + (&ruleEntry).Remove("DST_IP") + (&ruleEntry).Remove("DSCP") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config": + (&ruleEntry).Remove("IP_PROTOCOL") + (&ruleEntry).Remove("SRC_IPV6") + (&ruleEntry).Remove("DST_IPV6") + (&ruleEntry).Remove("DSCP") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config": + (&ruleEntry).Remove("L4_SRC_PORT") + (&ruleEntry).Remove("L4_SRC_PORT_RANGE") + (&ruleEntry).Remove("L4_DST_PORT") + (&ruleEntry).Remove("L4_DST_PORT_RANGE") + (&ruleEntry).Remove("TCP_FLAGS") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface_InterfaceRef", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface_InterfaceRef_Config": + (&ruleEntry).Remove("IN_PORTS") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Actions", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Actions_Config": + (&ruleEntry).Remove("PACKET_ACTION") + } + } else if nodeInfo.IsLeafList() { + switch nodeInfo.Name { + case "tcp-flags": + (&ruleEntry).Remove("TCP_FLAGS") + } + } else { + log.Error("This yang type is not handled currently") + } + err = d.SetEntry(app.ruleTs, asKey(aclKey, ruleKey), ruleEntry) + + return err +} + +func (app *AclApp) setAclDataInConfigDb(d *db.DB, aclData map[string]db.Value, createFlag bool) error { + var err error + for key := range aclData { + existingEntry, err := d.GetEntry(app.aclTs, db.Key{Comp: []string{key}}) + // If Create ACL request comes and ACL already exists, throw error + if createFlag && existingEntry.IsPopulated() { + return tlerr.AlreadyExists("Acl %s already exists", key) + } + if createFlag || (!createFlag && err != nil && !existingEntry.IsPopulated()) { + err := d.CreateEntry(app.aclTs, db.Key{Comp: []string{key}}, aclData[key]) + if err != nil { + return err + } + } else { + if existingEntry.IsPopulated() { + if existingEntry.Get(ACL_DESCRIPTION) != aclData[key].Field[ACL_DESCRIPTION] { + err := d.ModEntry(app.aclTs, db.Key{Comp: []string{key}}, aclData[key]) + if err != nil { + return err + } + } + } + } + } + return err +} + +func (app *AclApp) setAclRuleDataInConfigDb(d *db.DB, ruleData map[string]map[string]db.Value, createFlag bool) error { + var err error + for aclName := range ruleData { + for ruleName := range ruleData[aclName] { + existingRuleEntry, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}) + // If Create Rule request comes and Rule already exists, throw error + if createFlag && existingRuleEntry.IsPopulated() { + return tlerr.AlreadyExists("Rule %s already exists", ruleName) + } + if createFlag || (!createFlag && err != nil && !existingRuleEntry.IsPopulated()) { + err := d.CreateEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}, ruleData[aclName][ruleName]) + if err != nil { + return err + } + } else { + if existingRuleEntry.IsPopulated() && ruleName != "DEFAULT_RULE" { + err := d.ModEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}, ruleData[aclName][ruleName]) + if err != nil { + return err + } + } + } + } + } + return err +} + +func (app *AclApp) setAclBindDataInConfigDb(d *db.DB, aclData map[string]db.Value, opcode int) error { + var err error + for aclKey, aclInfo := range aclData { + // Get ACL info from DB + dbAcl, err := d.GetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}) + if err != nil { + return err + } + if REPLACE == opcode { + dbAcl.SetList("ports", aclInfo.GetList("ports")) + dbAcl.Set("stage", aclInfo.Get("stage")) + } else { + dbAclIntfs := dbAcl.GetList("ports") + if len(dbAclIntfs) > 0 { + dbAclDirec := dbAcl.Get("stage") + newDirec := aclInfo.Get("stage") + if (UPDATE == opcode) && (len(dbAclDirec) > 0) && (len(newDirec) > 0) && (dbAclDirec != newDirec) { + return tlerr.InvalidArgs("Acl direction of %s not allowed when it is already configured as %s", newDirec, dbAclDirec) + } + // Merge interfaces from DB to list in aclInfo and set back in DB + intfs := aclInfo.GetList("ports") + for _, ifId := range dbAclIntfs { + if !contains(intfs, ifId) { + intfs = append(intfs, ifId) + } + } + dbAcl.SetList("ports", intfs) + } else { + dbAcl.SetList("ports", aclInfo.GetList("ports")) + } + + if len(dbAcl.Get("stage")) == 0 { + dbAcl.Set("stage", aclInfo.Get("stage")) + } + } + err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, dbAcl) + //err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, dbAcl) + if err != nil { + return err + } + } + return err +} + +func getIpProtocol(proto int64) interface{} { + for k, v := range IP_PROTOCOL_MAP { + if uint8(proto) == v { + return k + } + } + return uint8(proto) +} + +func getTransportSrcDestPorts(portVal string, portType string) interface{} { + var portRange string = "" + + portNum, err := strconv.Atoi(portVal) + if err != nil && strings.Contains(portVal, "-") { + portRange = portVal + } + + if len(portRange) > 0 { + return portRange + } else if portNum > 0 { + return uint16(portNum) + } else { + if "src" == portType { + return ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_ANY + } else if "dest" == portType { + return ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_ANY + } + } + return nil +} + +func getTransportConfigTcpFlags(tcpFlags string) []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS { + var flags []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS + if len(tcpFlags) > 0 { + flagStr := strings.Split(tcpFlags, "/")[0] + flagNumber, _ := strconv.ParseUint(strings.Replace(flagStr, "0x", "", -1), 16, 32) + for i := 0; i < 8; i++ { + mask := 1 << uint(i) + if (int(flagNumber) & mask) > 0 { + switch int(flagNumber) & mask { + case 0x01: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN) + case 0x02: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN) + case 0x04: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST) + case 0x08: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH) + case 0x10: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK) + case 0x20: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG) + case 0x40: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE) + case 0x80: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR) + default: + } + } + } + } + return flags +} + +func getL2EtherType(etherType uint64) interface{} { + for k, v := range ETHERTYPE_MAP { + if uint32(etherType) == v { + return k + } + } + return uint16(etherType) +} + +func getAclKeysFromStrKey(aclKey string, aclType string) (string, ocbinds.E_OpenconfigAcl_ACL_TYPE) { + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + return aclOrigName, aclOrigType +} + +// getAclTypeOCEnumFromName returns the ACL_TYPE enum from name +func getAclTypeOCEnumFromName(val string) (ocbinds.E_OpenconfigAcl_ACL_TYPE, error) { + switch val { + case "ACL_IPV4", "openconfig-acl:ACL_IPV4": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4, nil + case "ACL_IPV6", "openconfig-acl:ACL_IPV6": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6, nil + case "ACL_L2", "openconfig-acl:ACL_L2": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2, nil + default: + return ocbinds.OpenconfigAcl_ACL_TYPE_UNSET, + tlerr.NotSupported("ACL Type '%s' not supported", val) + } +} + +func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_TYPE) string { + aclN := strings.Replace(strings.Replace(aclname, " ", "_", -1), "-", "_", -1) + aclT := acltype.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(acltype)].Name + 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/acl_app_test.go b/src/translib/acl_app_test.go new file mode 100644 index 0000000000..d1773866ac --- /dev/null +++ b/src/translib/acl_app_test.go @@ -0,0 +1,572 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + "testing" + db "translib/db" +) + +func init() { + fmt.Println("+++++ Init acl_app_test +++++") +} + +func TestMain(m *testing.M) { + if err := clearAclDataFromDb(); err != nil { + os.Exit(-1) + } + fmt.Println("+++++ Removed All Acl Data from Db +++++") + + ret := m.Run() + + if err := clearAclDataFromDb(); err != nil { + os.Exit(-1) + } + + os.Exit(ret) +} + +// This will test GET on /openconfig-acl:acl +func Test_AclApp_TopLevelPath(t *testing.T) { + url := "/openconfig-acl:acl" + + t.Run("Empty_Response_Top_Level", processGetRequest(url, emptyJson, false)) + + t.Run("Bulk_Create_Top_Level", processSetRequest(url, bulkAclCreateJsonRequest, "POST", false)) + + t.Run("Get_Full_Acl_Tree_Top_Level", processGetRequest(url, bulkAclCreateJsonResponse, false)) + + // Delete all bindings before deleting at top level + t.Run("Delete_All_Bindings_Top_Level", processDeleteRequest("/openconfig-acl:acl/interfaces")) + t.Run("Delete_Full_ACl_Tree_Top_Level", processDeleteRequest(url)) + + t.Run("Verify_Top_Level_Delete", processGetRequest(url, emptyJson, false)) +} + +func Test_AclApp_SingleAclOperations(t *testing.T) { + url := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(url, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(url, oneAclCreateWithRulesJsonResponse, false)) + + aclDescrUrl := url + "/config/description" + t.Run("Delete Acl_Description", processDeleteRequest(aclDescrUrl)) + t.Run("Verify_Acl_Description_Deletion", processGetRequest(aclDescrUrl, emptyAclDescriptionJson, false)) + + createAclDescrUrl := url + "/config" + t.Run("Create_new_Acl_Description", processSetRequest(createAclDescrUrl, aclDescrUpdateJson, "POST", false)) + t.Run("Verify_Description_of_Acl", processGetRequest(aclDescrUrl, aclDescrUpdateJson, false)) + + t.Run("Delete_One_Acl_With_All_Its_Rules", processDeleteRequest(url)) + + t.Run("Verify_One_Acl_Delete", processGetRequest(url, "", true)) +} + +func Test_AclApp_SingleRuleOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Get_One_Acl_Without_Rule", processGetRequest(aclUrl, oneAclCreateJsonResponse, false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Get_One_Rule", processGetRequest(ruleUrl, responseOneRuleJson, false)) + + // Change Source/Desination address and protocol + t.Run("Update_Existing_Rule", processSetRequest(ruleUrl, requestOneRulePatchJson, "PATCH", false)) + t.Run("Verify_One_Rule_Updation", processGetRequest(ruleUrl, responseOneRulePatchJson, false)) + + tcpFlagsUrl := ruleUrl + "/transport/config/tcp-flags" + t.Run("Delete_Tcp_Flags_Field", processDeleteRequest(tcpFlagsUrl)) + t.Run("Verify_Tcp_Flags_Deletion", processGetRequest(tcpFlagsUrl, emptyJson, false)) + + dscpUrl := ruleUrl + "/ipv4/config/dscp" + t.Run("Delete_IPv4_Dscp_Field", processDeleteRequest(dscpUrl)) + t.Run("Verify_IPv4_Dscp_Deletion", processGetRequest(dscpUrl, emptyRuleDscpJson, false)) + + protocolUrl := ruleUrl + "/ipv4/config/protocol" + t.Run("Delete_IPv4_Protocol_Field", processDeleteRequest(protocolUrl)) + t.Run("Verify_IPv4_Protocol_Deletion", processGetRequest(protocolUrl, emptyJson, false)) + + transportConfigUrl := ruleUrl + "/transport" + t.Run("Delete_Transport_Container", processDeleteRequest(transportConfigUrl)) + t.Run("Verify_Transport_Container_Deletion", processGetRequest(transportConfigUrl, emptyJson, false)) + + ipv4ConfigUrl := ruleUrl + "/ipv4/config" + t.Run("Delete_IPv4_Config_Container", processDeleteRequest(ipv4ConfigUrl)) + t.Run("Verify_IPv4_Config_Container_Deletion", processGetRequest(ipv4ConfigUrl, emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +// This will test PUT (Replace) operation by Replacing multiple Rules with one Rule in an Acl +func Test_AclApp_ReplaceMultipleRulesWithOneRule(t *testing.T) { + url := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(url, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(url, oneAclCreateWithRulesJsonResponse, false)) + + t.Run("Replace_All_Rules_With_One_Rule", processSetRequest(url, replaceMultiRulesWithOneRuleJsonRequest, "PUT", false)) + t.Run("Verify_Acl_With_Replaced_Rules", processGetRequest(url, replaceMultiRulesWithOneRuleJsonResponse, false)) + + t.Run("Delete_One_Acl_With_All_Its_Rules", processDeleteRequest(url)) + t.Run("Verify_One_Acl_Delete", processGetRequest(url, "", true)) +} + +// This will test PATCH operation by modifying Description of an Acl +func Test_AclApp_AclDescriptionUpdation(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + descrUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/config/description" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Update_Description_of_Acl", processSetRequest(descrUrl, aclDescrUpdateJson, "PATCH", false)) + t.Run("Verify_Description_of_Acl", processGetRequest(descrUrl, aclDescrUpdateJson, false)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AclIngressBindingOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/ingress-acl-sets/ingress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + + t.Run("Create_Ingress_Acl_set", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_Aclset_Creation", processGetRequest(bindingUrl, ingressAclSetCreateJsonResponse, false)) + t.Run("Get_Port_Binding_From_Ingress_AclEntry_Level", processGetRequest(bindingUrl+"/acl-entries/acl-entry[sequence-id=8]", getBindingAclEntryResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AclEgressBindingOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + + t.Run("Create_Egress_Acl_set", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Egress_Aclset_Creation", processGetRequest(bindingUrl, egressAclSetCreateJsonResponse, false)) + t.Run("Get_Port_Binding_From_Egress_AclEntry_Level", processGetRequest(bindingUrl+"/acl-entries/acl-entry[sequence-id=8]", getBindingAclEntryResponse, false)) + + t.Run("Delete_Binding_From_Egress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Egress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_GetOperationsFromMultipleTreeLevels(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Create_Egress_Acl_set_Port_Binding", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + t.Run("Get_Acl_Tree_From_AclSets_level", processGetRequest("/openconfig-acl:acl/acl-sets", getFromAclSetsTreeLevelResponse, false)) + + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getAllPortsFromInterfacesTreeLevelResponse, false)) + + t.Run("Get_One_Port_Binding_From_Interface_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces/interface[id=Ethernet4]", getPortBindingFromInterfaceTreeLevelResponse, false)) + + t.Run("Delete_Binding_From_Egress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Egress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AddNewPortBindingToAlreadyBindedAcl(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Create_Egress_Acl_set_Port_Binding", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + newBindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet0]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + t.Run("Create_New_Egress_Acl_set_Port_Binding", processSetRequest(newBindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getMultiportBindingOnSingleAclResponse, false)) + + t.Run("Delete_All_Bindings_Top_Level", processDeleteRequest("/openconfig-acl:acl/interfaces")) + t.Run("Delete_All_Rules_Not_Acl", processDeleteRequest("/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries")) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) + + t.Run("Verify_Top_Level_Delete", processGetRequest("/openconfig-acl:acl", emptyJson, false)) +} + +func Test_AclApp_IPv6AclAndRule(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL6][type=ACL_IPV6]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL6][type=ACL_IPV6]/acl-entries/acl-entry[sequence-id=6]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/ingress-acl-sets/ingress-acl-set[set-name=MyACL6][type=ACL_IPV6]" + + t.Run("Create_One_IPv6_Acl_Without_Rule", processSetRequest(aclUrl, oneIPv6AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_IPv6_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneIPv6AclCreateJsonResponse, false)) + + t.Run("Create_One_IPv6_Rule", processSetRequest(ruleUrl, oneIPv6RuleCreateJsonRequest, "POST", false)) + t.Run("Verify_One_IPv6_Rule_Creation", processGetRequest(ruleUrl, oneIPv6RuleCreateJsonResponse, false)) + + t.Run("Create_Ingress_Acl_set", processSetRequest(bindingUrl, ingressIPv6AclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_Aclset_Creation", processGetRequest(bindingUrl, ingressIPv6AclSetCreateJsonResponse, false)) + + t.Run("Get_Acl_Tree_From_AclSet_level", processGetRequest("/openconfig-acl:acl/acl-sets/acl-set", getIPv6AclsFromAclSetListLevelResponse, false)) + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getIPv6AllPortsBindingsResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + pktActionUrl := ruleUrl + "/actions/config/forwarding-action" + t.Run("Delete_Packet_Action_Field", processDeleteRequest(pktActionUrl)) + t.Run("Verify_Packet_Action_Field_Deletion", processGetRequest(pktActionUrl, emptyJson, false)) + + ipv6ConfigUrl := ruleUrl + "/ipv6/config" + t.Run("Delete_IPv6_Config", processDeleteRequest(ipv6ConfigUrl)) + t.Run("Verify_IPv6_Config_Deletion", processGetRequest(ipv6ConfigUrl, emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_L2AclAndRule(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]/acl-entries/acl-entry[sequence-id=2]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet0]/ingress-acl-sets/ingress-acl-set[set-name=MyACL2][type=ACL_L2]" + + t.Run("Create_One_L2_Acl_Without_Rule", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneL2AclCreateJsonResponse, false)) + + t.Run("Create_One_L2_Rule", processSetRequest(ruleUrl, oneL2RuleCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Rule_Creation", processGetRequest(ruleUrl, oneL2RuleCreateJsonResponse, false)) + + t.Run("Create_Ingress_L2_Acl_set", processSetRequest(bindingUrl, ingressL2AclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_L2_Aclset_Creation", processGetRequest(bindingUrl, ingressL2AclSetCreateJsonResponse, false)) + + t.Run("Get_Acl_Tree_From_AclSet_level", processGetRequest("/openconfig-acl:acl/acl-sets/acl-set", getL2AclsFromAclSetListLevelResponse, false)) + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getL2AllPortsBindingsResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + etherTypeUrl := ruleUrl + "/l2/config/ethertype" + t.Run("Delete_Ethertype_Field", processDeleteRequest(etherTypeUrl)) + t.Run("Verify_L2_Ethertype_Field_Deletion", processGetRequest(ruleUrl+"/l2/config", emptyJson, false)) + + t.Run("Delete_Transport_Src_Port_Field", processDeleteRequest(ruleUrl+"/transport/config/source-port")) + t.Run("Delete_Transport_Dst_Port_Field", processDeleteRequest(ruleUrl+"/transport/config/destination-port")) + t.Run("Delete_Transport_Tcp_Flags_Field", processDeleteRequest(ruleUrl+"/transport/config/tcp-flags")) + t.Run("Verify_Transport_Src_Dst_Fields_Deletion", processGetRequest(ruleUrl+"/transport/config", emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_NegativeTests(t *testing.T) { + // Verify GET returns errors for non-existing ACL + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]" + t.Run("Verify_Non_Existing_Acl_GET_Error", processGetRequest(aclUrl, "", true)) + + // Verify GET returns errors for non-existing Rule + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]/acl-entries/acl-entry[sequence-id=2]" + t.Run("Verify_Non_Existing_Rule_GET_Error", processGetRequest(ruleUrl, "", true)) + + // Verify Error on giving Invalid Interface in payload during binding creation + url := "/openconfig-acl:acl" + t.Run("Create_Acl_With_Invalid_Interface_Binding", processSetRequest(url, aclCreateWithInvalidInterfaceBinding, "POST", true)) + + // Verify error if duplicate Acl is created using POST + t.Run("Create_One_L2_Acl_Without_Rule", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneL2AclCreateJsonResponse, false)) + t.Run("Verify_Error_On_Create_Duplicate_L2_Acl", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", true)) + + // Verify error if duplicate Rule is created using POST + multiRuleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(multiRuleUrl, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(multiRuleUrl, oneAclCreateWithRulesJsonResponse, true)) + + duplicateRuleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=1]" + t.Run("Create_One_Duplicate_Rule", processSetRequest(duplicateRuleUrl, requestOneDuplicateRulePostJson, "POST", true)) + + topLevelUrl := "/openconfig-acl:acl" + t.Run("Delete_Full_ACl_Tree_Top_Level", processDeleteRequest(topLevelUrl)) + t.Run("Verify_Top_Level_Delete", processGetRequest(topLevelUrl, emptyJson, false)) +} + +func processGetRequest(url string, expectedRespJson string, errorCase bool) func(*testing.T) { + return func(t *testing.T) { + response, err := Get(GetRequest{url}) + if err != nil && !errorCase { + t.Errorf("Error %v received for Url: %s", err, url) + } + + respJson := response.Payload + if string(respJson) != expectedRespJson { + t.Errorf("Response for Url: %s received is not expected:\n%s", url, string(respJson)) + } + } +} + +func processSetRequest(url string, jsonPayload string, oper string, errorCase bool) func(*testing.T) { + return func(t *testing.T) { + var err error + switch oper { + case "POST": + _, err = Create(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + case "PATCH": + _, err = Update(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + case "PUT": + _, err = Replace(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + default: + t.Errorf("Operation not supported") + } + if err != nil && !errorCase { + t.Errorf("Error %v received for Url: %s", err, url) + } + } +} + +func processDeleteRequest(url string) func(*testing.T) { + return func(t *testing.T) { + _, err := Delete(SetRequest{Path: url}) + if err != nil { + t.Errorf("Error %v received for Url: %s", err, url) + } + } +} + +// THis will delete ACL table and Rules Table from DB +func clearAclDataFromDb() error { + var err error + ruleTable := db.TableSpec{Name: "ACL_RULE"} + aclTable := db.TableSpec{Name: "ACL_TABLE"} + + d := getConfigDb() + if d == nil { + err = errors.New("Failed to connect to config Db") + return err + } + if err = d.DeleteTable(&ruleTable); err != nil { + err = errors.New("Failed to delete Rules Table") + return err + } + if err = d.DeleteTable(&aclTable); err != nil { + err = errors.New("Failed to delete Acl Table") + return err + } + return err +} + +func getConfigDb() *db.DB { + configDb, _ := db.NewDB(db.Options{ + DBNo: db.ConfigDB, + InitIndicator: "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator: "|", + }) + + return configDb +} + +func Test_AclApp_Subscribe(t *testing.T) { + app := new(AclApp) + + t.Run("top", testSubsError(app, "/")) + t.Run("unknown", testSubsError(app, "/some/unknown/path")) + t.Run("topacl", testSubsError(app, "/openconfig-acl:acl")) + t.Run("aclsets", testSubsError(app, "/openconfig-acl:acl/acl-sets")) + t.Run("aclset*", testSubsError(app, "/openconfig-acl:acl/acl-sets/acl-set")) + t.Run("aclset", testSubsError(app, "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]")) + + t.Run("acl_config", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/config/description", + "ACL_TABLE", "X_ACL_IPV4", true)) + + t.Run("acl_state", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/state", + "ACL_TABLE", "X_ACL_IPV4", true)) + + t.Run("entries", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries", + "ACL_RULE", "X_ACL_IPV4|*", false)) + + t.Run("rule*", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry", + "ACL_RULE", "X_ACL_IPV4|*", false)) + + t.Run("rule", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=1]", + "ACL_RULE", "X_ACL_IPV4|RULE_1", false)) + + t.Run("rule_state", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=100]/state", + "ACL_RULE", "X_ACL_IPV4|RULE_100", true)) + + t.Run("rule_sip", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=200]/ipv4/config/source-address", + "ACL_RULE", "X_ACL_IPV4|RULE_200", true)) + +} + +// testSubs creates a test case which invokes translateSubscribe on an +// app interafce and check returned notificationInfo matches given values. +func testSubs(app appInterface, path, oTable, oKey string, oCache bool) func(*testing.T) { + return func(t *testing.T) { + _, nt, err := app.translateSubscribe([db.MaxDB]*db.DB{}, path) + if err != nil { + t.Fatalf("Unexpected error processing '%s'; err=%v", path, err) + } + if nt == nil || nt.needCache != oCache || nt.table.Name != oTable || + !reflect.DeepEqual(nt.key.Comp, strings.Split(oKey, "|")) { + t.Logf("translateSubscribe for path '%s'", path) + t.Logf("Expected table '%s', key '%v', cache %v", oTable, oKey, oCache) + if nt == nil { + t.Fatalf("Found nil") + } else { + t.Fatalf("Found table '%s', key '%s', cache %v", + nt.table.Name, strings.Join(nt.key.Comp, "|"), nt.needCache) + } + } + } +} + +// testSubsError creates a test case which invokes translateSubscribe on +// an app interafce and expects it to return an error +func testSubsError(app appInterface, path string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := app.translateSubscribe([db.MaxDB]*db.DB{}, path) + if err == nil { + t.Fatalf("Expected error for path '%s'", path) + } + } +} + +/***************************************************************************/ +/////////// JSON Data for Tests /////////////// +/***************************************************************************/ +var emptyJson string = "{}" + +var bulkAclCreateJsonRequest string = "{\"acl-sets\":{\"acl-set\":[{\"name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"config\":{\"name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"description\":\"Description for MyACL1\"},\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"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\"}}},{\"sequence-id\":2,\"config\":{\"sequence-id\":2,\"description\":\"Description for MyACL1 Rule Seq 2\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.2/32\",\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":102,\"destination-port\":202}},\"actions\":{\"config\":{\"forwarding-action\":\"DROP\"}}},{\"sequence-id\":3,\"config\":{\"sequence-id\":3,\"description\":\"Description for MyACL1 Rule Seq 3\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.3/32\",\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":103,\"destination-port\":203}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":4,\"config\":{\"sequence-id\":4,\"description\":\"Description for MyACL1 Rule Seq 4\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.4/32\",\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":104,\"destination-port\":204}},\"actions\":{\"config\":{\"forwarding-action\":\"DROP\"}}},{\"sequence-id\":5,\"config\":{\"sequence-id\":5,\"description\":\"Description for MyACL1 Rule Seq 5\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.5/32\",\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":105,\"destination-port\":205}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}}]}},{\"name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"config\":{\"name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"description\":\"Description for MyACL2\"},\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"config\":{\"sequence-id\":1,\"description\":\"Description for Rule Seq 1\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.1/32\",\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":101,\"destination-port\":201}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":2,\"config\":{\"sequence-id\":2,\"description\":\"Description for Rule Seq 2\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.2/32\",\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":102,\"destination-port\":202}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":3,\"config\":{\"sequence-id\":3,\"description\":\"Description for Rule Seq 3\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.3/32\",\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":103,\"destination-port\":203}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":4,\"config\":{\"sequence-id\":4,\"description\":\"Description for Rule Seq 4\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.4/32\",\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":104,\"destination-port\":204}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":5,\"config\":{\"sequence-id\":5,\"description\":\"Description for Rule Seq 5\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.5/32\",\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":105,\"destination-port\":205}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}}]}}]},\"interfaces\":{\"interface\":[{\"id\":\"Ethernet0\",\"config\":{\"id\":\"Ethernet0\"},\"interface-ref\":{\"config\":{\"interface\":\"Ethernet0\"}},\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"set-name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"config\":{\"set-name\":\"MyACL1\",\"type\":\"ACL_IPV4\"}}]}},{\"id\":\"Ethernet4\",\"config\":{\"id\":\"Ethernet4\"},\"interface-ref\":{\"config\":{\"interface\":\"Ethernet4\"}},\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"set-name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"config\":{\"set-name\":\"MyACL2\",\"type\":\"ACL_IPV4\"}}]}}]}}" + +var bulkAclCreateJsonResponse string = "{\"openconfig-acl:acl\":{\"acl-sets\":{\"acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"},\"state\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.2/32\"},\"state\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"},\"state\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"},\"state\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"},\"state\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL1\",\"name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL1\",\"state\":{\"description\":\"Description for MyACL1\",\"name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"},{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.1/32\"},\"state\":{\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.2/32\"},\"state\":{\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.3/32\"},\"state\":{\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.4/32\"},\"state\":{\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.5/32\"},\"state\":{\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"id\":\"Ethernet0\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1}},{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}},{\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3}},{\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4}},{\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5}}]},\"config\":{\"set-name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL1\",\"state\":{\"set-name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"state\":{\"id\":\"Ethernet0\"}},{\"config\":{\"id\":\"Ethernet4\"},\"id\":\"Ethernet4\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1}},{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}},{\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3}},{\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4}},{\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"state\":{\"id\":\"Ethernet4\"}}]}}}" + +var oneAclCreateWithRulesJsonRequest string = "{ \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL3\" }, \"acl-entries\": { \"acl-entry\": [ { \"sequence-id\": 1, \"config\": { \"sequence-id\": 1, \"description\": \"Description for MyACL3 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\" } } }, { \"sequence-id\": 2, \"config\": { \"sequence-id\": 2, \"description\": \"Description for MyACL3 Rule Seq 2\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.2/32\", \"destination-address\": \"21.1.1.2/32\", \"dscp\": 2, \"protocol\": \"IP_UDP\" } }, \"transport\": { \"config\": { \"source-port\": 102, \"destination-port\": 202 } }, \"actions\": { \"config\": { \"forwarding-action\": \"DROP\" } } }, { \"sequence-id\": 3, \"config\": { \"sequence-id\": 3, \"description\": \"Description for MyACL3 Rule Seq 3\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.3/32\", \"destination-address\": \"21.1.1.3/32\", \"dscp\": 3, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 103, \"destination-port\": 203 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } }, { \"sequence-id\": 4, \"config\": { \"sequence-id\": 4, \"description\": \"Description for MyACL3 Rule Seq 4\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.4/32\", \"destination-address\": \"21.1.1.4/32\", \"dscp\": 4, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 104, \"destination-port\": 204 } }, \"actions\": { \"config\": { \"forwarding-action\": \"DROP\" } } }, { \"sequence-id\": 5, \"config\": { \"sequence-id\": 5, \"description\": \"Description for MyACL3 Rule Seq 5\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.5/32\", \"destination-address\": \"21.1.1.5/32\", \"dscp\": 5, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 105, \"destination-port\": 205 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } } ] }}" + +var oneAclCreateWithRulesJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"},\"state\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_UDP\",\"source-address\":\"11.1.1.2/32\"},\"state\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_UDP\",\"source-address\":\"11.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"},\"state\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"},\"state\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"},\"state\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL3\",\"state\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var oneAclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL5\",\"type\": \"ACL_IPV4\",\"description\": \"Description for MyACL5\"}}" +var oneAclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL5\",\"state\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var aclDescrUpdateJson string = "{\"openconfig-acl:description\":\"Verifying ACL Description Update\"}" + +var requestOneRulePostJson string = "{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL5 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"4.4.4.4/24\",\"destination-address\": \"5.5.5.5/24\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var requestOneRulePatchJson string = "{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL5 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"4.8.4.8/24\",\"destination-address\": \"15.5.15.5/24\",\"protocol\": \"IP_L2TP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\",\"TCP_RST\",\"TCP_ECE\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var responseOneRuleJson string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"},\"state\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var responseOneRulePatchJson string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"15.5.15.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_L2TP\",\"source-address\":\"4.8.4.8/24\"},\"state\":{\"destination-address\":\"15.5.15.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_L2TP\",\"source-address\":\"4.8.4.8/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_RST\",\"openconfig-packet-match-types:TCP_ACK\",\"openconfig-packet-match-types:TCP_ECE\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_RST\",\"openconfig-packet-match-types:TCP_ACK\",\"openconfig-packet-match-types:TCP_ECE\"]}}}]}" + +var emptyAclDescriptionJson string = "{\"openconfig-acl:description\":\"\"}" +var emptyRuleDscpJson string = "{\"openconfig-acl:dscp\":0}" + +var ingressAclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL5\", \"type\": \"ACL_IPV4\" }}" +var ingressAclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var egressAclSetCreateJsonResponse string = "{\"openconfig-acl:egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var replaceMultiRulesWithOneRuleJsonRequest string = "{\"name\": \"MyACL3\",\"type\": \"ACL_IPV4\",\"config\": {\"name\": \"MyACL3\",\"type\": \"ACL_IPV4\",\"description\": \"Description for MyACL3\"},\"acl-entries\": {\"acl-entry\": [{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL3 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"81.1.1.1/32\",\"destination-address\": \"91.1.1.1/32\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": \"801..811\",\"destination-port\": \"901..921\"}},\"actions\": {\"config\": {\"forwarding-action\": \"REJECT\"}}}]}}" + +var replaceMultiRulesWithOneRuleJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"91.1.1.1/32\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"81.1.1.1/32\"},\"state\":{\"destination-address\":\"91.1.1.1/32\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"81.1.1.1/32\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":\"901-921\",\"source-port\":\"801-811\"},\"state\":{\"destination-port\":\"901-921\",\"source-port\":\"801-811\"}}}]},\"config\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL3\",\"state\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var getFromAclSetsTreeLevelResponse string = "{\"openconfig-acl:acl-sets\":{\"acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"},\"state\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL5\",\"state\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}}" + +var getAllPortsFromInterfacesTreeLevelResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var getPortBindingFromInterfaceTreeLevelResponse string = "{\"openconfig-acl:interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}" + +var getBindingAclEntryResponse string = "{\"openconfig-acl:acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]}" + +var getMultiportBindingOnSingleAclResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet0\",\"state\":{\"id\":\"Ethernet0\"}},{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var oneIPv6AclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL6\",\"type\": \"ACL_IPV6\",\"description\": \"Description for IPv6 ACL MyACL6\"}}" +var oneIPv6AclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"name\":\"MyACL6\",\"state\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var oneIPv6RuleCreateJsonRequest string = "{\"sequence-id\": 6,\"config\": {\"sequence-id\": 6,\"description\": \"Description for MyACL6 Rule Seq 6\"},\"ipv6\": {\"config\": {\"source-address\": \"11::67/64\",\"destination-address\": \"22::87/64\",\"protocol\": \"IP_TCP\",\"dscp\": 11}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" +var oneIPv6RuleCreateJsonResponse string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":6},\"ipv6\":{\"config\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"},\"state\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"}},\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var ingressIPv6AclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL6\", \"type\": \"ACL_IPV6\" }}" +var ingressIPv6AclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6}}]},\"config\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"set-name\":\"MyACL6\",\"state\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var getIPv6AclsFromAclSetListLevelResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":6},\"ipv6\":{\"config\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"},\"state\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"}},\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"name\":\"MyACL6\",\"state\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var getIPv6AllPortsBindingsResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"id\":\"Ethernet4\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6}}]},\"config\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"set-name\":\"MyACL6\",\"state\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]},\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var oneL2AclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL2\",\"type\": \"ACL_L2\",\"description\": \"Description for L2 ACL MyACL2\"}}" +var oneL2AclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var oneL2RuleCreateJsonRequest string = "{\"sequence-id\": 2,\"config\": {\"sequence-id\": 2,\"description\": \"Description for MyACL2 Rule Seq 2\"},\"l2\": {\"config\": {\"ethertype\": \"ETHERTYPE_VLAN\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var oneL2RuleCreateJsonResponse string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"l2\":{\"config\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"},\"state\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var ingressL2AclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL2\", \"type\": \"ACL_L2\" }}" +var ingressL2AclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var getL2AclsFromAclSetListLevelResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"l2\":{\"config\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"},\"state\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var getL2AllPortsBindingsResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"id\":\"Ethernet0\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]},\"state\":{\"id\":\"Ethernet0\"}}]}}" + +var aclCreateWithInvalidInterfaceBinding string = "{ \"acl-sets\": { \"acl-set\": [ { \"name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL1\" }, \"acl-entries\": { \"acl-entry\": [ { \"sequence-id\": 1, \"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\" } } } ] } } ] }, \"interfaces\": { \"interface\": [ { \"id\": \"Ethernet2112\", \"config\": { \"id\": \"Ethernet2112\" }, \"interface-ref\": { \"config\": { \"interface\": \"Ethernet2112\" } }, \"ingress-acl-sets\": { \"ingress-acl-set\": [ { \"set-name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"config\": { \"set-name\": \"MyACL1\", \"type\": \"ACL_IPV4\" } } ] } } ] }}" + +var requestOneDuplicateRulePostJson string = "{\"sequence-id\": 1,\"config\": {\"sequence-id\": 1,\"description\": \"Description for MyACL3 Rule Seq 1\"},\"ipv4\": {\"config\": {\"source-address\": \"4.4.4.4/24\",\"destination-address\": \"5.5.5.5/24\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" diff --git a/src/translib/app_interface.go b/src/translib/app_interface.go new file mode 100644 index 0000000000..0a8b477403 --- /dev/null +++ b/src/translib/app_interface.go @@ -0,0 +1,170 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib defines the interface for all the app modules + +It exposes register function for all the app modules to register + +It stores all the app module information in a map and presents it + +to the tranlib infra when it asks for the same. +*/ + +package translib + +import ( + "errors" + "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 +} + +//Structure containing the app data coming from translib infra +type appData struct { + path string + payload []byte + ygotRoot *ygot.GoStruct + ygotTarget *interface{} +} + +//map containing the base path to app module info +var appMap map[string]*appInfo + +//array containing all the supported models +var models []ModelData + +//Interface for all App Modules +type appInterface interface { + initialize(data appData) + translateCreate(d *db.DB) ([]db.WatchKeys, error) + translateUpdate(d *db.DB) ([]db.WatchKeys, error) + translateReplace(d *db.DB) ([]db.WatchKeys, error) + translateDelete(d *db.DB) ([]db.WatchKeys, error) + translateGet(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) +} + +//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) + + if appMap == nil { + appMap = make(map[string]*appInfo) + } + + if _, ok := appMap[path]; ok == false { + + appMap[path] = info + + } else { + log.Fatal("Duplicate path being registered. Path =", path) + err = errors.New("Duplicate path") + } + + return err +} + +//Adds the model information to the supported models array +func addModel(model *ModelData) error { + var err error + + models = append(models, *model) + + //log.Info("Models = ", models) + return err +} + +//App modules can use this function to unregister itself from the app interface +func unregister(path string) error { + var err error + log.Info("Unregister for path =", path) + + _, ok := appMap[path] + + if ok { + log.Info("deleting for path =", path) + delete(appMap, path) + } + + return err +} + +//Translib infra will use this function get the app info for a given path +func getAppModuleInfo(path string) (*appInfo, error) { + var err error + log.Info("getAppModule called for path =", path) + + for pattern, app := range appMap { + if !strings.HasPrefix(path, pattern) { + continue + } + + log.Info("found the entry in the map for path =", pattern) + + return app, err + } + + errStr := "Unsupported path=" + path + + err = errors.New(errStr) + log.Error(errStr) + + var app *appInfo + + return app, err +} + +//Get all the supported models +func getModels() []ModelData { + + return models +} + +//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) + } + + return app, err +} diff --git a/src/translib/app_utils.go b/src/translib/app_utils.go new file mode 100644 index 0000000000..0fa8b66fd3 --- /dev/null +++ b/src/translib/app_utils.go @@ -0,0 +1,231 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "bytes" + "encoding/json" + "reflect" + "strings" + "translib/db" + "translib/ocbinds" + "translib/tlerr" + + log "github.com/golang/glog" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" +) + +func getYangPathFromUri(uri string) (string, error) { + var path *gnmi.Path + var err error + + path, err = ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Errorf("Error in uri to path conversion: %v", err) + return "", err + } + + yangPath, yperr := ygot.PathToSchemaPath(path) + if yperr != nil { + log.Errorf("Error in Gnmi path to Yang path conversion: %v", yperr) + return "", yperr + } + + return yangPath, err +} + +func getYangPathFromYgotStruct(s ygot.GoStruct, yangPathPrefix string, appModuleName string) string { + tn := reflect.TypeOf(s).Elem().Name() + schema, ok := ocbinds.SchemaTree[tn] + if !ok { + log.Errorf("could not find schema for type %s", tn) + return "" + } else if schema != nil { + yPath := schema.Path() + //yPath = strings.Replace(yPath, "/device/acl", "/openconfig-acl:acl", 1) + yPath = strings.Replace(yPath, yangPathPrefix, appModuleName, 1) + return yPath + } + return "" +} + +func generateGetResponsePayload(targetUri string, deviceObj *ocbinds.Device, ygotTarget *interface{}) ([]byte, error) { + var err error + var payload []byte + + if len(targetUri) == 0 { + return payload, tlerr.InvalidArgs("GetResponse failed as target Uri is not valid") + } + path, err := ygot.StringToPath(targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return payload, tlerr.InvalidArgs("URI to path conversion failed: %v", err) + } + + // Get current node (corresponds to ygotTarget) and its parent node + var pathList []*gnmi.PathElem = path.Elem + parentPath := &gnmi.Path{} + for i := 0; i < len(pathList); i++ { + if log.V(3) { + log.Infof("pathList[%d]: %s\n", i, pathList[i]) + } + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + if i < (len(pathList) - 1) { + parentPath.Elem = append(parentPath.Elem, pathList[i]) + } + } + parentNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, parentPath) + if err != nil { + return payload, err + } + if len(parentNodeList) == 0 { + return payload, tlerr.InvalidArgs("Invalid URI: %s", targetUri) + } + parentNode := parentNodeList[0].Data + + currentNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path, &(ytypes.GetPartialKeyMatch{})) + if err != nil { + return payload, err + } + if len(currentNodeList) == 0 { + return payload, tlerr.NotFound("Resource not found") + } + //currentNode := currentNodeList[0].Data + currentNodeYangName := currentNodeList[0].Schema.Name + + // Create empty clone of parent node + parentNodeClone := reflect.New(reflect.TypeOf(parentNode).Elem()) + var parentCloneObj ygot.ValidatedGoStruct + var ok bool + if parentCloneObj, ok = (parentNodeClone.Interface()).(ygot.ValidatedGoStruct); ok { + ygot.BuildEmptyTree(parentCloneObj) + pcType := reflect.TypeOf(parentCloneObj).Elem() + pcValue := reflect.ValueOf(parentCloneObj).Elem() + + var currentNodeOCFieldName string + for i := 0; i < pcValue.NumField(); i++ { + fld := pcValue.Field(i) + fldType := pcType.Field(i) + if fldType.Tag.Get("path") == currentNodeYangName { + currentNodeOCFieldName = fldType.Name + // Take value from original parent and set in parent clone + valueFromParent := reflect.ValueOf(parentNode).Elem().FieldByName(currentNodeOCFieldName) + fld.Set(valueFromParent) + break + } + } + if log.V(3) { + log.Infof("Target yang name: %s OC Field name: %s\n", currentNodeYangName, currentNodeOCFieldName) + } + } + + payload, err = dumpIetfJson(parentCloneObj, true) + + return payload, err +} + +func getTargetNodeYangSchema(targetUri string, deviceObj *ocbinds.Device) (*yang.Entry, error) { + if len(targetUri) == 0 { + return nil, tlerr.InvalidArgs("GetResponse failed as target Uri is not valid") + } + path, err := ygot.StringToPath(targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return nil, tlerr.InvalidArgs("URI to path conversion failed: %v", err) + } + // Get current node (corresponds to ygotTarget) + var pathList []*gnmi.PathElem = path.Elem + for i := 0; i < len(pathList); i++ { + if log.V(3) { + log.Infof("pathList[%d]: %s\n", i, pathList[i]) + } + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + } + targetNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path, &(ytypes.GetPartialKeyMatch{})) + if err != nil { + return nil, tlerr.InvalidArgs("Getting node information failed: %v", err) + } + if len(targetNodeList) == 0 { + return nil, tlerr.NotFound("Resource not found") + } + targetNodeSchema := targetNodeList[0].Schema + //targetNode := targetNodeList[0].Data + if log.V(3) { + log.Infof("Target node yang name: %s\n", targetNodeSchema.Name) + } + return targetNodeSchema, nil +} + +func dumpIetfJson(s ygot.ValidatedGoStruct, skipValidation bool) ([]byte, error) { + jsonStr, err := ygot.EmitJSON(s, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + Indent: " ", + SkipValidation: skipValidation, + RFC7951Config: &ygot.RFC7951JSONConfig{ + AppendModuleName: true, + }, + }) + var buf bytes.Buffer + json.Compact(&buf, []byte(jsonStr)) + return []byte(buf.String()), err +} + +func contains(sl []string, str string) bool { + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +func removeElement(sl []string, str string) []string { + for i := 0; i < len(sl); i++ { + if sl[i] == str { + sl = append(sl[:i], sl[i+1:]...) + i-- + sl = sl[:len(sl)] + break + } + } + return sl +} + +// isNotFoundError return true if the error is a 'not found' error +func isNotFoundError(err error) bool { + switch err.(type) { + case tlerr.TranslibRedisClientEntryNotExist, tlerr.NotFoundError: + return true + default: + return false + } +} + +// asKey cretaes a db.Key from given key components +func asKey(parts ...string) db.Key { + return db.Key{Comp: parts} +} + +func createEmptyDbValue(fieldName string) db.Value { + return db.Value{Field: map[string]string{fieldName: ""}} +} diff --git a/src/translib/common_app.go b/src/translib/common_app.go new file mode 100644 index 0000000000..7571bb025c --- /dev/null +++ b/src/translib/common_app.go @@ -0,0 +1,485 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + "strings" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "translib/db" + "translib/ocbinds" + "translib/tlerr" + "translib/transformer" + "encoding/json" +) + +var () + +type CommonApp struct { + pathInfo *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + cmnAppTableMap map[string]map[string]db.Value + cmnAppOrdTbllist []string +} + +var cmnAppInfo = appInfo{appType: reflect.TypeOf(CommonApp{}), + ygotRootType: nil, + isNative: false, + tablesToWatch: nil} + +func init() { + + register_model_path := []string{"/sonic-", "*"} // register yang model path(s) to be supported via common app + for _, mdl_pth := range register_model_path { + err := register(mdl_pth, &cmnAppInfo) + + if err != nil { + log.Fatal("Register Common app module with App Interface failed with error=", err, "for path=", mdl_pth) + } + } + +} + +func (app *CommonApp) initialize(data appData) { + log.Info("initialize:path =", data.path) + pathInfo := NewPathInfo(data.path) + *app = CommonApp{pathInfo: pathInfo, ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + +} + +func (app *CommonApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, CREATE) + + return keys, err +} + +func (app *CommonApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, UPDATE) + + return keys, err +} + +func (app *CommonApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, REPLACE) + + return keys, err +} + +func (app *CommonApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:path =", app.pathInfo.Path) + keys, err = app.translateCRUDCommon(d, DELETE) + + return keys, err +} + +func (app *CommonApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:path =", app.pathInfo.Path) + return err +} + +func (app *CommonApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + err := errors.New("Not supported") + notifInfo := notificationInfo{dbno: db.ConfigDB} + return nil, ¬ifInfo, err +} + +func (app *CommonApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCreate:path =", app.pathInfo.Path) + targetType := reflect.TypeOf(*app.ygotTarget) + log.Infof("processCreate: Target object is a <%s> of Type: %s", targetType.Kind().String(), targetType.Elem().Name()) + if err = app.processCommon(d, CREATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processUpdate:path =", app.pathInfo.Path) + if err = app.processCommon(d, UPDATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processReplace:path =", app.pathInfo.Path) + if err = app.processCommon(d, REPLACE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *CommonApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processDelete:path =", app.pathInfo.Path) + + if err = app.processCommon(d, DELETE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + var resPayload []byte + log.Info("processGet:path =", app.pathInfo.Path) + + payload, err = transformer.GetAndXlateFromDB(app.pathInfo.Path, app.ygotRoot, dbs) + if err != nil { + log.Error("transformer.transformer.GetAndXlateFromDB failure. error:", err) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + targetObj, _ := (*app.ygotTarget).(ygot.GoStruct) + if targetObj != nil { + err = ocbinds.Unmarshal(payload, targetObj) + if err != nil { + log.Error("ocbinds.Unmarshal() failed. error:", err) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + resPayload, err = generateGetResponsePayload(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device), app.ygotTarget) + if err != nil { + log.Error("generateGetResponsePayload() failed") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + var dat map[string]interface{} + err = json.Unmarshal(resPayload, &dat) + } else { + log.Warning("processGet. targetObj is null. Unable to Unmarshal payload") + resPayload = payload + } + + return GetResponse{Payload: resPayload}, err +} + +func (app *CommonApp) translateCRUDCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + var tblsToWatch []*db.TableSpec + var OrdTblList []string + var moduleNm string + log.Info("translateCRUDCommon:path =", app.pathInfo.Path) + + /* retrieve schema table order for incoming module name request */ + moduleNm, err = transformer.GetModuleNmFromPath(app.pathInfo.Path) + if (err != nil) || (len(moduleNm) == 0) { + log.Error("GetModuleNmFromPath() failed") + return keys, err + } + log.Info("getModuleNmFromPath() returned module name = ", moduleNm) + OrdTblList, err = transformer.GetOrdDBTblList(moduleNm) + if (err != nil) || (len(OrdTblList) == 0) { + log.Error("GetOrdDBTblList() failed") + return keys, err + } + + log.Info("GetOrdDBTblList() returned ordered table list = ", OrdTblList) + app.cmnAppOrdTbllist = OrdTblList + + /* enhance this to handle dependent tables - need CVL to provide list of such tables for a given request */ + for _, tblnm := range OrdTblList { // OrdTblList already has has all tables corresponding to a module + tblsToWatch = append(tblsToWatch, &db.TableSpec{Name: tblnm}) + } + log.Info("Tables to watch", tblsToWatch) + + cmnAppInfo.tablesToWatch = tblsToWatch + + // translate yang to db + result, err := transformer.XlateToDb(app.pathInfo.Path, opcode, d, (*app).ygotRoot, (*app).ygotTarget) + fmt.Println(result) + log.Info("transformer.XlateToDb() returned", result) + + if err != nil { + log.Error(err) + return keys, err + } + if len(result) == 0 { + log.Error("XlatetoDB() returned empty map") + err = errors.New("transformer.XlatetoDB() returned empty map") + return keys, err + } + app.cmnAppTableMap = result + + keys, err = app.generateDbWatchKeys(d, false) + + return keys, err +} + +func (app *CommonApp) processCommon(d *db.DB, opcode int) error { + + var err error + + log.Info("Processing DB operation for ", app.cmnAppTableMap) + switch opcode { + case CREATE: + log.Info("CREATE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case UPDATE: + log.Info("UPDATE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case REPLACE: + log.Info("REPLACE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case DELETE: + log.Info("DELETE case") + err = app.cmnAppDelDbOpn(d, opcode) + } + if err != nil { + log.Info("Returning from processCommon() - fail") + } else { + log.Info("Returning from processCommon() - success") + } + return err +} + +func (app *CommonApp) cmnAppCRUCommonDbOpn(d *db.DB, opcode int) error { + var err error + var cmnAppTs *db.TableSpec + + /* currently ordered by schema table order needs to be discussed */ + for _, tblNm := range app.cmnAppOrdTbllist { + log.Info("In Yang to DB map returned from transformer looking for table = ", tblNm) + if tblVal, ok := app.cmnAppTableMap[tblNm]; ok { + cmnAppTs = &db.TableSpec{Name: tblNm} + log.Info("Found table entry in yang to DB map") + for tblKey, tblRw := range tblVal { + log.Info("Processing Table key and row ", tblKey, tblRw) + existingEntry, _ := d.GetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + switch opcode { + case CREATE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence return.") + return tlerr.AlreadyExists("Entry %s already exists", tblKey) + } else { + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("CREATE case - d.CreateEntry() failure") + return err + } + } + case UPDATE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence modifying it.") + /* Handle leaf-list merge if any leaf-list exists + A leaf-list field in redis has "@" suffix as per swsssdk convention. + */ + resTblRw := db.Value{Field: map[string]string{}} + resTblRw = checkAndProcessLeafList(existingEntry, tblRw, UPDATE, d, tblNm, tblKey) + err = d.ModEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, resTblRw) + if err != nil { + log.Error("UPDATE case - d.ModEntry() failure") + return err + } + } else { + // workaround to patch operation from CLI + log.Info("Create(pathc) an entry.") + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("UPDATE case - d.CreateEntry() failure") + return err + } + } + case REPLACE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence execute db.SetEntry") + err := d.SetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("REPLACE case - d.SetEntry() failure") + return err + } + } else { + log.Info("Entry doesn't exist hence create it.") + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("REPLACE case - d.CreateEntry() failure") + return err + } + } + } + } + } + } + return err +} + +func (app *CommonApp) cmnAppDelDbOpn(d *db.DB, opcode int) error { + var err error + var cmnAppTs, dbTblSpec *db.TableSpec + + /* needs enhancements from CVL to give table dependencies, and grouping of related tables only + if such a case where the sonic yang has unrelated tables */ + for tblidx, tblNm := range app.cmnAppOrdTbllist { + log.Info("In Yang to DB map returned from transformer looking for table = ", tblNm) + if tblVal, ok := app.cmnAppTableMap[tblNm]; ok { + cmnAppTs = &db.TableSpec{Name: tblNm} + log.Info("Found table entry in yang to DB map") + if len(tblVal) == 0 { + log.Info("DELETE case - No table instances/rows found hence delete entire table = ", tblNm) + for idx := len(app.cmnAppOrdTbllist)-1; idx >= tblidx+1; idx-- { + log.Info("Since parent table is to be deleted, first deleting child table = ", app.cmnAppOrdTbllist[idx]) + dbTblSpec = &db.TableSpec{Name: app.cmnAppOrdTbllist[idx]} + err = d.DeleteTable(dbTblSpec) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", app.cmnAppOrdTbllist[idx]) + return err + } + } + err = d.DeleteTable(cmnAppTs) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", tblNm) + return err + } + log.Info("DELETE case - Deleted entire table = ", tblNm) + log.Info("Done processing all tables.") + break + + } + + for tblKey, tblRw := range tblVal { + if len(tblRw.Field) == 0 { + log.Info("DELETE case - no fields/cols to delete hence delete the entire row.") + log.Info("First, delete child table instances that correspond to parent table instance to be deleted = ", tblKey) + for idx := len(app.cmnAppOrdTbllist)-1; idx >= tblidx+1; idx-- { + dbTblSpec = &db.TableSpec{Name: app.cmnAppOrdTbllist[idx]} + keyPattern := tblKey + "|*" + log.Info("Key pattern to be matched for deletion = ", keyPattern) + err = d.DeleteKeys(dbTblSpec, db.Key{Comp: []string{keyPattern}}) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", app.cmnAppOrdTbllist[idx]) + return err + } + log.Info("Deleted keys matching parent table key pattern for child table = ", app.cmnAppOrdTbllist[idx]) + + } + err = d.DeleteEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + if err != nil { + log.Warning("DELETE case - d.DeleteEntry() failure") + return err + } + log.Info("Finally deleted the parent table row with key = ", tblKey) + } else { + log.Info("DELETE case - fields/cols to delete hence delete only those fields.") + existingEntry, _ := d.GetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + if !existingEntry.IsPopulated() { + log.Info("Table Entry from which the fields are to be deleted does not exist") + return err + } + /* handle leaf-list merge if any leaf-list exists */ + resTblRw := checkAndProcessLeafList(existingEntry, tblRw, DELETE, d, tblNm, tblKey) + err := d.DeleteEntryFields(cmnAppTs, db.Key{Comp: []string{tblKey}}, resTblRw) + if err != nil { + log.Error("DELETE case - d.DeleteEntryFields() failure") + return err + } + } + + } + } + } /* end of ordered table list for loop */ + return err +} + +func (app *CommonApp) generateDbWatchKeys(d *db.DB, isDeleteOp bool) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + + return keys, err +} + +/*check if any field is leaf-list , if yes perform merge*/ +func checkAndProcessLeafList(existingEntry db.Value, tblRw db.Value, opcode int, d *db.DB, tblNm string, tblKey string) db.Value { + dbTblSpec := &db.TableSpec{Name: tblNm} + mergeTblRw := db.Value{Field: map[string]string{}} + for field, value := range tblRw.Field { + if strings.HasSuffix(field, "@") { + exstLst := existingEntry.GetList(field) + if len(exstLst) != 0 { + valueLst := strings.Split(value, ",") + for _, item := range valueLst { + if !contains(exstLst, item) { + if opcode == UPDATE { + exstLst = append(exstLst, item) + } + } else { + if opcode == DELETE { + exstLst = removeElement(exstLst, item) + } + + } + } + log.Infof("For field %v value after merge %v", field, exstLst) + if opcode == DELETE { + mergeTblRw.SetList(field, exstLst) + delete(tblRw.Field, field) + } + } + tblRw.SetList(field, exstLst) + } + } + /* delete specific item from leaf-list */ + if opcode == DELETE { + if len(mergeTblRw.Field) == 0 { + return tblRw + } + err := d.ModEntry(dbTblSpec, db.Key{Comp: []string{tblKey}}, mergeTblRw) + if err != nil { + log.Warning("DELETE case(merge leaf-list) - d.ModEntry() failure") + } + } + log.Infof("Returning Table Row %v", tblRw) + return tblRw +} + diff --git a/src/translib/db/db.go b/src/translib/db/db.go new file mode 100644 index 0000000000..451bc92383 --- /dev/null +++ b/src/translib/db/db.go @@ -0,0 +1,1375 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 db implements a wrapper over the go-redis/redis. + +There may be an attempt to mimic sonic-py-swsssdk to ease porting of +code written in python using that SDK to Go Language. + +Example: + + * Initialization: + + d, _ := db.NewDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }) + + * Close: + + d.DeleteDB() + + + * No-Transaction SetEntry + + tsa := db.TableSpec { Name: "ACL_TABLE" } + tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + + ca[0] = "MyACL1_ACL_IPV4" + akey := db.Key { Comp: ca} + avalue := db.Value {map[string]string {"ports":"eth0","type":"mirror" }} + + d.SetEntry(&tsa, akey, avalue) + + * GetEntry + + avalue, _ := d.GetEntry(&tsa, akey) + + * GetKeys + + keys, _ := d.GetKeys(&tsa); + + * No-Transaction DeleteEntry + + d.DeleteEntry(&tsa, akey) + + * GetTable + + ta, _ := d.GetTable(&tsa) + + * No-Transaction DeleteTable + + d.DeleteTable(&ts) + + * Transaction + + rkey := db.Key { Comp: []string { "MyACL2_ACL_IPV4", "RULE_1" }} + rvalue := db.Value { Field: map[string]string { + "priority" : "0", + "packet_action" : "eth1", + }, + } + + d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }, + []*db.TableSpec { &tsa, &tsr }) + + d.SetEntry( &tsa, akey, avalue) + d.SetEntry( &tsr, rkey, rvalue) + + e := d.CommitTx() + + * Transaction Abort + + d.StartTx([]db.WatchKeys {}, + []*db.TableSpec { &tsa, &tsr }) + d.DeleteEntry( &tsa, rkey) + d.AbortTx() + + +*/ +package db + +import ( + "fmt" + "strconv" + + // "reflect" + "errors" + "strings" + + "github.com/go-redis/redis" + "github.com/golang/glog" + "cvl" + "translib/tlerr" +) + +const ( + DefaultRedisUNIXSocket string = "/var/run/redis/redis.sock" + DefaultRedisLocalTCPEP string = "localhost:6379" + DefaultRedisRemoteTCPEP string = "127.0.0.1:6379" +) + +func init() { +} + +// DBNum type indicates the type of DB (Eg: ConfigDB, ApplDB, ...). +type DBNum int + +const ( + ApplDB DBNum = iota // 0 + AsicDB // 1 + CountersDB // 2 + LogLevelDB // 3 + ConfigDB // 4 + FlexCounterDB // 5 + StateDB // 6 + + // All DBs added above this line, please ---- + MaxDB // 7 The Number of DBs +) + +func(dbNo DBNum) String() string { + return fmt.Sprintf("%d", dbNo) +} + +// Options gives parameters for opening the redis client. +type Options struct { + DBNo DBNum + InitIndicator string + TableNameSeparator string + KeySeparator string + + DisableCVLCheck bool +} + +func (o Options) String() string { + return fmt.Sprintf( + "{ DBNo: %v, InitIndicator: %v, TableNameSeparator: %v, KeySeparator: %v , DisableCVLCheck: %v }", + o.DBNo, o.InitIndicator, o.TableNameSeparator, o.KeySeparator, + o.DisableCVLCheck) +} + +type _txState int + +const ( + txStateNone _txState = iota // Idle (No transaction) + txStateWatch // WATCH issued + txStateSet // At least one Set|Mod|Delete done. + txStateMultiExec // Between MULTI & EXEC +) + +func (s _txState) String() string { + var state string + switch s { + case txStateNone: + state = "txStateNone" + case txStateWatch: + state = "txStateWatch" + case txStateSet: + state = "txStateSet" + case txStateMultiExec: + state = "txStateMultiExec" + default: + state = "Unknown _txState" + } + return state +} + +const ( + InitialTxPipelineSize int = 100 +) + +// TableSpec gives the name of the table, and other per-table customizations. +// (Eg: { Name: ACL_TABLE" }). +type TableSpec struct { + Name string + // https://github.com/project-arlo/sonic-mgmt-framework/issues/29 + // CompCt tells how many components in the key. Only the last component + // can have TableSeparator as part of the key. Otherwise, we cannot + // tell where the key component begins. + CompCt int +} + +// Key gives the key components. +// (Eg: { Comp : [] string { "acl1", "rule1" } } ). +type Key struct { + Comp []string +} + +func (k Key) String() string { + return fmt.Sprintf("{ Comp: %v }", k.Comp) +} + +// Value gives the fields as a map. +// (Eg: { Field: map[string]string { "type" : "l3v6", "ports" : "eth0" } } ). +type Value struct { + Field map[string]string +} + +// Table gives the entire table a a map. +// (Eg: { ts: &TableSpec{ Name: "ACL_TABLE" }, +// entry: map[string]Value { +// "ACL_TABLE|acl1|rule1_1": Value { +// Field: map[string]string { +// "type" : "l3v6", "ports" : "Ethernet0", +// } +// }, +// "ACL_TABLE|acl1|rule1_2": Value { +// Field: map[string]string { +// "type" : "l3v6", "ports" : "eth0", +// } +// }, +// } +// }) + +type Table struct { + ts *TableSpec + entry map[string]Value + db *DB +} + +type _txOp int + +const ( + txOpNone _txOp = iota // No Op + txOpHMSet // key, value gives the field:value to be set in key + txOpHDel // key, value gives the fields to be deleted in key + txOpDel // key +) + +type _txCmd struct { + ts *TableSpec + op _txOp + key *Key + value *Value +} + +// DB is the main type. +type DB struct { + client *redis.Client + Opts *Options + + txState _txState + txCmds []_txCmd + cv *cvl.CVL + cvlEditConfigData [] cvl.CVLEditConfigData + +/* + sKeys []*SKey // Subscribe Key array + sHandler HFunc // Handler Function + sCh <-chan *redis.Message // non-Nil implies SubscribeDB +*/ + sPubSub *redis.PubSub // PubSub. non-Nil implies SubscribeDB + sCIP bool // Close in Progress +} + +func (d DB) String() string { + return fmt.Sprintf("{ client: %v, Opts: %v, txState: %v, tsCmds: %v }", + d.client, d.Opts, d.txState, d.txCmds) +} + +// NewDB is the factory method to create new DB's. +func NewDB(opt Options) (*DB, error) { + + var e error + + if glog.V(3) { + glog.Info("NewDB: Begin: opt: ", opt) + } + + d := DB{client: redis.NewClient(&redis.Options{ + Network: "tcp", + Addr: DefaultRedisLocalTCPEP, + //Addr: DefaultRedisRemoteTCPEP, + Password: "", /* TBD */ + // DB: int(4), /* CONFIG_DB DB No. */ + DB: int(opt.DBNo), + DialTimeout: 0, + // For Transactions, limit the pool + PoolSize: 1, + // Each DB gets it own (single) connection. + }), + Opts: &opt, + txState: txStateNone, + txCmds: make([]_txCmd, 0, InitialTxPipelineSize), + cvlEditConfigData: make([]cvl.CVLEditConfigData, 0, InitialTxPipelineSize), + } + + if d.client == nil { + glog.Error("NewDB: Could not create redis client") + e = tlerr.TranslibDBCannotOpen { } + goto NewDBExit + } + + if opt.DBNo != ConfigDB { + if glog.V(3) { + glog.Info("NewDB: ! ConfigDB. Skip init. check.") + } + goto NewDBSkipInitIndicatorCheck + } + + if len(d.Opts.InitIndicator) == 0 { + + glog.Info("NewDB: Init indication not requested") + + } else if init, _ := d.client.Get(d.Opts.InitIndicator).Int(); init != 1 { + + glog.Error("NewDB: Database not inited") + e = tlerr.TranslibDBNotInit { } + goto NewDBExit + } + +NewDBSkipInitIndicatorCheck: + +NewDBExit: + + if glog.V(3) { + glog.Info("NewDB: End: d: ", d, " e: ", e) + } + + return &d, e +} + +// DeleteDB is the gentle way to close the DB connection. +func (d *DB) DeleteDB() error { + + if glog.V(3) { + glog.Info("DeleteDB: Begin: d: ", d) + } + + if d.txState != txStateNone { + glog.Warning("DeleteDB: not txStateNone, txState: ", d.txState) + } + + return d.client.Close() +} + +func (d *DB) key2redis(ts *TableSpec, key Key) string { + + if glog.V(5) { + glog.Info("key2redis: Begin: ", + ts.Name+ + d.Opts.TableNameSeparator+ + strings.Join(key.Comp, d.Opts.KeySeparator)) + } + return ts.Name + + d.Opts.TableNameSeparator + + strings.Join(key.Comp, d.Opts.KeySeparator) +} + +func (d *DB) redis2key(ts *TableSpec, redisKey string) Key { + + splitTable := strings.SplitN(redisKey, d.Opts.TableNameSeparator, 2) + + if ts.CompCt > 0 { + return Key{strings.SplitN(splitTable[1],d.Opts.KeySeparator, ts.CompCt)} + } else { + return Key{strings.Split(splitTable[1], d.Opts.KeySeparator)} + } + +} + +func (d *DB) ts2redisUpdated(ts *TableSpec) string { + + if glog.V(5) { + glog.Info("ts2redisUpdated: Begin: ", ts.Name) + } + + var updated string + + if strings.Contains(ts.Name, "*") { + updated = string("CONFIG_DB_UPDATED") + } else { + updated = string("CONFIG_DB_UPDATED_") + ts.Name + } + + return updated +} + +// GetEntry retrieves an entry(row) from the table. +func (d *DB) GetEntry(ts *TableSpec, key Key) (Value, error) { + + if glog.V(3) { + glog.Info("GetEntry: Begin: ", "ts: ", ts, " key: ", key) + } + + var value Value + + /* + m := make(map[string]string) + m["f0.0"] = "v0.0" + m["f0.1"] = "v0.1" + m["f0.2"] = "v0.2" + v := Value{Field: m} + */ + + v, e := d.client.HGetAll(d.key2redis(ts, key)).Result() + + if len(v) != 0 { + value = Value{Field: v} + } else { + if glog.V(4) { + glog.Info("GetEntry: HGetAll(): empty map") + } + // e = errors.New("Entry does not exist") + e = tlerr.TranslibRedisClientEntryNotExist { Entry: d.key2redis(ts, key) } + } + + if glog.V(3) { + glog.Info("GetEntry: End: ", "value: ", value, " e: ", e) + } + + return value, e +} + +// GetKeys retrieves all entry/row keys. +func (d *DB) GetKeys(ts *TableSpec) ([]Key, error) { + + if glog.V(3) { + glog.Info("GetKeys: Begin: ", "ts: ", ts) + } + + /* + k := []Key{ + {[]string{"k0.0", "k0.1"}}, + {[]string{"k1.0", "k1.1"}}, + } + */ + redisKeys, e := d.client.Keys(d.key2redis(ts, + Key{Comp: []string{"*"}})).Result() + if glog.V(4) { + glog.Info("GetKeys: redisKeys: ", redisKeys, " e: ", e) + } + + keys := make([]Key, 0, len(redisKeys)) + for i := 0; i < len(redisKeys); i++ { + keys = append(keys, d.redis2key(ts, redisKeys[i])) + } + + if glog.V(3) { + glog.Info("GetKeys: End: ", "keys: ", keys, " e: ", e) + } + + return keys, e +} + +// DeleteKeys deletes all entry/row keys matching a pattern. +func (d *DB) DeleteKeys(ts *TableSpec, key Key) error { + if glog.V(3) { + glog.Info("DeleteKeys: Begin: ", "ts: ", ts, " key: ", key) + } + + // This can be done via a LUA script as well. For now do this. TBD + redisKeys, e := d.client.Keys(d.key2redis(ts, key)).Result() + if glog.V(4) { + glog.Info("DeleteKeys: redisKeys: ", redisKeys, " e: ", e) + } + + for i := 0; i < len(redisKeys); i++ { + if glog.V(4) { + glog.Info("DeleteKeys: Deleting redisKey: ", redisKeys[i]) + } + e = d.DeleteEntry(ts, d.redis2key(ts, redisKeys[i])) + if e != nil { + glog.Warning("DeleteKeys: Deleting: ts: ", ts, " key", + d.redis2key(ts, redisKeys[i]), " : ", e) + } + } + + if glog.V(3) { + glog.Info("DeleteKeys: End: e: ", e) + } + return e +} + + +func (d *DB) doCVL(ts * TableSpec, cvlOps []cvl.CVLOperation, key Key, vals []Value) error { + var e error = nil + + var cvlRetCode cvl.CVLRetCode + var cei cvl.CVLErrorInfo + + if d.Opts.DisableCVLCheck { + glog.Info("doCVL: CVL Disabled. Skipping CVL") + goto doCVLExit + } + + // No Transaction case. No CVL. + if d.txState == txStateNone { + glog.Info("doCVL: No Transactions. Skipping CVL") + goto doCVLExit + } + + if len(cvlOps) != len(vals) { + glog.Error("doCVL: Incorrect arguments len(cvlOps) != len(vals)") + e = errors.New("CVL Incorrect args") + return e + } + for i := 0; i < len(cvlOps); i++ { + + cvlEditConfigData := cvl.CVLEditConfigData { + VType: cvl.VALIDATE_ALL, + VOp: cvlOps[i], + Key: d.key2redis(ts, key), + } + + switch cvlOps[i] { + case cvl.OP_CREATE, cvl.OP_UPDATE: + cvlEditConfigData.Data = vals[i].Field + d.cvlEditConfigData = append(d.cvlEditConfigData, cvlEditConfigData) + + case cvl.OP_DELETE: + if len(vals[i].Field) == 0 { + cvlEditConfigData.Data = map[string]string {} + } else { + cvlEditConfigData.Data = vals[i].Field + } + d.cvlEditConfigData = append(d.cvlEditConfigData, cvlEditConfigData) + + default: + glog.Error("doCVL: Unknown, op: ", cvlOps[i]) + e = errors.New("Unknown Op: " + string(cvlOps[i])) + } + + } + + if e != nil { + goto doCVLExit + } + + if glog.V(3) { + glog.Info("doCVL: calling ValidateEditConfig: ", d.cvlEditConfigData) + } + + cei, cvlRetCode = d.cv.ValidateEditConfig(d.cvlEditConfigData) + + if cvl.CVL_SUCCESS != cvlRetCode { + glog.Error("doCVL: CVL Failure: " , cvlRetCode) + // e = errors.New("CVL Failure: " + string(cvlRetCode)) + e = tlerr.TranslibCVLFailure { Code: int(cvlRetCode), + CVLErrorInfo: cei } + glog.Error("doCVL: " , len(d.cvlEditConfigData), len(cvlOps)) + d.cvlEditConfigData = d.cvlEditConfigData[:len(d.cvlEditConfigData) - len(cvlOps)] + } else { + for i := 0; i < len(cvlOps); i++ { + d.cvlEditConfigData[len(d.cvlEditConfigData)-1-i].VType = cvl.VALIDATE_NONE; + } + } + +doCVLExit: + + if glog.V(3) { + glog.Info("doCVL: End: e: ", e) + } + + return e +} + +func (d *DB) doWrite(ts * TableSpec, op _txOp, key Key, val interface{}) error { + var e error = nil + var value Value + + switch d.txState { + case txStateNone: + if glog.V(2) { + glog.Info("doWrite: No Transaction.") + } + break + case txStateWatch: + if glog.V(2) { + glog.Info("doWrite: Change to txStateSet, txState: ", d.txState) + } + d.txState = txStateSet + break + case txStateSet: + if glog.V(5) { + glog.Info("doWrite: Remain in txStateSet, txState: ", d.txState) + } + case txStateMultiExec: + glog.Error("doWrite: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue {Set|Mod|Delete}Entry in txStateMultiExec") + default: + glog.Error("doWrite: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto doWriteExit + } + + // No Transaction case. No CVL. + if d.txState == txStateNone { + + switch op { + + case txOpHMSet: + value = Value { Field: make(map[string]string, + len(val.(Value).Field)) } + vintf := make(map[string]interface{}) + for k, v := range val.(Value).Field { + vintf[k] = v + } + e = d.client.HMSet(d.key2redis(ts, key), vintf).Err() + + if e!= nil { + glog.Error("doWrite: HMSet: ", key, " : ", value, " e: ", e) + } + + case txOpHDel: + fields := make([]string, 0, len(val.(Value).Field)) + for k, _ := range val.(Value).Field { + fields = append(fields, k) + } + + e = d.client.HDel(d.key2redis(ts, key), fields...).Err() + if e!= nil { + glog.Error("doWrite: HDel: ", key, " : ", fields, " e: ", e) + } + + case txOpDel: + e = d.client.Del(d.key2redis(ts, key)).Err() + if e!= nil { + glog.Error("doWrite: Del: ", key, " : ", e) + } + + default: + glog.Error("doWrite: Unknown, op: ", op) + e = errors.New("Unknown Op: " + string(op)) + } + + goto doWriteExit + } + + // Transaction case. + + glog.Info("doWrite: op: ", op, " ", key, " : ", value) + + switch op { + case txOpHMSet, txOpHDel: + value = val.(Value) + + case txOpDel: + + default: + glog.Error("doWrite: Unknown, op: ", op) + e = errors.New("Unknown Op: " + string(op)) + } + + if e != nil { + goto doWriteExit + } + + d.txCmds = append(d.txCmds, _txCmd{ + ts: ts, + op: op, + key: &key, + value: &value, + }) + +doWriteExit: + + if glog.V(3) { + glog.Info("doWrite: End: e: ", e) + } + + return e +} + +// setEntry either Creates, or Sets an entry(row) in the table. +func (d *DB) setEntry(ts *TableSpec, key Key, value Value, isCreate bool) error { + + var e error = nil + var valueComplement Value = Value { Field: make(map[string]string,len(value.Field))} + var valueCurrent Value + + if glog.V(3) { + glog.Info("setEntry: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value, " isCreate: ", isCreate) + } + + if len(value.Field) == 0 { + glog.Info("setEntry: Mapping to DeleteEntry()") + e = d.DeleteEntry(ts, key) + goto setEntryExit + } + + if isCreate == false { + // Prepare the HDel list + // Note: This is for compatibililty with PySWSSDK semantics. + // The CVL library will likely fail the SetEntry when + // the item exists. + valueCurrent, e = d.GetEntry(ts, key) + if e == nil { + for k, _ := range valueCurrent.Field { + _, present := value.Field[k] + if ! present { + valueComplement.Field[k] = string("") + } + } + } + } + + if isCreate == false && e == nil { + if glog.V(3) { + glog.Info("setEntry: DoCVL for UPDATE") + } + if len(valueComplement.Field) == 0 { + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE}, + key, []Value { value} ) + } else { + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE, cvl.OP_DELETE}, + key, []Value { value, valueComplement} ) + } + } else { + if glog.V(3) { + glog.Info("setEntry: DoCVL for CREATE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_CREATE}, key, []Value { value }) + } + + if e != nil { + goto setEntryExit + } + + e = d.doWrite(ts, txOpHMSet, key, value) + + if (e == nil) && (len(valueComplement.Field) != 0) { + if glog.V(3) { + glog.Info("setEntry: DoCVL for HDEL (post-POC)") + } + e = d.doWrite(ts, txOpHDel, key, valueComplement) + } + +setEntryExit: + return e +} + +// CreateEntry creates an entry(row) in the table. +func (d * DB) CreateEntry(ts * TableSpec, key Key, value Value) error { + + return d.setEntry(ts, key, value, true) +} + +// SetEntry sets an entry(row) in the table. +func (d *DB) SetEntry(ts *TableSpec, key Key, value Value) error { + return d.setEntry(ts, key, value, false) +} + +// DeleteEntry deletes an entry(row) in the table. +func (d *DB) DeleteEntry(ts *TableSpec, key Key) error { + + var e error = nil + if glog.V(3) { + glog.Info("DeleteEntry: Begin: ", "ts: ", ts, " key: ", key) + } + + if glog.V(3) { + glog.Info("DeleteEntry: DoCVL for DELETE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_DELETE}, key, []Value {Value{}}) + + if e == nil { + e = d.doWrite(ts, txOpDel, key, nil) + } + + return e; +} + +// ModEntry modifies an entry(row) in the table. +func (d *DB) ModEntry(ts *TableSpec, key Key, value Value) error { + + var e error = nil + + if glog.V(3) { + glog.Info("ModEntry: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value) + } + + if len(value.Field) == 0 { + glog.Info("ModEntry: Mapping to DeleteEntry()") + e = d.DeleteEntry(ts, key) + goto ModEntryExit + } + + if glog.V(3) { + glog.Info("ModEntry: DoCVL for UPDATE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE}, key, []Value {value}) + + if e == nil { + e = d.doWrite(ts, txOpHMSet, key, value) + } + +ModEntryExit: + + return e +} + +// DeleteEntryFields deletes some fields/columns in an entry(row) in the table. +func (d *DB) DeleteEntryFields(ts *TableSpec, key Key, value Value) error { + + if glog.V(3) { + glog.Info("DeleteEntryFields: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value) + } + + if glog.V(3) { + glog.Info("DeleteEntryFields: DoCVL for HDEL (post-POC)") + } + + if glog.V(3) { + glog.Info("DeleteEntryFields: DoCVL for HDEL") + } + + e := d.doCVL(ts, []cvl.CVLOperation {cvl.OP_DELETE}, key, []Value{value}) + + if e == nil { + d.doWrite(ts, txOpHDel, key, value) + } + + return e +} + + +// GetTable gets the entire table. +func (d *DB) GetTable(ts *TableSpec) (Table, error) { + if glog.V(3) { + glog.Info("GetTable: Begin: ts: ", ts) + } + + /* + table := Table{ + ts: ts, + entry: map[string]Value{ + "table1|k0.0|k0.1": Value{ + map[string]string{ + "f0.0": "v0.0", + "f0.1": "v0.1", + "f0.2": "v0.2", + }, + }, + "table1|k1.0|k1.1": Value{ + map[string]string{ + "f1.0": "v1.0", + "f1.1": "v1.1", + "f1.2": "v1.2", + }, + }, + }, + db: d, + } + */ + + // Create Table + table := Table{ + ts: ts, + entry: make(map[string]Value), + db: d, + } + + // This can be done via a LUA script as well. For now do this. TBD + // Read Keys + keys, e := d.GetKeys(ts) + if e != nil { + glog.Error("GetTable: GetKeys: " + e.Error()) + goto GetTableExit + } + + // For each key in Keys + // Add Value into table.entry[key)] + for i := 0; i < len(keys); i++ { + value, e := d.GetEntry(ts, keys[i]) + if e != nil { + glog.Warning("GetTable: GetKeys: " + e.Error()) + continue + } + table.entry[d.key2redis(ts, keys[i])] = value + } + +GetTableExit: + + if glog.V(3) { + glog.Info("GetTable: End: table: ", table) + } + return table, e +} + +// DeleteTable deletes the entire table. +func (d *DB) DeleteTable(ts *TableSpec) error { + if glog.V(3) { + glog.Info("DeleteTable: Begin: ts: ", ts) + } + + // This can be done via a LUA script as well. For now do this. TBD + // Read Keys + keys, e := d.GetKeys(ts) + if e != nil { + glog.Error("DeleteTable: GetKeys: " + e.Error()) + goto DeleteTableExit + } + + // For each key in Keys + // Delete the entry + for i := 0; i < len(keys); i++ { + e := d.DeleteEntry(ts, keys[i]) + if e != nil { + glog.Warning("DeleteTable: DeleteEntry: " + e.Error()) + continue + } + } +DeleteTableExit: + if glog.V(3) { + glog.Info("DeleteTable: End: ") + } + return e +} + +// GetKeys method retrieves all entry/row keys from a previously read table. +func (t *Table) GetKeys() ([]Key, error) { + if glog.V(3) { + glog.Info("Table.GetKeys: Begin: t: ", t) + } + keys := make([]Key, 0, len(t.entry)) + for k, _ := range t.entry { + keys = append(keys, t.db.redis2key(t.ts, k)) + } + + if glog.V(3) { + glog.Info("Table.GetKeys: End: keys: ", keys) + } + return keys, nil +} + +// GetEntry method retrieves an entry/row from a previously read table. +func (t *Table) GetEntry(key Key) (Value, error) { + /* + return Value{map[string]string{ + "f0.0": "v0.0", + "f0.1": "v0.1", + "f0.2": "v0.2", + }, + }, nil + */ + if glog.V(3) { + glog.Info("Table.GetEntry: Begin: t: ", t, " key: ", key) + } + v := t.entry[t.db.key2redis(t.ts, key)] + if glog.V(3) { + glog.Info("Table.GetEntry: End: entry: ", v) + } + return v, nil +} + +//===== Functions for db.Key ===== + +// Len returns number of components in the Key +func (k *Key) Len() int { + return len(k.Comp) +} + +// Get returns the key component at given index +func (k *Key) Get(index int) string { + return k.Comp[index] +} + +//===== Functions for db.Value ===== + +func (v *Value) IsPopulated() bool { + return len(v.Field) > 0 +} + +// Has function checks if a field exists. +func (v *Value) Has(name string) bool { + _, flag := v.Field[name] + return flag +} + +// Get returns the value of a field. Returns empty string if the field +// does not exists. Use Has() function to check existance of field. +func (v *Value) Get(name string) string { + return v.Field[name] +} + +// Set function sets a string value for a field. +func (v *Value) Set(name, value string) { + v.Field[name] = value +} + +// GetInt returns value of a field as int. Returns 0 if the field does +// not exists. Returns an error if the field value is not a number. +func (v *Value) GetInt(name string) (int, error) { + data, ok := v.Field[name] + if ok { + return strconv.Atoi(data) + } + return 0, nil +} + +// SetInt sets an integer value for a field. +func (v *Value) SetInt(name string, value int) { + v.Set(name, strconv.Itoa(value)) +} + +// GetList returns the value of a an array field. A "@" suffix is +// automatically appended to the field name if not present (as per +// swsssdk convention). Field value is split by comma and resulting +// slice is returned. Empty slice is returned if field not exists. +func (v *Value) GetList(name string) []string { + var data string + if strings.HasSuffix(name, "@") { + data = v.Get(name) + } else { + data = v.Get(name + "@") + } + + if len(data) == 0 { + return []string{} + } + + return strings.Split(data, ",") +} + +// SetList function sets an list value to a field. Field name and +// value are formatted as per swsssdk conventions: +// - A "@" suffix is appended to key name +// - Field value is the comma separated string of list items +func (v *Value) SetList(name string, items []string) { + if !strings.HasSuffix(name, "@") { + name += "@" + } + + if len(items) != 0 { + data := strings.Join(items, ",") + v.Set(name, data) + } else { + v.Remove(name) + } +} + +// Remove function removes a field from this Value. +func (v *Value) Remove(name string) { + delete(v.Field, name) +} + +////////////////////////////////////////////////////////////////////////// +// The Transaction API for translib infra +////////////////////////////////////////////////////////////////////////// + +// WatchKeys is array of (TableSpec, Key) tuples to be watched in a Transaction. +type WatchKeys struct { + Ts *TableSpec + Key *Key +} + +func (w WatchKeys) String() string { + return fmt.Sprintf("{ Ts: %v, Key: %v }", w.Ts, w.Key) +} + +// Convenience function to make TableSpecs from strings. +// This only works on Tables having key components without TableSeparator +// as part of the key. +func Tables2TableSpecs(tables []string) []* TableSpec { + var tss []*TableSpec + + tss = make([]*TableSpec, 0, len(tables)) + + for i := 0; i < len(tables); i++ { + tss = append(tss, &(TableSpec{ Name: tables[i]})) + } + + return tss +} + +// StartTx method is used by infra to start a check-and-set Transaction. +func (d *DB) StartTx(w []WatchKeys, tss []*TableSpec) error { + + if glog.V(3) { + glog.Info("StartTx: Begin: w: ", w, " tss: ", tss) + } + + var e error = nil + var args []interface{} + var ret cvl.CVLRetCode + + //Start CVL session + if d.cv, ret = cvl.ValidationSessOpen(); ret != cvl.CVL_SUCCESS { + e = errors.New("StartTx: Unable to create CVL session") + goto StartTxExit + } + + // Validate State + if d.txState != txStateNone { + glog.Error("StartTx: Incorrect State, txState: ", d.txState) + e = errors.New("Transaction already in progress") + goto StartTxExit + } + + // For each watchkey + // If a pattern, Get the keys, appending results to Cmd args. + // Else append keys to the Cmd args + // Note: (LUA scripts do not support WATCH) + + args = make([]interface{}, 0, len(w) + len(tss) + 1) + args = append(args, "WATCH") + for i := 0; i < len(w); i++ { + + redisKey := d.key2redis(w[i].Ts, *(w[i].Key)) + + if !strings.Contains(redisKey, "*") { + args = append(args, redisKey) + continue + } + + redisKeys, e := d.client.Keys(redisKey).Result() + if e != nil { + glog.Warning("StartTx: Keys: " + e.Error()) + continue + } + for j := 0; j < len(redisKeys); j++ { + args = append(args, d.redis2key(w[i].Ts, redisKeys[j])) + } + } + + // for each TS, append to args the CONFIG_DB_UPDATED_ key + + for i := 0; i < len(tss); i++ { + args = append( args, d.ts2redisUpdated(tss[i])) + } + + if len(args) == 1 { + glog.Warning("StartTx: Empty WatchKeys. Skipping WATCH") + goto StartTxSkipWatch + } + + // Issue the WATCH + _, e = d.client.Do(args...).Result() + + if e != nil { + glog.Warning("StartTx: Do: WATCH ", args, " e: ", e.Error()) + } + +StartTxSkipWatch: + + // Switch State + d.txState = txStateWatch + +StartTxExit: + + if glog.V(3) { + glog.Info("StartTx: End: e: ", e) + } + return e +} + +// CommitTx method is used by infra to commit a check-and-set Transaction. +func (d *DB) CommitTx() error { + if glog.V(3) { + glog.Info("CommitTx: Begin:") + } + + var e error = nil + var tsmap map[TableSpec]bool = + make(map[TableSpec]bool, len(d.txCmds)) // UpperBound + + // Validate State + switch d.txState { + case txStateNone: + glog.Error("CommitTx: No WATCH done, txState: ", d.txState) + e = errors.New("StartTx() not done. No Transaction active.") + case txStateWatch: + if glog.V(1) { + glog.Info("CommitTx: No SET|DEL done, txState: ", d.txState) + } + case txStateSet: + break + case txStateMultiExec: + glog.Error("CommitTx: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue MULTI in txStateMultiExec") + default: + glog.Error("CommitTx: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto CommitTxExit + } + + // Issue MULTI + _, e = d.client.Do("MULTI").Result() + + if e != nil { + glog.Warning("CommitTx: Do: MULTI e: ", e.Error()) + } + + // For each cmd in txCmds + // Invoke it + for i := 0; i < len(d.txCmds); i++ { + + var args []interface{} + + redisKey := d.key2redis(d.txCmds[i].ts, *(d.txCmds[i].key)) + + // Add TS to the map of watchTables + tsmap[*(d.txCmds[i].ts)] = true; + + switch d.txCmds[i].op { + + case txOpHMSet: + + args = make([]interface{}, 0, len(d.txCmds[i].value.Field)*2+2) + args = append(args, "HMSET", redisKey) + + for k, v := range d.txCmds[i].value.Field { + args = append(args, k, v) + } + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + case txOpHDel: + + args = make([]interface{}, 0, len(d.txCmds[i].value.Field)+2) + args = append(args, "HDEL", redisKey) + + for k, _ := range d.txCmds[i].value.Field { + args = append(args, k) + } + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + case txOpDel: + + args = make([]interface{}, 0, 2) + args = append(args, "DEL", redisKey) + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + default: + glog.Error("CommitTx: Unknown, op: ", d.txCmds[i].op) + e = errors.New("Unknown Op: " + string(d.txCmds[i].op)) + } + + if e != nil { + glog.Warning("CommitTx: Do: ", args, " e: ", e.Error()) + } + } + + // Flag the Tables as updated. + for ts, _ := range tsmap { + _, e = d.client.Do("SET", d.ts2redisUpdated(&ts), "1").Result() + if e != nil { + glog.Warning("CommitTx: Do: SET ", + d.ts2redisUpdated(&ts), " 1: e: ", + e.Error()) + } + } + _, e = d.client.Do("SET", d.ts2redisUpdated(& TableSpec{Name: "*"}), + "1").Result() + if e != nil { + glog.Warning("CommitTx: Do: SET ", + "CONFIG_DB_UPDATED", " 1: e: ", e.Error()) + } + + // Issue EXEC + _, e = d.client.Do("EXEC").Result() + + if e != nil { + glog.Warning("CommitTx: Do: EXEC e: ", e.Error()) + e = tlerr.TranslibTransactionFail { } + } + + // Switch State, Clear Command list + d.txState = txStateNone + d.txCmds = d.txCmds[:0] + d.cvlEditConfigData = d.cvlEditConfigData[:0] + + //Close CVL session + if ret := cvl.ValidationSessClose(d.cv); ret != cvl.CVL_SUCCESS { + glog.Error("CommitTx: End: Error in closing CVL session") + } + d.cv = nil + +CommitTxExit: + if glog.V(3) { + glog.Info("CommitTx: End: e: ", e) + } + return e +} + +// AbortTx method is used by infra to abort a check-and-set Transaction. +func (d *DB) AbortTx() error { + if glog.V(3) { + glog.Info("AbortTx: Begin:") + } + + var e error = nil + + // Validate State + switch d.txState { + case txStateNone: + glog.Error("AbortTx: No WATCH done, txState: ", d.txState) + e = errors.New("StartTx() not done. No Transaction active.") + case txStateWatch: + if glog.V(1) { + glog.Info("AbortTx: No SET|DEL done, txState: ", d.txState) + } + case txStateSet: + break + case txStateMultiExec: + glog.Error("AbortTx: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue UNWATCH in txStateMultiExec") + default: + glog.Error("AbortTx: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto AbortTxExit + } + + // Issue UNWATCH + _, e = d.client.Do("UNWATCH").Result() + + if e != nil { + glog.Warning("AbortTx: Do: UNWATCH e: ", e.Error()) + } + + // Switch State, Clear Command list + d.txState = txStateNone + d.txCmds = d.txCmds[:0] + d.cvlEditConfigData = d.cvlEditConfigData[:0] + + //Close CVL session + if ret := cvl.ValidationSessClose(d.cv); ret != cvl.CVL_SUCCESS { + glog.Error("AbortTx: End: Error in closing CVL session") + } + d.cv = nil + +AbortTxExit: + if glog.V(3) { + glog.Info("AbortTx: End: e: ", e) + } + return e +} diff --git a/src/translib/db/db_test.go b/src/translib/db/db_test.go new file mode 100644 index 0000000000..edf05b281c --- /dev/null +++ b/src/translib/db/db_test.go @@ -0,0 +1,610 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 db + + +import ( + // "fmt" + // "errors" + // "flag" + // "github.com/golang/glog" + "time" + // "translib/tlerr" + // "os/exec" + "os" + "testing" + "strconv" + "reflect" +) + +func TestMain(m * testing.M) { + + exitCode := 0 + +/* Apparently, on an actual switch the swss container will have + * a redis-server running, which will be in a different container than + * mgmt, thus this pkill stuff to find out it is running will not work. + * + + redisServerAttemptedStart := false + +TestMainRedo: + o, e := exec.Command("/usr/bin/pkill", "-HUP", "redis-server").Output() + + if e == nil { + + } else if redisServerAttemptedStart { + + exitCode = 1 + + } else { + + fmt.Printf("TestMain: No redis server: pkill: %v\n", o) + fmt.Println("TestMain: Starting redis-server") + e = exec.Command("/tools/bin/redis-server").Start() + time.Sleep(3 * time.Second) + redisServerAttemptedStart = true + goto TestMainRedo + } +*/ + + if exitCode == 0 { + exitCode = m.Run() + } + + + os.Exit(exitCode) + +} + +/* + +1. Create, and close a DB connection. (NewDB(), DeleteDB()) + +*/ + +func TestNewDB(t * testing.T) { + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + } else if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* + +2. Get an entry (GetEntry()) +3. Set an entry without Transaction (SetEntry()) +4. Delete an entry without Transaction (DeleteEntry()) + +20. NT: GetEntry() EntryNotExist. + +*/ + +func TestNoTransaction(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteEntry() fails e = %v", e) + return + } + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* + +5. Get a Table (GetTable()) + +9. Get multiple keys (GetKeys()) +10. Delete multiple keys (DeleteKeys()) +11. Delete Table (DeleteTable()) + +*/ + +func TestTable(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + ca2 := make([]string, 1, 1) + ca2[0] = "MyACL2_ACL_IPVNOTEXIST" + akey2 := Key { Comp: ca2} + + // Add the Entries for Get|DeleteKeys + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey2, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + keys, e := d.GetKeys(&ts) + + if (e != nil) || (len(keys) != 2) { + t.Errorf("GetKeys() fails e = %v", e) + return + } + + e = d.DeleteKeys(&ts, Key {Comp: []string {"MyACL*_ACL_IPVNOTEXIST"}}) + + if e != nil { + t.Errorf("DeleteKeys() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteKeys() fails e = %v", e) + return + } + + + + // Add the Entries again for Table + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey2, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + tab, e := d.GetTable(&ts) + + if e != nil { + t.Errorf("GetTable() fails e = %v", e) + return + } + + v, e = tab.GetEntry(akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("Table.GetEntry() fails e = %v", e) + return + } + + e = d.DeleteTable(&ts) + + if e != nil { + t.Errorf("DeleteTable() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteTable() fails e = %v", e) + return + } + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* Tests for + +6. Set an entry with Transaction (StartTx(), SetEntry(), CommitTx()) +7. Delete an entry with Transaction (StartTx(), DeleteEntry(), CommitTx()) +8. Abort Transaction. (StartTx(), DeleteEntry(), AbortTx()) + +12. Set an entry with Transaction using WatchKeys Check-And-Set(CAS) +13. Set an entry with Transaction using Table CAS +14. Set an entry with Transaction using WatchKeys, and Table CAS + +15. Set an entry with Transaction with empty WatchKeys, and Table CAS +16. Negative Test(NT): Fail a Transaction using WatchKeys CAS +17. NT: Fail a Transaction using Table CAS +18. NT: Abort an Transaction with empty WatchKeys/Table CAS + +Cannot Automate 19 for now +19. NT: Check V logs, Error logs + + */ + +func TestTransaction(t * testing.T) { + for transRun := TransRunBasic ; transRun < TransRunEnd ; transRun++ { + testTransaction(t, transRun) + } +} + +type TransRun int + +const ( + TransRunBasic TransRun = iota // 0 + TransRunWatchKeys // 1 + TransRunTable // 2 + TransRunWatchKeysAndTable // 3 + TransRunEmptyWatchKeysAndTable // 4 + TransRunFailWatchKeys // 5 + TransRunFailTable // 6 + + // Nothing after this. + TransRunEnd +) + +func testTransaction(t * testing.T, transRun TransRun) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v, transRun = %v", e, transRun) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + var watchKeys []WatchKeys + var table []*TableSpec + + switch transRun { + case TransRunBasic, TransRunWatchKeysAndTable: + watchKeys = []WatchKeys{{Ts: &ts, Key: &akey}} + table = []*TableSpec { &ts } + case TransRunWatchKeys, TransRunFailWatchKeys: + watchKeys = []WatchKeys{{Ts: &ts, Key: &akey}} + table = []*TableSpec { } + case TransRunTable, TransRunFailTable: + watchKeys = []WatchKeys{} + table = []*TableSpec { &ts } + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.CommitTx() + + if e != nil { + t.Errorf("CommitTx() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() after Tx fails e = %v", e) + return + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + e = d.AbortTx() + + if e != nil { + t.Errorf("AbortTx() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() after Abort Tx fails e = %v", e) + return + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + switch transRun { + case TransRunFailWatchKeys, TransRunFailTable: + d2,_ := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + d2.StartTx(watchKeys, table); + d2.DeleteEntry(&ts, akey) + d2.CommitTx(); + d2.DeleteDB(); + default: + } + + e = d.CommitTx() + + switch transRun { + case TransRunFailWatchKeys, TransRunFailTable: + if e == nil { + t.Errorf("NT CommitTx() tr: %v fails e = %v", + transRun, e) + return + } + default: + if e != nil { + t.Errorf("CommitTx() fails e = %v", e) + return + } + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after Tx DeleteEntry() fails e = %v", e) + return + } + + d.DeleteMapAll(&ts) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +func TestMap(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TESTMAP_" + strconv.FormatInt(int64(pid), 10) } + + d.SetMap(&ts, "k1", "v1"); + d.SetMap(&ts, "k2", "v2"); + + if v, e := d.GetMap(&ts, "k1"); v != "v1" { + t.Errorf("GetMap() fails e = %v", e) + return + } + + if v, e := d.GetMapAll(&ts) ; + (e != nil) || + (!reflect.DeepEqual(v, + Value{ Field: map[string]string { + "k1" : "v1", "k2" : "v2" }})) { + t.Errorf("GetMapAll() fails e = %v", e) + return + } + + d.DeleteMapAll(&ts) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + +func TestSubscribe(t * testing.T) { + + var pid int = os.Getpid() + + var hSetCalled, hDelCalled, delCalled bool + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if (d == nil) || (e != nil) { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + var skeys [] *SKey = make([]*SKey, 1) + skeys[0] = & (SKey { Ts: &ts, Key: &akey, + SEMap: map[SEvent]bool { + SEventHSet: true, + SEventHDel: true, + SEventDel: true, + }}) + + s,e := SubscribeDB(Options { + DBNo : ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }, skeys, func (s *DB, + skey *SKey, key *Key, + event SEvent) error { + switch event { + case SEventHSet: hSetCalled = true + case SEventHDel: hDelCalled = true + case SEventDel: delCalled = true + default: + } + return nil }) + + if (s == nil) || (e != nil) { + t.Errorf("Subscribe() returns error e: %v", e) + return + } + + d.SetEntry(&ts, akey, avalue) + d.DeleteEntryFields(&ts, akey, avalue) + + time.Sleep(5 * time.Second) + + if !hSetCalled || !hDelCalled || !delCalled { + t.Errorf("Subscribe() callbacks missed: %v %v %v", hSetCalled, + hDelCalled, delCalled) + return + } + + s.UnsubscribeDB() + + time.Sleep(2 * time.Second) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + diff --git a/src/translib/db/map.go b/src/translib/db/map.go new file mode 100644 index 0000000000..9010cfb15f --- /dev/null +++ b/src/translib/db/map.go @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 db implements a wrapper over the go-redis/redis. +*/ +package db + +import ( + // "fmt" + // "strconv" + + // "reflect" + // "errors" + // "strings" + + // "github.com/go-redis/redis" + "github.com/golang/glog" + // "cvl" + "translib/tlerr" +) + +func init() { +} + + + + +func (d *DB) GetMap(ts *TableSpec, mapKey string) (string, error) { + + if glog.V(3) { + glog.Info("GetMap: Begin: ", "ts: ", ts, " mapKey: ", mapKey) + } + + v, e := d.client.HGet(ts.Name, mapKey).Result() + + if glog.V(3) { + glog.Info("GetMap: End: ", "v: ", v, " e: ", e) + } + + return v, e +} + +func (d *DB) GetMapAll(ts *TableSpec) (Value, error) { + + if glog.V(3) { + glog.Info("GetMapAll: Begin: ", "ts: ", ts) + } + + var value Value + + v, e := d.client.HGetAll(ts.Name).Result() + + if len(v) != 0 { + value = Value{Field: v} + } else { + if glog.V(1) { + glog.Info("GetMapAll: HGetAll(): empty map") + } + e = tlerr.TranslibRedisClientEntryNotExist { Entry: ts.Name } + } + + if glog.V(3) { + glog.Info("GetMapAll: End: ", "value: ", value, " e: ", e) + } + + return value, e +} + +// For Testing only. Do Not Use!!! ============================== +// There is no transaction support on these. +func (d *DB) SetMap(ts *TableSpec, mapKey string, mapValue string) error { + + if glog.V(3) { + glog.Info("SetMap: Begin: ", "ts: ", ts, " ", mapKey, + ":", mapValue) + } + + b, e := d.client.HSet(ts.Name, mapKey, mapValue).Result() + + if glog.V(3) { + glog.Info("GetMap: End: ", "b: ", b, " e: ", e) + } + + return e +} +// For Testing only. Do Not Use!!! ============================== + +// For Testing only. Do Not Use!!! +// There is no transaction support on these. +func (d *DB) DeleteMapAll(ts *TableSpec) error { + + if glog.V(3) { + glog.Info("DeleteMapAll: Begin: ", "ts: ", ts) + } + + e := d.client.Del(ts.Name).Err() + + if glog.V(3) { + glog.Info("DeleteMapAll: End: ", " e: ", e) + } + + return e +} +// For Testing only. Do Not Use!!! ============================== + + diff --git a/src/translib/db/subscribe.go b/src/translib/db/subscribe.go new file mode 100644 index 0000000000..65ea09811e --- /dev/null +++ b/src/translib/db/subscribe.go @@ -0,0 +1,275 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 db implements a wrapper over the go-redis/redis. +*/ +package db + +import ( + // "fmt" + // "strconv" + + // "reflect" + "errors" + "strings" + + // "github.com/go-redis/redis" + "github.com/golang/glog" + // "cvl" + "translib/tlerr" +) + +// SKey is (TableSpec, Key, []SEvent) 3-tuples to be watched in a Transaction. +type SKey struct { + Ts *TableSpec + Key *Key + SEMap map[SEvent]bool // nil map indicates subscribe to all +} + +type SEvent int + +const ( + SEventNone SEvent = iota // No Op + SEventHSet // HSET, HMSET, and its variants + SEventHDel // HDEL, also SEventDel generated, if HASH is becomes empty + SEventDel // DEL, & also if key gets deleted (empty HASH, expire,..) + SEventOther // Some other command not covered above. + + // The below two are always sent regardless of SEMap. + SEventClose // Close requested due to Unsubscribe() called. + SEventErr // Error condition. Call Unsubscribe, after return. +) + +var redisPayload2sEventMap map[string]SEvent = map[string]SEvent { + "" : SEventNone, + "hset" : SEventHSet, + "hdel" : SEventHDel, + "del" : SEventDel, +} + + +func init() { + // Optimization: Start the goroutine that is scanning the SubscribeDB + // channels. Instead of one goroutine per Subscribe. +} + + +// HFunc gives the name of the table, and other per-table customizations. +type HFunc func( *DB, *SKey, *Key, SEvent) (error) + + +// SubscribeDB is the factory method to create a subscription to the DB. +// The returned instance can only be used for Subscription. +func SubscribeDB(opt Options, skeys []*SKey, handler HFunc) (*DB, error) { + + if glog.V(3) { + glog.Info("SubscribeDB: Begin: opt: ", opt, + " skeys: ", skeys, " handler: ", handler) + } + + patterns := make([]string, 0, len(skeys)) + patMap := make(map[string]([]int), len(skeys)) + var s string + + // NewDB + d , e := NewDB(opt) + + if d.client == nil { + goto SubscribeDBExit + } + + // Make sure that the DB is configured for key space notifications + // Optimize with LUA scripts to atomically add "Kgshxe". + s, e = d.client.ConfigSet("notify-keyspace-events", "AKE").Result() + + if e != nil { + glog.Error("SubscribeDB: ConfigSet(): e: ", e, " s: ", s) + goto SubscribeDBExit + } + + for i := 0 ; i < len(skeys); i++ { + pattern := d.key2redisChannel(skeys[i].Ts, *(skeys[i].Key)) + if _,present := patMap[pattern] ; ! present { + patMap[pattern] = make([]int, 0, 5) + patterns = append(patterns, pattern) + } + patMap[pattern] = append(patMap[pattern], i) + + } + + glog.Info("SubscribeDB: patterns: ", patterns) + + d.sPubSub = d.client.PSubscribe(patterns[:]...) + + if d.sPubSub == nil { + glog.Error("SubscribeDB: PSubscribe() nil: pats: ", patterns) + e = tlerr.TranslibDBSubscribeFail { } + goto SubscribeDBExit + } + + // Wait for confirmation, of channel creation + _, e = d.sPubSub.Receive() + + if e != nil { + glog.Error("SubscribeDB: Receive() fails: e: ", e) + e = tlerr.TranslibDBSubscribeFail { } + goto SubscribeDBExit + } + + + // Start a goroutine to read messages and call handler. + go func() { + for msg := range d.sPubSub.Channel() { + if glog.V(4) { + glog.Info("SubscribeDB: msg: ", msg) + } + + // Should this be a goroutine, in case each notification CB + // takes a long time to run ? + for _, skeyIndex := range patMap[msg.Pattern] { + skey := skeys[skeyIndex] + key := d.redisChannel2key(skey.Ts, msg.Channel) + sevent := d.redisPayload2sEvent(msg.Payload) + + if len(skey.SEMap) == 0 || skey.SEMap[sevent] { + + if glog.V(2) { + glog.Info("SubscribeDB: handler( ", + &d, ", ", skey, ", ", key, ", ", sevent, " )") + } + + handler(d, skey, &key, sevent) + } + } + } + + // Send the Close|Err notification. + var sEvent = SEventClose + if d.sCIP == false { + sEvent = SEventErr + } + glog.Info("SubscribeDB: SEventClose|Err: ", sEvent) + handler(d, & SKey{}, & Key {}, sEvent) + } () + + +SubscribeDBExit: + + if e != nil { + if d.sPubSub != nil { + d.sPubSub.Close() + } + + if d.client != nil { + d.DeleteDB() + d.client = nil + } + d = nil + } + + if glog.V(3) { + glog.Info("SubscribeDB: End: d: ", d, " e: ", e) + } + + return d, e +} + +// UnsubscribeDB is used to close a DB subscription +func (d * DB) UnsubscribeDB() error { + + var e error = nil + + if glog.V(3) { + glog.Info("UnsubscribeDB: d:", d) + } + + if d.sCIP { + glog.Error("UnsubscribeDB: Close in Progress") + e = errors.New("UnsubscribeDB: Close in Progress") + goto UnsubscribeDBExit + } + + // Mark close in progress. + d.sCIP = true; + + // Do the close, ch gets closed too. + d.sPubSub.Close() + + // Wait for the goroutine to complete ? TBD + // Should not this happen because of the range statement on ch? + + // Close the DB + d.DeleteDB() + +UnsubscribeDBExit: + + if glog.V(3) { + glog.Info("UnsubscribeDB: End: d: ", d, " e: ", e) + } + + return e +} + + +func (d *DB) key2redisChannel(ts *TableSpec, key Key) string { + + if glog.V(5) { + glog.Info("key2redisChannel: ", *ts, " key: " + key.String()) + } + + return "__keyspace@" + (d.Opts.DBNo).String() + "__:" + d.key2redis(ts, key) +} + +func (d *DB) redisChannel2key(ts *TableSpec, redisChannel string) Key { + + if glog.V(5) { + glog.Info("redisChannel2key: ", *ts, " redisChannel: " + redisChannel) + } + + splitRedisKey := strings.SplitN(redisChannel, ":", 2) + + if len(splitRedisKey) > 1 { + return d.redis2key(ts, splitRedisKey[1]) + } + + glog.Warning("redisChannel2key: Missing key: redisChannel: ", redisChannel) + + return Key{} +} + +func (d *DB) redisPayload2sEvent(redisPayload string) SEvent { + + if glog.V(5) { + glog.Info("redisPayload2sEvent: ", redisPayload) + } + + sEvent := redisPayload2sEventMap[redisPayload] + + if sEvent == 0 { + sEvent = SEventOther + } + + if glog.V(3) { + glog.Info("redisPayload2sEvent: ", sEvent) + } + + return sEvent +} + diff --git a/src/translib/db/test/arloIssue29.go b/src/translib/db/test/arloIssue29.go new file mode 100644 index 0000000000..d42613d664 --- /dev/null +++ b/src/translib/db/test/arloIssue29.go @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +UT for +https://github.com/project-arlo/sonic-mgmt-framework/issues/29 +*/ + +package main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "translib/db" + // "time" + // "translib/tlerr" +) + +func main() { + var avalue db.Value + var akey db.Key + var e error + + defer glog.Flush() + + flag.Parse() + + fmt.Println("https://github.com/project-arlo/sonic-mgmt-framework/issues/29") + fmt.Println("Creating the DB ==============") + d,_ := db.NewDB(db.Options { + DBNo : db.ApplDB, + InitIndicator : "", + TableNameSeparator: ":", + KeySeparator : ":", + }) + + tsi := db.TableSpec { Name: "INTF_TABLE", CompCt: 2 } + + ca := make([]string, 2, 2) + + fmt.Println("Testing SetEntry ==============") + ca[0] = "Ethernet20" + ca[1] = "a::b/64" + akey = db.Key { Comp: ca} + avalue = db.Value { Field: map[string]string { + "scope" : "global", + "family" : "IPv4", + } } + + e = d.SetEntry(&tsi, akey, avalue) + if e != nil { + fmt.Println("SetEntry() ERROR: e: ", e) + return + } + + fmt.Println("Testing GetEntry ==============") + + avalue, e = d.GetEntry(&tsi, akey) + if e != nil { + fmt.Println("GetEntry() ERROR: e: ", e) + return + } + + fmt.Println("ts: ", tsi, " ", akey, ": ", avalue) + + d.DeleteDB() +} diff --git a/src/translib/db/test/testdb.go b/src/translib/db/test/testdb.go new file mode 100644 index 0000000000..c28b368e3a --- /dev/null +++ b/src/translib/db/test/testdb.go @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "translib/db" + "time" + "translib/tlerr" +) + +func main() { + var avalue,rvalue db.Value + var akey,rkey db.Key + var e error + + defer glog.Flush() + + flag.Parse() + + fmt.Println("Creating the DB ==============") + d,_ := db.NewDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }) + +// fmt.Println("key: CONFIG_DB_INITIALIZED value: ", +// d.Client.Get("CONFIG_DB_INITIALIZED").String()) + + tsa := db.TableSpec { Name: "ACL_TABLE" } + tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + + fmt.Println("Testing GetEntry error ==============") + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey = db.Key { Comp: ca} + avalue, e = d.GetEntry(&tsa, akey) + fmt.Println("ts: ", tsa, " ", akey, ": ", avalue, " error: ", e) + if _, ok := e.(tlerr.TranslibRedisClientEntryNotExist) ; ok { + fmt.Println("Type is TranslibRedisClientEntryNotExist") + } + + + fmt.Println("Testing NoTransaction SetEntry ==============") + ca[0] = "MyACL1_ACL_IPV4" + akey = db.Key { Comp: ca} + avalue = db.Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + d.SetEntry(&tsa, akey, avalue) + + fmt.Println("Testing GetEntry ==============") + avalue, _ = d.GetEntry(&tsa, akey) + fmt.Println("ts: ", tsa, " ", akey, ": ", avalue) + + fmt.Println("Testing GetKeys ==============") + keys, _ := d.GetKeys(&tsa); + fmt.Println("ts: ", tsa, " keys: ", keys) + + fmt.Println("Testing NoTransaction DeleteEntry ==============") + akey = db.Key { Comp: ca} + + d.DeleteEntry(&tsa, akey) + + avalue, e = d.GetEntry(&tsa, akey) + if e == nil { + fmt.Println("!!! ts: ", tsa, " ", akey, ": ", avalue) + } + + fmt.Println("Testing 2 more ACLs ==============") + ca[0] = "MyACL2_ACL_IPV4" + avalue = db.Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + d.SetEntry(&tsa, akey, avalue) + + ca[0] = "MyACL3_ACL_IPV4" + d.SetEntry(&tsa, akey, avalue) + + ta, _ := d.GetTable(&tsa) + fmt.Println("ts: ", tsa, " table: ", ta) + + tr, _ := d.GetTable(&tsr) + fmt.Println("ts: ", tsr, " table: ", tr) + + fmt.Println("Testing Transaction =================") + rkey = db.Key { Comp: []string { "MyACL2_ACL_IPV4", "RULE_1" }} + rvalue = db.Value { Field: map[string]string { + "priority" : "0", + "packet_action" : "DROP", + }, + } + +// d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }) + d.StartTx([]db.WatchKeys {{Ts: &tsr, Key: &rkey} }, + []*db.TableSpec { &tsr, &tsa}) + + fmt.Println("Sleeping 5...") + time.Sleep(5 * time.Second) + + d.SetEntry( &tsr, rkey, rvalue) + + e = d.CommitTx() + if e != nil { + fmt.Println("Transaction Failed ======= e: ", e) + } + + + fmt.Println("Testing AbortTx =================") +// d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }) + d.StartTx([]db.WatchKeys {}, []*db.TableSpec { &tsr, &tsa}) + d.DeleteEntry( &tsa, rkey) + d.AbortTx() + avalue, e = d.GetEntry(&tsr, rkey) + fmt.Println("ts: ", tsr, " ", akey, ": ", avalue) + + fmt.Println("Testing DeleteKeys =================") + d.DeleteKeys(&tsr, db.Key { Comp: []string {"ToBeDeletedACLs*"} }) + + fmt.Println("Testing GetTable") + tr, _ = d.GetTable(&tsr) + fmt.Println("ts: ", tsr, " table: ", tr) + + +// d.DeleteTable(&ts) + + fmt.Println("Testing Tables2TableSpecs =================") + var tables []string + tables = []string { "ACL_TABLE", "ACL_RULE" } + fmt.Println("Tables: ", tables) + fmt.Println("TableSpecs: ") + for _, tsi := range db.Tables2TableSpecs(tables) { + fmt.Println(" ", *tsi) + } + + fmt.Println("Empty TableSpecs: ") + for _, tsi := range db.Tables2TableSpecs([]string { } ) { + fmt.Println(" ", *tsi) + } + + + d.DeleteDB() +} diff --git a/src/translib/db/test/testmap.go b/src/translib/db/test/testmap.go new file mode 100644 index 0000000000..728b8842ac --- /dev/null +++ b/src/translib/db/test/testmap.go @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "translib/db" + // "time" + // "translib/tlerr" +) + + +func handler(d *db.DB, skey *db.SKey, key *db.Key, event db.SEvent) error { + fmt.Println("***handler: d: ", d, " skey: ", *skey, " key: ", *key, + " event: ", event) + return nil +} + + +func main() { + defer glog.Flush() + + flag.Parse() + + tsc := db.TableSpec { Name: "COUNTERS_PORT_NAME_MAP" } + + fmt.Println("Creating the SubscribeDB ==============") + d,e := db.NewDB(db.Options { + DBNo : db.CountersDB, + InitIndicator : "", + TableNameSeparator: ":", + KeySeparator : ":", + }) + + if e != nil { + fmt.Println("NewDB() returns error e: ", e) + } + + fmt.Println("Setting Some Maps ==============") + d.SetMap(&tsc, "Ethernet2", "oid:0x1000000000002") + d.SetMap(&tsc, "Ethernet5", "oid:0x1000000000005") + d.SetMap(&tsc, "Ethernet3", "oid:0x1000000000003") + + fmt.Println("GetMapAll ==============") + v, e := d.GetMapAll(&tsc) + if e != nil { + fmt.Println("GetMapAll() returns error e: ", e) + } + fmt.Println("v: ", v) + + fmt.Println("GetMap ==============") + r2, e := d.GetMap(&tsc, "Ethernet2") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + r5, e := d.GetMap(&tsc, "Ethernet5") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + r3, e := d.GetMap(&tsc, "Ethernet3") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + + fmt.Println("r2, r5, r3", r2, r5, r3) + + + fmt.Println("GetMap NotExist mapKey ==============") + rN, e := d.GetMap(&tsc, "EthernetN") + if e == nil { + fmt.Println("GetMap() NotExist mapKey returns nil !!! ", rN) + } + + vN, e := d.GetMapAll(& db.TableSpec { Name: "NOTEXITMAP" } ) + if e == nil { + fmt.Println("GetMapAll() NotExist returns nil !!! ", vN) + } + + d.DeleteMapAll(&tsc) + + d.DeleteDB() +} diff --git a/src/translib/db/test/testsubscribe.go b/src/translib/db/test/testsubscribe.go new file mode 100644 index 0000000000..298bf3443a --- /dev/null +++ b/src/translib/db/test/testsubscribe.go @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "translib/db" + "time" + // "translib/tlerr" +) + + +func handler(d *db.DB, skey *db.SKey, key *db.Key, event db.SEvent) error { + fmt.Println("***handler: d: ", d, " skey: ", *skey, " key: ", *key, + " event: ", event) + return nil +} + + +func main() { + // var avalue,rvalue db.Value + var akey db.Key + // var rkey db.Key + // var e error + + defer glog.Flush() + + flag.Parse() + + tsa := db.TableSpec { Name: "ACL_TABLE" } + // tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST*" + akey = db.Key { Comp: ca} + var skeys [] *db.SKey = make([]*db.SKey, 1) + skeys[0] = & (db.SKey { Ts: &tsa, Key: &akey, + SEMap: map[db.SEvent]bool { + db.SEventHSet: true, + db.SEventHDel: true, + db.SEventDel: true, + }}) + + fmt.Println("Creating the SubscribeDB ==============") + d,e := db.SubscribeDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }, skeys, handler) + + if e != nil { + fmt.Println("Subscribe() returns error e: ", e) + } + + fmt.Println("Sleeping 15 ==============") + time.Sleep(15 * time.Second) + + + fmt.Println("Testing UnsubscribeDB ==============") + + d.UnsubscribeDB() + + fmt.Println("Sleeping 5 ==============") + time.Sleep(5 * time.Second) + + +} diff --git a/src/translib/intf_app.go b/src/translib/intf_app.go new file mode 100644 index 0000000000..e57ab932d8 --- /dev/null +++ b/src/translib/intf_app.go @@ -0,0 +1,1291 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "net" + "reflect" + "regexp" + "strconv" + "strings" + "translib/db" + "translib/ocbinds" + "translib/tlerr" + "unsafe" +) + +type reqType int + +const ( + opCreate reqType = iota + 1 + opDelete + opUpdate +) + +type dbEntry struct { + op reqType + entry db.Value +} + +const ( + PORT = "PORT" + PORT_INDEX = "index" + PORT_MTU = "mtu" + PORT_ADMIN_STATUS = "admin_status" + PORT_SPEED = "speed" + PORT_DESC = "description" + PORT_OPER_STATUS = "oper_status" +) + +type Table int + +const ( + IF_TABLE_MAP Table = iota + PORT_STAT_MAP +) + +type IntfApp struct { + path *PathInfo + reqData []byte + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + + respJSON interface{} + allIpKeys []db.Key + + appDB *db.DB + countersDB *db.DB + + ifTableMap map[string]dbEntry + ifIPTableMap map[string]map[string]dbEntry + portOidMap dbEntry + portStatMap map[string]dbEntry + + portTs *db.TableSpec + portTblTs *db.TableSpec + intfIPTs *db.TableSpec + intfIPTblTs *db.TableSpec + intfCountrTblTs *db.TableSpec + portOidCountrTblTs *db.TableSpec +} + +func init() { + log.Info("Init called for INTF module") + err := register("/openconfig-interfaces:interfaces", + &appInfo{appType: reflect.TypeOf(IntfApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigInterfaces_Interfaces{}), + isNative: false}) + if err != nil { + log.Fatal("Register INTF app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-interfaces", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *IntfApp) initialize(data appData) { + log.Info("initialize:if:path =", data.path) + + app.path = NewPathInfo(data.path) + app.reqData = data.payload + app.ygotRoot = data.ygotRoot + app.ygotTarget = data.ygotTarget + + app.portTs = &db.TableSpec{Name: "PORT"} + app.portTblTs = &db.TableSpec{Name: "PORT_TABLE"} + app.intfIPTs = &db.TableSpec{Name: "INTERFACE"} + app.intfIPTblTs = &db.TableSpec{Name: "INTF_TABLE", CompCt: 2} + app.intfCountrTblTs = &db.TableSpec{Name: "COUNTERS"} + app.portOidCountrTblTs = &db.TableSpec{Name: "COUNTERS_PORT_NAME_MAP"} + + app.ifTableMap = make(map[string]dbEntry) + app.ifIPTableMap = make(map[string]map[string]dbEntry) + app.portStatMap = make(map[string]dbEntry) +} + +func (app *IntfApp) getAppRootObject() *ocbinds.OpenconfigInterfaces_Interfaces { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Interfaces +} + +func (app *IntfApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:intf:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *IntfApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + + log.Info("translateUpdate:intf:path =", app.path) + + keys, err = app.translateCommon(d, opUpdate) + + if err != nil { + log.Info("Something wrong:=", err) + } + + return keys, err +} + +func (app *IntfApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:intf:path =", app.path) + err = errors.New("Not implemented") + return keys, err +} + +func (app *IntfApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + pathInfo := app.path + + log.Infof("Received Delete for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("uripath:=", targetUriPath) + log.Info("err:=", err) + + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + log.Info("len:=", len(intfObj.Interface)) + for ifKey, _ := range intfObj.Interface { + log.Info("Name:=", ifKey) + intf := intfObj.Interface[ifKey] + + if intf.Subinterfaces == nil { + continue + } + subIf := intf.Subinterfaces.Subinterface[0] + if subIf != nil { + if subIf.Ipv4 != nil && subIf.Ipv4.Addresses != nil { + for ip, _ := range subIf.Ipv4.Addresses.Address { + addr := subIf.Ipv4.Addresses.Address[ip] + if addr != nil { + ipAddr := addr.Ip + log.Info("IPv4 address = ", *ipAddr) + if !validIPv4(*ipAddr) { + errStr := "Invalid IPv4 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + err = app.validateIp(d, ifKey, *ipAddr) + if err != nil { + errStr := "Invalid IPv4 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + } + } + } + if subIf.Ipv6 != nil && subIf.Ipv6.Addresses != nil { + for ip, _ := range subIf.Ipv6.Addresses.Address { + addr := subIf.Ipv6.Addresses.Address[ip] + if addr != nil { + ipAddr := addr.Ip + log.Info("IPv6 address = ", *ipAddr) + if !validIPv6(*ipAddr) { + errStr := "Invalid IPv6 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + err = app.validateIp(d, ifKey, *ipAddr) + if err != nil { + errStr := "Invalid IPv6 address:" + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + } + } + } + } else { + err = errors.New("Only subinterface index 0 is supported") + return keys, err + } + } + } else { + err = errors.New("Not implemented") + } + return keys, err +} + +func (app *IntfApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:intf:path =", app.path) + return err +} + +func (app *IntfApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + app.appDB = dbs[db.ApplDB] + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ApplDB} + notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-interfaces:interfaces") { + if pathInfo.HasSuffix("/interface{}") || + pathInfo.HasSuffix("/config") || + pathInfo.HasSuffix("/state") { + log.Errorf("Subscribe not supported for %s!", pathInfo.Template) + return nil, nil, notSupported + } + ifKey := pathInfo.Var("name") + if len(ifKey) == 0 { + return nil, nil, errors.New("ifKey given is empty!") + } + log.Info("Interface name = ", ifKey) + err := app.validateInterface(app.appDB, ifKey, db.Key{Comp: []string{ifKey}}) + if err != nil { + return nil, nil, err + } + if pathInfo.HasSuffix("/state/oper-status") { + notifInfo.table = db.TableSpec{Name: "PORT_TABLE"} + notifInfo.key = asKey(ifKey) + notifInfo.needCache = true + return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil + } + } + return nil, nil, notSupported +} + +func (app *IntfApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCreate:intf:path =", app.path) + log.Info("ProcessCreate: Target Type is " + reflect.TypeOf(*app.ygotTarget).Elem().Name()) + + err = errors.New("Not implemented") + return resp, err +} + +func (app *IntfApp) processUpdate(d *db.DB) (SetResponse, error) { + + log.Infof("Calling processCommon()") + + resp, err := app.processCommon(d) + return resp, err +} + +func (app *IntfApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processReplace:intf:path =", app.path) + err = errors.New("Not implemented") + return resp, err +} + +func (app *IntfApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processDelete:intf:path =", app.path) + + if len(app.ifIPTableMap) == 0 { + return resp, err + } + for ifKey, entrylist := range app.ifIPTableMap { + for ip, _ := range entrylist { + err = d.DeleteEntry(app.intfIPTs, db.Key{Comp: []string{ifKey, ip}}) + log.Infof("Deleted IP : %s for Interface : %s", ip, ifKey) + } + } + return resp, err +} + +/* Note : Registration already happened, followed by filling the internal DS and filling the JSON */ +func (app *IntfApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + + var err error + var payload []byte + pathInfo := app.path + + log.Infof("Received GET for path %s; template: %s vars=%v", pathInfo.Path, pathInfo.Template, pathInfo.Vars) + app.appDB = dbs[db.ApplDB] + app.countersDB = dbs[db.CountersDB] + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("URI Path = ", targetUriPath) + + if isSubtreeRequest(targetUriPath, "/openconfig-interfaces:interfaces/interface") { + /* Request for a specific interface */ + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + /* Interface name is the key */ + for ifKey, _ := range intfObj.Interface { + log.Info("Interface Name = ", ifKey) + ifInfo := intfObj.Interface[ifKey] + /* Filling Interface Info to internal DS */ + err = app.convertDBIntfInfoToInternal(app.appDB, ifKey, db.Key{Comp: []string{ifKey}}) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /*Check if the request is for a specific attribute in Interfaces state container*/ + oc_val := &ocbinds.OpenconfigInterfaces_Interfaces_Interface_State{} + ok, e := app.getSpecificAttr(targetUriPath, ifKey, oc_val) + if ok { + if e != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, e + } + + payload, err = dumpIetfJson(oc_val, false) + if err == nil { + return GetResponse{Payload: payload}, err + } else { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } + + /* Filling the counter Info to internal DS */ + err = app.getPortOidMapForCounters(app.countersDB) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + err = app.convertDBIntfCounterInfoToInternal(app.countersDB, ifKey) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /*Check if the request is for a specific attribute in Interfaces state COUNTERS container*/ + counter_val := &ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_Counters{} + ok, e = app.getSpecificCounterAttr(targetUriPath, ifKey, counter_val) + if ok { + if e != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, e + } + + payload, err = dumpIetfJson(counter_val, false) + if err == nil { + return GetResponse{Payload: payload}, err + } else { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } + + /* Filling Interface IP info to internal DS */ + err = app.convertDBIntfIPInfoToInternal(app.appDB, ifKey) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /* Filling the tree with the info we have in Internal DS */ + ygot.BuildEmptyTree(ifInfo) + if *app.ygotTarget == ifInfo.State { + ygot.BuildEmptyTree(ifInfo.State) + } + app.convertInternalToOCIntfInfo(&ifKey, ifInfo) + if *app.ygotTarget == ifInfo { + payload, err = dumpIetfJson(intfObj, false) + } else { + dummyifInfo := &ocbinds.OpenconfigInterfaces_Interfaces_Interface{} + if *app.ygotTarget == ifInfo.Config { + dummyifInfo.Config = ifInfo.Config + payload, err = dumpIetfJson(dummyifInfo, false) + } else if *app.ygotTarget == ifInfo.State { + dummyifInfo.State = ifInfo.State + payload, err = dumpIetfJson(dummyifInfo, false) + } else { + log.Info("Not supported get type!") + err = errors.New("Requested get-type not supported!") + } + } + } + } + return GetResponse{Payload: payload}, err + } + + /* Get all Interfaces */ + if isSubtreeRequest(targetUriPath, "/openconfig-interfaces:interfaces") { + log.Info("Get all Interfaces request!") + /* Filling Interface Info to internal DS */ + err = app.convertDBIntfInfoToInternal(app.appDB, "", db.Key{}) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + /* Filling Interface IP info to internal DS */ + err = app.convertDBIntfIPInfoToInternal(app.appDB, "") + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + /* Filling the counter Info to internal DS */ + err = app.getPortOidMapForCounters(app.countersDB) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + err = app.convertDBIntfCounterInfoToInternal(app.countersDB, "") + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(intfObj) + for ifKey, _ := range app.ifTableMap { + log.Info("If Key = ", ifKey) + ifInfo, err := intfObj.NewInterface(ifKey) + if err != nil { + log.Errorf("Creation of interface subtree for %s failed!", ifKey) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(ifInfo) + app.convertInternalToOCIntfInfo(&ifKey, ifInfo) + } + if *app.ygotTarget == intfObj { + payload, err = dumpIetfJson((*app.ygotRoot).(*ocbinds.Device), true) + } else { + log.Error("Wrong request!") + } + } + return GetResponse{Payload: payload}, err +} + +/* Checking IP adderss is v4 */ +func validIPv4(ipAddress string) bool { + ipAddress = strings.Trim(ipAddress, " ") + + re, _ := regexp.Compile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`) + if re.MatchString(ipAddress) { + return true + } + return false +} + +/* Checking IP address is v6 */ +func validIPv6(ip6Address string) bool { + ip6Address = strings.Trim(ip6Address, " ") + re, _ := regexp.Compile(`(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`) + if re.MatchString(ip6Address) { + return true + } + return false +} + +func (app *IntfApp) doGetAllIpKeys(d *db.DB, dbSpec *db.TableSpec) ([]db.Key, error) { + + var keys []db.Key + + intfTable, err := d.GetTable(dbSpec) + if err != nil { + return keys, err + } + + keys, err = intfTable.GetKeys() + log.Infof("Found %d INTF table keys", len(keys)) + return keys, err +} + +func (app *IntfApp) getSpecificAttr(targetUriPath string, ifKey string, oc_val *ocbinds.OpenconfigInterfaces_Interfaces_Interface_State) (bool, error) { + switch targetUriPath { + case "/openconfig-interfaces:interfaces/interface/state/oper-status": + val, e := app.getIntfAttr(ifKey, PORT_OPER_STATUS, IF_TABLE_MAP) + if len(val) > 0 { + switch val { + case "up": + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UP + case "down": + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_DOWN + default: + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UNSET + } + return true, nil + } else { + return true, e + } + case "/openconfig-interfaces:interfaces/interface/state/admin-status": + val, e := app.getIntfAttr(ifKey, PORT_ADMIN_STATUS, IF_TABLE_MAP) + if len(val) > 0 { + switch val { + case "up": + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UP + case "down": + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_DOWN + default: + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UNSET + } + return true, nil + } else { + return true, e + } + case "/openconfig-interfaces:interfaces/interface/state/mtu": + val, e := app.getIntfAttr(ifKey, PORT_MTU, IF_TABLE_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 16) + if e == nil { + oc_val.Mtu = (*uint16)(unsafe.Pointer(&v)) + return true, nil + } + } + return true, e + case "/openconfig-interfaces:interfaces/interface/state/ifindex": + val, e := app.getIntfAttr(ifKey, PORT_INDEX, IF_TABLE_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 32) + if e == nil { + oc_val.Ifindex = (*uint32)(unsafe.Pointer(&v)) + return true, nil + } + } + return true, e + case "/openconfig-interfaces:interfaces/interface/state/description": + val, e := app.getIntfAttr(ifKey, PORT_DESC, IF_TABLE_MAP) + if e == nil { + oc_val.Description = &val + return true, nil + } + return true, e + + default: + log.Infof(targetUriPath + " - Not an interface state attribute") + } + return false, nil +} + +func (app *IntfApp) getSpecificCounterAttr(targetUriPath string, ifKey string, counter_val *ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_Counters) (bool, error) { + + var e error + + switch targetUriPath { + case "/openconfig-interfaces:interfaces/interface/state/counters/in-octets": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_OCTETS", &counter_val.InOctets) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-unicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_UCAST_PKTS", &counter_val.InUnicastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-broadcast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_BROADCAST_PKTS", &counter_val.InBroadcastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-multicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_MULTICAST_PKTS", &counter_val.InMulticastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-errors": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_ERRORS", &counter_val.InErrors) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-discards": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_DISCARDS", &counter_val.InDiscards) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-pkts": + var inNonUCastPkt, inUCastPkt *uint64 + var in_pkts uint64 + + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS", &inNonUCastPkt) + if e == nil { + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_UCAST_PKTS", &inUCastPkt) + if e != nil { + return true, e + } + in_pkts = *inUCastPkt + *inNonUCastPkt + counter_val.InPkts = &in_pkts + return true, e + } else { + return true, e + } + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-octets": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_OCTETS", &counter_val.OutOctets) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-unicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_UCAST_PKTS", &counter_val.OutUnicastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-broadcast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_BROADCAST_PKTS", &counter_val.OutBroadcastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-multicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_MULTICAST_PKTS", &counter_val.OutMulticastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-errors": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_ERRORS", &counter_val.OutErrors) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-discards": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_DISCARDS", &counter_val.OutDiscards) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-pkts": + var outNonUCastPkt, outUCastPkt *uint64 + var out_pkts uint64 + + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS", &outNonUCastPkt) + if e == nil { + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_UCAST_PKTS", &outUCastPkt) + if e != nil { + return true, e + } + out_pkts = *outUCastPkt + *outNonUCastPkt + counter_val.OutPkts = &out_pkts + return true, e + } else { + return true, e + } + + default: + log.Infof(targetUriPath + " - Not an interface state counter attribute") + } + return false, nil +} + +func (app *IntfApp) getCounters(ifKey string, attr string, counter_val **uint64) error { + val, e := app.getIntfAttr(ifKey, attr, PORT_STAT_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 64) + if e == nil { + *counter_val = &v + return nil + } + } + return e +} + +func (app *IntfApp) getIntfAttr(ifName string, attr string, table Table) (string, error) { + + var ok bool = false + var entry dbEntry + + if table == IF_TABLE_MAP { + entry, ok = app.ifTableMap[ifName] + } else if table == PORT_STAT_MAP { + entry, ok = app.portStatMap[ifName] + } else { + return "", errors.New("Unsupported table") + } + + if ok { + ifData := entry.entry + + if val, ok := ifData.Field[attr]; ok { + return val, nil + } + } + return "", errors.New("Attr " + attr + "doesn't exist in IF table Map!") +} + +/*********** Translation Helper fn to convert DB Interface info to Internal DS ***********/ +func (app *IntfApp) getPortOidMapForCounters(dbCl *db.DB) error { + var err error + ifCountInfo, err := dbCl.GetMapAll(app.portOidCountrTblTs) + if err != nil { + log.Error("Port-OID (Counters) get for all the interfaces failed!") + return err + } + if ifCountInfo.IsPopulated() { + app.portOidMap.entry = ifCountInfo + } else { + return errors.New("Get for OID info from all the interfaces from Counters DB failed!") + } + return err +} + +func (app *IntfApp) convertDBIntfCounterInfoToInternal(dbCl *db.DB, ifKey string) error { + var err error + + if len(ifKey) > 0 { + oid := app.portOidMap.entry.Field[ifKey] + log.Infof("OID : %s received for Interface : %s", oid, ifKey) + + /* Get the statistics for the port */ + var ifStatKey db.Key + ifStatKey.Comp = []string{oid} + + ifStatInfo, err := dbCl.GetEntry(app.intfCountrTblTs, ifStatKey) + if err != nil { + log.Infof("Fetching port-stat for port : %s failed!", ifKey) + return err + } + app.portStatMap[ifKey] = dbEntry{entry: ifStatInfo} + } else { + for ifKey, _ := range app.ifTableMap { + app.convertDBIntfCounterInfoToInternal(dbCl, ifKey) + } + } + return err +} + +func (app *IntfApp) validateInterface(dbCl *db.DB, ifName string, ifKey db.Key) error { + var err error + if len(ifName) == 0 { + return errors.New("Empty Interface name") + } + app.portTblTs = &db.TableSpec{Name: "PORT_TABLE"} + _, err = dbCl.GetEntry(app.portTblTs, ifKey) + if err != nil { + log.Errorf("Error found on fetching Interface info from App DB for If Name : %s", ifName) + errStr := "Invalid Interface:" + ifName + err = tlerr.InvalidArgsError{Format: errStr} + return err + } + return err +} + +func (app *IntfApp) convertDBIntfInfoToInternal(dbCl *db.DB, ifName string, ifKey db.Key) error { + + var err error + /* Fetching DB data for a specific Interface */ + if len(ifName) > 0 { + log.Info("Updating Interface info from APP-DB to Internal DS for Interface name : ", ifName) + ifInfo, err := dbCl.GetEntry(app.portTblTs, ifKey) + if err != nil { + log.Errorf("Error found on fetching Interface info from App DB for If Name : %s", ifName) + errStr := "Invalid Interface:" + ifName + err = tlerr.InvalidArgsError{Format: errStr} + return err + } + if ifInfo.IsPopulated() { + log.Info("Interface Info populated for ifName : ", ifName) + app.ifTableMap[ifName] = dbEntry{entry: ifInfo} + } else { + return errors.New("Populating Interface info for " + ifName + "failed") + } + } else { + log.Info("App-DB get for all the interfaces") + tbl, err := dbCl.GetTable(app.portTblTs) + if err != nil { + log.Error("App-DB get for list of interfaces failed!") + return err + } + keys, _ := tbl.GetKeys() + for _, key := range keys { + app.convertDBIntfInfoToInternal(dbCl, key.Get(0), db.Key{Comp: []string{key.Get(0)}}) + } + } + return err +} + +/*********** Translation Helper fn to convert DB Interface IP info to Internal DS ***********/ +func (app *IntfApp) convertDBIntfIPInfoToInternal(dbCl *db.DB, ifName string) error { + + var err error + log.Info("Updating Interface IP Info from APP-DB to Internal DS for Interface Name : ", ifName) + app.allIpKeys, _ = app.doGetAllIpKeys(dbCl, app.intfIPTblTs) + + for _, key := range app.allIpKeys { + if len(key.Comp) <= 1 { + continue + } + ipInfo, err := dbCl.GetEntry(app.intfIPTblTs, key) + if err != nil { + log.Errorf("Error found on fetching Interface IP info from App DB for Interface Name : %s", ifName) + return err + } + if len(app.ifIPTableMap[key.Get(0)]) == 0 { + app.ifIPTableMap[key.Get(0)] = make(map[string]dbEntry) + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } else { + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } + } + return err +} + +func (app *IntfApp) convertInternalToOCIntfInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + app.convertInternalToOCIntfAttrInfo(ifName, ifInfo) + app.convertInternalToOCIntfIPAttrInfo(ifName, ifInfo) + app.convertInternalToOCPortStatInfo(ifName, ifInfo) +} + +func (app *IntfApp) convertInternalToOCIntfAttrInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + + /* Handling the Interface attributes */ + if entry, ok := app.ifTableMap[*ifName]; ok { + ifData := entry.entry + + name := *ifName + ifInfo.Config.Name = &name + ifInfo.State.Name = &name + + for ifAttr := range ifData.Field { + switch ifAttr { + case PORT_ADMIN_STATUS: + adminStatus := ifData.Get(ifAttr) + ifInfo.State.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_DOWN + if adminStatus == "up" { + ifInfo.State.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UP + } + case PORT_OPER_STATUS: + operStatus := ifData.Get(ifAttr) + ifInfo.State.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_DOWN + if operStatus == "up" { + ifInfo.State.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UP + } + case PORT_DESC: + descVal := ifData.Get(ifAttr) + descr := new(string) + *descr = descVal + ifInfo.Config.Description = descr + ifInfo.State.Description = descr + case PORT_MTU: + mtuStr := ifData.Get(ifAttr) + mtuVal, err := strconv.Atoi(mtuStr) + mtu := new(uint16) + *mtu = uint16(mtuVal) + if err == nil { + ifInfo.Config.Mtu = mtu + ifInfo.State.Mtu = mtu + } + case PORT_SPEED: + speed := ifData.Get(ifAttr) + var speedEnum ocbinds.E_OpenconfigIfEthernet_ETHERNET_SPEED + + switch speed { + case "2500": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_2500MB + case "1000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_1GB + case "5000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_5GB + case "10000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_10GB + case "25000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_25GB + case "40000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_40GB + case "50000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_50GB + case "100000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_100GB + default: + log.Infof("Not supported speed: %s!", speed) + } + ifInfo.Ethernet.Config.PortSpeed = speedEnum + ifInfo.Ethernet.State.PortSpeed = speedEnum + case PORT_INDEX: + ifIdxStr := ifData.Get(ifAttr) + ifIdxNum, err := strconv.Atoi(ifIdxStr) + if err == nil { + ifIdx := new(uint32) + *ifIdx = uint32(ifIdxNum) + ifInfo.State.Ifindex = ifIdx + } + default: + log.Info("Not a valid attribute!") + } + } + } + +} + +func (app *IntfApp) convertInternalToOCIntfIPAttrInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + + /* Handling the Interface IP attributes */ + subIntf, err := ifInfo.Subinterfaces.NewSubinterface(0) + if err != nil { + log.Error("Creation of subinterface subtree failed!") + return + } + ygot.BuildEmptyTree(subIntf) + if ipMap, ok := app.ifIPTableMap[*ifName]; ok { + for ipKey, _ := range ipMap { + log.Info("IP address = ", ipKey) + ipB, ipNetB, _ := net.ParseCIDR(ipKey) + + v4Flag := false + v6Flag := false + + var v4Address *ocbinds.OpenconfigInterfaces_Interfaces_Interface_Subinterfaces_Subinterface_Ipv4_Addresses_Address + var v6Address *ocbinds.OpenconfigInterfaces_Interfaces_Interface_Subinterfaces_Subinterface_Ipv6_Addresses_Address + + if validIPv4(ipB.String()) { + v4Address, err = subIntf.Ipv4.Addresses.NewAddress(ipB.String()) + v4Flag = true + } else if validIPv6(ipB.String()) { + v6Address, err = subIntf.Ipv6.Addresses.NewAddress(ipB.String()) + v6Flag = true + } else { + log.Error("Invalid IP address " + ipB.String()) + continue + } + + if err != nil { + log.Error("Creation of address subtree failed!") + return + } + if v4Flag { + ygot.BuildEmptyTree(v4Address) + + ipStr := new(string) + *ipStr = ipB.String() + v4Address.Ip = ipStr + v4Address.Config.Ip = ipStr + v4Address.State.Ip = ipStr + + ipNetBNum, _ := ipNetB.Mask.Size() + prfxLen := new(uint8) + *prfxLen = uint8(ipNetBNum) + v4Address.Config.PrefixLength = prfxLen + v4Address.State.PrefixLength = prfxLen + } + if v6Flag { + ygot.BuildEmptyTree(v6Address) + + ipStr := new(string) + *ipStr = ipB.String() + v6Address.Ip = ipStr + v6Address.Config.Ip = ipStr + v6Address.State.Ip = ipStr + + ipNetBNum, _ := ipNetB.Mask.Size() + prfxLen := new(uint8) + *prfxLen = uint8(ipNetBNum) + v6Address.Config.PrefixLength = prfxLen + v6Address.State.PrefixLength = prfxLen + } + } + } +} + +func (app *IntfApp) convertInternalToOCPortStatInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + if len(app.portStatMap) == 0 { + log.Errorf("Port stat info not present for interface : %s", *ifName) + return + } + if portStatInfo, ok := app.portStatMap[*ifName]; ok { + + inOctet := new(uint64) + inOctetVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_OCTETS"]) + *inOctet = uint64(inOctetVal) + ifInfo.State.Counters.InOctets = inOctet + + inUCastPkt := new(uint64) + inUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_UCAST_PKTS"]) + *inUCastPkt = uint64(inUCastPktVal) + ifInfo.State.Counters.InUnicastPkts = inUCastPkt + + inNonUCastPkt := new(uint64) + inNonUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS"]) + *inNonUCastPkt = uint64(inNonUCastPktVal) + + inPkt := new(uint64) + *inPkt = *inUCastPkt + *inNonUCastPkt + ifInfo.State.Counters.InPkts = inPkt + + inBCastPkt := new(uint64) + inBCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_BROADCAST_PKTS"]) + *inBCastPkt = uint64(inBCastPktVal) + ifInfo.State.Counters.InBroadcastPkts = inBCastPkt + + inMCastPkt := new(uint64) + inMCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_MULTICAST_PKTS"]) + *inMCastPkt = uint64(inMCastPktVal) + ifInfo.State.Counters.InMulticastPkts = inMCastPkt + + inErrPkt := new(uint64) + inErrPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_ERRORS"]) + *inErrPkt = uint64(inErrPktVal) + ifInfo.State.Counters.InErrors = inErrPkt + + inDiscPkt := new(uint64) + inDiscPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_DISCARDS"]) + *inDiscPkt = uint64(inDiscPktVal) + ifInfo.State.Counters.InDiscards = inDiscPkt + + outOctet := new(uint64) + outOctetVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_OCTETS"]) + *outOctet = uint64(outOctetVal) + ifInfo.State.Counters.OutOctets = outOctet + + outUCastPkt := new(uint64) + outUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_UCAST_PKTS"]) + *outUCastPkt = uint64(outUCastPktVal) + ifInfo.State.Counters.OutUnicastPkts = outUCastPkt + + outNonUCastPkt := new(uint64) + outNonUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS"]) + *outNonUCastPkt = uint64(outNonUCastPktVal) + + outPkt := new(uint64) + *outPkt = *outUCastPkt + *outNonUCastPkt + ifInfo.State.Counters.OutPkts = outPkt + + outBCastPkt := new(uint64) + outBCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_BROADCAST_PKTS"]) + *outBCastPkt = uint64(outBCastPktVal) + ifInfo.State.Counters.OutBroadcastPkts = outBCastPkt + + outMCastPkt := new(uint64) + outMCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_MULTICAST_PKTS"]) + *outMCastPkt = uint64(outMCastPktVal) + ifInfo.State.Counters.OutMulticastPkts = outMCastPkt + + outErrPkt := new(uint64) + outErrPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_ERRORS"]) + *outErrPkt = uint64(outErrPktVal) + ifInfo.State.Counters.OutErrors = outErrPkt + + outDiscPkt := new(uint64) + outDiscPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_DISCARDS"]) + *outDiscPkt = uint64(outDiscPktVal) + ifInfo.State.Counters.OutDiscards = outDiscPkt + } +} + +func (app *IntfApp) translateCommon(d *db.DB, inpOp reqType) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + pathInfo := app.path + + log.Infof("Received UPDATE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + app.allIpKeys, _ = app.doGetAllIpKeys(d, app.intfIPTs) + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("uripath:=", targetUriPath) + log.Info("err:=", err) + + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + log.Info("len:=", len(intfObj.Interface)) + for ifKey, _ := range intfObj.Interface { + log.Info("Name:=", ifKey) + intf := intfObj.Interface[ifKey] + curr, err := d.GetEntry(app.portTs, db.Key{Comp: []string{ifKey}}) + if err != nil { + errStr := "Invalid Interface:" + ifKey + ifValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ifValidErr + } + if !curr.IsPopulated() { + log.Error("Interface ", ifKey, " doesn't exist in DB") + return keys, errors.New("Interface: " + ifKey + " doesn't exist in DB") + } + if intf.Config != nil { + if intf.Config.Description != nil { + log.Info("Description = ", *intf.Config.Description) + curr.Field["description"] = *intf.Config.Description + } else if intf.Config.Mtu != nil { + log.Info("mtu:= ", *intf.Config.Mtu) + curr.Field["mtu"] = strconv.Itoa(int(*intf.Config.Mtu)) + } else if intf.Config.Enabled != nil { + log.Info("enabled = ", *intf.Config.Enabled) + if *intf.Config.Enabled == true { + curr.Field["admin_status"] = "up" + } else { + curr.Field["admin_status"] = "down" + } + } + log.Info("Writing to db for ", ifKey) + var entry dbEntry + entry.op = opUpdate + entry.entry = curr + + app.ifTableMap[ifKey] = entry + } + if intf.Subinterfaces == nil { + continue + } + subIf := intf.Subinterfaces.Subinterface[0] + if subIf != nil { + if subIf.Ipv4 != nil && subIf.Ipv4.Addresses != nil { + for ip, _ := range subIf.Ipv4.Addresses.Address { + addr := subIf.Ipv4.Addresses.Address[ip] + if addr.Config != nil { + log.Info("Ip:=", *addr.Config.Ip) + log.Info("prefix:=", *addr.Config.PrefixLength) + if !validIPv4(*addr.Config.Ip) { + errStr := "Invalid IPv4 address " + *addr.Config.Ip + err = tlerr.InvalidArgsError{Format: errStr} + return keys, err + } + err = app.translateIpv4(d, ifKey, *addr.Config.Ip, int(*addr.Config.PrefixLength)) + if err != nil { + return keys, err + } + } + } + } + if subIf.Ipv6 != nil && subIf.Ipv6.Addresses != nil { + for ip, _ := range subIf.Ipv6.Addresses.Address { + addr := subIf.Ipv6.Addresses.Address[ip] + if addr.Config != nil { + log.Info("Ip:=", *addr.Config.Ip) + log.Info("prefix:=", *addr.Config.PrefixLength) + if !validIPv6(*addr.Config.Ip) { + errStr := "Invalid IPv6 address " + *addr.Config.Ip + err = tlerr.InvalidArgsError{Format: errStr} + return keys, err + } + err = app.translateIpv4(d, ifKey, *addr.Config.Ip, int(*addr.Config.PrefixLength)) + if err != nil { + return keys, err + } + } + } + } + } else { + err = errors.New("Only subinterface index 0 is supported") + return keys, err + } + } + } else { + err = errors.New("Not implemented") + } + + return keys, err +} + +/* Validates whether the IP exists in the DB */ +func (app *IntfApp) validateIp(dbCl *db.DB, ifName string, ip string) error { + app.allIpKeys, _ = app.doGetAllIpKeys(dbCl, app.intfIPTs) + + for _, key := range app.allIpKeys { + if len(key.Comp) < 2 { + continue + } + if key.Get(0) != ifName { + continue + } + ipAddr, _, _ := net.ParseCIDR(key.Get(1)) + ipStr := ipAddr.String() + if ipStr == ip { + log.Infof("IP address %s exists, updating the DS for deletion!", ipStr) + ipInfo, err := dbCl.GetEntry(app.intfIPTs, key) + if err != nil { + log.Error("Error found on fetching Interface IP info from App DB for Interface Name : ", ifName) + return err + } + if len(app.ifIPTableMap[key.Get(0)]) == 0 { + app.ifIPTableMap[key.Get(0)] = make(map[string]dbEntry) + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } else { + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } + return nil + } + } + return errors.New(fmt.Sprintf("IP address : %s doesn't exist!", ip)) +} + +func (app *IntfApp) translateIpv4(d *db.DB, intf string, ip string, prefix int) error { + var err error + var ifsKey db.Key + + ifsKey.Comp = []string{intf} + + ipPref := ip + "/" + strconv.Itoa(prefix) + ifsKey.Comp = []string{intf, ipPref} + + log.Info("ifsKey:=", ifsKey) + + log.Info("Checking for IP overlap ....") + ipA, ipNetA, _ := net.ParseCIDR(ipPref) + + for _, key := range app.allIpKeys { + if len(key.Comp) < 2 { + continue + } + ipB, ipNetB, _ := net.ParseCIDR(key.Get(1)) + + if ipNetA.Contains(ipB) || ipNetB.Contains(ipA) { + log.Info("IP ", ipPref, "overlaps with ", key.Get(1), " of ", key.Get(0)) + + if intf != key.Get(0) { + //IP overlap across different interface, reject + log.Error("IP ", ipPref, " overlaps with ", key.Get(1), " of ", key.Get(0)) + + errStr := "IP " + ipPref + " overlaps with IP " + key.Get(1) + " of Interface " + key.Get(0) + err = tlerr.InvalidArgsError{Format: errStr} + return err + } else { + //IP overlap on same interface, replace + var entry dbEntry + entry.op = opDelete + + log.Info("Entry ", key.Get(1), " on ", intf, " needs to be deleted") + if app.ifIPTableMap[intf] == nil { + app.ifIPTableMap[intf] = make(map[string]dbEntry) + } + app.ifIPTableMap[intf][key.Get(1)] = entry + } + } + } + + //At this point, we need to add the entry to db + { + var entry dbEntry + entry.op = opCreate + + m := make(map[string]string) + m["NULL"] = "NULL" + value := db.Value{Field: m} + entry.entry = value + if app.ifIPTableMap[intf] == nil { + app.ifIPTableMap[intf] = make(map[string]dbEntry) + } + app.ifIPTableMap[intf][ipPref] = entry + } + return err +} + +func (app *IntfApp) processCommon(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCommon:intf:path =", app.path) + log.Info("ProcessCommon: Target Type is " + reflect.TypeOf(*app.ygotTarget).Elem().Name()) + + for key, entry := range app.ifTableMap { + if entry.op == opUpdate { + log.Info("Updating entry for ", key) + err = d.SetEntry(app.portTs, db.Key{Comp: []string{key}}, entry.entry) + } + } + + for key, entry1 := range app.ifIPTableMap { + ifEntry, err := d.GetEntry(app.intfIPTs, db.Key{Comp: []string{key}}) + if err != nil || !ifEntry.IsPopulated() { + log.Infof("Interface Entry not present for Key:%s for IP config!", key) + m := make(map[string]string) + m["NULL"] = "NULL" + err = d.CreateEntry(app.intfIPTs, db.Key{Comp: []string{key}}, db.Value{Field: m}) + if err != nil { + return resp, err + } + log.Infof("Created Interface entry with Interface name : %s alone!", key) + } + for ip, entry := range entry1 { + if entry.op == opCreate { + log.Info("Creating entry for ", key, ":", ip) + err = d.CreateEntry(app.intfIPTs, db.Key{Comp: []string{key, ip}}, entry.entry) + } else if entry.op == opDelete { + log.Info("Deleting entry for ", key, ":", ip) + err = d.DeleteEntry(app.intfIPTs, db.Key{Comp: []string{key, ip}}) + } + } + } + return resp, err +} diff --git a/src/translib/lldp_app.go b/src/translib/lldp_app.go new file mode 100644 index 0000000000..1b185279a6 --- /dev/null +++ b/src/translib/lldp_app.go @@ -0,0 +1,456 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "strconv" + "reflect" + "errors" + "translib/db" + "translib/ocbinds" + "github.com/openconfig/ygot/ygot" + log "github.com/golang/glog" + "strings" + "encoding/hex" + "translib/tlerr" +) + +const ( + LLDP_REMOTE_CAP_ENABLED = "lldp_rem_sys_cap_enabled" + LLDP_REMOTE_SYS_NAME = "lldp_rem_sys_name" + LLDP_REMOTE_PORT_DESC = "lldp_rem_port_desc" + LLDP_REMOTE_CHASS_ID = "lldp_rem_chassis_id" + LLDP_REMOTE_CAP_SUPPORTED = "lldp_rem_sys_cap_supported" + LLDP_REMOTE_PORT_ID_SUBTYPE = "lldp_rem_port_id_subtype" + LLDP_REMOTE_SYS_DESC = "lldp_rem_sys_desc" + LLDP_REMOTE_REM_TIME = "lldp_rem_time_mark" + LLDP_REMOTE_PORT_ID = "lldp_rem_port_id" + LLDP_REMOTE_REM_ID = "lldp_rem_index" + LLDP_REMOTE_CHASS_ID_SUBTYPE = "lldp_rem_chassis_id_subtype" + LLDP_REMOTE_MAN_ADDR = "lldp_rem_man_addr" +) + +type lldpApp struct { + path *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + appDb *db.DB + neighTs *db.TableSpec + lldpTableMap map[string]db.Value + lldpNeighTableMap map[string]map[string]string + lldpCapTableMap map[string]map[string]bool +} + +func init() { + log.Info("Init called for LLDP modules module") + err := register("/openconfig-lldp:lldp", + &appInfo{appType: reflect.TypeOf(lldpApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigLldp_Lldp{}), + isNative: false}) + if err != nil { + log.Fatal("Register LLDP app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-lldp", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *lldpApp) initialize(data appData) { + log.Info("initialize:lldp:path =", data.path) + *app = lldpApp{path: NewPathInfo(data.path), ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + app.neighTs = &db.TableSpec{Name: "LLDP_ENTRY_TABLE"} + app.lldpTableMap = make(map[string]db.Value) + app.lldpNeighTableMap = make(map[string]map[string]string) + app.lldpCapTableMap = make(map[string]map[string]bool) +} + +func (app *lldpApp) getAppRootObject() (*ocbinds.OpenconfigLldp_Lldp) { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Lldp +} + +func (app *lldpApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:lldp:path = ", app.path) + + return err +} + +func (app *lldpApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ApplDB} + notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-lldp:lldp/interfaces") { + if pathInfo.HasSuffix("/neighbors") || + pathInfo.HasSuffix("/config") || + pathInfo.HasSuffix("/state") { + log.Errorf("Subscribe not supported for %s!", pathInfo.Template) + return nil, nil, notSupported + } + ifKey := pathInfo.Var("name") + if len(ifKey) == 0 { + return nil, nil, errors.New("ifKey given is empty!") + } + log.Info("Interface name = ", ifKey) + if pathInfo.HasSuffix("/interface{}") { + notifInfo.table = db.TableSpec{Name: "LLDP_ENTRY_TABLE"} + notifInfo.key = asKey(ifKey) + notifInfo.needCache = true + return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil + } + } + return nil, nil, notSupported +} + +func (app *lldpApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + err = errors.New("Not implemented") + + return resp, err +} + +func (app *lldpApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + + app.appDb = dbs[db.ApplDB] + lldpIntfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("lldp processGet") + log.Info("targetUriPath: ", targetUriPath) + + if targetUriPath == "/openconfig-lldp:lldp/interfaces" { + log.Info("Requesting interfaces") + app.getLldpInfoFromDB(nil) + ygot.BuildEmptyTree(lldpIntfObj) + ifInfo := lldpIntfObj.Interfaces + ygot.BuildEmptyTree(ifInfo) + for ifname,_ := range app.lldpNeighTableMap { + oneIfInfo, err := ifInfo.NewInterface(ifname) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(oneIfInfo) + app.getLldpNeighInfoFromInternalMap(&ifname, oneIfInfo) + if *app.ygotTarget == lldpIntfObj.Interfaces { + payload, err = dumpIetfJson(lldpIntfObj, true) + } else { + log.Info("Wrong request!") + } + + } + } else if targetUriPath == "/openconfig-lldp:lldp/interfaces/interface" { + intfObj := lldpIntfObj.Interfaces + ygot.BuildEmptyTree(intfObj) + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + for ifname, _ := range intfObj.Interface { + log.Info("if-name = ", ifname) + app.getLldpInfoFromDB(&ifname) + ifInfo := intfObj.Interface[ifname] + ygot.BuildEmptyTree(ifInfo) + app.getLldpNeighInfoFromInternalMap(&ifname, ifInfo) + + if *app.ygotTarget == intfObj.Interface[ifname] { + payload, err = dumpIetfJson(intfObj, true) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } else { + log.Info("Wrong request!") + } + } + } else { + log.Info("No data") + } + } + + return GetResponse{Payload:payload}, err +} + +/** Helper function to populate JSON response for GET request **/ +func (app *lldpApp) getLldpNeighInfoFromInternalMap(ifName *string, ifInfo *ocbinds.OpenconfigLldp_Lldp_Interfaces_Interface) { + + ngInfo, err := ifInfo.Neighbors.NewNeighbor(*ifName) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return + } + ygot.BuildEmptyTree(ngInfo) + neighAttrMap:= app.lldpNeighTableMap[*ifName] + for attr, value := range neighAttrMap { + switch attr { + case LLDP_REMOTE_SYS_NAME: + name := new(string) + *name = value + ngInfo.State.SystemName = name + case LLDP_REMOTE_PORT_DESC: + pdescr := new(string) + *pdescr = value + ngInfo.State.PortDescription = pdescr + case LLDP_REMOTE_CHASS_ID: + chId := new (string) + *chId = value + ngInfo.State.ChassisId = chId + case LLDP_REMOTE_PORT_ID_SUBTYPE: + remPortIdTypeVal, err := strconv.Atoi(value) + if err == nil { + ngInfo.State.PortIdType =ocbinds.E_OpenconfigLldp_PortIdType(remPortIdTypeVal) + } + case LLDP_REMOTE_SYS_DESC: + sdesc:= new(string) + *sdesc = value + ngInfo.State.SystemDescription = sdesc + case LLDP_REMOTE_REM_TIME: + /* Ignore Remote System time */ + case LLDP_REMOTE_PORT_ID: + remPortIdPtr := new(string) + *remPortIdPtr = value + ngInfo.State.PortId = remPortIdPtr + case LLDP_REMOTE_REM_ID: + Id := new(string) + *Id = value + ngInfo.State.Id = Id + case LLDP_REMOTE_CHASS_ID_SUBTYPE: + remChassIdTypeVal , err:=strconv.Atoi(value) + if err == nil { + ngInfo.State.ChassisIdType =ocbinds.E_OpenconfigLldp_ChassisIdType(remChassIdTypeVal) + } + case LLDP_REMOTE_MAN_ADDR: + mgmtAdr:= new(string) + *mgmtAdr = value + ngInfo.State.ManagementAddress = mgmtAdr + default: + log.Info("Not a valid attribute!") + } + } + capLst := app.lldpCapTableMap[*ifName] + for capName, enabled := range capLst { + if capName == "Router" { + capInfo, err := ngInfo.Capabilities.NewCapability(6) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 6 + capInfo.State.Enabled = &enabled + } + } else if capName == "Repeater" { + capInfo, err := ngInfo.Capabilities.NewCapability(5) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 5 + capInfo.State.Enabled = &enabled + } + } else if capName == "Bridge" { + capInfo, err := ngInfo.Capabilities.NewCapability(3) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 3 + capInfo.State.Enabled = &enabled + } + } else { + + } + } +} + +/** Helper function to get information from applDB **/ +func (app *lldpApp) getLldpInfoFromDB(ifname *string) { + + lldpTbl, err := app.appDb.GetTable(app.neighTs) + if err != nil { + log.Info("Can't get lldp table") + return + } + + keys, err := lldpTbl.GetKeys() + if err != nil { + log.Info("Can't get lldp keys") + return + } + + + for _, key := range keys { + log.Info("lldp key = ", key.Get(0)) + + lldpEntry, err := app.appDb.GetEntry(app.neighTs, db.Key{Comp: []string{key.Get(0)}}) + if err != nil { + log.Info("can't access neighbor table for key: ", key.Get(0)) + return + } + + if lldpEntry.IsPopulated() { + log.Info("lldp entry populated for key: ", key.Get(0)) + app.lldpTableMap[key.Get(0)] = lldpEntry + } + } + + for _, key := range keys { + if (ifname != nil && key.Get(0) != *ifname) { + continue + } + entryData := app.lldpTableMap[key.Get(0)] + if len(app.lldpNeighTableMap[key.Get(0)]) == 0 { + app.lldpNeighTableMap[key.Get(0)] = make(map[string]string) + } + for lldpAttr := range entryData.Field { + switch lldpAttr { + case LLDP_REMOTE_CAP_ENABLED: + app.getRemoteSysCap(entryData.Get(lldpAttr), key.Get(0), true) + case LLDP_REMOTE_SYS_NAME: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_SYS_NAME] = entryData.Get(lldpAttr) + case LLDP_REMOTE_PORT_DESC: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_DESC] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CHASS_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_CHASS_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CAP_SUPPORTED: + app.getRemoteSysCap(entryData.Get(lldpAttr), key.Get(0), false) + case LLDP_REMOTE_PORT_ID_SUBTYPE: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_ID_SUBTYPE] = entryData.Get(lldpAttr) + case LLDP_REMOTE_SYS_DESC: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_SYS_DESC] = entryData.Get(lldpAttr) + case LLDP_REMOTE_REM_TIME: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_REM_TIME] = entryData.Get(lldpAttr) + case LLDP_REMOTE_PORT_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_REM_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_REM_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CHASS_ID_SUBTYPE: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_CHASS_ID_SUBTYPE] = entryData.Get(lldpAttr) + case LLDP_REMOTE_MAN_ADDR: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_MAN_ADDR] = entryData.Get(lldpAttr) + default: + log.Info("Unknown LLDP Attribute") + } + } + } +} + +/** Helper function to get remote system capabilities into a map **/ +func (app *lldpApp) getRemoteSysCap(capb string, ifname string, setCap bool) { + num_str := strings.Split(capb, " ") + byte, _ := hex.DecodeString(num_str[0] + num_str[1]) + sysCap := byte[0] + sysCap |= byte[1] + + log.Info("sysCap: ", sysCap) + + if (sysCap & (128 >> 1)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Repeater"] = false + } + if (setCap) { + log.Info("Repeater ENABLED") + app.lldpCapTableMap[ifname]["Repeater"] = true + } + } + + if (sysCap & (128 >> 2)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Bridge"] = false + } + if (setCap) { + log.Info("Bridge ENABLED") + app.lldpCapTableMap[ifname]["Bridge"] = true + } + } + + if (sysCap & (128 >> 4)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Router"] = false + } + if (setCap) { + log.Info("Router ENABLED") + app.lldpCapTableMap[ifname]["Router"] = true + } + } +} + diff --git a/src/translib/nonyang_app.go.demo b/src/translib/nonyang_app.go.demo new file mode 100644 index 0000000000..17553ce328 --- /dev/null +++ b/src/translib/nonyang_app.go.demo @@ -0,0 +1,412 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "encoding/json" + "fmt" + "reflect" + "translib/db" + "translib/tlerr" + + log "github.com/golang/glog" +) + +// nonYangDemoApp holds all invocation and state information for +// the non-yang demo app +type nonYangDemoApp struct { + // request information + path *PathInfo + reqData []byte + + // DB client to operate on config_db + confDB *db.DB + + // Cahce for read operation + respJSON interface{} +} + +type jsonObject map[string]interface{} +type jsonArray []interface{} + +var ( + vlanTable = &db.TableSpec{Name: "VLAN"} + memberTable = &db.TableSpec{Name: "VLAN_MEMBER"} +) + +func init() { + register( + "/nonyang/", + &appInfo{appType: reflect.TypeOf(nonYangDemoApp{}), + tablesToWatch: []*db.TableSpec{vlanTable, memberTable}, + isNative: true}) +} + +// initialize function prepares this nonYangDemoApp instance +// for a new request handling. +func (app *nonYangDemoApp) initialize(data appData) { + app.path = NewPathInfo(data.path) + app.reqData = data.payload +} + +func (app *nonYangDemoApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + app.confDB = d + return nil, nil +} + +func (app *nonYangDemoApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + return nil, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + return nil, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + app.confDB = d + return nil, nil +} + +func (app *nonYangDemoApp) translateGet(dbs [db.MaxDB]*db.DB) error { + return nil +} + +func (app *nonYangDemoApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + err := tlerr.NotSupported("Unsuppoted operation") + return nil, nil, err +} + +func (app *nonYangDemoApp) processCreate(d *db.DB) (SetResponse, error) { + var resp SetResponse + pathInfo := app.path + var err error + + log.Infof("Received CREATE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan": + err = app.doCreateVlans() + + case "/nonyang/vlan/{}/member": + err = app.doCreateVlanMembers() + + default: + err = tlerr.NotSupported("Unknown path") + } + + return resp, err +} + +func (app *nonYangDemoApp) processUpdate(d *db.DB) (SetResponse, error) { + var resp SetResponse + return resp, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) processReplace(d *db.DB) (SetResponse, error) { + var resp SetResponse + return resp, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) processDelete(d *db.DB) (SetResponse, error) { + var resp SetResponse + pathInfo := app.path + var err error + + log.Infof("Received DELETE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan/{}": + err = app.doDeleteVlan() + + case "/nonyang/vlan/{}/member/{}": + err = app.doDeleteVlanMember() + + default: + err = tlerr.NotSupported("Unknown path") + } + + return resp, err +} + +func (app *nonYangDemoApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + app.confDB = dbs[db.ConfigDB] + pathInfo := app.path + var err error + + log.Infof("Received GET for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan": + err = app.doGetAllVlans() + + case "/nonyang/vlan/{}": + err = app.doGetVlanByID() + + default: + err = tlerr.NotSupported("Unknown path") + } + + var respData []byte + if err == nil && app.respJSON != nil { + respData, err = json.Marshal(app.respJSON) + } + + return GetResponse{Payload: respData}, err +} + +// doGetAllVlans is the handler for "/nonyang/vlan" path +// Loads all vlans and member data from db and prepares +// a json array - each item being one vlan info. +func (app *nonYangDemoApp) doGetAllVlans() error { + log.Infof("in GetAllVlans") + + // Get all vlans from db + t, err := app.confDB.GetTable(vlanTable) + if err != nil { + return err + } + + var allVlansJSON jsonArray + + keys, _ := t.GetKeys() + log.Infof("Found %d VLAN table keys", len(keys)) + for _, key := range keys { + log.Infof("Processing %v", key.Get(0)) + + vlanInfo, _ := t.GetEntry(key) + vlanJSON, err := app.getVlanJSON(&vlanInfo) + if err != nil { + return err + } + + allVlansJSON = append(allVlansJSON, *vlanJSON) + } + + app.respJSON = &allVlansJSON + return nil +} + +// doGetVlanByID is the handler for "/nonyang/vlan/{id}" path. +// Loads data for one vlan and its members and prepares a json +// object. +func (app *nonYangDemoApp) doGetVlanByID() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in GetVlanByID(), vid=%d", vlanID) + + vlanEntry, err := app.getVlanEntry(vlanID) + if err == nil { + app.respJSON, err = app.getVlanJSON(&vlanEntry) + } + + return err +} + +// getVlanJSON prepares a raw json object for given VLAN table +// entry. Member information are fetched from VLAN_MEMBER table. +func (app *nonYangDemoApp) getVlanJSON(vlanEntry *db.Value) (*jsonObject, error) { + vlanJSON := make(jsonObject) + var memberListJSON jsonArray + + vlanID, _ := vlanEntry.GetInt("vlanid") + vlanName := toVlanName(vlanID) + + log.Infof("Preparing json for vlan %d", vlanID) + + memberPorts := vlanEntry.GetList("members") + log.Infof("%s members = %v", vlanName, memberPorts) + + for _, portName := range memberPorts { + memberJSON := make(jsonObject) + memberJSON["port"] = portName + + memberEntry, err := app.confDB.GetEntry(memberTable, asKey(vlanName, portName)) + if isNotFoundError(err) { + log.Infof("Failed to load VLAN_MEMBER %s,%s; err=%v", vlanName, portName, err) + } else if err != nil { + return nil, err + } else { + memberJSON["mode"] = memberEntry.Get("tagging_mode") + } + + memberListJSON = append(memberListJSON, memberJSON) + } + + vlanJSON["id"] = vlanID + vlanJSON["name"] = vlanName + vlanJSON["members"] = memberListJSON + + return &vlanJSON, nil +} + +// doCreateVlans handles CREATE operation on "/nonyang/vlan" path. +func (app *nonYangDemoApp) doCreateVlans() error { + log.Infof("in doCreateVlans()") + + // vlan creation expects array of vlan ids. + var vlansJSON []int + err := json.Unmarshal(app.reqData, &vlansJSON) + if err != nil { + log.Errorf("Failed to parse input.. err=%v", err) + return tlerr.InvalidArgs("Invalid input") + } + + log.Infof("Receieved %d vlan ids; %v", len(vlansJSON), vlansJSON) + + for _, vid := range vlansJSON { + vlanName := toVlanName(vid) + log.Infof("NEW vlan entry '%s'", vlanName) + + entry := db.Value{Field: make(map[string]string)} + entry.SetInt("vlanid", vid) + err = app.confDB.CreateEntry(vlanTable, asKey(vlanName), entry) + if err != nil { + return err + } + } + + return nil +} + +// doCreateVlanMembers handles CREATE operation on path +// "/nonyang/vlan/{}/member" +func (app *nonYangDemoApp) doCreateVlanMembers() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in doCreateVlanMembers(), vid=%d", vlanID) + + var memberListJSON []map[string]string + err := json.Unmarshal(app.reqData, &memberListJSON) + if err != nil { + log.Errorf("Failed to parse input.. err=%v", err) + return tlerr.InvalidArgs("Invalid input") + } + + vlanName := toVlanName(vlanID) + vlanEntry, err := app.getVlanEntry(vlanID) + if err != nil { + return err + } + + membersList := vlanEntry.GetList("members") + + for _, memberJSON := range memberListJSON { + log.Infof("Processing member info %v", memberJSON) + + portName, _ := memberJSON["port"] + membersList = append(membersList, portName) + + taggingMode, ok := memberJSON["mode"] + if !ok { + taggingMode = "tagged" + } + + log.Infof("NEW vlan_member entry '%s|%s'; mode=%s", vlanName, portName, taggingMode) + memberEntry := db.Value{Field: make(map[string]string)} + memberEntry.Set("tagging_mode", taggingMode) + err = app.confDB.CreateEntry(memberTable, asKey(vlanName, portName), memberEntry) + if err != nil { + return err + } + } + + // Update the vlan table with new member list + log.Infof("SET vlan entry '%s', members=%v", vlanName, membersList) + vlanEntry.SetList("members", membersList) + err = app.confDB.ModEntry(vlanTable, asKey(vlanName), vlanEntry) + + return err +} + +func (app *nonYangDemoApp) doDeleteVlan() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in doDeleteVlan(); vid=%d", vlanID) + + vlanName := toVlanName(vlanID) + vlanKey := asKey(vlanName) + vlanEntry, err := app.confDB.GetEntry(vlanTable, vlanKey) + if isNotFoundError(err) { + log.Infof("Vlan %d not found.. nothing to delete", vlanID) + return nil + } else if err != nil { + return err + } + + // Delete VLAN_MEMBER table entry for each member port + for _, portName := range vlanEntry.GetList("members") { + log.Infof("DEL vlan_member entry '%s|%s'", vlanName, portName) + err = app.confDB.DeleteEntry(memberTable, asKey(vlanName, portName)) + if err != nil { + return err + } + } + + // Delete VLAN table entry + log.Infof("DEL vlan entry '%s'", vlanName) + err = app.confDB.DeleteEntry(vlanTable, vlanKey) + + return err +} + +func (app *nonYangDemoApp) doDeleteVlanMember() error { + vlanID, _ := app.path.IntVar("id") + portName := app.path.Var("port") + log.Infof("in doDeleteVlanMember(); vid=%d, member=%s", vlanID, portName) + + vlanName := toVlanName(vlanID) + vlanEntry, err := app.getVlanEntry(vlanID) + if err != nil { + return err + } + + membersList := vlanEntry.GetList("members") + updatedList := removeElement(membersList, portName) + + // Ignore the request if the port is not a member + if len(membersList) == len(updatedList) { + log.Infof("Vlan %d has no member %s", vlanID, portName) + return nil + } + + // Update VLAN entry with new member list + log.Infof("SET vlan entry '%s', members=%v", vlanName, updatedList) + vlanEntry.SetList("members", updatedList) + err = app.confDB.SetEntry(vlanTable, asKey(vlanName), vlanEntry) + if err != nil { + return err + } + + // Delete VLAN_MEMBER entry + log.Infof("DEL vlan_member entry '%s|%s'", vlanName, portName) + err = app.confDB.DeleteEntry(memberTable, asKey(vlanName, portName)) + + return err +} + +func (app *nonYangDemoApp) getVlanEntry(vlanID int) (db.Value, error) { + entry, err := app.confDB.GetEntry(vlanTable, asKey(toVlanName(vlanID))) + if isNotFoundError(err) { + err = tlerr.NotFound("VLAN %v does not exists", vlanID) + } + return entry, err +} + +// toVlanName returns the vlan name for given vlan id. +func toVlanName(vid int) string { + return fmt.Sprintf("Vlan%d", vid) +} diff --git a/src/translib/ocbinds/oc.go b/src/translib/ocbinds/oc.go new file mode 100644 index 0000000000..3d5333decc --- /dev/null +++ b/src/translib/ocbinds/oc.go @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 ocbinds + +//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 -name '*.yang' -not -name '*annot.yang' | sort)" diff --git a/src/translib/path_utils.go b/src/translib/path_utils.go new file mode 100644 index 0000000000..bf7a35dcab --- /dev/null +++ b/src/translib/path_utils.go @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "translib/ocbinds" + + log "github.com/golang/glog" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" +) + +// PathInfo structure contains parsed path information. +type PathInfo struct { + Path string + Template string + Vars map[string]string +} + +// Var returns the string value for a path variable. Returns +// empty string if no such variable exists. +func (p *PathInfo) Var(name string) string { + return p.Vars[name] +} + +// IntVar returns the value for a path variable as an int. +// Returns 0 if no such variable exists. Returns an error +// if the value is not an integer. +func (p *PathInfo) IntVar(name string) (int, error) { + val := p.Vars[name] + if len(val) == 0 { + return 0, nil + } + + return strconv.Atoi(val) +} + +// HasPrefix checks if this path template starts with given +// prefix.. Shorthand for strings.HasPrefix(p.Template, s) +func (p *PathInfo) HasPrefix(s string) bool { + return strings.HasPrefix(p.Template, s) +} + +// HasSuffix checks if this path template ends with given +// suffix.. Shorthand for strings.HasSuffix(p.Template, s) +func (p *PathInfo) HasSuffix(s string) bool { + return strings.HasSuffix(p.Template, s) +} + +// NewPathInfo parses given path string into a PathInfo structure. +func NewPathInfo(path string) *PathInfo { + var info PathInfo + info.Path = path + info.Vars = make(map[string]string) + + //TODO optimize using regexp + var template strings.Builder + r := strings.NewReader(path) + + for r.Len() > 0 { + c, _ := r.ReadByte() + if c != '[' { + template.WriteByte(c) + continue + } + + name := readUntil(r, '=') + value := readUntil(r, ']') + if len(name) != 0 { + fmt.Fprintf(&template, "{}") + info.Vars[name] = value + } + } + + info.Template = template.String() + + return &info +} + +func readUntil(r *strings.Reader, delim byte) string { + var buff strings.Builder + for { + c, err := r.ReadByte() + if err == nil && c != delim { + buff.WriteByte(c) + } else { + break + } + } + + return buff.String() +} + +func getParentNode(targetUri *string, deviceObj *ocbinds.Device) (*interface{}, *yang.Entry, error) { + path, err := ygot.StringToPath(*targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return nil, nil, err + } + + var pathList []*gnmi.PathElem = path.Elem + + parentPath := &gnmi.Path{} + + for i := 0; i < (len(pathList) - 1); i++ { + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + parentPath.Elem = append(parentPath.Elem, pathList[i]) + } + + treeNodeList, err2 := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, parentPath) + if err2 != nil { + return nil, nil, err2 + } + + if len(treeNodeList) == 0 { + return nil, nil, errors.New("Invalid URI") + } + + return &(treeNodeList[0].Data), treeNodeList[0].Schema, nil +} + +func getNodeName(targetUri *string, deviceObj *ocbinds.Device) (string, error) { + path, err := ygot.StringToPath(*targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Error("Error in uri to path conversion: ", err) + return "", err + } + + pathList := path.Elem + for i := 0; i < len(pathList); i++ { + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + } + + treeNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path) + if err != nil { + log.Error("Error in uri to path conversion: ", err) + return "", err + } + + if len(treeNodeList) == 0 { + return "", errors.New("Invalid URI") + } + + return treeNodeList[0].Schema.Name, nil +} + +func getObjectFieldName(targetUri *string, deviceObj *ocbinds.Device, ygotTarget *interface{}) (string, error) { + parentObjIntf, _, err := getParentNode(targetUri, deviceObj) + if err != nil { + return "", err + } + valObj := reflect.ValueOf(*parentObjIntf).Elem() + parentObjType := reflect.TypeOf(*parentObjIntf).Elem() + + for i := 0; i < parentObjType.NumField(); i++ { + if reflect.ValueOf(*ygotTarget).Kind() == reflect.Ptr && valObj.Field(i).Kind() == reflect.Ptr { + if valObj.Field(i).Pointer() == reflect.ValueOf(*ygotTarget).Pointer() { + return parentObjType.Field(i).Name, nil + } + } else if valObj.Field(i).String() == reflect.ValueOf(*ygotTarget).String() { + return parentObjType.Field(i).Name, nil + } + } + return "", errors.New("Target object not found") +} diff --git a/src/translib/path_utils_test.go b/src/translib/path_utils_test.go new file mode 100644 index 0000000000..e8d9c19b63 --- /dev/null +++ b/src/translib/path_utils_test.go @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib + +import ( + "reflect" + "strings" + "testing" + + "translib/ocbinds" +) + +func TestGetParentNode(t *testing.T) { + + tests := []struct { + tid int + targetUri string + appRootType reflect.Type + want string + }{{ + tid: 1, + targetUri: "/openconfig-acl:acl/acl-sets/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl", + }, { + tid: 2, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=MyACL1][type=ACL_IPV4]/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl_AclSets", + }, { + tid: 3, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/description", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl_AclSets_AclSet_State", + }} + + for _, tt := range tests { + var deviceObj ocbinds.Device = ocbinds.Device{} + _, err := getRequestBinder(&tt.targetUri, nil, 1, &tt.appRootType).unMarshallUri(&deviceObj) + if err != nil { + t.Error("TestGetParentNode: Error in unmarshalling the URI", err) + } else { + parentNode, _, err := getParentNode(&tt.targetUri, &deviceObj) + if err != nil { + t.Error("TestGetParentNode: Error in getting the parent node: ", err) + } else if parentNode == nil { + t.Error("TestGetParentNode: Error in getting the parent node") + } else if reflect.TypeOf(*parentNode).Elem().Name() != tt.want { + t.Error("TestGetParentNode: Error in getting the parent node: ", reflect.TypeOf(*parentNode).Elem().Name()) + } + } + } +} + +func TestGetNodeName(t *testing.T) { + + tests := []struct { + tid int + targetUri string + appRootType reflect.Type + want string + }{{ + tid: 1, + targetUri: "/openconfig-acl:acl/acl-sets/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "acl-sets", + }, { + tid: 2, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=MyACL1][type=ACL_IPV4]/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "acl-set", + }, { + tid: 3, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/description", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "description", + }, { + tid: 4, // Negative test case + targetUri: "/openconfig-acl:acl/acl-sets/acl-set[name=Sample][type=ACL_IPV4]/state/descriptXX", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "rpc error: code = InvalidArgument desc = no match found in *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_State, for path elem:", + }, { + //Negative test case + tid: 9, + uri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/descriptXX", + opcode: 1, + payload: []byte{}, + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "rpc error: code = InvalidArgument desc = no match found in *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_State, for path elem: 0 { + for _, err := range errs { + fmt.Fprintln(os.Stderr, err) + } + } +} + +func getOcModelsList () ([]string) { + var fileList []string + file, err := os.Open(YangPath + "models_list") + if err != nil { + return fileList + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + fileEntry := scanner.Text() + if strings.HasPrefix(fileEntry, "#") != true { + _, err := os.Stat(YangPath + fileEntry) + if err != nil { + continue + } + fileList = append(fileList, fileEntry) + } + } + return fileList +} + +func getDefaultModelsList () ([]string) { + var files []string + fileInfo, err := ioutil.ReadDir(YangPath) + if err != nil { + return files + } + + for _, file := range fileInfo { + if strings.HasPrefix(file.Name(), "sonic-") && !strings.HasSuffix(file.Name(), "-dev.yang") && filepath.Ext(file.Name()) == ".yang" { + files = append(files, file.Name()) + } + } + return files +} + +func init() { + yangFiles := []string{} + ocList := getOcModelsList() + yangFiles = getDefaultModelsList() + yangFiles = append(yangFiles, ocList...) + fmt.Println("Yang model List:", yangFiles) + err := loadYangModules(yangFiles...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } +} + +func loadYangModules(files ...string) error { + + var err error + + paths := []string{YangPath} + + for _, path := range paths { + expanded, err := yang.PathsWithModules(path) + if err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + yang.AddPath(expanded...) + } + + ms := yang.NewModules() + + for _, name := range files { + if err := ms.Read(name); err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + } + + // Process the Yang files + reportIfError(ms.Process()) + + // Keep track of the top level modules we read in. + // Those are the only modules we want to print below. + mods := map[string]*yang.Module{} + var names []string + + for _, m := range ms.Modules { + if mods[m.Name] == nil { + mods[m.Name] = m + names = append(names, m.Name) + } + } + + sonic_entries := make([]*yang.Entry, len(names)) + oc_entries := make(map[string]*yang.Entry) + oc_annot_entries := make([]*yang.Entry, len(names)) + sonic_annot_entries := make([]*yang.Entry, len(names)) + + for _, n := range names { + if strings.Contains(n, "annot") && strings.Contains(n, "sonic") { + sonic_annot_entries = append(sonic_annot_entries, yang.ToEntry(mods[n])) + } else if strings.Contains(n, "annot") { + oc_annot_entries = append(oc_annot_entries, yang.ToEntry(mods[n])) + } else if strings.Contains(n, "sonic") { + sonic_entries = append(sonic_entries, yang.ToEntry(mods[n])) + } else if oc_entries[n] == nil { + oc_entries[n] = yang.ToEntry(mods[n]) + } + } + + dbMapBuild(sonic_entries) + annotDbSpecMap(sonic_annot_entries) + annotToDbMapBuild(oc_annot_entries) + yangToDbMapBuild(oc_entries) + + return err +} diff --git a/src/translib/transformer/xconst.go b/src/translib/transformer/xconst.go new file mode 100644 index 0000000000..8fcdabdc75 --- /dev/null +++ b/src/translib/transformer/xconst.go @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +const ( + YANG_MODULE = "module" + YANG_LIST = "list" + YANG_CONTAINER = "container" + YANG_LEAF = "leaf" + YANG_LEAF_LIST = "leaf-list" + + YANG_ANNOT_DB_NAME = "db-name" + YANG_ANNOT_TABLE_NAME = "table-name" + YANG_ANNOT_FIELD_NAME = "field-name" + YANG_ANNOT_KEY_DELIM = "key-delimiter" + YANG_ANNOT_TABLE_XFMR = "table-transformer" + YANG_ANNOT_FIELD_XFMR = "field-transformer" + YANG_ANNOT_KEY_XFMR = "key-transformer" + YANG_ANNOT_POST_XFMR = "post-transformer" + YANG_ANNOT_SUBTREE_XFMR = "subtree-transformer" + YANG_ANNOT_VALIDATE_FUNC = "get-validate" + + REDIS_DB_TYPE_APPLN = "APPL_DB" + REDIS_DB_TYPE_ASIC = "ASIC_DB" + REDIS_DB_TYPE_CONFIG = "CONFIG_DB" + REDIS_DB_TYPE_COUNTER = "COUNTERS_DB" + REDIS_DB_TYPE_LOG_LVL = "LOGLEVEL_DB" + REDIS_DB_TYPE_STATE = "STATE_DB" + REDIS_DB_TYPE_FLX_COUNTER = "FLEX_COUNTER_DB" + + XPATH_SEP_FWD_SLASH = "/" + XFMR_EMPTY_STRING = "" + SONIC_TABLE_INDEX = 2 + +) diff --git a/src/translib/transformer/xfmr_acl.go b/src/translib/transformer/xfmr_acl.go new file mode 100644 index 0000000000..2ded0fe655 --- /dev/null +++ b/src/translib/transformer/xfmr_acl.go @@ -0,0 +1,976 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "bytes" + "errors" + "fmt" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "strconv" + "strings" + "translib/db" + "translib/ocbinds" + "translib/tlerr" +) + +func init() { + XlateFuncBind("DbToYang_acl_set_name_xfmr", DbToYang_acl_set_name_xfmr) + XlateFuncBind("YangToDb_acl_type_field_xfmr", YangToDb_acl_type_field_xfmr) + XlateFuncBind("DbToYang_acl_type_field_xfmr", DbToYang_acl_type_field_xfmr) + XlateFuncBind("YangToDb_acl_set_key_xfmr", YangToDb_acl_set_key_xfmr) + XlateFuncBind("DbToYang_acl_set_key_xfmr", DbToYang_acl_set_key_xfmr) + XlateFuncBind("YangToDb_acl_entry_key_xfmr", YangToDb_acl_entry_key_xfmr) + XlateFuncBind("DbToYang_acl_entry_key_xfmr", DbToYang_acl_entry_key_xfmr) + XlateFuncBind("DbToYang_acl_entry_sequenceid_xfmr", DbToYang_acl_entry_sequenceid_xfmr) + XlateFuncBind("YangToDb_acl_l2_ethertype_xfmr", YangToDb_acl_l2_ethertype_xfmr) + XlateFuncBind("DbToYang_acl_l2_ethertype_xfmr", DbToYang_acl_l2_ethertype_xfmr) + XlateFuncBind("YangToDb_acl_ip_protocol_xfmr", YangToDb_acl_ip_protocol_xfmr) + XlateFuncBind("DbToYang_acl_ip_protocol_xfmr", DbToYang_acl_ip_protocol_xfmr) + XlateFuncBind("YangToDb_acl_source_port_xfmr", YangToDb_acl_source_port_xfmr) + XlateFuncBind("DbToYang_acl_source_port_xfmr", DbToYang_acl_source_port_xfmr) + XlateFuncBind("YangToDb_acl_destination_port_xfmr", YangToDb_acl_destination_port_xfmr) + XlateFuncBind("DbToYang_acl_destination_port_xfmr", DbToYang_acl_destination_port_xfmr) + XlateFuncBind("YangToDb_acl_tcp_flags_xfmr", YangToDb_acl_tcp_flags_xfmr) + XlateFuncBind("DbToYang_acl_tcp_flags_xfmr", DbToYang_acl_tcp_flags_xfmr) + XlateFuncBind("YangToDb_acl_port_bindings_xfmr", YangToDb_acl_port_bindings_xfmr) + XlateFuncBind("DbToYang_acl_port_bindings_xfmr", DbToYang_acl_port_bindings_xfmr) + XlateFuncBind("YangToDb_acl_forwarding_action_xfmr", YangToDb_acl_forwarding_action_xfmr) + XlateFuncBind("DbToYang_acl_forwarding_action_xfmr", DbToYang_acl_forwarding_action_xfmr) + XlateFuncBind("validate_ipv4", validate_ipv4) + XlateFuncBind("validate_ipv6", validate_ipv6) + XlateFuncBind("acl_post_xfmr", acl_post_xfmr) +} + +const ( + ACL_TABLE = "ACL_TABLE" + RULE_TABLE = "ACL_RULE" + SONIC_ACL_TYPE_IPV4 = "L3" + SONIC_ACL_TYPE_L2 = "L2" + SONIC_ACL_TYPE_IPV6 = "L3V6" + OPENCONFIG_ACL_TYPE_IPV4 = "ACL_IPV4" + OPENCONFIG_ACL_TYPE_IPV6 = "ACL_IPV6" + OPENCONFIG_ACL_TYPE_L2 = "ACL_L2" + ACL_TYPE = "type" + MIN_PRIORITY = 1 + MAX_PRIORITY = 65535 +) + +/* E_OpenconfigAcl_ACL_TYPE */ +var ACL_TYPE_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4), 10): SONIC_ACL_TYPE_IPV4, + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6), 10): SONIC_ACL_TYPE_IPV6, + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2), 10): SONIC_ACL_TYPE_L2, +} + +/* E_OpenconfigAcl_FORWARDING_ACTION */ +var ACL_FORWARDING_ACTION_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT), 10): "FORWARD", + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP), 10): "DROP", + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_REJECT), 10): "REDIRECT", +} + +/* E_OpenconfigPacketMatchTypes_IP_PROTOCOL */ +var IP_PROTOCOL_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_ICMP), 10): "1", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_IGMP), 10): "2", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_TCP), 10): "6", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_UDP), 10): "17", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_RSVP), 10): "46", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_GRE), 10): "47", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_AUTH), 10): "51", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_PIM), 10): "103", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_L2TP), 10): "115", +} + +var ETHERTYPE_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_ETHERTYPE]uint32{ + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_LLDP: 0x88CC, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_VLAN: 0x8100, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ROCE: 0x8915, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ARP: 0x0806, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4: 0x0800, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6: 0x86DD, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_MPLS: 0x8847, +} + +func getAclRoot(s *ygot.GoStruct) *ocbinds.OpenconfigAcl_Acl { + deviceObj := (*s).(*ocbinds.Device) + return deviceObj.Acl +} + +func getAclTypeOCEnumFromName(val string) (ocbinds.E_OpenconfigAcl_ACL_TYPE, error) { + switch val { + case "ACL_IPV4", "openconfig-acl:ACL_IPV4": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4, nil + case "ACL_IPV6", "openconfig-acl:ACL_IPV6": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6, nil + case "ACL_L2", "openconfig-acl:ACL_L2": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2, nil + default: + return ocbinds.OpenconfigAcl_ACL_TYPE_UNSET, + tlerr.NotSupported("ACL Type '%s' not supported", val) + } +} +func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_TYPE) string { + aclN := strings.Replace(strings.Replace(aclname, " ", "_", -1), "-", "_", -1) + aclT := acltype.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(acltype)].Name + return aclN + "_" + aclT +} + +func getOCAclKeysFromStrDBKey(aclKey string) (string, ocbinds.E_OpenconfigAcl_ACL_TYPE) { + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + + if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + return aclOrigName, aclOrigType +} + +func getTransportConfigTcpFlags(tcpFlags string) []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS { + var flags []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS + if len(tcpFlags) > 0 { + flagStr := strings.Split(tcpFlags, "/")[0] + flagNumber, _ := strconv.ParseUint(strings.Replace(flagStr, "0x", "", -1), 16, 32) + for i := 0; i < 8; i++ { + mask := 1 << uint(i) + if (int(flagNumber) & mask) > 0 { + switch int(flagNumber) & mask { + case 0x01: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN) + case 0x02: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN) + case 0x04: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST) + case 0x08: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH) + case 0x10: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK) + case 0x20: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG) + case 0x40: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE) + case 0x80: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR) + default: + } + } + } + } + return flags +} + +func getL2EtherType(etherType uint64) interface{} { + for k, v := range ETHERTYPE_MAP { + if uint32(etherType) == v { + return k + } + } + return uint16(etherType) +} + +//////////////////////////////////////////// +// Validate callpoints +//////////////////////////////////////////// +var validate_ipv4 ValidateCallpoint = func(inParams XfmrParams) (bool) { + if strings.Contains(inParams.key, "ACL_IPV4") { + return true + } + return false +} +var validate_ipv6 ValidateCallpoint = func(inParams XfmrParams) (bool) { + if strings.Contains(inParams.key, "ACL_IPV6") { + return true + } + return false +} + +//////////////////////////////////////////// +// Post Transformer +//////////////////////////////////////////// +var acl_post_xfmr PostXfmrFunc = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + log.Info("In Post transformer") + //TODO: check if a default ACL Rule exists, else create one and update the resultMap with default rule + // Return will be the updated result map + return (*inParams.dbDataMap)[inParams.curDb], nil +} + +//////////////////////////////////////////// +// Bi-directoonal overloaded methods +//////////////////////////////////////////// +var YangToDb_acl_forwarding_action_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["PACKET_ACTION"] = "" + return res_map, err + } + action, _ := inParams.param.(ocbinds.E_OpenconfigAcl_FORWARDING_ACTION) + log.Info("YangToDb_acl_forwarding_action_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " forwarding_action: ", action) + res_map["PACKET_ACTION"] = findInMap(ACL_FORWARDING_ACTION_MAP, strconv.FormatInt(int64(action), 10)) + return res_map, err +} +var DbToYang_acl_forwarding_action_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_forwarding_action_xfmr", data, inParams.ygRoot) + oc_action := findInMap(ACL_FORWARDING_ACTION_MAP, data[RULE_TABLE][inParams.key].Field["PACKET_ACTION"]) + n, err := strconv.ParseInt(oc_action, 10, 64) + result["forwarding-action"] = ocbinds.E_OpenconfigAcl_FORWARDING_ACTION(n).ΛMap()["E_OpenconfigAcl_FORWARDING_ACTION"][n].Name + return result, err +} + +var YangToDb_acl_type_field_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map[ACL_TYPE] = "" + return res_map, err + } + + acltype, _ := inParams.param.(ocbinds.E_OpenconfigAcl_ACL_TYPE) + log.Info("YangToDb_acl_type_field_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " acltype: ", acltype) + res_map[ACL_TYPE] = findInMap(ACL_TYPE_MAP, strconv.FormatInt(int64(acltype), 10)) + return res_map, err +} +var DbToYang_acl_type_field_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_type_field_xfmr", data, inParams.ygRoot) + oc_acltype := findInMap(ACL_TYPE_MAP, data[ACL_TABLE][inParams.key].Field[ACL_TYPE]) + n, err := strconv.ParseInt(oc_acltype, 10, 64) + result[ACL_TYPE] = ocbinds.E_OpenconfigAcl_ACL_TYPE(n).ΛMap()["E_OpenconfigAcl_ACL_TYPE"][n].Name + return result, err +} + +var YangToDb_acl_set_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + var aclkey string + var err error + var oc_aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + log.Info("YangToDb_acl_set_key_xfmr: ", inParams.ygRoot, inParams.uri) + pathInfo := NewPathInfo(inParams.uri) + + if len(pathInfo.Vars) < 2 { + err = errors.New("Invalid xpath, key attributes not found") + return aclkey, err + } + + oc_aclType, err = getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + err = errors.New("OC Acl type name to OC Acl Enum failed") + return aclkey, err + } + + aclkey = getAclKeyStrFromOCKey(pathInfo.Var("name"), oc_aclType) + log.Info("YangToDb_acl_set_key_xfmr - acl_set_key : ", aclkey) + + return aclkey, err +} + +var DbToYang_acl_set_key_xfmr KeyXfmrDbToYang = func(inParams XfmrParams) (map[string]interface{}, error) { + rmap := make(map[string]interface{}) + var err error + var aclNameStr string + var aclTypeStr string + aclkey := inParams.key + log.Info("DbToYang_acl_set_key_xfmr: ", aclkey) + if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV4) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclTypeStr = "ACL_IPV4" + } else if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV6) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclTypeStr = "ACL_IPV6" + } else if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_L2) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclTypeStr = "ACL_L2" + } else { + err = errors.New("Invalid key for acl set.") + log.Info("Invalid Keys for acl acl set", aclkey) + } + rmap["name"] = aclNameStr + rmap["type"] = aclTypeStr + return rmap, err +} + +var DbToYang_acl_set_name_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + res_map := make(map[string]interface{}) + var err error + log.Info("DbToYang_acl_set_name_xfmr: ", inParams.key) + /*name attribute corresponds to key in redis table*/ + aclName, _ := getOCAclKeysFromStrDBKey(inParams.key) + res_map["name"] = aclName + log.Info("acl-set/config/name ", res_map) + return res_map, err +} + +var YangToDb_acl_entry_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + var entry_key string + var err error + var oc_aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + log.Info("YangToDb_acl_entry_key_xfmr: ", inParams.ygRoot, inParams.uri) + pathInfo := NewPathInfo(inParams.uri) + + if len(pathInfo.Vars) < 3 { + err = errors.New("Invalid xpath, key attributes not found") + return entry_key, err + } + + oc_aclType, err = getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + err = errors.New("OC Acl type name to OC Acl Enum failed") + return entry_key, err + } + + aclkey := getAclKeyStrFromOCKey(pathInfo.Var("name"), oc_aclType) + var rulekey string + if strings.Contains(pathInfo.Template, "/acl-entry{sequence-id}") { + rulekey = "RULE_" + pathInfo.Var("sequence-id") + } + entry_key = aclkey + "|" + rulekey + + log.Info("YangToDb_acl_entry_key_xfmr - entry_key : ", entry_key) + + return entry_key, err +} + +var DbToYang_acl_entry_key_xfmr KeyXfmrDbToYang = func(inParams XfmrParams) (map[string]interface{}, error) { + rmap := make(map[string]interface{}) + var err error + entry_key := inParams.key + log.Info("DbToYang_acl_entry_key_xfmr: ", entry_key) + + key := strings.Split(entry_key, "|") + if len(key) < 2 { + err = errors.New("Invalid key for acl entries.") + log.Info("Invalid Keys for acl enmtries", entry_key) + return rmap, err + } + + dbAclRule := key[1] + seqId := strings.Replace(dbAclRule, "RULE_", "", 1) + rmap["sequence-id"], _ = strconv.ParseFloat(seqId, 64) + return rmap, err +} + +var DbToYang_acl_entry_sequenceid_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + res_map := make(map[string]interface{}) + var err error + log.Info("DbToYang_acl_entry_sequenceid_xfmr: ", inParams.key) + /*sequenec-id attribute corresponds to key in redis table*/ + res, err := DbToYang_acl_entry_key_xfmr(inParams) + log.Info("acl-entry/config/sequence-id ", res) + if err != nil { + return res_map, err + } + if seqId, ok := res["sequence-id"]; !ok { + log.Error("sequence-id not found in acl entry") + return res_map, err + } else { + res_map["sequence-id"] = seqId + } + return res_map, err +} + +var YangToDb_acl_l2_ethertype_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + + if inParams.param == nil { + res_map["ETHER_TYPE"] = "" + return res_map, err + } + ethertypeType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " ethertypeType: ", ethertypeType) + var b bytes.Buffer + switch ethertypeType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE) + fmt.Fprintf(&b, "0x%0.4x", ETHERTYPE_MAP[v.E_OpenconfigPacketMatchTypes_ETHERTYPE]) + res_map["ETHER_TYPE"] = b.String() + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16) + fmt.Fprintf(&b, "0x%0.4x", v.Uint16) + res_map["ETHER_TYPE"] = b.String() + break + } + return res_map, err +} + +var DbToYang_acl_l2_ethertype_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_l2_ethertype_xfmr", data, inParams.ygRoot) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + etype, ok := ruleInst.Field["ETHER_TYPE"] + + if ok { + etypeVal, _ := strconv.ParseUint(strings.Replace(etype, "0x", "", -1), 16, 32) + result["protocol"] = getL2EtherType(etypeVal) + } else { + err = errors.New("ETHER_TYPE field not found in DB") + } + return result, nil +} + +var YangToDb_acl_ip_protocol_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + + if inParams.param == nil { + res_map["IP_PROTOCOL"] = "" + return res_map, err + } + protocolType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " protocolType: ", protocolType) + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + res_map["IP_PROTOCOL"] = findInMap(IP_PROTOCOL_MAP, strconv.FormatInt(int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL), 10)) + v = nil + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8) + res_map["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + break + } + return res_map, err +} + +var DbToYang_acl_ip_protocol_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_ip_protocol_xfmr", data, inParams.ygRoot) + oc_protocol := findByValue(IP_PROTOCOL_MAP, data[RULE_TABLE][inParams.key].Field["IP_PROTOCOL"]) + n, err := strconv.ParseInt(oc_protocol, 10, 64) + result["protocol"] = ocbinds.E_OpenconfigPacketMatchTypes_IP_PROTOCOL(n).ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][n].Name + return result, err +} + +var YangToDb_acl_source_port_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["L4_SRC_PORT"] = "" + return res_map, err + } + sourceportType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " sourceportType: ", sourceportType) + switch sourceportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort) + res_map["L4_SRC_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort)].Name + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String) + res_map["L4_SRC_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16) + res_map["L4_SRC_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + break + } + return res_map, err +} + +var DbToYang_acl_source_port_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_source_port_xfmr: ", data, inParams.ygRoot) + result := make(map[string]interface{}) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + port, ok := ruleInst.Field["L4_SRC_PORT"] + if ok { + result["source-port"] = port + return result, nil + } + + portRange, ok := ruleInst.Field["L4_SRC_PORT_RANGE"] + if ok { + result["source-port"] = portRange + return result, nil + } else { + err = errors.New("PORT/PORT_RANGE field not found in DB") + } + return result, err +} + +var YangToDb_acl_destination_port_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["L4_DST_PORT_RANGE"] = "" + return res_map, err + } + destportType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " destportType: ", destportType) + switch destportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort) + res_map["L4_DST_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort)].Name + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String) + res_map["L4_DST_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16) + res_map["L4_DST_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + break + } + return res_map, err +} + +var DbToYang_acl_destination_port_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_destination_port_xfmr: ", data, inParams.ygRoot) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + port, ok := ruleInst.Field["L4_DST_PORT"] + if ok { + result["destination-port"] = port + return result, nil + } + + portRange, ok := ruleInst.Field["L4_DST_PORT_RANGE"] + if ok { + result["destination-port"] = portRange + return result, nil + } else { + err = errors.New("DST PORT/PORT_RANGE field not found in DB") + } + return result, err +} + +var YangToDb_acl_tcp_flags_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + log.Info("YangToDb_acl_tcp_flags_xfmr: ") + var tcpFlags uint32 = 0x00 + var b bytes.Buffer + if inParams.param == nil { + res_map["TCP_FLAGS"] = b.String() + return res_map, err + } + log.Info("YangToDb_acl_tcp_flags_xfmr: ", inParams.ygRoot, inParams.uri) + v := reflect.ValueOf(inParams.param) + + flags := v.Interface().([]ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS) + for _, flag := range flags { + fmt.Println("TCP Flag name: " + flag.ΛMap()["E_OpenconfigPacketMatchTypes_TCP_FLAGS"][int64(flag)].Name) + switch flag { + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN: + tcpFlags |= 0x01 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN: + tcpFlags |= 0x02 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST: + tcpFlags |= 0x04 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH: + tcpFlags |= 0x08 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK: + tcpFlags |= 0x10 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG: + tcpFlags |= 0x20 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE: + tcpFlags |= 0x40 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR: + tcpFlags |= 0x80 + break + } + } + fmt.Fprintf(&b, "0x%0.2x/0x%0.2x", tcpFlags, tcpFlags) + res_map["TCP_FLAGS"] = b.String() + return res_map, err +} + +var DbToYang_acl_tcp_flags_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_tcp_flags_xfmr: ", data, inParams.ygRoot) + result := make(map[string]interface{}) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + tcpFlag, ok := ruleInst.Field["TCP_FLAGS"] + if ok { + result["tcp-flags"] = getTransportConfigTcpFlags(tcpFlag) + return result, nil + } + return result, nil +} + +var YangToDb_acl_port_bindings_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + var err error + res_map := make(map[string]map[string]db.Value) + aclTableMap := make(map[string]db.Value) + log.Info("YangToDb_acl_port_bindings_xfmr: ", inParams.ygRoot, inParams.uri) + + aclObj := getAclRoot(inParams.ygRoot) + if aclObj.Interfaces == nil { + return res_map, err + } + aclInterfacesMap := make(map[string][]string) + for intfId, _ := range aclObj.Interfaces.Interface { + intf := aclObj.Interfaces.Interface[intfId] + if intf != nil { + if intf.IngressAclSets != nil && len(intf.IngressAclSets.IngressAclSet) > 0 { + for inAclKey, _ := range intf.IngressAclSets.IngressAclSet { + aclName := getAclKeyStrFromOCKey(inAclKey.SetName, inAclKey.Type) + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + _, ok := aclTableMap[aclName] + if !ok { + aclTableMap[aclName] = db.Value{Field: make(map[string]string)} + } + aclTableMap[aclName].Field["stage"] = "INGRESS" + } + } + if intf.EgressAclSets != nil && len(intf.EgressAclSets.EgressAclSet) > 0 { + for outAclKey, _ := range intf.EgressAclSets.EgressAclSet { + aclName := getAclKeyStrFromOCKey(outAclKey.SetName, outAclKey.Type) + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + _, ok := aclTableMap[aclName] + if !ok { + aclTableMap[aclName] = db.Value{Field: make(map[string]string)} + } + aclTableMap[aclName].Field["stage"] = "EGRESS" + } + } + } + } + for k, _ := range aclInterfacesMap { + val := aclTableMap[k] + (&val).SetList("ports", aclInterfacesMap[k]) + } + res_map[ACL_TABLE] = aclTableMap + return res_map, err +} + +var DbToYang_acl_port_bindings_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_port_bindings_xfmr: ", data, inParams.ygRoot) + + aclTbl := data["ACL_TABLE"] + var ruleTbl map[string]map[string]db.Value + + // repopulate to use existing code + ruleTbl = make(map[string]map[string]db.Value) + for key, element := range data["ACL_RULE"] { + // split into aclKey and ruleKey + tokens := strings.Split(key, "|") + if ruleTbl[tokens[0]] == nil { + ruleTbl[tokens[0]] = make(map[string]db.Value) + } + ruleTbl[tokens[0]][tokens[1]] = db.Value{Field: make(map[string]string)} + ruleTbl[tokens[0]][tokens[1]] = element + } + + pathInfo := NewPathInfo(inParams.uri) + + acl := getAclRoot(inParams.ygRoot) + targetUriPath, _ := getYangPathFromUri(pathInfo.Path) + if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + for intfId := range acl.Interfaces.Interface { + intfData := acl.Interfaces.Interface[intfId] + ygot.BuildEmptyTree(intfData) + if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/ingress-acl-sets") { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "INGRESS") + } else if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/egress-acl-sets") { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "EGRESS") + } else { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "INGRESS") + if err != nil { + return err + } + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "EGRESS") + } + } + } else { + err = getAllBindingsInfo(aclTbl, ruleTbl, inParams.ygRoot) + } + + return err +} + +func convertInternalToOCAclRuleBinding(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, priority uint32, seqId int64, direction string, aclSet ygot.GoStruct, entrySet ygot.GoStruct) { + if seqId == -1 { + seqId = int64(MAX_PRIORITY - priority) + } + + var num uint64 + num = 0 + var ruleId uint32 = uint32(seqId) + + if direction == "INGRESS" { + var ingressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + ingressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet) + if ingressEntrySet, ok = ingressAclSet.AclEntries.AclEntry[ruleId]; !ok { + ingressEntrySet, _ = ingressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + ingressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry) + } + if ingressEntrySet != nil { + ygot.BuildEmptyTree(ingressEntrySet) + ingressEntrySet.State.SequenceId = &ruleId + ingressEntrySet.State.MatchedPackets = &num + ingressEntrySet.State.MatchedOctets = &num + } + } else if direction == "EGRESS" { + var egressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + egressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet) + if egressEntrySet, ok = egressAclSet.AclEntries.AclEntry[ruleId]; !ok { + egressEntrySet, _ = egressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + egressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry) + } + if egressEntrySet != nil { + ygot.BuildEmptyTree(egressEntrySet) + egressEntrySet.State.SequenceId = &ruleId + egressEntrySet.State.MatchedPackets = &num + egressEntrySet.State.MatchedOctets = &num + } + } +} + +func convertInternalToOCAclBinding(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, aclName string, intfId string, direction string, intfAclSet ygot.GoStruct) error { + var err error + if _, ok := aclTableMap[aclName]; !ok { + err = errors.New("Acl entry not found, convertInternalToOCAclBinding") + return err + } else { + aclEntry := aclTableMap[aclName] + if !contains(aclEntry.GetList("ports"), intfId) { + return tlerr.InvalidArgs("Acl %s not binded with %s", aclName, intfId) + } + } + + for ruleName := range ruleTableMap[aclName] { + if ruleName != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(ruleName, "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, intfAclSet, nil) + } + } + + return err +} + +func getAllBindingsInfo(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, ygRoot *ygot.GoStruct) error { + var err error + acl := getAclRoot(ygRoot) + + var interfaces []string + for aclName := range aclTableMap { + aclData := aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + ygot.BuildEmptyTree(acl) + for _, intfId := range interfaces { + var intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface + intfData, ok := acl.Interfaces.Interface[intfId] + if !ok { + intfData, _ = acl.Interfaces.NewInterface(intfId) + } + ygot.BuildEmptyTree(intfData) + err = getAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfData, intfId, "INGRESS") + err = getAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfData, intfId, "EGRESS") + } + return err +} + +func getAclBindingInfoForInterfaceData(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface, intfId string, direction string) error { + var err error + if intfData != nil { + intfData.Config.Id = intfData.Id + intfData.State.Id = intfData.Id + } + if direction == "INGRESS" { + if intfData.IngressAclSets != nil && len(intfData.IngressAclSets.IngressAclSet) > 0 { + for ingressAclSetKey, _ := range intfData.IngressAclSets.IngressAclSet { + aclName := strings.Replace(strings.Replace(ingressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := ingressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(ingressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + ingressAclSet := intfData.IngressAclSets.IngressAclSet[ingressAclSetKey] + if ingressAclSet != nil && ingressAclSet.AclEntries != nil && len(ingressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range ingressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := ingressAclSet.AclEntries.AclEntry[seqId] + _, ok := ruleTableMap[aclKey+"|"+rulekey] + if !ok { + log.Info("Acl Rule not found ", aclKey, rulekey) + err = errors.New("Acl Rule not found ingress, getAclBindingInfoForInterfaceData") + return err + } + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclName, Type: ingressAclSetKey.Type} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclName, Type: ingressAclSetKey.Type} + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclKey, intfId, direction, ingressAclSet) + } + } + } else { + err = findAndGetAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfId, direction, intfData) + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil && len(intfData.EgressAclSets.EgressAclSet) > 0 { + for egressAclSetKey, _ := range intfData.EgressAclSets.EgressAclSet { + aclName := strings.Replace(strings.Replace(egressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := egressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(egressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + egressAclSet := intfData.EgressAclSets.EgressAclSet[egressAclSetKey] + if egressAclSet != nil && egressAclSet.AclEntries != nil && len(egressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range egressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := egressAclSet.AclEntries.AclEntry[seqId] + _, ok := ruleTableMap[aclKey+"|"+rulekey] + if !ok { + log.Info("Acl Rule not found ", aclKey, rulekey) + err = errors.New("Acl Rule not found egress, getAclBindingInfoForInterfaceData") + return err + } + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclName, Type: egressAclSetKey.Type} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclName, Type: egressAclSetKey.Type} + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclKey, intfId, direction, egressAclSet) + } + } + } else { + err = findAndGetAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfId, direction, intfData) + } + } else { + log.Error("Unknown direction") + } + return err +} + +func findAndGetAclBindingInfoForInterfaceData(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, intfId string, direction string, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface) error { + var err error + for aclName, _ := range aclTableMap { + aclData := aclTableMap[aclName] + aclIntfs := aclData.GetList("ports") + aclType := aclData.Get(ACL_TYPE) + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + if contains(aclIntfs, intfId) && direction == aclData.Get("stage") { + if direction == "INGRESS" { + if intfData.IngressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + ingressAclSet, ok := intfData.IngressAclSets.IngressAclSet[aclSetKey] + if !ok { + ingressAclSet, _ = intfData.IngressAclSets.NewIngressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclName, intfId, direction, ingressAclSet) + if err != nil { + return err + } + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + egressAclSet, ok := intfData.EgressAclSets.EgressAclSet[aclSetKey] + if !ok { + egressAclSet, _ = intfData.EgressAclSets.NewEgressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclName, intfId, direction, egressAclSet) + if err != nil { + return err + } + } + } + } + } + return err +} diff --git a/src/translib/transformer/xfmr_interface.go b/src/translib/transformer/xfmr_interface.go new file mode 100644 index 0000000000..35e17ed004 --- /dev/null +++ b/src/translib/transformer/xfmr_interface.go @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "github.com/openconfig/ygot/ygot" + "translib/db" + log "github.com/golang/glog" +) + +type XfmrParams struct { + d *db.DB + dbs [db.MaxDB]*db.DB + curDb db.DBNum + ygRoot *ygot.GoStruct + uri string + oper int + key string + dbDataMap *map[db.DBNum]map[string]map[string]db.Value + param interface{} +} + +/** + * KeyXfmrYangToDb type is defined to use for conversion of Yang key to DB Key + * Transformer function definition. + * Param: XfmrParams structure having Database info, YgotRoot, operation, Xpath + * Return: Database keys to access db entry, error + **/ +type KeyXfmrYangToDb func (inParams XfmrParams) (string, error) +/** + * KeyXfmrDbToYang type is defined to use for conversion of DB key to Yang key + * Transformer function definition. + * Param: XfmrParams structure having Database info, operation, Database keys to access db entry + * Return: multi dimensional map to hold the yang key attributes of complete xpath, error + **/ +type KeyXfmrDbToYang func (inParams XfmrParams) (map[string]interface{}, error) + +/** + * FieldXfmrYangToDb type is defined to use for conversion of yang Field to DB field + * Transformer function definition. + * Param: Database info, YgotRoot, operation, Xpath + * Return: multi dimensional map to hold the DB data, error + **/ +type FieldXfmrYangToDb func (inParams XfmrParams) (map[string]string, error) +/** + * FieldXfmrDbtoYang type is defined to use for conversion of DB field to Yang field + * Transformer function definition. + * Param: XfmrParams structure having Database info, operation, DB data in multidimensional map, output param YgotRoot + * Return: error + **/ +type FieldXfmrDbtoYang func (inParams XfmrParams) (map[string]interface{}, error) + +/** + * SubTreeXfmrYangToDb type is defined to use for handling the yang subtree to DB + * Transformer function definition. + * Param: XfmrParams structure having Database info, YgotRoot, operation, Xpath + * Return: multi dimensional map to hold the DB data, error + **/ +type SubTreeXfmrYangToDb func (inParams XfmrParams) (map[string]map[string]db.Value, error) +/** + * SubTreeXfmrDbToYang type is defined to use for handling the DB to Yang subtree + * Transformer function definition. + * Param : XfmrParams structure having Database pointers, current db, operation, DB data in multidimensional map, output param YgotRoot, uri + * Return : error + **/ +type SubTreeXfmrDbToYang func (inParams XfmrParams) (error) +/** + * ValidateCallpoint is used to validate a YANG node during data translation back to YANG as a response to GET + * Param : XfmrParams structure having Database pointers, current db, operation, DB data in multidimensional map, output param YgotRoot, uri + * Return : bool + **/ +type ValidateCallpoint func (inParams XfmrParams) (bool) + +/** + * PostXfmrFunc type is defined to use for handling any default handling operations required as part of the CREATE + * Transformer function definition. + * Param: XfmrParams structure having database pointers, current db, operation, DB data in multidimensional map, YgotRoot, uri + * Return: multi dimensional map to hold the DB data, error + **/ +type PostXfmrFunc func (inParams XfmrParams) (map[string]map[string]db.Value, error) + + +/** + * TableXfmrFunc type is defined to use for table transformer function for dynamic derviation of redis table. + * Param: XfmrParams structure having database pointers, current db, operation, DB data in multidimensional map, YgotRoot, uri + * Return: List of table names, error + **/ +type TableXfmrFunc func (inParams XfmrParams) ([]string, error) + + +/** + * Xfmr validation interface for validating the callback registration of app modules + * transformer methods. + **/ +type XfmrInterface interface { + xfmrInterfaceValiidate() +} + +func (KeyXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for KeyXfmrYangToDb") +} +func (KeyXfmrDbToYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for KeyXfmrDbToYang") +} +func (FieldXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for FieldXfmrYangToDb") +} +func (FieldXfmrDbtoYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for FieldXfmrDbtoYang") +} +func (SubTreeXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for SubTreeXfmrYangToDb") +} +func (SubTreeXfmrDbToYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for SubTreeXfmrDbToYang") +} +func (TableXfmrFunc) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for TableXfmrFunc") +} diff --git a/src/translib/transformer/xfmr_path_utils.go b/src/translib/transformer/xfmr_path_utils.go new file mode 100644 index 0000000000..8a052cd24c --- /dev/null +++ b/src/translib/transformer/xfmr_path_utils.go @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Broadcom. All rights reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +/////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "bytes" + "fmt" + "strings" +) + +// PathInfo structure contains parsed path information. +type PathInfo struct { + Path string + Template string + Vars map[string]string +} + +// Var returns the string value for a path variable. Returns +// empty string if no such variable exists. +func (p *PathInfo) Var(name string) string { + return p.Vars[name] +} + +// NewPathInfo parses given path string into a PathInfo structure. +func NewPathInfo(path string) *PathInfo { + var info PathInfo + info.Path = path + info.Vars = make(map[string]string) + + //TODO optimize using regexp + var template strings.Builder + r := strings.NewReader(path) + + for r.Len() > 0 { + c, _ := r.ReadByte() + if c != '[' { + template.WriteByte(c) + continue + } + + name := readUntil(r, '=') + value := readUntil(r, ']') + if len(name) != 0 { + fmt.Fprintf(&template, "{%s}", name) + info.Vars[name] = value + } + } + + info.Template = template.String() + + return &info +} + +func readUntil(r *strings.Reader, delim byte) string { + var buff strings.Builder + for { + c, err := r.ReadByte() + if err == nil && c != delim { + buff.WriteByte(c) + } else { + break + } + } + + return buff.String() +} + +func RemoveXPATHPredicates(s string) (string, error) { + var b bytes.Buffer + for i := 0; i < len(s); { + ss := s[i:] + si, ei := strings.Index(ss, "["), strings.Index(ss, "]") + switch { + case si == -1 && ei == -1: + // This substring didn't contain a [] pair, therefore write it + // to the buffer. + b.WriteString(ss) + // Move to the last character of the substring. + i += len(ss) + case si == -1 || ei == -1: + // This substring contained a mismatched pair of []s. + return "", fmt.Errorf("Mismatched brackets within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) + case si > ei: + // This substring contained a ] before a [. + return "", fmt.Errorf("Incorrect ordering of [] within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) + default: + // This substring contained a matched set of []s. + b.WriteString(ss[0:si]) + i += ei + 1 + } + } + + return b.String(), nil +} diff --git a/src/translib/transformer/xlate.go b/src/translib/transformer/xlate.go new file mode 100644 index 0000000000..f1b000cce1 --- /dev/null +++ b/src/translib/transformer/xlate.go @@ -0,0 +1,407 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "encoding/json" + "errors" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "strings" + "translib/db" + "translib/ocbinds" +) + +const ( + GET = 1 + iota + CREATE + REPLACE + UPDATE + DELETE +) + +type KeySpec struct { + dbNum db.DBNum + Ts db.TableSpec + Key db.Key + Child []KeySpec +} + +var XlateFuncs = make(map[string]reflect.Value) + +var ( + ErrParamsNotAdapted = errors.New("The number of params is not adapted.") +) + +func XlateFuncBind(name string, fn interface{}) (err error) { + defer func() { + if e := recover(); e != nil { + err = errors.New(name + " is not valid Xfmr function.") + } + }() + + if _, ok := XlateFuncs[name]; !ok { + v := reflect.ValueOf(fn) + v.Type().NumIn() + XlateFuncs[name] = v + } else { + log.Info("Duplicate entry found in the XlateFunc map " + name) + } + return +} + +func XlateFuncCall(name string, params ...interface{}) (result []reflect.Value, err error) { + if _, ok := XlateFuncs[name]; !ok { + err = errors.New(name + " Xfmr function does not exist.") + return nil, err + } + if len(params) != XlateFuncs[name].Type().NumIn() { + err = ErrParamsNotAdapted + return nil, nil + } + in := make([]reflect.Value, len(params)) + for k, param := range params { + in[k] = reflect.ValueOf(param) + } + result = XlateFuncs[name].Call(in) + return result, nil +} + +func TraverseDb(dbs [db.MaxDB]*db.DB, spec KeySpec, result *map[db.DBNum]map[string]map[string]db.Value, parentKey *db.Key) error { + var err error + var dbOpts db.Options + + dbOpts = getDBOptions(spec.dbNum) + separator := dbOpts.KeySeparator + log.Infof("key separator for table %v in Db %v is %v", spec.Ts.Name, spec.dbNum, separator) + + if spec.Key.Len() > 0 { + // get an entry with a specific key + data, err := dbs[spec.dbNum].GetEntry(&spec.Ts, spec.Key) + if err != nil { + return err + } + + if (*result)[spec.dbNum][spec.Ts.Name] == nil { + (*result)[spec.dbNum][spec.Ts.Name] = map[string]db.Value{strings.Join(spec.Key.Comp, separator): data} + } else { + (*result)[spec.dbNum][spec.Ts.Name][strings.Join(spec.Key.Comp, separator)] = data + } + + if len(spec.Child) > 0 { + for _, ch := range spec.Child { + err = TraverseDb(dbs, ch, result, &spec.Key) + } + } + } else { + // TODO - GetEntry support with regex patten, 'abc*' for optimization + keys, err := dbs[spec.dbNum].GetKeys(&spec.Ts) + if err != nil { + return err + } + for i, _ := range keys { + if parentKey != nil { + // TODO - multi-depth with a custom delimiter + if strings.Index(strings.Join(keys[i].Comp, separator), strings.Join((*parentKey).Comp, separator)) == -1 { + continue + } + } + spec.Key = keys[i] + err = TraverseDb(dbs, spec, result, parentKey) + } + } + return err +} + +func XlateUriToKeySpec(uri string, ygRoot *ygot.GoStruct, t *interface{}) (*[]KeySpec, error) { + + var err error + var retdbFormat = make([]KeySpec, 0) + + // In case of SONIC yang, the tablename and key info is available in the xpath + if isSonicYang(uri) { + /* Extract the xpath and key from input xpath */ + xpath, keyStr, tableName := sonicXpathKeyExtract(uri) + retdbFormat = fillSonicKeySpec(xpath, tableName, keyStr) + } else { + /* Extract the xpath and key from input xpath */ + xpath, keyStr, _ := xpathKeyExtract(nil, ygRoot, 0, uri) + retdbFormat = FillKeySpecs(xpath, keyStr, &retdbFormat) + } + + return &retdbFormat, err +} + +func FillKeySpecs(yangXpath string , keyStr string, retdbFormat *[]KeySpec) ([]KeySpec){ + if xYangSpecMap == nil { + return *retdbFormat + } + _, ok := xYangSpecMap[yangXpath] + if ok { + xpathInfo := xYangSpecMap[yangXpath] + if xpathInfo.tableName != nil { + dbFormat := KeySpec{} + dbFormat.Ts.Name = *xpathInfo.tableName + dbFormat.dbNum = xpathInfo.dbIndex + if keyStr != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, keyStr) + } + for _, child := range xpathInfo.childTable { + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[child]; ok { + chlen := len(xDbSpecMap[child].yangXpath) + if chlen > 0 { + children := make([]KeySpec, 0) + for _, childXpath := range xDbSpecMap[child].yangXpath { + children = FillKeySpecs(childXpath, "", &children) + dbFormat.Child = append(dbFormat.Child, children...) + } + } + } + } + } + *retdbFormat = append(*retdbFormat, dbFormat) + } else { + for _, child := range xpathInfo.childTable { + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[child]; ok { + chlen := len(xDbSpecMap[child].yangXpath) + if chlen > 0 { + for _, childXpath := range xDbSpecMap[child].yangXpath { + *retdbFormat = FillKeySpecs(childXpath, "", retdbFormat) + } + } + } + } + } + } + } + return *retdbFormat +} + +func fillSonicKeySpec(xpath string , tableName string, keyStr string) ( []KeySpec ) { + + var retdbFormat = make([]KeySpec, 0) + + if tableName != "" { + dbFormat := KeySpec{} + dbFormat.Ts.Name = tableName + cdb := db.ConfigDB + if _, ok := xDbSpecMap[tableName]; ok { + cdb = xDbSpecMap[tableName].dbIndex + } + dbFormat.dbNum = cdb + if keyStr != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, keyStr) + } + retdbFormat = append(retdbFormat, dbFormat) + } else { + // If table name not available in xpath get top container name + container := xpath + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[container]; ok { + dbInfo := xDbSpecMap[container] + if dbInfo.fieldType == "container" { + for dir, _ := range dbInfo.dbEntry.Dir { + _, ok := xDbSpecMap[dir] + if ok && xDbSpecMap[dir].dbEntry.Node.Statement().Keyword == "container" { + cdb := xDbSpecMap[dir].dbIndex + dbFormat := KeySpec{} + dbFormat.Ts.Name = dir + dbFormat.dbNum = cdb + retdbFormat = append(retdbFormat, dbFormat) + } + } + } + } + } + } + return retdbFormat +} + +func XlateToDb(path string, opcode int, d *db.DB, yg *ygot.GoStruct, yt *interface{}) (map[string]map[string]db.Value, error) { + + var err error + + device := (*yg).(*ocbinds.Device) + jsonStr, err := ygot.EmitJSON(device, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + Indent: " ", + SkipValidation: true, + RFC7951Config: &ygot.RFC7951JSONConfig{ + AppendModuleName: true, + }, + }) + + jsonData := make(map[string]interface{}) + err = json.Unmarshal([]byte(jsonStr), &jsonData) + if err != nil { + log.Errorf("Error: failed to unmarshal json.") + return nil, err + } + + // Map contains table.key.fields + var result = make(map[string]map[string]db.Value) + switch opcode { + case CREATE: + log.Info("CREATE case") + err = dbMapCreate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for create request.") + } + + case UPDATE: + log.Info("UPDATE case") + err = dbMapUpdate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for update request.") + } + + case REPLACE: + log.Info("REPLACE case") + err = dbMapUpdate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for replace request.") + } + + case DELETE: + log.Info("DELETE case") + err = dbMapDelete(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for delete request.") + } + } + return result, err +} + +func GetAndXlateFromDB(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB) ([]byte, error) { + var err error + var payload []byte + log.Info("received xpath =", uri) + + keySpec, err := XlateUriToKeySpec(uri, ygRoot, nil) + var dbresult = make(map[db.DBNum]map[string]map[string]db.Value) + for i := db.ApplDB; i < db.MaxDB; i++ { + dbresult[i] = make(map[string]map[string]db.Value) + } + + for _, spec := range *keySpec { + err := TraverseDb(dbs, spec, &dbresult, nil) + if err != nil { + log.Error("TraverseDb() failure") + return payload, err + } + } + + payload, err = XlateFromDb(uri, ygRoot, dbs, dbresult) + if err != nil { + log.Error("XlateFromDb() failure.") + return payload, err + } + + return payload, err +} + +func XlateFromDb(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB, data map[db.DBNum]map[string]map[string]db.Value) ([]byte, error) { + + var err error + var result []byte + var dbData = make(map[db.DBNum]map[string]map[string]db.Value) + var cdb db.DBNum = db.ConfigDB + + dbData = data + if isSonicYang(uri) { + xpath, keyStr, tableName := sonicXpathKeyExtract(uri) + if (tableName != "") { + dbInfo, ok := xDbSpecMap[tableName] + if !ok { + log.Warningf("No entry in xDbSpecMap for xpath %v", tableName) + } else { + cdb = dbInfo.dbIndex + } + tokens:= strings.Split(xpath, "/") + // Format /module:container/tableName/listname[key]/fieldName + if tokens[SONIC_TABLE_INDEX] == tableName { + fieldName := tokens[len(tokens)-1] + dbSpecField := tableName + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok && xDbSpecMap[dbSpecField].fieldType == "leaf" { + dbData[cdb] = extractFieldFromDb(tableName, keyStr, fieldName, data[cdb]) + } + } + } + } else { + xpath, _ := XfmrRemoveXPATHPredicates(uri) + if _, ok := xYangSpecMap[xpath]; ok { + cdb = xYangSpecMap[xpath].dbIndex + } + } + payload, err := dbDataToYangJsonCreate(uri, ygRoot, dbs, &dbData, cdb) + log.Info("Payload generated:", payload) + + if err != nil { + log.Errorf("Error: failed to create json response from DB data.") + return nil, err + } + + result = []byte(payload) + return result, err + +} + +func extractFieldFromDb(tableName string, keyStr string, fieldName string, data map[string]map[string]db.Value) (map[string]map[string]db.Value) { + + var dbVal db.Value + var dbData = make(map[string]map[string]db.Value) + + if tableName != "" && keyStr != "" && fieldName != "" { + if data[tableName][keyStr].Field != nil { + dbData[tableName] = make(map[string]db.Value) + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = data[tableName][keyStr].Field[fieldName] + dbData[tableName][keyStr] = dbVal + } + } + return dbData +} + +func GetModuleNmFromPath(uri string) (string, error) { + log.Infof("received uri %s to extract module name from ", uri) + moduleNm, err := uriModuleNameGet(uri) + return moduleNm, err +} + +func GetOrdDBTblList(ygModuleNm string) ([]string, error) { + var result []string + var err error + if dbTblList, ok := xDbSpecOrdTblMap[ygModuleNm]; ok { + result = dbTblList + if len(dbTblList) == 0 { + log.Error("Ordered DB Table list is empty for module name = ", ygModuleNm) + err = fmt.Errorf("Ordered DB Table list is empty for module name %v", ygModuleNm) + + } + } else { + log.Error("No entry found in the map of module names to ordered list of DB Tables for module = ", ygModuleNm) + err = fmt.Errorf("No entry found in the map of module names to ordered list of DB Tables for module = %v", ygModuleNm) + } + return result, err +} diff --git a/src/translib/transformer/xlate_from_db.go b/src/translib/transformer/xlate_from_db.go new file mode 100644 index 0000000000..3744670692 --- /dev/null +++ b/src/translib/transformer/xlate_from_db.go @@ -0,0 +1,766 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "translib/db" + "strings" + "encoding/json" + "os" + "strconv" + "errors" + "translib/ocbinds" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" + + log "github.com/golang/glog" +) + +type typeMapOfInterface map[string]interface{} + +func xfmrHandlerFunc(inParams XfmrParams) (map[string]interface{}, error) { + result := make(map[string]interface{}) + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + log.Infof("Subtree transformer function(\"%v\") invoked for yang path(\"%v\").", xYangSpecMap[xpath].xfmrFunc, xpath) + _, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + log.Infof("Failed to retrieve data for xpath(\"%v\") err(%v).", inParams.uri, err) + return result, err + } + + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + device := (*inParams.ygRoot).(*ocbinds.Device) + + path, _ := ygot.StringToPath(inParams.uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + + nodeList, nodeErr := ytypes.GetNode(schRoot, device, path) + if nodeErr != nil { + log.Infof("Failed to get node for xpath(\"%v\") err(%v).", inParams.uri, err) + return result, err + } + node := nodeList[0].Data + nodeYgot, _ := (node).(ygot.ValidatedGoStruct) + payload, err := ygot.EmitJSON(nodeYgot, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, + Indent: " ", SkipValidation: true, + RFC7951Config: &ygot.RFC7951JSONConfig{ AppendModuleName: false, }, + }) + err = json.Unmarshal([]byte(payload), &result) + return result, err +} + +func leafXfmrHandlerFunc(inParams XfmrParams) (map[string]interface{}, error) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return nil, err + } + if ret != nil { + fldValMap := ret[0].Interface().(map[string]interface{}) + return fldValMap, nil + } else { + return nil, nil + } +} + +func validateHandlerFunc(inParams XfmrParams) (bool) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(xYangSpecMap[xpath].validateFunc, inParams) + if err != nil { + return false + } + return ret[0].Interface().(bool) +} + +func xfmrTblHandlerFunc(xfmrTblFunc string, inParams XfmrParams) []string { + ret, err := XlateFuncCall(xfmrTblFunc, inParams) + if err != nil { + return []string{} + } + return ret[0].Interface().([]string) +} + + +func DbValToInt(dbFldVal string, base int, size int, isUint bool) (interface{}, error) { + var res interface{} + var err error + if isUint { + if res, err = strconv.ParseUint(dbFldVal, base, size); err != nil { + log.Warningf("Non Yint%v type for yang leaf-list item %v", size, dbFldVal) + } + } else { + if res, err = strconv.ParseInt(dbFldVal, base, size); err != nil { + log.Warningf("Non Yint %v type for yang leaf-list item %v", size, dbFldVal) + } + } + return res, err +} + +func DbToYangType(yngTerminalNdDtType yang.TypeKind, fldXpath string, dbFldVal string) (interface{}, error) { + log.Infof("Received FieldXpath %v, yngTerminalNdDtType %v and Db field value %v to be converted to yang data-type.", fldXpath, yngTerminalNdDtType, dbFldVal) + var res interface{} + var err error + const INTBASE = 10 + switch yngTerminalNdDtType { + case yang.Ynone: + log.Warning("Yang node data-type is non base yang type") + //TODO - enhance to handle non base data types depending on future use case + err = errors.New("Yang node data-type is non base yang type") + case yang.Yint8: + res, err = DbValToInt(dbFldVal, INTBASE, 8, false) + case yang.Yint16: + res, err = DbValToInt(dbFldVal, INTBASE, 16, false) + case yang.Yint32: + res, err = DbValToInt(dbFldVal, INTBASE, 32, false) + case yang.Yuint8: + res, err = DbValToInt(dbFldVal, INTBASE, 8, true) + case yang.Yuint16: + res, err = DbValToInt(dbFldVal, INTBASE, 16, true) + case yang.Yuint32: + res, err = DbValToInt(dbFldVal, INTBASE, 32, true) + case yang.Ybool: + if res, err = strconv.ParseBool(dbFldVal); err != nil { + log.Warningf("Non Bool type for yang leaf-list item %v", dbFldVal) + } + case yang.Ybinary, yang.Ydecimal64, yang.Yenum, yang.Yidentityref, yang.Yint64, yang.Yuint64, yang.Ystring, yang.Yunion,yang.Yleafref: + // TODO - handle the union type + // Make sure to encode as string, expected by util_types.go: ytypes.yangToJSONType + log.Info("Yenum/Ystring/Yunion(having all members as strings) type for yangXpath ", fldXpath) + res = dbFldVal + case yang.Yempty: + logStr := fmt.Sprintf("Yang data type for xpath %v is Yempty.", fldXpath) + log.Error(logStr) + err = errors.New(logStr) + default: + logStr := fmt.Sprintf("Unrecognized/Unhandled yang-data type(%v) for xpath %v.", fldXpath, yang.TypeKindToName[yngTerminalNdDtType]) + log.Error(logStr) + err = errors.New(logStr) + } + return res, err +} + +/*convert leaf-list in Db to leaf-list in yang*/ +func processLfLstDbToYang(fieldXpath string, dbFldVal string) []interface{} { + valLst := strings.Split(dbFldVal, ",") + var resLst []interface{} + const INTBASE = 10 + yngTerminalNdDtType := xDbSpecMap[fieldXpath].dbEntry.Type.Kind + switch yngTerminalNdDtType { + case yang.Yenum, yang.Ystring, yang.Yunion, yang.Yleafref: + // TODO handle leaf-ref base type + log.Info("DB leaf-list and Yang leaf-list are of same data-type") + for _, fldVal := range valLst { + resLst = append(resLst, fldVal) + } + default: + for _, fldVal := range valLst { + resVal, err := DbToYangType(yngTerminalNdDtType, fieldXpath, fldVal) + if err == nil { + resLst = append(resLst, resVal) + } + } + } + return resLst +} + +func sonicDbToYangTerminalNodeFill(tblName string, field string, value string, resultMap map[string]interface{}) { + resField := field + if len(value) == 0 { + return + } + if strings.HasSuffix(field, "@") { + fldVals := strings.Split(field, "@") + resField = fldVals[0] + } + fieldXpath := tblName + "/" + resField + xDbSpecMapEntry, ok := xDbSpecMap[fieldXpath] + if !ok { + log.Warningf("No entry found in xDbSpecMap for xpath %v", fieldXpath) + return + } + if xDbSpecMapEntry.dbEntry == nil { + log.Warningf("Yang entry is nil in xDbSpecMap for xpath %v", fieldXpath) + return + } + + yangType := yangTypeGet(xDbSpecMapEntry.dbEntry) + if yangType == YANG_LEAF_LIST { + /* this should never happen but just adding for safetty */ + if !strings.HasSuffix(field, "@") { + log.Warningf("Leaf-list in Sonic yang should also be a leaf-list in DB, its not for xpath %v", fieldXpath) + return + } + resLst := processLfLstDbToYang(fieldXpath, value) + resultMap[resField] = resLst + } else { /* yangType is leaf - there are only 2 types of yang terminal node leaf and leaf-list */ + yngTerminalNdDtType := xDbSpecMapEntry.dbEntry.Type.Kind + resVal, err := DbToYangType(yngTerminalNdDtType, fieldXpath, value) + if err != nil { + log.Warningf("Failure in converting Db value type to yang type for xpath", fieldXpath) + } else { + resultMap[resField] = resVal + } + } + return +} + +func sonicDbToYangListFill(uri string, xpath string, dbIdx db.DBNum, table string, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value) []typeMapOfInterface { + var mapSlice []typeMapOfInterface + dbTblData := (*dbDataMap)[dbIdx][table] + + for keyStr, _ := range dbTblData { + curMap := make(map[string]interface{}) + sonicDbToYangDataFill(uri, xpath, dbIdx, table, keyStr, dbDataMap, curMap) + dbSpecData, ok := xDbSpecMap[table] + if ok && dbSpecData.keyName == nil { + yangKeys := yangKeyFromEntryGet(xDbSpecMap[xpath].dbEntry) + sonicKeyDataAdd(dbIdx, yangKeys, keyStr, curMap) + } + if curMap != nil { + mapSlice = append(mapSlice, curMap) + } + } + return mapSlice +} + +func sonicDbToYangDataFill(uri string, xpath string, dbIdx db.DBNum, table string, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}) { + yangNode, ok := xDbSpecMap[xpath] + + if ok && yangNode.dbEntry != nil { + xpathPrefix := table + if len(table) > 0 { xpathPrefix += "/" } + + for yangChldName := range yangNode.dbEntry.Dir { + chldXpath := xpathPrefix+yangChldName + if xDbSpecMap[chldXpath] != nil && xDbSpecMap[chldXpath].dbEntry != nil { + chldYangType := yangTypeGet(xDbSpecMap[chldXpath].dbEntry) + + if chldYangType == YANG_LEAF || chldYangType == YANG_LEAF_LIST { + log.Infof("tbl(%v), k(%v), yc(%v)", table, key, yangChldName) + fldName := yangChldName + if chldYangType == YANG_LEAF_LIST { + fldName = fldName + "@" + } + sonicDbToYangTerminalNodeFill(table, fldName, (*dbDataMap)[dbIdx][table][key].Field[fldName], resultMap) + } else if chldYangType == YANG_CONTAINER { + curMap := make(map[string]interface{}) + curUri := xpath + "/" + yangChldName + // container can have a static key, so extract key for current container + _, curKey, curTable := sonicXpathKeyExtract(curUri) + // use table-name as xpath from now on + sonicDbToYangDataFill(curUri, curTable, xDbSpecMap[curTable].dbIndex, curTable, curKey, dbDataMap, curMap) + if len(curMap) > 0 { + resultMap[yangChldName] = curMap + } else { + log.Infof("Empty container for xpath(%v)", curUri) + } + } else if chldYangType == YANG_LIST { + var mapSlice []typeMapOfInterface + curUri := xpath + "/" + yangChldName + mapSlice = sonicDbToYangListFill(curUri, curUri, dbIdx, table, key, dbDataMap) + if len(key) > 0 && len(mapSlice) == 1 {// Single instance query. Don't return array of maps + for k, val := range mapSlice[0] { + resultMap[k] = val + } + + } else if len(mapSlice) > 0 { + resultMap[yangChldName] = mapSlice + } else { + log.Infof("Empty list for xpath(%v)", curUri) + } + } + } + } + } + return +} + +/* Traverse db map and create json for cvl yang */ +func directDbToYangJsonCreate(uri string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}) (string, error) { + xpath, key, table := sonicXpathKeyExtract(uri) + + if len(xpath) > 0 { + var dbNode *dbInfo + + if len(table) > 0 { + tokens:= strings.Split(xpath, "/") + if tokens[SONIC_TABLE_INDEX] == table { + fieldName := tokens[len(tokens)-1] + dbSpecField := table + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok && (xDbSpecMap[dbSpecField].fieldType == YANG_LEAF || xDbSpecMap[dbSpecField].fieldType == YANG_LEAF_LIST) { + dbNode = xDbSpecMap[dbSpecField] + xpath = dbSpecField + } else { + dbNode = xDbSpecMap[table] + } + } + } else { + dbNode, _ = xDbSpecMap[xpath] + } + + if dbNode != nil && dbNode.dbEntry != nil { + cdb := db.ConfigDB + yangType := yangTypeGet(dbNode.dbEntry) + if len(table) > 0 { + cdb = xDbSpecMap[table].dbIndex + } + + if yangType == YANG_LEAF || yangType == YANG_LEAF_LIST { + fldName := xDbSpecMap[xpath].dbEntry.Name + if yangType == YANG_LEAF_LIST { + fldName = fldName + "@" + } + sonicDbToYangTerminalNodeFill(table, fldName, (*dbDataMap)[cdb][table][key].Field[fldName], resultMap) + } else if yangType == YANG_CONTAINER { + if len(table) > 0 { + xpath = table + } + sonicDbToYangDataFill(uri, xpath, cdb, table, key, dbDataMap, resultMap) + } else if yangType == YANG_LIST { + mapSlice := sonicDbToYangListFill(uri, xpath, cdb, table, key, dbDataMap) + if len(key) > 0 && len(mapSlice) == 1 {// Single instance query. Don't return array of maps + for k, val := range mapSlice[0] { + resultMap[k] = val + } + + } else if len(mapSlice) > 0 { + pathl := strings.Split(xpath, "/") + lname := pathl[len(pathl) - 1] + resultMap[lname] = mapSlice + } + } + } + } + + jsonMapData, _ := json.Marshal(resultMap) + jsonData := fmt.Sprintf("%v", string(jsonMapData)) + jsonDataPrint(jsonData) + return jsonData, nil +} + +func tableNameAndKeyFromDbMapGet(dbDataMap map[string]map[string]db.Value) (string, string, error) { + tableName := "" + tableKey := "" + for tn, tblData := range dbDataMap { + tableName = tn + for kname, _ := range tblData { + tableKey = kname + } + } + return tableName, tableKey, nil +} + +func fillDbDataMapForTbl(uri string, xpath string, tblName string, tblKey string, cdb db.DBNum, dbs [db.MaxDB]*db.DB) (map[db.DBNum]map[string]map[string]db.Value, error) { + var err error + dbresult := make(map[db.DBNum]map[string]map[string]db.Value) + dbresult[cdb] = make(map[string]map[string]db.Value) + dbFormat := KeySpec{} + dbFormat.Ts.Name = tblName + dbFormat.dbNum = cdb + if tblKey != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, tblKey) + } + err = TraverseDb(dbs, dbFormat, &dbresult, nil) + if err != nil { + log.Errorf("TraverseDb() failure for tbl(DB num) %v(%v) for xpath %v", tblName, cdb, xpath) + return nil, err + } + if _, ok := dbresult[cdb]; !ok { + logStr := fmt.Sprintf("TraverseDb() did not populate Db data for tbl(DB num) %v(%v) for xpath %v", tblName, cdb, xpath) + err = fmt.Errorf("%v", logStr) + return nil, err + } + return dbresult, err + +} + +// Assumption: All tables are from the same DB +func dbDataFromTblXfmrGet(tbl string, inParams XfmrParams, dbDataMap *map[db.DBNum]map[string]map[string]db.Value) error { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + curDbDataMap, err := fillDbDataMapForTbl(inParams.uri, xpath, tbl, inParams.key, inParams.curDb, inParams.dbs) + if err == nil { + mapCopy((*dbDataMap)[inParams.curDb], curDbDataMap[inParams.curDb]) + } + return nil +} + +func yangListDataFill(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}, tbl string, tblKey string, cdb db.DBNum, validate bool) error { + var tblList []string + + if tbl == "" && xYangSpecMap[xpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[xpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + tblList = xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) != 0 { + for _, curTbl := range tblList { + dbDataFromTblXfmrGet(curTbl, inParams, dbDataMap) + } + } + } + } else if tbl != "" && xYangSpecMap[xpath].xfmrTbl == nil { + tblList = append(tblList, tbl) + } else if tbl != "" && xYangSpecMap[xpath].xfmrTbl != nil { + /*key instance level GET, table name and table key filled from xpathKeyExtract which internally calls table transformer*/ + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + dbDataFromTblXfmrGet(tbl, inParams, dbDataMap) + tblList = append(tblList, tbl) + + } + + for _, tbl = range(tblList) { + tblData, ok := (*dbDataMap)[cdb][tbl] + + if ok { + var mapSlice []typeMapOfInterface + for dbKey, _ := range tblData { + curMap := make(map[string]interface{}) + curKeyMap, curUri, _ := dbKeyToYangDataConvert(uri, xpath, dbKey, dbs[cdb].Opts.KeySeparator) + if len(xYangSpecMap[xpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, curUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + mapSlice = append(mapSlice, curMap) + } else { + log.Infof("Empty container returned from overloaded transformer for(\"%v\")", curUri) + } + } else { + _, keyFromCurUri, _ := xpathKeyExtract(dbs[cdb], ygRoot, GET, curUri) + if dbKey == keyFromCurUri { + for k, kv := range curKeyMap { + curMap[k] = kv + } + curXpath, _ := XfmrRemoveXPATHPredicates(curUri) + yangDataFill(dbs, ygRoot, curUri, curXpath, dbDataMap, curMap, tbl, dbKey, cdb, validate) + mapSlice = append(mapSlice, curMap) + } + } + } + if len(mapSlice) > 0 { + resultMap[xYangSpecMap[xpath].yangEntry.Name] = mapSlice + } else { + log.Infof("Empty slice for (\"%v\").\r\n", uri) + } + } + }// end of tblList for + return nil +} + +func terminalNodeProcess(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, tbl string, tblKey string) (map[string]interface{}, error) { + log.Infof("Received xpath - %v, uri - %v, table - %v, table key - %v", xpath, uri, tbl, tblKey) + var err error + resFldValMap := make(map[string]interface{}) + if xYangSpecMap[xpath].yangEntry == nil { + logStr := fmt.Sprintf("No yang entry found for xpath %v.", xpath) + err = fmt.Errorf("%v", logStr) + return resFldValMap, err + } + + cdb := xYangSpecMap[xpath].dbIndex + if len(xYangSpecMap[xpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + fldValMap, err := leafXfmrHandlerFunc(inParams) + if err != nil { + logStr := fmt.Sprintf("%Failed to get data from overloaded function for %v -v.", uri, err) + err = fmt.Errorf("%v", logStr) + return resFldValMap, err + } + if fldValMap != nil { + for lf, val := range fldValMap { + resFldValMap[lf] = val + } + } + } else { + dbFldName := xYangSpecMap[xpath].fieldName + if dbFldName == "NONE" { + return resFldValMap, err + } + /* if there is no transformer extension/annotation then it means leaf-list in yang is also leaflist in db */ + if len(dbFldName) > 0 && !xYangSpecMap[xpath].isKey { + yangType := yangTypeGet(xYangSpecMap[xpath].yangEntry) + if yangType == YANG_LEAF_LIST { + dbFldName += "@" + val, ok := (*dbDataMap)[cdb][tbl][tblKey].Field[dbFldName] + if ok { + resLst := processLfLstDbToYang(xpath, val) + resFldValMap[xYangSpecMap[xpath].yangEntry.Name] = resLst + } + } else { + val, ok := (*dbDataMap)[cdb][tbl][tblKey].Field[dbFldName] + if ok { + yngTerminalNdDtType := xYangSpecMap[xpath].yangEntry.Type.Kind + resVal, err := DbToYangType(yngTerminalNdDtType, xpath, val) + if err != nil { + log.Error("Failure in converting Db value type to yang type for field", xpath) + } else { + resFldValMap[xYangSpecMap[xpath].yangEntry.Name] = resVal + } + } + } + } + } + return resFldValMap, err +} + +func yangDataFill(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}, tbl string, tblKey string, cdb db.DBNum, validate bool) error { + var err error + isValid := validate + yangNode, ok := xYangSpecMap[xpath] + + if ok && yangNode.yangEntry != nil { + for yangChldName := range yangNode.yangEntry.Dir { + chldXpath := xpath+"/"+yangChldName + chldUri := uri+"/"+yangChldName + if xYangSpecMap[chldXpath] != nil && xYangSpecMap[chldXpath].yangEntry != nil { + cdb = xYangSpecMap[chldXpath].dbIndex + if len(xYangSpecMap[chldXpath].validateFunc) > 0 && !validate { + _, key, _ := xpathKeyExtract(dbs[cdb], ygRoot, GET, chldUri) + // TODO - handle non CONFIG-DB + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, key, dbDataMap, nil) + res := validateHandlerFunc(inParams) + if res != true { + continue + } else { + isValid = res + } + } + chldYangType := yangTypeGet(xYangSpecMap[chldXpath].yangEntry) + if chldYangType == YANG_LEAF || chldYangType == YANG_LEAF_LIST { + fldValMap, err := terminalNodeProcess(dbs, ygRoot, chldUri, chldXpath, dbDataMap, tbl, tblKey) + if err != nil { + log.Infof("Failed to get data(\"%v\").", chldUri) + } + for lf, val := range fldValMap { + resultMap[lf] = val + } + } else if chldYangType == YANG_CONTAINER { + cname := xYangSpecMap[chldXpath].yangEntry.Name + if xYangSpecMap[chldXpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[chldXpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, tblKey, dbDataMap, nil) + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) > 1 { + log.Warningf("Table transformer returned more than one table for container %v", chldXpath) + } + if len(tblList) == 0 { + continue + } + dbDataFromTblXfmrGet(tblList[0], inParams, dbDataMap) + tbl = tblList[0] + } + } + if len(xYangSpecMap[chldXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap[cname] = cmap + } else { + log.Infof("Empty container(\"%v\").\r\n", chldUri) + } + continue + } else { + cmap := make(map[string]interface{}) + err = yangDataFill(dbs, ygRoot, chldUri, chldXpath, dbDataMap, cmap, tbl, tblKey, cdb, isValid) + if len(cmap) > 0 { + resultMap[cname] = cmap + } else { + log.Infof("Empty container(\"%v\").\r\n", chldUri) + } + } + } else if chldYangType == YANG_LIST { + cdb = xYangSpecMap[chldXpath].dbIndex + if len(xYangSpecMap[chldXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap = cmap + } else { + log.Infof("Empty list(\"%v\").\r\n", chldUri) + } + } else { + ynode, ok := xYangSpecMap[chldXpath] + lTblName := "" + if ok && ynode.tableName != nil { + lTblName = *ynode.tableName + } + yangListDataFill(dbs, ygRoot, chldUri, chldXpath, dbDataMap, resultMap, lTblName, "", cdb, isValid) + } + } else { + return err + } + } + } + } + return err +} + +/* Traverse linear db-map data and add to nested json data */ +func dbDataToYangJsonCreate(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, cdb db.DBNum) (string, error) { + var err error + jsonData := "" + resultMap := make(map[string]interface{}) + if isSonicYang(uri) { + return directDbToYangJsonCreate(uri, dbDataMap, resultMap) + } else { + var d *db.DB + reqXpath, keyName, tableName := xpathKeyExtract(d, ygRoot, GET, uri) + yangNode, ok := xYangSpecMap[reqXpath] + if ok { + yangType := yangTypeGet(yangNode.yangEntry) + validateHandlerFlag := false + tableXfmrFlag := false + IsValidate := false + if len(xYangSpecMap[reqXpath].validateFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, keyName, dbDataMap, nil) + res := validateHandlerFunc(inParams) + if !res { + validateHandlerFlag = true + /* cannot immediately return from here since reXpath yangtype decides the return type */ + } else { + IsValidate = res + } + } + isList := false + switch yangType { + case YANG_LIST: + isList = true + case YANG_LEAF, YANG_LEAF_LIST, YANG_CONTAINER: + isList = false + default: + log.Infof("Unknown yang object type for path %v", reqXpath) + isList = true //do not want non-list processing to happen + } + /*If yangtype is a list separate code path is to be taken in case of table transformer + since that code path already handles the calling of table transformer and subsequent processing + */ + if (!validateHandlerFlag) && (!isList) { + if xYangSpecMap[reqXpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[reqXpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, keyName, dbDataMap, nil) + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) > 1 { + log.Warningf("Table transformer returned more than one table for container %v", reqXpath) + tableXfmrFlag = true + } + if len(tblList) == 0 { + log.Warningf("Table transformer returned no table for conatiner %v", reqXpath) + tableXfmrFlag = true + } + if !tableXfmrFlag { + dbDataFromTblXfmrGet(tblList[0], inParams, dbDataMap) + } + } else { + log.Warningf("empty table transformer function name for xpath - %v", reqXpath) + tableXfmrFlag = true + } + } + } + + for { + if yangType == YANG_LEAF || yangType == YANG_LEAF_LIST { + yangName := xYangSpecMap[reqXpath].yangEntry.Name + if validateHandlerFlag || tableXfmrFlag { + resultMap[yangName] = "" + break + } + tbl, key, _ := tableNameAndKeyFromDbMapGet((*dbDataMap)[cdb]) + fldValMap, err := terminalNodeProcess(dbs, ygRoot, uri, reqXpath, dbDataMap, tbl, key) + if err != nil { + log.Infof("Empty terminal node (\"%v\").", uri) + } + resultMap = fldValMap + break + + } else if yangType == YANG_CONTAINER { + cname := xYangSpecMap[reqXpath].yangEntry.Name + cmap := make(map[string]interface{}) + resultMap[cname] = cmap + if validateHandlerFlag || tableXfmrFlag { + break + } + if len(xYangSpecMap[reqXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, "", dbDataMap, nil) + cmap, _ = xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap[cname] = cmap + } + break + } + err = yangDataFill(dbs, ygRoot, uri, reqXpath, dbDataMap, resultMap, tableName, keyName, cdb, IsValidate) + if err != nil { + log.Infof("Empty container(\"%v\").\r\n", uri) + } + break + } else if yangType == YANG_LIST { + if len(xYangSpecMap[reqXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap = cmap + } else { + log.Infof("Empty list(\"%v\").\r\n", uri) + } + } else { + err = yangListDataFill(dbs, ygRoot, uri, reqXpath, dbDataMap, resultMap, tableName, keyName, cdb, IsValidate) + if err != nil { + log.Infof("yangListDataFill failed for list case(\"%v\").\r\n", uri) + } + } + break + } else { + log.Warningf("Unknown yang object type for path %v", reqXpath) + break + } + } //end of for + } + } + + jsonMapData, _ := json.Marshal(resultMap) + jsonData = fmt.Sprintf("%v", string(jsonMapData)) + jsonDataPrint(jsonData) + return jsonData, nil +} + +func jsonDataPrint(data string) { + fp, err := os.Create("/tmp/dbToYangJson.txt") + if err != nil { + return + } + defer fp.Close() + + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf (fp, "%v \r\n", data) + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") +} + diff --git a/src/translib/transformer/xlate_to_db.go b/src/translib/transformer/xlate_to_db.go new file mode 100644 index 0000000000..7ee22a6b33 --- /dev/null +++ b/src/translib/transformer/xlate_to_db.go @@ -0,0 +1,565 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "errors" + "fmt" + "github.com/openconfig/ygot/ygot" + "os" + "reflect" + "strings" + "translib/db" + "translib/ocbinds" + "github.com/openconfig/ygot/ytypes" + "github.com/openconfig/goyang/pkg/yang" + + log "github.com/golang/glog" +) + +/* Invoke the post tansformer */ +func postXfmrHandlerFunc(inParams XfmrParams) (map[string]map[string]db.Value, error) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(xYangSpecMap[xpath].xfmrPost, inParams) + if err != nil { + return nil, err + } + retData := ret[0].Interface().(map[string]map[string]db.Value) + log.Info("Post Transformer function :", xYangSpecMap[xpath].xfmrPost, " Xpath: ", xpath, " retData: ", retData) + return retData, err +} + +/* Fill redis-db map with field & value info */ +func dataToDBMapAdd(tableName string, dbKey string, result map[string]map[string]db.Value, field string, value string) { + _, ok := result[tableName] + if !ok { + result[tableName] = make(map[string]db.Value) + } + + _, ok = result[tableName][dbKey] + if !ok { + result[tableName][dbKey] = db.Value{Field: make(map[string]string)} + } + + if field == "NONE" { + result[tableName][dbKey].Field["NULL"] = "NULL" + return + } + + result[tableName][dbKey].Field[field] = value + return +} + +func tblNameFromTblXfmrGet(xfmrTblFunc string, inParams XfmrParams) (string, error){ + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) != 1 { + logStr := fmt.Sprintf("Invalid return value(%v) from table transformer for (%v)", tblList, inParams.uri) + log.Error(logStr) + err := errors.New(logStr) + return "", err + } + return tblList[0], nil +} + +/* Fill the redis-db map with data */ +func mapFillData(d *db.DB, ygRoot *ygot.GoStruct, oper int, uri string, dbKey string, result map[string]map[string]db.Value, xpathPrefix string, name string, value interface{}) error { + var dbs [db.MaxDB]*db.DB + var err error + xpath := xpathPrefix + "/" + name + xpathInfo, ok := xYangSpecMap[xpath] + log.Infof("name: \"%v\", xpathPrefix(\"%v\").", name, xpathPrefix) + + if !ok || xpathInfo == nil { + log.Errorf("Yang path(\"%v\") not found.", xpath) + return errors.New("Invalid URI") + } + + if xpathInfo.tableName == nil && xpathInfo.xfmrTbl == nil{ + log.Errorf("Table for yang-path(\"%v\") not found.", xpath) + return errors.New("Invalid table name") + } + + if len(dbKey) == 0 { + log.Errorf("Table key for yang path(\"%v\") not found.", xpath) + return errors.New("Invalid table key") + } + + if xpathInfo.isKey { + return nil + } + + tableName := "" + if xpathInfo.xfmrTbl != nil { + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, uri, oper, "", nil, "") + // expecting only one table name from tbl-xfmr + tableName, err = tblNameFromTblXfmrGet(*xYangSpecMap[xpath].xfmrTbl, inParams) + if err != nil { + return err + } + } else { + tableName = *xpathInfo.tableName + } + + if len(xpathInfo.xfmrFunc) > 0 { + uri = uri + "/" + name + + /* field transformer present */ + log.Infof("Transformer function(\"%v\") invoked for yang path(\"%v\").", xpathInfo.xfmrFunc, xpath) + path, _ := ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + node, nErr := ytypes.GetNode(schRoot, (*ygRoot).(*ocbinds.Device), path) + log.Info("GetNode data: ", node[0].Data, " nErr :", nErr) + if nErr != nil { + return nErr + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, uri, oper, "", nil, node[0].Data) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return err + } + if ret != nil { + retData := ret[0].Interface().(map[string]string) + log.Info("Transformer function :", xpathInfo.xfmrFunc, " Xpath: ", xpath, " retData: ", retData) + for f, v := range retData { + dataToDBMapAdd(tableName, dbKey, result, f, v) + } + } + return nil + } + + if len(xpathInfo.fieldName) == 0 { + log.Infof("Field for yang-path(\"%v\") not found in DB.", xpath) + return errors.New("Invalid field name") + } + fieldName := xpathInfo.fieldName + valueStr := "" + if xpathInfo.yangEntry.IsLeafList() { + /* Both yang side and Db side('@' suffix field) the data type is leaf-list */ + log.Info("Yang type and Db type is Leaflist for field = ", xpath) + fieldName += "@" + if reflect.ValueOf(value).Kind() != reflect.Slice { + logStr := fmt.Sprintf("Value for yang xpath %v which is a leaf-list should be a slice", xpath) + log.Error(logStr) + err := errors.New(logStr) + return err + } + valData := reflect.ValueOf(value) + for fidx := 0; fidx < valData.Len(); fidx++ { + if fidx > 0 { + valueStr += "," + } + fVal := fmt.Sprintf("%v", valData.Index(fidx).Interface()) + valueStr = valueStr + fVal + } + log.Infof("leaf-list value after conversion to DB format %v : %v", fieldName, valueStr) + + } else { // xpath is a leaf + valueStr = fmt.Sprintf("%v", value) + if strings.Contains(valueStr, ":") { + valueStr = strings.Split(valueStr, ":")[1] + } + } + + dataToDBMapAdd(tableName, dbKey, result, fieldName, valueStr) + log.Infof("TblName: \"%v\", key: \"%v\", field: \"%v\", valueStr: \"%v\".", tableName, dbKey, + fieldName, valueStr) + return nil +} + +func sonicYangReqToDbMapCreate(jsonData interface{}, result map[string]map[string]db.Value) error { + if reflect.ValueOf(jsonData).Kind() == reflect.Map { + data := reflect.ValueOf(jsonData) + for _, key := range data.MapKeys() { + _, ok := xDbSpecMap[key.String()] + if ok { + directDbMapData("", key.String(), data.MapIndex(key).Interface(), result) + } else { + sonicYangReqToDbMapCreate(data.MapIndex(key).Interface(), result) + } + } + } + return nil +} + +func dbMapDataFill(uri string, tableName string, keyName string, d map[string]interface{}, result map[string]map[string]db.Value) { + result[tableName][keyName] = db.Value{Field: make(map[string]string)} + for field, value := range d { + fieldXpath := tableName + "/" + field + if _, fieldOk := xDbSpecMap[fieldXpath]; (fieldOk && (xDbSpecMap[fieldXpath].dbEntry != nil)) { + log.Info("Found non-nil yang entry in xDbSpecMap for field xpath = ", fieldXpath) + if xDbSpecMap[fieldXpath].dbEntry.IsLeafList() { + log.Info("Yang type is Leaflist for field = ", field) + field += "@" + fieldDt := reflect.ValueOf(value) + fieldValue := "" + for fidx := 0; fidx < fieldDt.Len(); fidx++ { + if fidx > 0 { + fieldValue += "," + } + fVal := fmt.Sprintf("%v", fieldDt.Index(fidx).Interface()) + fieldValue = fieldValue + fVal + } + result[tableName][keyName].Field[field] = fieldValue + continue + } + } else { + // should ideally never happen , just adding for safety + log.Info("Did not find entry in xDbSpecMap for field xpath = ", fieldXpath) + } + result[tableName][keyName].Field[field] = fmt.Sprintf("%v", value) + } + return +} + +func dbMapListDataFill(uri string, tableName string, dbEntry *yang.Entry, jsonData interface{}, result map[string]map[string]db.Value) { + data := reflect.ValueOf(jsonData) + tblKeyName := strings.Split(dbEntry.Key, " ") + for idx := 0; idx < data.Len(); idx++ { + keyName := "" + d := data.Index(idx).Interface().(map[string]interface{}) + for i, k := range tblKeyName { + if i > 0 { + keyName += "|" + } + keyName += fmt.Sprintf("%v", d[k]) + delete(d, k) + } + dbMapDataFill(uri, tableName, keyName, d, result) + } + return +} + +func directDbMapData(uri string, tableName string, jsonData interface{}, result map[string]map[string]db.Value) bool { + _, ok := xDbSpecMap[tableName] + if ok && xDbSpecMap[tableName].dbEntry != nil { + data := reflect.ValueOf(jsonData).Interface().(map[string]interface{}) + key := "" + dbSpecData := xDbSpecMap[tableName] + result[tableName] = make(map[string]db.Value) + + if dbSpecData.keyName != nil { + key = *dbSpecData.keyName + log.Infof("Fill data for container uri(%v), key(%v)", uri, key) + dbMapDataFill(uri, tableName, key, data, result) + return true + } + + for k, v := range data { + xpath := tableName + "/" + k + curDbSpecData, ok := xDbSpecMap[xpath] + if ok && curDbSpecData.dbEntry != nil { + eType := yangTypeGet(curDbSpecData.dbEntry) + switch eType { + case "list": + log.Infof("Fill data for list uri(%v)", uri) + dbMapListDataFill(uri, tableName, curDbSpecData.dbEntry, v, result) + default: + log.Infof("Invalid node type for uri(%v)", uri) + } + } + } + } + return true +} + +/* Get the db table, key and field name for the incoming delete request */ +func dbMapDelete(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + var err error + if isSonicYang(path) { + xpathPrefix, keyName, tableName := sonicXpathKeyExtract(path) + log.Infof("Delete req: path(\"%v\"), key(\"%v\"), xpathPrefix(\"%v\"), tableName(\"%v\").", path, keyName, xpathPrefix, tableName) + err = sonicYangReqToDbMapDelete(xpathPrefix, tableName, keyName, result) + } else { + xpathPrefix, keyName, tableName := xpathKeyExtract(d, ygRoot, oper, path) + log.Infof("Delete req: path(\"%v\"), key(\"%v\"), xpathPrefix(\"%v\"), tableName(\"%v\").", path, keyName, xpathPrefix, tableName) + spec, ok := xYangSpecMap[xpathPrefix] + if ok { + if spec.tableName != nil { + result[*spec.tableName] = make(map[string]db.Value) + if len(keyName) > 0 { + result[*spec.tableName][keyName] = db.Value{Field: make(map[string]string)} + if spec.yangEntry != nil && spec.yangEntry.Node.Statement().Keyword == "leaf" { + result[*spec.tableName][keyName].Field[spec.fieldName] = "" + } + } + } else if len(spec.childTable) > 0 { + for _, child := range spec.childTable { + result[child] = make(map[string]db.Value) + } + } + } + } + log.Infof("Delete req: path(\"%v\") result(\"%v\").", path, result) + return err +} + +func sonicYangReqToDbMapDelete(xpathPrefix string, tableName string, keyName string, result map[string]map[string]db.Value) error { + if (tableName != "") { + // Specific table entry case + result[tableName] = make(map[string]db.Value) + if (keyName != "") { + // Specific key case + var dbVal db.Value + tokens:= strings.Split(xpathPrefix, "/") + if tokens[SONIC_TABLE_INDEX] == tableName { + fieldName := tokens[len(tokens)-1] + dbSpecField := tableName + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok { + yangType := xDbSpecMap[dbSpecField].fieldType + // terminal node case + if yangType == YANG_LEAF_LIST { + fieldName = fieldName + "@" + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = "" + } + if yangType == YANG_LEAF { + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = "" + } + } + } + result[tableName][keyName] = dbVal + } else { + // Get all keys + } + } else { + // Get all table entries + // If table name not available in xpath get top container name + _, ok := xDbSpecMap[xpathPrefix] + if ok && xDbSpecMap[xpathPrefix] != nil { + dbInfo := xDbSpecMap[xpathPrefix] + if dbInfo.fieldType == "container" { + for dir, _ := range dbInfo.dbEntry.Dir { + result[dir] = make(map[string]db.Value) + } + } + } + } + return nil +} + +/* Get the data from incoming update/replace request, create map and fill with dbValue(ie. field:value to write into redis-db */ +func dbMapUpdate(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + log.Infof("Update/replace req: path(\"%v\").", path) + dbMapCreate(d, ygRoot, oper, path, jsonData, result) + log.Infof("Update/replace req: path(\"%v\") result(\"%v\").", path, result) + printDbData(result, "/tmp/yangToDbDataUpRe.txt") + return nil +} + +/* Get the data from incoming create request, create map and fill with dbValue(ie. field:value to write into redis-db */ +func dbMapCreate(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + var err error + root := xpathRootNameGet(path) + if isSonicYang(path) { + err = sonicYangReqToDbMapCreate(jsonData, result) + } else { + err = yangReqToDbMapCreate(d, ygRoot, oper, root, "", "", jsonData, result) + } + if err == nil { + if oper == CREATE { + moduleNm := "/" + strings.Split(path, "/")[1] + log.Infof("Module name for path %s is %s", path, moduleNm) + if _, ok := xYangSpecMap[moduleNm]; ok { + if xYangSpecMap[moduleNm].yangDataType == "container" && len(xYangSpecMap[moduleNm].xfmrPost) > 0 { + log.Info("Invoke post transformer: ", xYangSpecMap[moduleNm].xfmrPost) + dbDataMap := make(map[db.DBNum]map[string]map[string]db.Value) + dbDataMap[db.ConfigDB] = result + var dbs [db.MaxDB]*db.DB + inParams := formXfmrInputRequest(d, dbs, db.ConfigDB, ygRoot, path, oper, "", &dbDataMap, nil) + result, err = postXfmrHandlerFunc(inParams) + } + } else { + log.Errorf("No Entry exists for module %s in xYangSpecMap. Unable to process post xfmr (\"%v\") path(\"%v\") error (\"%v\").", oper, path, err) + } + } + printDbData(result, "/tmp/yangToDbDataCreate.txt") + } else { + log.Errorf("DBMapCreate req failed for oper (\"%v\") path(\"%v\") error (\"%v\").", oper, path, err) + } + return err +} + +func yangNodeForUriGet(uri string, ygRoot *ygot.GoStruct) (interface{}, error) { + path, _ := ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + node, nErr := ytypes.GetNode(schRoot, (*ygRoot).(*ocbinds.Device), path) + if nErr != nil { + return nil, nErr + } + return node[0].Data, nil +} + +func yangReqToDbMapCreate(d *db.DB, ygRoot *ygot.GoStruct, oper int, uri string, xpathPrefix string, keyName string, jsonData interface{}, result map[string]map[string]db.Value) error { + log.Infof("key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + var dbs [db.MaxDB]*db.DB + + if reflect.ValueOf(jsonData).Kind() == reflect.Slice { + log.Infof("slice data: key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + jData := reflect.ValueOf(jsonData) + dataMap := make([]interface{}, jData.Len()) + for idx := 0; idx < jData.Len(); idx++ { + dataMap[idx] = jData.Index(idx).Interface() + } + for _, data := range dataMap { + curKey := "" + curUri, _ := uriWithKeyCreate(uri, xpathPrefix, data) + _, ok := xYangSpecMap[xpathPrefix] + if ok && len(xYangSpecMap[xpathPrefix].xfmrKey) > 0 { + /* key transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpathPrefix].xfmrKey), inParams) + if err != nil { + return err + } + if ret != nil { + curKey = ret[0].Interface().(string) + } + } else if xYangSpecMap[xpathPrefix].keyName != nil { + curKey = *xYangSpecMap[xpathPrefix].keyName + } else { + curKey = keyCreate(keyName, xpathPrefix, data, d.Opts.KeySeparator) + } + yangReqToDbMapCreate(d, ygRoot, oper, curUri, xpathPrefix, curKey, data, result) + } + } else { + if reflect.ValueOf(jsonData).Kind() == reflect.Map { + jData := reflect.ValueOf(jsonData) + for _, key := range jData.MapKeys() { + typeOfValue := reflect.TypeOf(jData.MapIndex(key).Interface()).Kind() + + log.Infof("slice/map data: key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + xpath := uri + curUri := uri + curKey := keyName + pathAttr := key.String() + if len(xpathPrefix) > 0 { + if strings.Contains(pathAttr, ":") { + pathAttr = strings.Split(pathAttr, ":")[1] + } + xpath = xpathPrefix + "/" + pathAttr + curUri = uri + "/" + pathAttr + } + _, ok := xYangSpecMap[xpath] + log.Infof("slice/map data: curKey(\"%v\"), xpath(\"%v\"), curUri(\"%v\").", + curKey, xpath, curUri) + if ok && xYangSpecMap[xpath] != nil && len(xYangSpecMap[xpath].xfmrKey) > 0 { + /* key transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrKey), inParams) + if err != nil { + return err + } + if ret != nil { + curKey = ret[0].Interface().(string) + } + } else if xYangSpecMap[xpath].keyName != nil { + curKey = *xYangSpecMap[xpath].keyName + } + + if (typeOfValue == reflect.Map || typeOfValue == reflect.Slice) && xYangSpecMap[xpath].yangDataType != "leaf-list" { + if ok && xYangSpecMap[xpath] != nil && len(xYangSpecMap[xpath].xfmrFunc) > 0 { + /* subtree transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return nil + } + if ret != nil { + mapCopy(result, ret[0].Interface().(map[string]map[string]db.Value)) + } + } else { + yangReqToDbMapCreate(d, ygRoot, oper, curUri, xpath, curKey, jData.MapIndex(key).Interface(), result) + } + } else { + pathAttr := key.String() + if strings.Contains(pathAttr, ":") { + pathAttr = strings.Split(pathAttr, ":")[1] + } + value := jData.MapIndex(key).Interface() + log.Infof("data field: key(\"%v\"), value(\"%v\").", key, value) + err := mapFillData(d, ygRoot, oper, uri, curKey, result, xpathPrefix, + pathAttr, value) + if err != nil { + log.Errorf("Failed constructing data for db write: key(\"%v\"), value(\"%v\"), path(\"%v\").", + pathAttr, value, xpathPrefix) + } + } + } + } + } + return nil +} + +/* Debug function to print the map data into file */ +func printDbData(db map[string]map[string]db.Value, fileName string) { + fp, err := os.Create(fileName) + if err != nil { + return + } + defer fp.Close() + + for k, v := range db { + fmt.Fprintf(fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf(fp, "table name : %v\r\n", k) + for ik, iv := range v { + fmt.Fprintf(fp, " key : %v\r\n", ik) + for k, d := range iv.Field { + fmt.Fprintf(fp, " %v :%v\r\n", k, d) + } + } + } + fmt.Fprintf(fp, "-----------------------------------------------------------------\r\n") + return +} diff --git a/src/translib/transformer/xlate_utils.go b/src/translib/transformer/xlate_utils.go new file mode 100644 index 0000000000..10c4861e29 --- /dev/null +++ b/src/translib/transformer/xlate_utils.go @@ -0,0 +1,591 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "strings" + "reflect" + "regexp" + "translib/db" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" + log "github.com/golang/glog" +) + +/* Create db key from data xpath(request) */ +func keyCreate(keyPrefix string, xpath string, data interface{}, dbKeySep string) string { + _, ok := xYangSpecMap[xpath] + if ok { + if xYangSpecMap[xpath].yangEntry != nil { + yangEntry := xYangSpecMap[xpath].yangEntry + delim := dbKeySep + if len(xYangSpecMap[xpath].delim) > 0 { + delim = xYangSpecMap[xpath].delim + log.Infof("key concatenater(\"%v\") found for xpath %v ", delim, xpath) + } + + if len(keyPrefix) > 0 { keyPrefix += delim } + keyVal := "" + for i, k := range (strings.Split(yangEntry.Key, " ")) { + if i > 0 { keyVal = keyVal + delim } + val := fmt.Sprint(data.(map[string]interface{})[k]) + if strings.Contains(val, ":") { + val = strings.Split(val, ":")[1] + } + keyVal += val + } + keyPrefix += string(keyVal) + } + } + return keyPrefix +} + +/* Copy redis-db source to destn map */ +func mapCopy(destnMap map[string]map[string]db.Value, srcMap map[string]map[string]db.Value) { + for table, tableData := range srcMap { + _, ok := destnMap[table] + if !ok { + destnMap[table] = make(map[string]db.Value) + } + for rule, ruleData := range tableData { + _, ok = destnMap[table][rule] + if !ok { + destnMap[table][rule] = db.Value{Field: make(map[string]string)} + } + for field, value := range ruleData.Field { + destnMap[table][rule].Field[field] = value + } + } + } +} + +func parentXpathGet(xpath string) string { + path := "" + if len(xpath) > 0 { + p := strings.Split(xpath, "/") + path = strings.Join(p[:len(p)-1], "/") + } + return path +} + +func isYangResType(ytype string) bool { + if ytype == "choose" || ytype == "case" { + return true + } + return false +} + +func yangTypeGet(entry *yang.Entry) string { + if entry != nil && entry.Node != nil { + return entry.Node.Statement().Keyword + } + return "" +} + +func dbKeyToYangDataConvert(uri string, xpath string, dbKey string, dbKeySep string) (map[string]interface{}, string, error) { + var err error + if len(uri) == 0 && len(xpath) == 0 && len(dbKey) == 0 { + err = fmt.Errorf("Insufficient input") + return nil, "", err + } + + if _, ok := xYangSpecMap[xpath]; ok { + if xYangSpecMap[xpath].yangEntry == nil { + err = fmt.Errorf("Yang Entry not available for xpath ", xpath) + return nil, "", nil + } + } + + keyNameList := yangKeyFromEntryGet(xYangSpecMap[xpath].yangEntry) + id := xYangSpecMap[xpath].keyLevel + keyDataList := strings.SplitN(dbKey, dbKeySep, id) + uriWithKey := fmt.Sprintf("%v", xpath) + uriWithKeyCreate := true + if len(keyDataList) == 0 { + keyDataList = append(keyDataList, dbKey) + } + + /* if uri contins key, use it else use xpath */ + if strings.Contains(uri, "[") { + uriXpath, _ := XfmrRemoveXPATHPredicates(uri) + if (uriXpath == xpath && (strings.HasSuffix(uri, "]") || strings.HasSuffix(uri, "]/"))) { + uriWithKeyCreate = false + } + uriWithKey = fmt.Sprintf("%v", uri) + } + + if len(xYangSpecMap[xpath].xfmrKey) > 0 { + var dbs [db.MaxDB]*db.DB + inParams := formXfmrInputRequest(nil, dbs, db.MaxDB, nil, uri, GET, dbKey, nil, nil) + ret, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrKey), inParams) + if err != nil { + return nil, "", err + } + rmap := ret[0].Interface().(map[string]interface{}) + if uriWithKeyCreate { + for k, v := range rmap { + uriWithKey += fmt.Sprintf("[%v=%v]", k, v) + } + } + return rmap, uriWithKey, nil + } + + if len(keyNameList) == 0 { + return nil, "", nil + } + + + rmap := make(map[string]interface{}) + if len(keyNameList) > 1 { + log.Infof("No key transformer found for multi element yang key mapping to a single redis key string.") + return rmap, uriWithKey, nil + } + rmap[keyNameList[0]] = keyDataList[0] + if uriWithKeyCreate { + uriWithKey += fmt.Sprintf("[%v=%v]", keyNameList[0], keyDataList[0]) + } + + return rmap, uriWithKey, nil +} + +func contains(sl []string, str string) bool { + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +func isSubtreeRequest(targetUriPath string, nodePath string) bool { + if len(targetUriPath) > 0 && len(nodePath) > 0 { + return strings.HasPrefix(targetUriPath, nodePath) + } + return false +} + +func getYangPathFromUri(uri string) (string, error) { + var path *gnmi.Path + var err error + + path, err = ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Errorf("Error in uri to path conversion: %v", err) + return "", err + } + + yangPath, yperr := ygot.PathToSchemaPath(path) + if yperr != nil { + log.Errorf("Error in Gnmi path to Yang path conversion: %v", yperr) + return "", yperr + } + + return yangPath, err +} + +func yangKeyFromEntryGet(entry *yang.Entry) []string { + var keyList []string + for _, key := range strings.Split(entry.Key, " ") { + keyList = append(keyList, key) + } + return keyList +} + +func isSonicYang(path string) bool { + if strings.HasPrefix(path, "/sonic") { + return true + } + return false +} + +func sonicKeyDataAdd(dbIndex db.DBNum, keyNameList []string, keyStr string, resultMap map[string]interface{}) { + var dbOpts db.Options + dbOpts = getDBOptions(dbIndex) + keySeparator := dbOpts.KeySeparator + keyValList := strings.Split(keyStr, keySeparator) + + if len(keyNameList) != len(keyValList) { + return + } + + for i, keyName := range keyNameList { + resultMap[keyName] = keyValList[i] + } +} + +func yangToDbXfmrFunc(funcName string) string { + return ("YangToDb_" + funcName) +} + +func uriWithKeyCreate (uri string, xpathTmplt string, data interface{}) (string, error) { + var err error + if _, ok := xYangSpecMap[xpathTmplt]; ok { + yangEntry := xYangSpecMap[xpathTmplt].yangEntry + if yangEntry != nil { + for _, k := range (strings.Split(yangEntry.Key, " ")) { + uri += fmt.Sprintf("[%v=%v]", k, data.(map[string]interface{})[k]) + } + } else { + err = fmt.Errorf("Yang Entry not available for xpath ", xpathTmplt) + } + } else { + err = fmt.Errorf("No entry in xYangSpecMap for xpath ", xpathTmplt) + } + return uri, err +} + +func xpathRootNameGet(path string) string { + if len(path) > 0 { + pathl := strings.Split(path, "/") + return ("/" + pathl[1]) + } + return "" +} + +func getDbNum(xpath string ) db.DBNum { + _, ok := xYangSpecMap[xpath] + if ok { + xpathInfo := xYangSpecMap[xpath] + return xpathInfo.dbIndex + } + // Default is ConfigDB + return db.ConfigDB +} + +func dbToYangXfmrFunc(funcName string) string { + return ("DbToYang_" + funcName) +} + +func uriModuleNameGet(uri string) (string, error) { + var err error + result := "" + if len(uri) == 0 { + log.Error("Empty uri string supplied") + err = fmt.Errorf("Empty uri string supplied") + return result, err + } + urislice := strings.Split(uri, ":") + if len(urislice) == 1 { + log.Errorf("uri string %s does not have module name", uri) + err = fmt.Errorf("uri string does not have module name: ", uri) + return result, err + } + moduleNm := strings.Split(urislice[0], "/") + result = moduleNm[1] + if len(strings.Trim(result, " ")) == 0 { + log.Error("Empty module name") + err = fmt.Errorf("No module name found in uri %s", uri) + } + log.Info("module name = ", result) + return result, err +} + +func recMap(rMap interface{}, name []string, id int, max int) { + if id == max || id < 0 { + return + } + val := name[id] + if reflect.ValueOf(rMap).Kind() == reflect.Map { + data := reflect.ValueOf(rMap) + dMap := data.Interface().(map[string]interface{}) + _, ok := dMap[val] + if ok { + recMap(dMap[val], name, id+1, max) + } else { + dMap[val] = make(map[string]interface{}) + recMap(dMap[val], name, id+1, max) + } + } + return +} + +func mapCreate(xpath string) map[string]interface{} { + retMap := make(map[string]interface{}) + if len(xpath) > 0 { + attrList := strings.Split(xpath, "/") + alLen := len(attrList) + recMap(retMap, attrList, 1, alLen) + } + return retMap +} + +func mapInstGet(name []string, id int, max int, inMap interface{}) map[string]interface{} { + if inMap == nil { + return nil + } + result := reflect.ValueOf(inMap).Interface().(map[string]interface{}) + if id == max { + return result + } + val := name[id] + if reflect.ValueOf(inMap).Kind() == reflect.Map { + data := reflect.ValueOf(inMap) + dMap := data.Interface().(map[string]interface{}) + _, ok := dMap[val] + if ok { + result = mapInstGet(name, id+1, max, dMap[val]) + } else { + return result + } + } + return result +} + +func mapGet(xpath string, inMap map[string]interface{}) map[string]interface{} { + attrList := strings.Split(xpath, "/") + alLen := len(attrList) + recMap(inMap, attrList, 1, alLen) + retMap := mapInstGet(attrList, 1, alLen, inMap) + return retMap +} + +func formXfmrInputRequest(d *db.DB, dbs [db.MaxDB]*db.DB, cdb db.DBNum, ygRoot *ygot.GoStruct, uri string, oper int, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, param interface{}) XfmrParams { + var inParams XfmrParams + inParams.d = d + inParams.dbs = dbs + inParams.curDb = cdb + inParams.ygRoot = ygRoot + inParams.uri = uri + inParams.oper = oper + inParams.key = key + inParams.dbDataMap = dbDataMap + inParams.param = param // generic param + + return inParams +} + +func findByValue(m map[string]string, value string) string { + for key, val := range m { + if val == value { + return key + } + } + return "" +} +func findByKey(m map[string]string, key string) string { + if val, found := m[key]; found { + return val + } + return "" +} +func findInMap(m map[string]string, str string) string { + // Check if str exists as a value in map m. + if val := findByKey(m, str); val != "" { + return val + } + + // Check if str exists as a key in map m. + if val := findByValue(m, str); val != "" { + return val + } + + // str doesn't exist in map m. + return "" +} + +func getDBOptions(dbNo db.DBNum) db.Options { + var opt db.Options + + switch dbNo { + case db.ApplDB, db.CountersDB: + opt = getDBOptionsWithSeparator(dbNo, "", ":", ":") + break + case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB: + opt = getDBOptionsWithSeparator(dbNo, "", "|", "|") + break + } + + return opt +} + +func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparator string, keySeparator string) db.Options { + return(db.Options { + DBNo : dbNo, + InitIndicator : initIndicator, + TableNameSeparator: tableSeparator, + KeySeparator : keySeparator, + }) +} + +func stripAugmentedModuleNames(xpath string) string { + pathList := strings.Split(xpath, "/") + pathList = pathList[1:] + for i, pvar := range pathList { + if (i > 0) && strings.Contains(pvar, ":") { + pvar = strings.Split(pvar,":")[1] + pathList[i] = pvar + } + } + path := "/" + strings.Join(pathList, "/") + return path +} + +func XfmrRemoveXPATHPredicates(xpath string) (string, error) { + pathList := strings.Split(xpath, "/") + pathList = pathList[1:] + for i, pvar := range pathList { + if strings.Contains(pvar, "[") && strings.Contains(pvar, "]") { + si, ei := strings.Index(pvar, "["), strings.Index(pvar, "]") + // substring contains [] entries + if (si < ei) { + pvar = strings.Split(pvar, "[")[0] + pathList[i] = pvar + + } else { + // This substring contained a ] before a [. + return "", fmt.Errorf("Incorrect ordering of [] within substring %s of %s, [ pos: %d, ] pos: %d", pvar, xpath, si, ei) + } + } else if strings.Contains(pvar, "[") || strings.Contains(pvar, "]") { + // This substring contained a mismatched pair of []s. + return "", fmt.Errorf("Mismatched brackets within substring %s of %s", pvar, xpath) + } + if (i > 0) && strings.Contains(pvar, ":") { + pvar = strings.Split(pvar,":")[1] + pathList[i] = pvar + } + } + path := "/" + strings.Join(pathList, "/") + return path,nil +} + + /* Extract key vars, create db key and xpath */ + func xpathKeyExtract(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string) (string, string, string) { + keyStr := "" + tableName := "" + pfxPath := "" + rgp := regexp.MustCompile(`\[([^\[\]]*)\]`) + curPathWithKey := "" + cdb := db.ConfigDB + var dbs [db.MaxDB]*db.DB + + pfxPath, _ = XfmrRemoveXPATHPredicates(path) + xpathInfo, ok := xYangSpecMap[pfxPath] + if !ok { + log.Errorf("No entry found in xYangSpecMap for xpath %v.", pfxPath) + return pfxPath, keyStr, tableName + } + cdb = xpathInfo.dbIndex + dbOpts := getDBOptions(cdb) + keySeparator := dbOpts.KeySeparator + if len(xpathInfo.delim) > 0 { + keySeparator = xpathInfo.delim + } + + for _, k := range strings.Split(path, "/") { + curPathWithKey += k + yangXpath, _ := XfmrRemoveXPATHPredicates(curPathWithKey) + _, ok := xYangSpecMap[yangXpath] + if ok { + if strings.Contains(k, "[") { + if len(keyStr) > 0 { + keyStr += keySeparator + } + if len(xYangSpecMap[yangXpath].xfmrKey) > 0 { + xfmrFuncName := yangToDbXfmrFunc(xYangSpecMap[yangXpath].xfmrKey) + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curPathWithKey, oper, "", nil, nil) + ret, err := XlateFuncCall(xfmrFuncName, inParams) + if err != nil { + return "", "", "" + } + if ret != nil { + keyStr = ret[0].Interface().(string) + } + } else if xYangSpecMap[yangXpath].keyName != nil { + keyStr += *xYangSpecMap[yangXpath].keyName + } else { + /* multi-leaf yang key together forms a single key-string in redis. + There should be key-transformer, if not then the yang key leaves + will be concatenated with respective default DB type key-delimiter + */ + for idx, kname := range rgp.FindAllString(k, -1) { + if idx > 0 { keyStr += keySeparator } + keyl := strings.TrimRight(strings.TrimLeft(kname, "["), "]") + if strings.Contains(keyl, ":") { + keyl = strings.Split(keyl, ":")[1] + } + keyStr += strings.Split(keyl, "=")[1] + } + } + } else if len(xYangSpecMap[yangXpath].xfmrKey) > 0 { + xfmrFuncName := yangToDbXfmrFunc(xYangSpecMap[yangXpath].xfmrKey) + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curPathWithKey, oper, "", nil, nil) + ret, err := XlateFuncCall(xfmrFuncName, inParams) + if err != nil { + return "", "", "" + } + if ret != nil { + keyStr = ret[0].Interface().(string) + } + } else if xYangSpecMap[yangXpath].keyName != nil { + keyStr += *xYangSpecMap[yangXpath].keyName + } + } + curPathWithKey += "/" + } + curPathWithKey = strings.TrimSuffix(curPathWithKey, "/") + tblPtr := xpathInfo.tableName + if tblPtr != nil { + tableName = *tblPtr + } else if xpathInfo.xfmrTbl != nil { + inParams := formXfmrInputRequest(d, dbs, cdb, ygRoot, curPathWithKey, oper, "", nil, nil) + tableName, _ = tblNameFromTblXfmrGet(*xpathInfo.xfmrTbl, inParams) + } + return pfxPath, keyStr, tableName + } + + func sonicXpathKeyExtract(path string) (string, string, string) { + xpath, keyStr, tableName := "", "", "" + var err error + xpath, err = XfmrRemoveXPATHPredicates(path) + if err != nil { + return xpath, keyStr, tableName + } + rgp := regexp.MustCompile(`\[([^\[\]]*)\]`) + pathsubStr := strings.Split(path , "/") + if len(pathsubStr) > SONIC_TABLE_INDEX { + if strings.Contains(pathsubStr[2], "[") { + tableName = strings.Split(pathsubStr[SONIC_TABLE_INDEX], "[")[0] + } else { + tableName = pathsubStr[SONIC_TABLE_INDEX] + } + dbInfo, ok := xDbSpecMap[tableName] + cdb := db.ConfigDB + if !ok { + log.Infof("No entry in xDbSpecMap for xpath %v in order to fetch DB index.", tableName) + return xpath, keyStr, tableName + } + cdb = dbInfo.dbIndex + dbOpts := getDBOptions(cdb) + if dbInfo.keyName != nil { + keyStr = *dbInfo.keyName + } else { + for i, kname := range rgp.FindAllString(path, -1) { + if i > 0 { + keyStr += dbOpts.KeySeparator + } + val := strings.Split(kname, "=")[1] + keyStr += strings.TrimRight(val, "]") + } + } + } + return xpath, keyStr, tableName + } + diff --git a/src/translib/transformer/xspec.go b/src/translib/transformer/xspec.go new file mode 100644 index 0000000000..6c556deb85 --- /dev/null +++ b/src/translib/transformer/xspec.go @@ -0,0 +1,598 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "os" + "strings" + log "github.com/golang/glog" + "translib/db" + + "github.com/openconfig/goyang/pkg/yang" +) + +/* Data needed to construct lookup table from yang */ +type yangXpathInfo struct { + yangDataType string + tableName *string + xfmrTbl *string + childTable []string + dbEntry *yang.Entry + yangEntry *yang.Entry + keyXpath map[int]*[]string + delim string + fieldName string + xfmrFunc string + xfmrPost string + validateFunc string + xfmrKey string + keyName *string + dbIndex db.DBNum + keyLevel int + isKey bool +} + +type dbInfo struct { + dbIndex db.DBNum + keyName *string + fieldType string + dbEntry *yang.Entry + yangXpath []string + module string +} + +var xYangSpecMap map[string]*yangXpathInfo +var xDbSpecMap map[string]*dbInfo +var xDbSpecOrdTblMap map[string][]string //map of module-name to ordered list of db tables { "sonic-acl" : ["ACL_TABLE", "ACL_RULE"] } + +/* update transformer spec with db-node */ +func updateDbTableData (xpath string, xpathData *yangXpathInfo, tableName string) { + _, ok := xDbSpecMap[tableName] + if ok { + xDbSpecMap[tableName].yangXpath = append(xDbSpecMap[tableName].yangXpath, xpath) + xpathData.dbEntry = xDbSpecMap[tableName].dbEntry + } +} + +/* Recursive api to fill the map with yang details */ +func yangToDbMapFill (keyLevel int, xYangSpecMap map[string]*yangXpathInfo, entry *yang.Entry, xpathPrefix string) { + xpath := "" + /* create the yang xpath */ + if xYangSpecMap[xpathPrefix] != nil && xYangSpecMap[xpathPrefix].yangDataType == "module" { + /* module name is separated from the rest of xpath with ":" */ + xpath = xpathPrefix + ":" + entry.Name + } else { + xpath = xpathPrefix + "/" + entry.Name + } + + xpathData, ok := xYangSpecMap[xpath] + if !ok { + xpathData = new(yangXpathInfo) + xYangSpecMap[xpath] = xpathData + xpathData.dbIndex = db.ConfigDB // default value + } else { + xpathData = xYangSpecMap[xpath] + } + + xpathData.yangDataType = entry.Node.Statement().Keyword + if entry.Node.Statement().Keyword == "list" && xpathData.tableName != nil { + childToUpdateParent(xpath, *xpathData.tableName) + } + + parentXpathData, ok := xYangSpecMap[xpathPrefix] + /* init current xpath table data with its parent data, change only if needed. */ + if ok { + if xpathData.tableName == nil && parentXpathData.tableName != nil && xpathData.xfmrTbl == nil { + xpathData.tableName = parentXpathData.tableName + } else if xpathData.xfmrTbl == nil && parentXpathData.xfmrTbl != nil { + xpathData.xfmrTbl = parentXpathData.xfmrTbl + } + } + + if ok && xpathData.dbIndex == db.ConfigDB && parentXpathData.dbIndex != db.ConfigDB { + // If DB Index is not annotated and parent DB index is annotated inherit the DB Index of the parent + xpathData.dbIndex = parentXpathData.dbIndex + } + + if ok && len(parentXpathData.validateFunc) > 0 { + xpathData.validateFunc = parentXpathData.validateFunc + } + + if ok && len(parentXpathData.xfmrFunc) > 0 && len(xpathData.xfmrFunc) == 0 { + xpathData.xfmrFunc = parentXpathData.xfmrFunc + } + + if xpathData.yangDataType == "leaf" && len(xpathData.fieldName) == 0 { + if xpathData.tableName != nil && xDbSpecMap[*xpathData.tableName] != nil { + if xDbSpecMap[*xpathData.tableName].dbEntry.Dir[entry.Name] != nil { + xpathData.fieldName = entry.Name + } else if xDbSpecMap[*xpathData.tableName].dbEntry.Dir[strings.ToUpper(entry.Name)] != nil { + xpathData.fieldName = strings.ToUpper(entry.Name) + } + } else if xpathData.xfmrTbl != nil { + /* table transformer present */ + xpathData.fieldName = entry.Name + } + } + + if xpathData.yangDataType == "leaf" && len(xpathData.fieldName) > 0 && xpathData.tableName != nil { + dbPath := *xpathData.tableName + "/" + xpathData.fieldName + if xDbSpecMap[dbPath] != nil { + xDbSpecMap[dbPath].yangXpath = append(xDbSpecMap[dbPath].yangXpath, xpath) + } + } + + /* fill table with key data. */ + curKeyLevel := keyLevel + if len(entry.Key) != 0 { + parentKeyLen := 0 + + /* create list with current keys */ + keyXpath := make([]string, len(strings.Split(entry.Key, " "))) + for id, keyName := range(strings.Split(entry.Key, " ")) { + keyXpath[id] = xpath + "/" + keyName + keyXpathData := new(yangXpathInfo) + xYangSpecMap[xpath + "/" + keyName] = keyXpathData + xYangSpecMap[xpath + "/" + keyName].isKey = true + } + + xpathData.keyXpath = make(map[int]*[]string, (parentKeyLen + 1)) + k := 0 + for ; k < parentKeyLen; k++ { + /* copy parent key-list to child key-list*/ + xpathData.keyXpath[k] = parentXpathData.keyXpath[k] + } + xpathData.keyXpath[k] = &keyXpath + xpathData.keyLevel = curKeyLevel + curKeyLevel++ + } else if parentXpathData != nil && parentXpathData.keyXpath != nil { + xpathData.keyXpath = parentXpathData.keyXpath + } + + /* get current obj's children */ + var childList []string + for k := range entry.Dir { + childList = append(childList, k) + } + + xpathData.yangEntry = entry + /* now recurse, filling the map with current node's children info */ + for _, child := range childList { + yangToDbMapFill(curKeyLevel, xYangSpecMap, entry.Dir[child], xpath) + } +} + +/* Build lookup table based of yang xpath */ +func yangToDbMapBuild(entries map[string]*yang.Entry) { + if entries == nil { + return + } + + if xYangSpecMap == nil { + xYangSpecMap = make(map[string]*yangXpathInfo) + } + + for module, e := range entries { + if e == nil || len(e.Dir) == 0 { + continue + } + + /* Start to fill xpath based map with yang data */ + keyLevel := 0 + yangToDbMapFill(keyLevel, xYangSpecMap, e, "") + + // Fill the ordered map of child tables list for oc yangs + updateSchemaOrderedMap(module, e) + } + mapPrint(xYangSpecMap, "/tmp/fullSpec.txt") + dbMapPrint("/tmp/dbSpecMap.txt") +} + +/* Fill the map with db details */ +func dbMapFill(tableName string, curPath string, moduleNm string, trkTpCnt bool, xDbSpecMap map[string]*dbInfo, entry *yang.Entry) { + entryType := entry.Node.Statement().Keyword + + if entry.Name != moduleNm { + if entryType == "container" { + tableName = entry.Name + } + + if !isYangResType(entryType) { + dbXpath := tableName + if entryType != "container" { + dbXpath = tableName + "/" + entry.Name + } + xDbSpecMap[dbXpath] = new(dbInfo) + xDbSpecMap[dbXpath].dbIndex = db.MaxDB + xDbSpecMap[dbXpath].dbEntry = entry + xDbSpecMap[dbXpath].fieldType = entryType + xDbSpecMap[dbXpath].module = moduleNm + if entryType == "container" { + xDbSpecMap[dbXpath].dbIndex = db.ConfigDB + if entry.Exts != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "key-name" : + if xDbSpecMap[dbXpath].keyName == nil { + xDbSpecMap[dbXpath].keyName = new(string) + } + *xDbSpecMap[dbXpath].keyName = ext.NName() + default : + log.Infof("Unsupported ext type(%v) for xpath(%v).", tagType, dbXpath) + } + } + } + } + } + } else { + moduleXpath := "/" + moduleNm + ":" + entry.Name + xDbSpecMap[moduleXpath] = new(dbInfo) + xDbSpecMap[moduleXpath].dbEntry = entry + xDbSpecMap[moduleXpath].fieldType = entryType + xDbSpecMap[moduleXpath].module = moduleNm + } + + var childList []string + for _, k := range entry.DirOKeys { + childList = append(childList, k) + } + + if entryType == "container" && trkTpCnt { + xDbSpecOrdTblMap[moduleNm] = childList + log.Info("xDbSpecOrdTblMap after appending ", xDbSpecOrdTblMap) + trkTpCnt = false + } + + for _, child := range childList { + childPath := tableName + "/" + entry.Dir[child].Name + dbMapFill(tableName, childPath, moduleNm, trkTpCnt, xDbSpecMap, entry.Dir[child]) + } +} + +/* Build redis db lookup map */ +func dbMapBuild(entries []*yang.Entry) { + if entries == nil { + return + } + xDbSpecMap = make(map[string]*dbInfo) + xDbSpecOrdTblMap = make(map[string][]string) + + for _, e := range entries { + if e == nil || len(e.Dir) == 0 { + continue + } + moduleNm := e.Name + log.Infof("Module name(%v)", moduleNm) + trkTpCnt := true + dbMapFill("", "", moduleNm, trkTpCnt, xDbSpecMap, e) + } +} + +func childToUpdateParent( xpath string, tableName string) { + var xpathData *yangXpathInfo + parent := parentXpathGet(xpath) + if len(parent) == 0 || parent == "/" { + return + } + + _, ok := xYangSpecMap[parent] + if !ok { + xpathData = new(yangXpathInfo) + xYangSpecMap[parent] = xpathData + } + xYangSpecMap[parent].childTable = append(xYangSpecMap[parent].childTable, tableName) + if xYangSpecMap[parent].yangEntry != nil && + xYangSpecMap[parent].yangEntry.Node.Statement().Keyword == "list" { + return + } + childToUpdateParent(parent, tableName) +} + +/* Build lookup map based on yang xpath */ +func annotEntryFill(xYangSpecMap map[string]*yangXpathInfo, xpath string, entry *yang.Entry) { + xpathData := new(yangXpathInfo) + _, ok := xYangSpecMap[xpath] + if !ok { + fmt.Printf("Xpath not found(%v) \r\n", xpath) + } + + xpathData.dbIndex = db.ConfigDB // default value + /* fill table with yang extension data. */ + if entry != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "table-name" : + if xpathData.tableName == nil { + xpathData.tableName = new(string) + } + *xpathData.tableName = ext.NName() + updateDbTableData(xpath, xpathData, *xpathData.tableName) + case "key-name" : + if xpathData.keyName == nil { + xpathData.keyName = new(string) + } + *xpathData.keyName = ext.NName() + case "table-transformer" : + if xpathData.xfmrTbl == nil { + xpathData.xfmrTbl = new(string) + } + *xpathData.xfmrTbl = ext.NName() + case "field-name" : + xpathData.fieldName = ext.NName() + case "subtree-transformer" : + xpathData.xfmrFunc = ext.NName() + case "key-transformer" : + xpathData.xfmrKey = ext.NName() + case "key-delimiter" : + xpathData.delim = ext.NName() + case "field-transformer" : + xpathData.xfmrFunc = ext.NName() + case "post-transformer" : + xpathData.xfmrPost = ext.NName() + case "get-validate" : + xpathData.validateFunc = ext.NName() + case "use-self-key" : + xpathData.keyXpath = nil + case "db-name" : + if ext.NName() == "APPL_DB" { + xpathData.dbIndex = db.ApplDB + } else if ext.NName() == "ASIC_DB" { + xpathData.dbIndex = db.AsicDB + } else if ext.NName() == "COUNTERS_DB" { + xpathData.dbIndex = db.CountersDB + } else if ext.NName() == "LOGLEVEL_DB" { + xpathData.dbIndex = db.LogLevelDB + } else if ext.NName() == "CONFIG_DB" { + xpathData.dbIndex = db.ConfigDB + } else if ext.NName() == "FLEX_COUNTER_DB" { + xpathData.dbIndex = db.FlexCounterDB + } else if ext.NName() == "STATE_DB" { + xpathData.dbIndex = db.StateDB + } else { + xpathData.dbIndex = db.ConfigDB + } + } + } + } + xYangSpecMap[xpath] = xpathData +} + +/* Build xpath from yang-annotation */ +func xpathFromDevCreate(path string) string { + p := strings.Split(path, "/") + for i, k := range p { + if len(k) > 0 { p[i] = strings.Split(k, ":")[1] } + } + return strings.Join(p[1:], "/") +} + +/* Build lookup map based on yang xpath */ +func annotToDbMapBuild(annotEntries []*yang.Entry) { + if annotEntries == nil { + return + } + if xYangSpecMap == nil { + xYangSpecMap = make(map[string]*yangXpathInfo) + } + + for _, e := range annotEntries { + if e != nil && len(e.Deviations) > 0 { + for _, d := range e.Deviations { + xpath := xpathFromDevCreate(d.Name) + xpath = "/" + strings.Replace(e.Name, "-annot", "", -1) + ":" + xpath + for i, deviate := range d.Deviate { + if i == 2 { + for _, ye := range deviate { + annotEntryFill(xYangSpecMap, xpath, ye) + } + } + } + } + } + } + mapPrint(xYangSpecMap, "/tmp/annotSpec.txt") +} + +func annotDbSpecMapFill(xDbSpecMap map[string]*dbInfo, dbXpath string, entry *yang.Entry) error { + var err error + var dbXpathData *dbInfo + var ok bool + + //Currently sonic-yang annotation is supported for "list" type only. + listName := strings.Split(dbXpath, "/") + if len(listName) < 3 { + log.Errorf("Invalid list xpath length(%v) \r\n", dbXpath) + return err + } + dbXpathData, ok = xDbSpecMap[listName[2]] + if !ok { + log.Errorf("DB spec-map data not found(%v) \r\n", dbXpath) + return err + } + log.Infof("Annotate dbSpecMap for (%v)(listName:%v)\r\n", dbXpath, listName[2]) + dbXpathData.dbIndex = db.ConfigDB // default value + + /* fill table with cvl yang extension data. */ + if entry != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "key-name" : + if dbXpathData.keyName == nil { + dbXpathData.keyName = new(string) + } + *dbXpathData.keyName = ext.NName() + case "db-name" : + if ext.NName() == "APPL_DB" { + dbXpathData.dbIndex = db.ApplDB + } else if ext.NName() == "ASIC_DB" { + dbXpathData.dbIndex = db.AsicDB + } else if ext.NName() == "COUNTERS_DB" { + dbXpathData.dbIndex = db.CountersDB + } else if ext.NName() == "LOGLEVEL_DB" { + dbXpathData.dbIndex = db.LogLevelDB + } else if ext.NName() == "CONFIG_DB" { + dbXpathData.dbIndex = db.ConfigDB + } else if ext.NName() == "FLEX_COUNTER_DB" { + dbXpathData.dbIndex = db.FlexCounterDB + } else if ext.NName() == "STATE_DB" { + dbXpathData.dbIndex = db.StateDB + } else { + dbXpathData.dbIndex = db.ConfigDB + } + default : + } + } + } + + dbMapPrint("/tmp/dbSpecMapFull.txt") + return err +} + +func annotDbSpecMap(annotEntries []*yang.Entry) { + if annotEntries == nil || xDbSpecMap == nil { + return + } + for _, e := range annotEntries { + if e != nil && len(e.Deviations) > 0 { + for _, d := range e.Deviations { + xpath := xpathFromDevCreate(d.Name) + xpath = "/" + strings.Replace(e.Name, "-annot", "", -1) + ":" + xpath + for i, deviate := range d.Deviate { + if i == 2 { + for _, ye := range deviate { + annotDbSpecMapFill(xDbSpecMap, xpath, ye) + } + } + } + } + } + } +} + +/* Debug function to print the yang xpath lookup map */ +func mapPrint(inMap map[string]*yangXpathInfo, fileName string) { + fp, err := os.Create(fileName) + if err != nil { + return + } + defer fp.Close() + + for k, d := range inMap { + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf(fp, "%v:\r\n", k) + fmt.Fprintf(fp, " yangDataType: %v\r\n", d.yangDataType) + fmt.Fprintf(fp, " tableName: ") + if d.tableName != nil { + fmt.Fprintf(fp, "%v", *d.tableName) + } + fmt.Fprintf(fp, "\r\n xfmrTbl : ") + if d.xfmrTbl != nil { + fmt.Fprintf(fp, "%v", *d.xfmrTbl) + } + fmt.Fprintf(fp, "\r\n keyName : ") + if d.keyName != nil { + fmt.Fprintf(fp, "%v", *d.keyName) + } + fmt.Fprintf(fp, "\r\n childTbl : %v", d.childTable) + fmt.Fprintf(fp, "\r\n FieldName: %v", d.fieldName) + fmt.Fprintf(fp, "\r\n keyLevel : %v", d.keyLevel) + fmt.Fprintf(fp, "\r\n xfmrKeyFn: %v", d.xfmrKey) + fmt.Fprintf(fp, "\r\n xfmrFunc : %v", d.xfmrFunc) + fmt.Fprintf(fp, "\r\n dbIndex : %v", d.dbIndex) + fmt.Fprintf(fp, "\r\n validateFunc : %v", d.validateFunc) + fmt.Fprintf(fp, "\r\n yangEntry: ") + if d.yangEntry != nil { + fmt.Fprintf(fp, "%v", *d.yangEntry) + } + fmt.Fprintf(fp, "\r\n dbEntry: ") + if d.dbEntry != nil { + fmt.Fprintf(fp, "%v", *d.dbEntry) + } + fmt.Fprintf(fp, "\r\n keyXpath: %d\r\n", d.keyXpath) + for i, kd := range d.keyXpath { + fmt.Fprintf(fp, " %d. %#v\r\n", i, kd) + } + fmt.Fprintf(fp, "\r\n isKey : %v\r\n", d.isKey) + } + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + +} + +/* Debug function to print redis db lookup map */ +func dbMapPrint( fname string) { + fp, err := os.Create(fname) + if err != nil { + return + } + defer fp.Close() + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + for k, v := range xDbSpecMap { + fmt.Fprintf(fp, " field:%v \r\n", k) + fmt.Fprintf(fp, " type :%v \r\n", v.fieldType) + fmt.Fprintf(fp, " db-type :%v \r\n", v.dbIndex) + fmt.Fprintf(fp, " module :%v \r\n", v.module) + fmt.Fprintf(fp, " KeyName: ") + if v.keyName != nil { + fmt.Fprintf(fp, "%v", *v.keyName) + } + fmt.Fprintf(fp, "\r\n oc-yang :%v \r\n", v.yangXpath) + fmt.Fprintf(fp, " cvl-yang :%v \r\n", v.dbEntry) + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + + } +} + +func updateSchemaOrderedMap(module string, entry *yang.Entry) { + var children []string + if entry.Node.Statement().Keyword == "module" { + for _, dir := range entry.DirOKeys { + // Gives the yang xpath for the top level container + xpath := "/" + module + ":" + dir + _, ok := xYangSpecMap[xpath] + if ok { + yentry := xYangSpecMap[xpath].yangEntry + if yentry.Node.Statement().Keyword == "container" { + var keyspec = make([]KeySpec, 0) + keyspec = FillKeySpecs(xpath, "" , &keyspec) + children = updateChildTable(keyspec, &children) + } + } + } + } + xDbSpecOrdTblMap[module] = children +} + +func updateChildTable(keyspec []KeySpec, chlist *[]string) ([]string) { + for _, ks := range keyspec { + if (ks.Ts.Name != "") { + if !contains(*chlist, ks.Ts.Name) { + *chlist = append(*chlist, ks.Ts.Name) + } + } + *chlist = updateChildTable(ks.Child, chlist) + } + return *chlist +} diff --git a/src/translib/translib.go b/src/translib/translib.go new file mode 100644 index 0000000000..3170128a21 --- /dev/null +++ b/src/translib/translib.go @@ -0,0 +1,745 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 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 + +Redis ABNF format and persisting them in the Redis DB. + +It can also translate the ABNF format to YANG specific JSON IETF format + +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" +) + +//Write lock for all write operations to be synchronized +var writeMutex = &sync.Mutex{} + +//minimum global interval for subscribe in secs +var minSubsInterval = 20 +var maxSubsInterval = 60 + +type ErrSource int + +const( + ProtoErr ErrSource = iota + AppErr +) + +type SetRequest struct{ + Path string + Payload []byte +} + +type SetResponse struct{ + ErrSrc ErrSource +} + +type GetRequest struct{ + Path string +} + +type GetResponse struct{ + Payload []byte + ErrSrc ErrSource +} + +type SubscribeResponse struct{ + Path string + Payload []byte + Timestamp int64 + SyncComplete bool + IsTerminated bool +} + +type NotificationType int + +const( + Sample NotificationType = iota + OnChange +) + +type IsSubscribeResponse struct{ + Path string + IsOnChangeSupported bool + MinInterval int + Err error + PreferredType NotificationType +} + +type ModelData struct{ + Name string + Org string + Ver string +} + +type notificationOpts struct { + mInterval int + pType NotificationType // for TARGET_DEFINED +} + +//initializes logging and app modules +func init() { + log.Flush() +} + +//Creates entries in the redis DB pertaining to the path and payload +func Create(req SetRequest) (SetResponse, error){ + var keys []db.WatchKeys + var resp SetResponse + + path := req.Path + payload := req.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 + } + + err = appInitialize(app, appInfo, path, &payload, CREATE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateCreate(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processCreate (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + 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 + var resp SetResponse + + path := req.Path + payload := req.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 { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, &payload, UPDATE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateUpdate(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processUpdate (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + 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 + var resp SetResponse + + path := req.Path + payload := req.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 { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, &payload, REPLACE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateReplace(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processReplace (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + 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 + var resp SetResponse + + path := req.Path + + log.Info("Delete request received with path =", path) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, nil, DELETE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateDelete(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processDelete(d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + 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 + var resp GetResponse + + path := req.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 + } + + err = appInitialize(app, appInfo, path, nil, GET) + + 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 + } + + defer closeAllDbs(dbs[:]) + + err = (*app).translateGet (dbs) + + if err != nil { + resp = GetResponse{Payload:payload, ErrSrc:AppErr} + return resp, err + } + + resp, err = (*app).processGet(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 sErr error + //err = errors.New("Not implemented") + + dbNotificationMap := make(map[db.DBNum][]*notificationInfo) + + resp := make ([]*IsSubscribeResponse, len(paths)) + + 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 + } + + //Do NOT close the DBs here as we need to use them during subscribe notification + + for i, path := range paths { + + app, appInfo, err := getAppModule(path) + + if err != nil { + + if sErr == nil { + sErr = err + } + + resp[i].Err = err + continue + } + + nOpts, nInfo, errApp := (*app).translateSubscribe (dbs, path) + + if errApp != nil { + resp[i].Err = errApp + + if sErr == nil { + sErr = errApp + } + + resp[i].MinInterval = maxSubsInterval + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + + resp[i].PreferredType = nOpts.pType + } + + continue + } else { + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + + resp[i].PreferredType = nOpts.pType + } + + if nInfo == nil { + sErr = tlerr.NotSupportedError{ + Format: "Subscribe not supported", Path: path} + resp[i].Err = sErr + continue + } + + resp[i].IsOnChangeSupported = true + + nInfo.path = path + nInfo.app = app + nInfo.appInfo = appInfo + nInfo.dbs = dbs + + dbNotificationMap[nInfo.dbno] = append(dbNotificationMap[nInfo.dbno], nInfo) + } + + } + + log.Info("map=", dbNotificationMap) + + if sErr != nil { + return resp, sErr + } + + sInfo := &subscribeInfo {syncDone:false, + q:q, + stop:stop} + + sErr = startSubscribe(sInfo, dbNotificationMap) + + return resp, sErr +} + +//Check if subscribe is supported on the given paths +func IsSubscribeSupported(paths []string) ([]*IsSubscribeResponse, error) { + + resp := make ([]*IsSubscribeResponse, len(paths)) + + 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 + } + + defer closeAllDbs(dbs[:]) + + for i, path := range paths { + + app, _, err := getAppModule(path) + + if err != nil { + resp[i].Err = err + continue + } + + nOpts, _, errApp := (*app).translateSubscribe (dbs, path) + + if errApp != nil { + resp[i].Err = errApp + err = errApp + continue + } else { + resp[i].IsOnChangeSupported= true + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + resp[i].PreferredType = nOpts.pType + } + } + } + + return resp, err +} + +//Gets all the models supported by Translib +func GetModels() ([]ModelData, error) { + var err error + + return getModels(), err +} + +//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 + + //Create Application DB connection + 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)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Counter DB connection + dbs[db.CountersDB], err = db.NewDB(getDBOptions(db.CountersDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Log Level DB connection + dbs[db.LogLevelDB], err = db.NewDB(getDBOptions(db.LogLevelDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Config DB connection + dbs[db.ConfigDB], err = db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Flex Counter DB connection + dbs[db.FlexCounterDB], err = db.NewDB(getDBOptions(db.FlexCounterDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create State DB connection + dbs[db.StateDB], err = db.NewDB(getDBOptions(db.StateDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + return dbs, err +} + +//Closes the dbs, and nils out the arr. +func closeAllDbs(dbs []*db.DB) { + for dbsi, d := range dbs { + if d != nil { + d.DeleteDB() + dbs[dbsi] = nil + } + } +} + +// Implement Compare method for priority queue for SubscribeResponse struct +func (val SubscribeResponse) Compare(other queue.Item) int { + o := other.(*SubscribeResponse) + if val.Timestamp > o.Timestamp { + return 1 + } else if val.Timestamp == o.Timestamp { + return 0 + } + return -1 +} + +func getDBOptions(dbNo db.DBNum) db.Options { + var opt db.Options + + switch dbNo { + case db.ApplDB, db.CountersDB: + opt = getDBOptionsWithSeparator(dbNo, "", ":", ":") + break + case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB: + opt = getDBOptionsWithSeparator(dbNo, "", "|", "|") + break + } + + return opt +} + +func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparator string, keySeparator string) db.Options { + return(db.Options { + DBNo : dbNo, + InitIndicator : initIndicator, + TableNameSeparator: tableSeparator, + KeySeparator : keySeparator, + }) +} + +func getAppModule (path string) (*appInterface, *appInfo, error) { + var app appInterface + + aInfo, err := getAppModuleInfo(path) + + if err != nil { + return nil, aInfo, err + } + + app, err = getAppInterface(aInfo.appType) + + 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 +} diff --git a/src/translib/translib_test.go b/src/translib/translib_test.go new file mode 100644 index 0000000000..9ceb1e1a80 --- /dev/null +++ b/src/translib/translib_test.go @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 translib_test + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +// assert fails the test if the condition is false. +func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { + if !condition { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) + tb.FailNow() + } +} + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +// equals fails the test if exp is not equal to act. +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} + +func Test_Create(t *testing.T) { + +} diff --git a/tools/.gitkeep b/tools/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/pyang/pyang_plugins/openapi.py b/tools/pyang/pyang_plugins/openapi.py new file mode 100644 index 0000000000..45a407d88d --- /dev/null +++ b/tools/pyang/pyang_plugins/openapi.py @@ -0,0 +1,832 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +import optparse +import sys + +from pyang import plugin +from pyang import statements +import pdb +import yaml +from collections import OrderedDict +import copy +import os + +# globals +codegenTypesToYangTypesMap = {"int8": {"type":"integer", "format": "int32"}, + "int16": {"type":"integer", "format": "int32"}, + "int32": {"type":"integer", "format": "int32"}, + "int64": {"type":"integer", "format": "int64"}, + "uint8": {"type":"integer", "format": "int32"}, + "uint16": {"type":"integer", "format": "int32"}, + "uint32": {"type":"integer", "format": "int32"}, + "uint64": {"type":"integer", "format": "int64"}, + "decimal64": {"type":"number", "format": "double"}, + "string": {"type":"string"}, + "binary": {"type":"string", "format": "binary"}, + "boolean": {"type":"boolean"}, + "bits": {"type":"integer", "format": "int32"}, + "identityref": {"type":"string"}, + "union": {"type":"string"}, + "counter32": {"type":"integer", "format": "int64"}, + "counter64": {"type":"integer", "format": "int64"}, + "long": {"type":"integer", "format": "int64"}, + } +moduleDict = OrderedDict() +nodeDict = OrderedDict() +XpathToBodyTagDict = OrderedDict() +keysToLeafRefObjSet = set() +currentTag = None +base_path = '/restconf/data' +verbs = ["post", "put", "patch", "get", "delete"] +responses = { # Common to all verbs + "500": {"description": "Internal Server Error"}, + "401": {"description": "Unauthorized"}, + "405": {"description": "Method Not Allowed"}, + "400": {"description": "Bad request"}, + "415": {"description": "Unsupported Media Type"}, +} +verb_responses = {} +verb_responses["post"] = { + "201": {"description": "Created"}, + "409": {"description": "Conflict"}, + "404": {"description": "Not Found"}, + "403": {"description": "Forbidden"}, +} +verb_responses["put"] = { + "201": {"description": "Created"}, + "204": {"description": "No Content"}, + "404": {"description": "Not Found"}, + "409": {"description": "Conflict"}, + "403": {"description": "Forbidden"}, +} +verb_responses["patch"] = { + "204": {"description": "No Content"}, + "404": {"description": "Not Found"}, + "409": {"description": "Conflict"}, + "403": {"description": "Forbidden"}, +} +verb_responses["delete"] = { + "204": {"description": "No Content"}, + "404": {"description": "Not Found"}, +} +verb_responses["get"] = { + "200": {"description": "Ok"}, + "404": {"description": "Not Found"}, +} + +def merge_two_dicts(x, y): + z = x.copy() # start with x's keys and values + z.update(y) # modifies z with y's keys and values & returns None + return z + +def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds): + class OrderedDumper(Dumper): + pass + def _dict_representer(dumper, data): + return dumper.represent_mapping( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + data.items()) + OrderedDumper.add_representer(OrderedDict, _dict_representer) + return yaml.dump(data, stream, OrderedDumper, **kwds) + +swaggerDict = OrderedDict() +swaggerDict["swagger"] = "2.0" +swaggerDict["info"] = OrderedDict() +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 +swaggerDict["paths"] = OrderedDict() +swaggerDict["definitions"] = OrderedDict() + +def resetSwaggerDict(): + global moduleDict + global nodeDict + global XpathToBodyTagDict + global keysToLeafRefObjSet + global swaggerDict + global swagger_tags + global currentTag + + moduleDict = OrderedDict() + XpathToBodyTagDict = OrderedDict() + keysToLeafRefObjSet = set() + + swaggerDict = OrderedDict() + swaggerDict["swagger"] = "2.0" + swaggerDict["info"] = OrderedDict() + 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 + swaggerDict["tags"] = swagger_tags + swaggerDict["paths"] = OrderedDict() + swaggerDict["definitions"] = OrderedDict() + +def pyang_plugin_init(): + plugin.register_plugin(OpenApiPlugin()) + +class OpenApiPlugin(plugin.PyangPlugin): + def add_output_format(self, fmts): + self.multiple_modules = True + fmts['swaggerapi'] = self + + def add_opts(self, optparser): + optlist = [ + optparse.make_option("--outdir", + type="string", + dest="outdir", + help="Output directory for specs"), + ] + g = optparser.add_option_group("OpenApiPlugin options") + g.add_options(optlist) + + def setup_fmt(self, ctx): + ctx.implicit_errors = False + + def emit(self, ctx, modules, fd): + + global currentTag + + if ctx.opts.outdir is None: + print("[Error]: Output directory is not mentioned") + sys.exit(2) + + if not os.path.exists(ctx.opts.outdir): + print("[Error]: Specified outdir: ", ctx.opts.outdir, " does not exists") + sys.exit(2) + + for module in modules: + print("===> processing ", module.i_modulename) + if module.keyword == "submodule": + continue + resetSwaggerDict() + currentTag = module.i_modulename + walk_module(module) + # delete root '/' as we dont support it. + + if len(swaggerDict["paths"]) > 0: + if "/" in swaggerDict["paths"]: + del(swaggerDict["paths"]["/"]) + + if len(swaggerDict["paths"]) <= 0: + continue + + # check if file is same + yamlFn = ctx.opts.outdir + '/' + module.i_modulename + ".yaml" + code = ordered_dump(swaggerDict, Dumper=yaml.SafeDumper) + if os.path.isfile(yamlFn): + f=open(yamlFn,'r') + oldCode = f.read() + if (oldCode==code): + print('code unchanged.. skipping write for file:'+yamlFn) + f.close() + continue + else: + print('code changed.. overwriting file:'+yamlFn) + fout = open(yamlFn,'w') + fout.write(code) + fout.close() + else: + with open(ctx.opts.outdir + '/' + module.i_modulename + ".yaml", "w") as spec: + spec.write(ordered_dump(swaggerDict, Dumper=yaml.SafeDumper)) + +def walk_module(module): + for child in module.i_children: + walk_child(child) + +def add_swagger_tag(module): + if module.i_modulename not in moduleDict: + moduleDict[module.i_modulename] = OrderedDict() + moduleDict[module.i_modulename]["name"] = module.i_modulename + moduleDict[module.i_modulename]["description"] = "Operations for " + module.i_modulename + swagger_tags.append(moduleDict[module.i_modulename]) + else: + return + +def swagger_it(child, defName, pathstr, payload, metadata, verb, operId=False): + + firstEncounter = True + verbPathStr = pathstr + global currentTag + if verb == "post": + pathstrList = pathstr.split('/') + pathstrList.pop() + verbPathStr = "/".join(pathstrList) + if not verbPathStr.startswith("/"): + verbPathStr = "/" + verbPathStr + + if verbPathStr not in swaggerDict["paths"]: + swaggerDict["paths"][verbPathStr] = OrderedDict() + + if verb not in swaggerDict["paths"][verbPathStr]: + swaggerDict["paths"][verbPathStr][verb] = OrderedDict() + swaggerDict["paths"][verbPathStr][verb]["tags"] = [currentTag] + if verb != "delete" and verb != "get": + swaggerDict["paths"][verbPathStr][verb]["consumes"] = ["application/yang-data+json"] + swaggerDict["paths"][verbPathStr][verb]["produces"] = ["application/yang-data+json"] + swaggerDict["paths"][verbPathStr][verb]["parameters"] = [] + swaggerDict["paths"][verbPathStr][verb]["responses"] = copy.deepcopy(merge_two_dicts(responses, verb_responses[verb])) + firstEncounter = False + + opId = None + if "operationId" not in swaggerDict["paths"][verbPathStr][verb]: + if not operId: + swaggerDict["paths"][verbPathStr][verb]["operationId"] = verb + '_' + defName + else: + swaggerDict["paths"][verbPathStr][verb]["operationId"] = operId + + opId = swaggerDict["paths"][verbPathStr][verb]["operationId"] + + 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 + + else: + opId = swaggerDict["paths"][verbPathStr][verb]["operationId"] + + verbPath = swaggerDict["paths"][verbPathStr][verb] + + if not firstEncounter: + for meta in metadata: + metaTag = OrderedDict() + metaTag["in"] = "path" + metaTag["name"] = meta["name"] + metaTag["required"] = True + metaTag["type"] = meta["type"] + if 'enums' in meta: + metaTag["enum"] = meta["enums"] + if hasattr(meta,'format'): + if meta["format"] != "": + metaTag["format"] = meta["format"] + metaTag["description"] = meta["desc"] + verbPath["parameters"].append(metaTag) + + + if verb in ["post", "put", "patch"]: + if not firstEncounter: + bodyTag = OrderedDict() + bodyTag["in"] = "body" + bodyTag["name"] = "body" + bodyTag["required"] = True + bodyTag["schema"] = OrderedDict() + operationDefnName = opId + swaggerDict["definitions"][operationDefnName] = OrderedDict() + swaggerDict["definitions"][operationDefnName]["allOf"] = [] + bodyTag["schema"]["$ref"] = "#/definitions/" + operationDefnName + verbPath["parameters"].append(bodyTag) + swaggerDict["definitions"][operationDefnName]["allOf"].append({"$ref" : "#/definitions/" + defName}) + else: + bodyTag = None + for entry in verbPath["parameters"]: + if entry["name"] == "body" and entry["in"] == "body": + bodyTag = entry + break + operationDefnName = bodyTag["schema"]["$ref"].split('/')[-1] + swaggerDict["definitions"][operationDefnName]["allOf"].append({"$ref" : "#/definitions/" + defName}) + + if verb == "get": + verbPath["responses"]["200"]["schema"] = OrderedDict() + verbPath["responses"]["200"]["schema"]["$ref"] = "#/definitions/" + defName + +def walk_child(child): + global XpathToBodyTagDict + + actXpath = statements.mk_path_str(child, True) + metadata = [] + keyNodesInPath = [] + pathstr = mk_path_refine(child, metadata, keyNodesInPath) + + if actXpath in keysToLeafRefObjSet: + return + + if child.keyword in ["list", "container", "leaf", "leaf-list"]: + payload = OrderedDict() + + add_swagger_tag(child.i_module) + build_payload(child, payload, pathstr, True, actXpath, True) + + if len(payload) == 0 and child.i_config == True: + return + + if child.keyword == "leaf" or child.keyword == "leaf-list": + if hasattr(child, 'i_is_key'): + if child.i_leafref is not None: + listKeyPath = statements.mk_path_str(child.i_leafref_ptr[0], True) + if listKeyPath not in keysToLeafRefObjSet: + keysToLeafRefObjSet.add(listKeyPath) + return + + defName = shortenNodeName(child) + + if child.i_config == False: + payload_get = OrderedDict() + build_payload(child, payload_get, pathstr, True, actXpath, True, True) + if len(payload_get) == 0: + return + + defName_get = "get" + '_' + defName + swaggerDict["definitions"][defName_get] = OrderedDict() + swaggerDict["definitions"][defName_get]["type"] = "object" + swaggerDict["definitions"][defName_get]["properties"] = copy.deepcopy(payload_get) + swagger_it(child, defName_get, pathstr, payload_get, metadata, "get", defName_get) + else: + swaggerDict["definitions"][defName] = OrderedDict() + swaggerDict["definitions"][defName]["type"] = "object" + swaggerDict["definitions"][defName]["properties"] = copy.deepcopy(payload) + + for verb in verbs: + if child.keyword == "leaf-list": + metadata_leaf_list = [] + keyNodesInPath_leaf_list = [] + pathstr_leaf_list = mk_path_refine(child, metadata_leaf_list, keyNodesInPath_leaf_list, True) + + if verb == "get": + payload_get = OrderedDict() + build_payload(child, payload_get, pathstr, True, actXpath, True, True) + if len(payload_get) == 0: + continue + defName_get = "get" + '_' + defName + swaggerDict["definitions"][defName_get] = OrderedDict() + swaggerDict["definitions"][defName_get]["type"] = "object" + swaggerDict["definitions"][defName_get]["properties"] = copy.deepcopy(payload_get) + swagger_it(child, defName_get, pathstr, payload_get, metadata, verb, defName_get) + + if child.keyword == "leaf-list": + defName_get_leaf_list = "get" + '_llist_' + defName + swagger_it(child, defName_get, pathstr_leaf_list, payload_get, metadata_leaf_list, verb, defName_get_leaf_list) + + continue + + if verb == "post" and child.keyword == "list": + continue + + if verb == "delete" and child.keyword == "container": + # Check to see if any of the child is part of + # key list, if so skip delete operation + if isUriKeyInPayload(child,keyNodesInPath): + continue + + swagger_it(child, defName, pathstr, payload, metadata, verb) + if verb == "delete" and child.keyword == "leaf-list": + defName_del_leaf_list = "del" + '_llist_' + defName + swagger_it(child, defName, pathstr_leaf_list, payload, metadata_leaf_list, verb, defName_del_leaf_list) + + if child.keyword == "list": + listMetaData = copy.deepcopy(metadata) + walk_child_for_list_base(child,actXpath,pathstr, listMetaData, defName) + + if hasattr(child, 'i_children'): + for ch in child.i_children: + walk_child(ch) + +def walk_child_for_list_base(child, actXpath, pathstr, metadata, nonBaseDefName=None): + + payload = OrderedDict() + pathstrList = pathstr.split('/') + + lastNode = pathstrList[-1] + nodeName = lastNode.split('=')[0] + pathstrList.pop() + pathstrList.append(nodeName) + + verbPathStr = "/".join(pathstrList) + if not verbPathStr.startswith("/"): + pathstr = "/" + verbPathStr + else: + pathstr = verbPathStr + + for key in child.i_key: + metadata.pop() + + add_swagger_tag(child.i_module) + build_payload(child, payload, pathstr, False, "", True) + + if len(payload) == 0 and child.i_config == True: + return + + defName = shortenNodeName(child) + defName = "list"+'_'+defName + + if child.i_config == False: + + payload_get = OrderedDict() + build_payload(child, payload_get, pathstr, False, "", True, True) + + if len(payload_get) == 0: + return + + defName_get = "get" + '_' + defName + if nonBaseDefName is not None: + swagger_it(child, "get" + '_' + nonBaseDefName, pathstr, payload_get, metadata, "get", defName_get) + else: + swaggerDict["definitions"][defName_get] = OrderedDict() + swaggerDict["definitions"][defName_get]["type"] = "object" + swaggerDict["definitions"][defName_get]["properties"] = copy.deepcopy(payload_get) + swagger_it(child, defName_get, pathstr, payload_get, metadata, "get", defName_get) + else: + if nonBaseDefName is None: + swaggerDict["definitions"][defName] = OrderedDict() + swaggerDict["definitions"][defName]["type"] = "object" + swaggerDict["definitions"][defName]["properties"] = copy.deepcopy(payload) + + for verb in verbs: + if verb == "get": + payload_get = OrderedDict() + build_payload(child, payload_get, pathstr, False, "", True, True) + + if len(payload_get) == 0: + continue + + defName_get = "get" + '_' + defName + if nonBaseDefName is not None: + swagger_it(child, "get" + '_' + nonBaseDefName, pathstr, payload_get, metadata, verb, defName_get) + else: + swaggerDict["definitions"][defName_get] = OrderedDict() + swaggerDict["definitions"][defName_get]["type"] = "object" + swaggerDict["definitions"][defName_get]["properties"] = copy.deepcopy(payload_get) + swagger_it(child, defName_get, pathstr, payload_get, metadata, verb, defName_get) + continue + + if nonBaseDefName is not None: + swagger_it(child, nonBaseDefName, pathstr, payload, metadata, verb, verb + '_' + defName) + else: + swagger_it(child, defName, pathstr, payload, metadata, verb, verb + '_' + defName) + +def build_payload(child, payloadDict, uriPath="", oneInstance=False, Xpath="", firstCall=False, config_false=False, moduleList=[]): + + nodeModuleName = child.i_module.i_modulename + if nodeModuleName not in moduleList: + moduleList.append(nodeModuleName) + firstCall = True + + global keysToLeafRefObjSet + + if child.i_config == False and not config_false: + return # temporary + + chs=[] + try: + chs = [ch for ch in child.i_children + if ch.keyword in statements.data_definition_keywords] + except: + # do nothing as it could be due to i_children not present + pass + + childJson = None + if child.keyword == "container" and len(chs) > 0: + if firstCall: + nodeName = child.i_module.i_modulename + ':' + child.arg + else: + nodeName = child.arg + payloadDict[nodeName] = OrderedDict() + payloadDict[nodeName]["type"] = "object" + payloadDict[nodeName]["properties"] = OrderedDict() + childJson = payloadDict[nodeName]["properties"] + + elif child.keyword == "list" and len(chs) > 0: + if firstCall: + nodeName = child.i_module.i_modulename + ':' + child.arg + else: + nodeName = child.arg + payloadDict[nodeName] = OrderedDict() + returnJson = None + + payloadDict[nodeName]["type"] = "array" + payloadDict[nodeName]["items"] = OrderedDict() + payloadDict[nodeName]["items"]["type"] = "object" + payloadDict[nodeName]["items"]["required"] = [] + + for listKey in child.i_key: + payloadDict[nodeName]["items"]["required"].append(listKey.arg) + + payloadDict[nodeName]["items"]["properties"] = OrderedDict() + returnJson = payloadDict[nodeName]["items"]["properties"] + + childJson = returnJson + + elif child.keyword == "leaf": + + if firstCall: + nodeName = child.i_module.i_modulename + ':' + child.arg + else: + nodeName = child.arg + payloadDict[nodeName] = OrderedDict() + typeInfo = getType(child) + enums = None + if isinstance(typeInfo, tuple): + enums = typeInfo[1] + typeInfo = typeInfo[0] + + if 'type' in typeInfo: + dType = typeInfo["type"] + else: + dType = "string" + + payloadDict[nodeName]["type"] = dType + if enums is not None: + payloadDict[nodeName]["enum"] = enums + + if 'format' in typeInfo: + payloadDict[nodeName]["format"] = typeInfo["format"] + + elif child.keyword == "leaf-list": + + if firstCall: + nodeName = child.i_module.i_modulename + ':' + child.arg + else: + nodeName = child.arg + + payloadDict[nodeName] = OrderedDict() + payloadDict[nodeName]["type"] = "array" + payloadDict[nodeName]["items"] = OrderedDict() + + typeInfo = getType(child) + enums = None + if isinstance(typeInfo, tuple): + enums = typeInfo[1] + typeInfo = typeInfo[0] + + if 'type' in typeInfo: + dType = typeInfo["type"] + else: + dType = "string" + + payloadDict[nodeName]["items"]["type"] = dType + if enums is not None: + payloadDict[nodeName]["items"]["enum"] = enums + + if 'format' in typeInfo: + payloadDict[nodeName]["items"]["format"] = typeInfo["format"] + + elif child.keyword == "choice" or child.keyword == "case": + childJson = payloadDict + + if hasattr(child, 'i_children'): + for ch in child.i_children: + build_payload(ch,childJson,uriPath, False, Xpath, False, config_false, copy.deepcopy(moduleList)) + +def mk_path_refine(node, metadata, keyNodes=[], restconf_leaflist=False): + def mk_path(node): + """Returns the XPath path of the node""" + if node.keyword in ['choice', 'case']: + return mk_path(node.parent) + def name(node): + extra = "" + if node.keyword == "leaf-list" and restconf_leaflist: + extraKeys = [] + extraKeys.append('{' + node.arg + '}') + desc = node.search_one('description') + if desc is None: + desc = '' + else: + desc = desc.arg + metaInfo = OrderedDict() + metaInfo["desc"] = desc + metaInfo["name"] = node.arg + metaInfo["type"] = "string" + metaInfo["format"] = "" + metadata.append(metaInfo) + extra = ",".join(extraKeys) + + if node.keyword == "list": + extraKeys = [] + for index, list_key in enumerate(node.i_key): + keyNodes.append(list_key) + if list_key.i_leafref is not None: + keyNodes.append(list_key.i_leafref_ptr[0]) + extraKeys.append('{' + list_key.arg + '}') + desc = list_key.search_one('description') + if desc is None: + desc = '' + else: + desc = desc.arg + metaInfo = OrderedDict() + metaInfo["desc"] = desc + metaInfo["name"] = list_key.arg + typeInfo = getType(list_key) + + if isinstance(typeInfo, tuple): + metaInfo["enums"] = typeInfo[1] + typeInfo = typeInfo[0] + + if 'type' in typeInfo: + dType = typeInfo["type"] + else: + dType = "string" + + metaInfo["type"] = dType + + if 'format' in typeInfo: + metaInfo["format"] = typeInfo["format"] + else: + metaInfo["format"] = "" + + metadata.append(metaInfo) + extra = ",".join(extraKeys) + + if len(extra) > 0: + xpathToReturn = node.i_module.i_modulename + ':' + node.arg + '=' + extra + else: + xpathToReturn = node.i_module.i_modulename + ':' + node.arg + return xpathToReturn + + if node.parent.keyword in ['module', 'submodule']: + return "/" + name(node) + else: + p = mk_path(node.parent) + return p + "/" + name(node) + + xpath = mk_path(node) + module_name = "" + final_xpathList = [] + for path in xpath.split('/')[1:]: + mod_name, node_name = path.split(':') + if mod_name != module_name: + final_xpathList.append(path) + module_name = mod_name + else: + final_xpathList.append(node_name) + + xpath = "/".join(final_xpathList) + if not xpath.startswith('/'): + xpath = '/' + xpath + return xpath + +def handle_leafref(node,xpath): + path_type_spec = node.i_leafref + target_node = path_type_spec.i_target_node + if target_node.keyword in ["leaf", "leaf-list"]: + return getType(target_node) + else: + print("leafref not pointing to leaf/leaflist") + sys.exit(2) + +def shortenNodeName(node): + global nodeDict + xpath = statements.mk_path_str(node, False) + name = node.i_module.i_modulename + xpath.replace('/','_') + 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 + return name + +def getCamelForm(moName): + hasHiphen = False + moName = moName.replace('_','-') + if '-' in moName: + hasHiphen = True + + while (hasHiphen): + index = moName.find('-') + if index != -1: + moNameList = list(moName) + # capitalize character hiphen + moNameList[index+1] = moNameList[index+1].upper() + # delete '-' + del(moNameList[index]) + moName = "".join(moNameList) + + if '-' in moName: + hasHiphen = True + else: + hasHiphen = False + else: + break + + return moName + +def getType(node): + + global codegenTypesToYangTypesMap + xpath = statements.mk_path_str(node, True) + + def resolveType(stmt, nodeType): + if nodeType == "string" \ + or nodeType == "instance-identifier" \ + or nodeType == "identityref": + return codegenTypesToYangTypesMap["string"] + elif nodeType == "enumeration": + enums = [] + for enum in stmt.substmts: + if enum.keyword == "enum": + enums.append(enum.arg) + return codegenTypesToYangTypesMap["string"], enums + elif nodeType == "empty" or nodeType == "boolean": + return {"type": "boolean", "format": "boolean"} + elif nodeType == "leafref": + return handle_leafref(node,xpath) + elif nodeType == "union": + return codegenTypesToYangTypesMap["string"] + elif nodeType == "decimal64": + return codegenTypesToYangTypesMap[nodeType] + elif nodeType in ['int8', 'int16', 'int32', 'int64', + 'uint8', 'uint16', 'uint32', 'uint64', 'binary', 'bits']: + return codegenTypesToYangTypesMap[nodeType] + else: + print("no base type found") + sys.exit(2) + + base_types = ['int8', 'int16', 'int32', 'int64', + 'uint8', 'uint16', 'uint32', 'uint64', + 'decimal64', 'string', 'boolean', 'enumeration', + 'bits', 'binary', 'leafref', 'identityref', 'empty', + 'union', 'instance-identifier' + ] + # Get Type of a node + t = node.search_one('type') + + while t.arg not in base_types: + # chase typedef + name = t.arg + if name.find(":") == -1: + prefix = None + else: + [prefix, name] = name.split(':', 1) + if prefix is None or t.i_module.i_prefix == prefix: + # check local typedefs + pmodule = node.i_module + typedef = statements.search_typedef(t, name) + else: + # this is a prefixed name, check the imported modules + err = [] + pmodule = statements.prefix_to_module(t.i_module,prefix,t.pos,err) + if pmodule is None: + return + typedef = statements.search_typedef(pmodule, name) + + if typedef is None: + print("Typedef ", name, " is not found, make sure all dependent modules are present") + sys.exit(2) + t=typedef.search_one('type') + + return resolveType(t, t.arg) + + +class Abort(Exception): + """used to abort an iteration""" + pass + +def isUriKeyInPayload(stmt, keyNodesList): + result = False # atleast one key is present + + def checkFunc(node): + result = "continue" + if node in keyNodesList: + result = "stop" + return result + + def _iterate(stmt): + res = "continue" + if stmt.keyword == "leaf" or \ + stmt.keyword == "leaf-list": + res = checkFunc(stmt) + if res == 'stop': + raise Abort + else: + # default is to recurse + if hasattr(stmt, 'i_children'): + for s in stmt.i_children: + _iterate(s) + + try: + _iterate(stmt) + except Abort: + result = True + + return result + diff --git a/tools/pyang/pyang_plugins/yin_cvl.py b/tools/pyang/pyang_plugins/yin_cvl.py new file mode 100644 index 0000000000..71689003b0 --- /dev/null +++ b/tools/pyang/pyang_plugins/yin_cvl.py @@ -0,0 +1,179 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ +"""CVL YIN output plugin""" + +from xml.sax.saxutils import quoteattr +from xml.sax.saxutils import escape + +import optparse +import re + +from pyang import plugin +from pyang import util +from pyang import grammar +from pyang import syntax +from pyang import statements + +new_line ='' #replace with '\n' for adding new line +indent_space = '' #replace with ' ' for indentation +ns_indent_space = '' #replace with ' ' for indentation +yin_namespace = "urn:ietf:params:xml:ns:yang:yin:1" +revision_added = False + +def pyang_plugin_init(): + plugin.register_plugin(YINPluginCVL()) + +class YINPluginCVL(plugin.PyangPlugin): + def add_output_format(self, fmts): + fmts['yin-cvl'] = self + def emit(self, ctx, modules, fd): + module = modules[0] + emit_yin(ctx, module, fd) + +def emit_yin(ctx, module, fd): + fd.write('' + new_line) + fd.write(('<%s name="%s"' + new_line) % (module.keyword, module.arg)) + fd.write(ns_indent_space * len(module.keyword) + ns_indent_space + ' xmlns="%s"' % yin_namespace) + + prefix = module.search_one('prefix') + if prefix is not None: + namespace = module.search_one('namespace') + fd.write('' + new_line) + fd.write(ns_indent_space * len(module.keyword)) + fd.write(ns_indent_space + ' xmlns:' + prefix.arg + '=' + + quoteattr(namespace.arg)) + else: + belongs_to = module.search_one('belongs-to') + if belongs_to is not None: + prefix = belongs_to.search_one('prefix') + if prefix is not None: + # read the parent module in order to find the namespace uri + res = ctx.read_module(belongs_to.arg, extra={'no_include':True}) + if res is not None: + namespace = res.search_one('namespace') + if namespace is None or namespace.arg is None: + pass + else: + # success - namespace found + fd.write('' + new_line) + fd.write(sonic-acl.yin * len(module.keyword)) + fd.write(sonic-acl.yin + ' xmlns:' + prefix.arg + '=' + + quoteattr(namespace.arg)) + + for imp in module.search('import'): + prefix = imp.search_one('prefix') + if prefix is not None: + rev = None + r = imp.search_one('revision-date') + if r is not None: + rev = r.arg + mod = statements.modulename_to_module(module, imp.arg, rev) + if mod is not None: + ns = mod.search_one('namespace') + if ns is not None: + fd.write('' + new_line) + fd.write(ns_indent_space * len(module.keyword)) + fd.write(ns_indent_space + ' xmlns:' + prefix.arg + '=' + + quoteattr(ns.arg)) + fd.write('>' + new_line) + + substmts = module.substmts + for s in substmts: + emit_stmt(ctx, module, s, fd, indent_space, indent_space) + fd.write(('' + new_line) % module.keyword) + +def emit_stmt(ctx, module, stmt, fd, indent, indentstep): + global revision_added + + if stmt.raw_keyword == "revision" and revision_added == False: + revision_added = True + elif stmt.raw_keyword == "revision" and revision_added == True: + #Only add the latest revision + return + + #Don't keep the following keywords as they are not used in CVL + # stmt.raw_keyword == "revision" or + if ((stmt.raw_keyword == "organization" or + stmt.raw_keyword == "contact" or + stmt.raw_keyword == "rpc" or + stmt.raw_keyword == "notification" or + stmt.raw_keyword == "description") or + (len(stmt.substmts) > 0 and stmt.substmts[0].raw_keyword == "config" and + stmt.substmts[0].arg == "false")): + return + + if util.is_prefixed(stmt.raw_keyword): + # this is an extension. need to find its definition + (prefix, identifier) = stmt.raw_keyword + tag = prefix + ':' + identifier + if stmt.i_extension is not None: + ext_arg = stmt.i_extension.search_one('argument') + if ext_arg is not None: + yin_element = ext_arg.search_one('yin-element') + if yin_element is not None and yin_element.arg == 'true': + argname = prefix + ':' + ext_arg.arg + argiselem = True + else: + # explicit false or no yin-element given + argname = ext_arg.arg + argiselem = False + else: + argiselem = False + argname = None + else: + argiselem = False + argname = None + else: + (argname, argiselem) = syntax.yin_map[stmt.raw_keyword] + tag = stmt.raw_keyword + if argiselem == False or argname is None: + if argname is None: + attr = '' + else: + attr = ' ' + argname + '=' + quoteattr(stmt.arg) + if len(stmt.substmts) == 0: + fd.write(indent + '<' + tag + attr + '/>' + new_line) + else: + fd.write(indent + '<' + tag + attr + '>' + new_line) + for s in stmt.substmts: + emit_stmt(ctx, module, s, fd, indent + indentstep, + indentstep) + fd.write(indent + '' + new_line) + else: + fd.write(indent + '<' + tag + '>' + new_line) + fd.write(indent + indentstep + '<' + argname + '>' + \ + escape(stmt.arg) + \ + '' + new_line) + substmts = stmt.substmts + + for s in substmts: + emit_stmt(ctx, module, s, fd, indent + indentstep, indentstep) + + fd.write(indent + '' + new_line) + +def fmt_text(indent, data): + res = [] + for line in re.split("(\n)", escape(data)): + if line == '': + continue + if line == '' + new_line: + res.extend(line) + else: + res.extend(indent + line) + return ''.join(res) diff --git a/tools/swagger_codegen/.gitignore b/tools/swagger_codegen/.gitignore new file mode 100644 index 0000000000..31be659360 --- /dev/null +++ b/tools/swagger_codegen/.gitignore @@ -0,0 +1 @@ +swagger-codegen-*.jar diff --git a/tools/swagger_codegen/go-server/src/swagger/routes.go b/tools/swagger_codegen/go-server/src/swagger/routes.go new file mode 100644 index 0000000000..ce305b72aa --- /dev/null +++ b/tools/swagger_codegen/go-server/src/swagger/routes.go @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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 swagger + +// Load function logs swagger generated routes into REST server. +func Load() { +} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/README.mustache b/tools/swagger_codegen/go-server/templates-nonyang/README.mustache new file mode 100644 index 0000000000..89019f8e21 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/README.mustache @@ -0,0 +1,30 @@ +# Go API Server for {{packageName}} + +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +## Overview +This server was generated by the [swagger-codegen] +(https://github.com/swagger-api/swagger-codegen) project. +By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. +- + +To see how to make this your own, look here: + +[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) + +- API version: {{appVersion}}{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}}{{/hideGenerationTimestamp}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + + +### Running the server +To run the server, follow these simple steps: + +``` +go run main.go +``` + diff --git a/tools/swagger_codegen/go-server/templates-nonyang/controller-api.mustache b/tools/swagger_codegen/go-server/templates-nonyang/controller-api.mustache new file mode 100644 index 0000000000..c1bbec0e8c --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/controller-api.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} + +{{#operations}} +import ( + "net/http" + + "rest/server" +){{#operation}} + +func {{nickname}}(w http.ResponseWriter, r *http.Request) { + rc, r := server.GetContext(r) + rc.Name = "{{operationId}}" + {{#consumes}} + rc.Consumes.Add("{{mediaType}}") + {{/consumes}} + {{#produces}} + rc.Produces.Add("{{mediaType}}") + {{/produces}} + {{#bodyParam.required}} + rc.Model = &{{bodyParam.dataType}}{} + {{/bodyParam.required}} + server.Process(w, r) +}{{/operation}}{{/operations}} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/logger.mustache b/tools/swagger_codegen/go-server/templates-nonyang/logger.mustache new file mode 100644 index 0000000000..aa0d894d83 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/logger.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "log" + "net/http" + "time" +) + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s %s %s %s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/main.mustache b/tools/swagger_codegen/go-server/templates-nonyang/main.mustache new file mode 100644 index 0000000000..5a1de41602 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/main.mustache @@ -0,0 +1,33 @@ +{{>partial_header}} +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + + // WARNING! + // Change this to a fully-qualified import path + // once you place this file into your project. + // For example, + // + // sw "github.com/myname/myrepo/{{apiPath}}" + // + sw "./{{apiPath}}" +) + +func main() { + var port int + + flag.IntVar(&port, "port", {{serverPort}}, "Listen port") + flag.Parse() + + address := fmt.Sprintf(":%d", port) + + log.Printf("Server started on %s", address) + + router := sw.NewRouter() + + log.Fatal(http.ListenAndServe(address, router)) +} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/model.mustache b/tools/swagger_codegen/go-server/templates-nonyang/model.mustache new file mode 100644 index 0000000000..8754da0581 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/model.mustache @@ -0,0 +1,32 @@ +{{>partial_header}} +package {{packageName}} +{{#models}}{{#imports}} +import ({{/imports}}{{#imports}} + "{{import}}"{{/imports}}{{#imports}} +) +{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}} +type {{{name}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}} + +// List of {{{name}}} +const ( + {{#allowableValues}} + {{#enumVars}} + {{name}} {{{classname}}} = "{{{value}}}" + {{/enumVars}} + {{/allowableValues}} +){{/isEnum}}{{^isEnum}}{{#description}} +// {{{description}}}{{/description}} +type {{classname}} struct { +{{#requiredVars}} + {{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `validate:"required" json:"{{baseName}}{{^required}},omitempty{{/required}}"` +{{/requiredVars}} +{{#vars}}{{#description}} + // {{{description}}}{{/description}} +{{^required}}{{#isContainer}} + {{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `validate:"dive" json:"{{baseName}}{{^required}},omitempty{{/required}}"` +{{/isContainer}}{{/required}} +{{^required}}{{^isContainer}} + {{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` +{{/isContainer}}{{/required}} +{{/vars}} +}{{/isEnum}}{{/model}}{{/models}} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/partial_header.mustache b/tools/swagger_codegen/go-server/templates-nonyang/partial_header.mustache new file mode 100644 index 0000000000..d24dfec369 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/partial_header.mustache @@ -0,0 +1,17 @@ +/* + {{#appName}} + * {{{appName}}} + * + {{/appName}} + {{#appDescription}} + * {{{appDescription}}} + * + {{/appDescription}} + {{#version}} + * API version: {{{version}}} + {{/version}} + {{#infoEmail}} + * Contact: {{{infoEmail}}} + {{/infoEmail}} + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ diff --git a/tools/swagger_codegen/go-server/templates-nonyang/routers.mustache b/tools/swagger_codegen/go-server/templates-nonyang/routers.mustache new file mode 100644 index 0000000000..cb0dc6e503 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/routers.mustache @@ -0,0 +1,17 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "rest/server" +) + +func init() { + {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + server.AddRoute( + "{{operationId}}", + "{{httpMethod}}", + "{{{basePathWithoutHost}}}{{{path}}}", + {{operationId}}, + ) + {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +} diff --git a/tools/swagger_codegen/go-server/templates-nonyang/swagger.mustache b/tools/swagger_codegen/go-server/templates-nonyang/swagger.mustache new file mode 100644 index 0000000000..51560926bb --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-nonyang/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file diff --git a/tools/swagger_codegen/go-server/templates-yang/controller-api.mustache b/tools/swagger_codegen/go-server/templates-yang/controller-api.mustache new file mode 100644 index 0000000000..790f1ee6b2 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-yang/controller-api.mustache @@ -0,0 +1,21 @@ +{{>partial_header}} +package {{packageName}} + +{{#operations}} +import ( + "net/http" + + "rest/server" +){{#operation}} + +func {{nickname}}(w http.ResponseWriter, r *http.Request) { + rc, r := server.GetContext(r) + rc.Name = "{{operationId}}" + {{#consumes}} + rc.Consumes.Add("{{mediaType}}") + {{/consumes}} + {{#produces}} + rc.Produces.Add("{{mediaType}}") + {{/produces}} + server.Process(w, r) +}{{/operation}}{{/operations}} diff --git a/tools/swagger_codegen/go-server/templates-yang/routers.mustache b/tools/swagger_codegen/go-server/templates-yang/routers.mustache new file mode 100644 index 0000000000..cb0dc6e503 --- /dev/null +++ b/tools/swagger_codegen/go-server/templates-yang/routers.mustache @@ -0,0 +1,17 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "rest/server" +) + +func init() { + {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + server.AddRoute( + "{{operationId}}", + "{{httpMethod}}", + "{{{basePathWithoutHost}}}{{{path}}}", + {{operationId}}, + ) + {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +} diff --git a/tools/swagger_codegen/ui-dist/favicon-16x16.png b/tools/swagger_codegen/ui-dist/favicon-16x16.png new file mode 100644 index 0000000000..8b194e617a Binary files /dev/null and b/tools/swagger_codegen/ui-dist/favicon-16x16.png differ diff --git a/tools/swagger_codegen/ui-dist/favicon-32x32.png b/tools/swagger_codegen/ui-dist/favicon-32x32.png new file mode 100644 index 0000000000..249737fe44 Binary files /dev/null and b/tools/swagger_codegen/ui-dist/favicon-32x32.png differ diff --git a/tools/swagger_codegen/ui-dist/index.html_notused b/tools/swagger_codegen/ui-dist/index.html_notused new file mode 100644 index 0000000000..f2e69e4f8b --- /dev/null +++ b/tools/swagger_codegen/ui-dist/index.html_notused @@ -0,0 +1,60 @@ + + + + + + Swagger UI + + + + + + + +
+ + + + + + diff --git a/tools/swagger_codegen/ui-dist/oauth2-redirect.html b/tools/swagger_codegen/ui-dist/oauth2-redirect.html new file mode 100644 index 0000000000..fb68399d26 --- /dev/null +++ b/tools/swagger_codegen/ui-dist/oauth2-redirect.html @@ -0,0 +1,67 @@ + + + + + + diff --git a/tools/swagger_codegen/ui-dist/swagger-ui-bundle.js b/tools/swagger_codegen/ui-dist/swagger-ui-bundle.js new file mode 100644 index 0000000000..55e2f50c0a --- /dev/null +++ b/tools/swagger_codegen/ui-dist/swagger-ui-bundle.js @@ -0,0 +1,93 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=446)}([function(e,t,n){"use strict";e.exports=n(75)},function(e,t,n){e.exports=n(854)()},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";t.__esModule=!0;var r,o=n(263),i=(r=o)&&r.__esModule?r:{default:r};t.default=function(){function e(e,t){for(var n=0;n>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?C(e)+t:t}function A(){return!0}function O(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function P(e,t){return M(e,t,0)}function T(e,t){return M(e,t,t)}function M(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var I=0,j=1,N=2,R="function"==typeof Symbol&&Symbol.iterator,D="@@iterator",L=R||D;function U(e){this.next=e}function q(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function F(){return{value:void 0,done:!0}}function z(e){return!!H(e)}function B(e){return e&&"function"==typeof e.next}function V(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(R&&e[R]||e[D]);if("function"==typeof t)return t}function W(e){return e&&"number"==typeof e.length}function J(e){return null===e||void 0===e?ie():a(e)?e.toSeq():function(e){var t=se(e)||"object"==typeof e&&new te(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}(e)}function Y(e){return null===e||void 0===e?ie().toKeyedSeq():a(e)?u(e)?e.toSeq():e.fromEntrySeq():ae(e)}function K(e){return null===e||void 0===e?ie():a(e)?u(e)?e.entrySeq():e.toIndexedSeq():ue(e)}function G(e){return(null===e||void 0===e?ie():a(e)?u(e)?e.entrySeq():e:ue(e)).toSetSeq()}U.prototype.toString=function(){return"[Iterator]"},U.KEYS=I,U.VALUES=j,U.ENTRIES=N,U.prototype.inspect=U.prototype.toSource=function(){return this.toString()},U.prototype[L]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return le(this,e,t,!0)},J.prototype.__iterator=function(e,t){return ce(this,e,t,!0)},t(Y,J),Y.prototype.toKeyedSeq=function(){return this},t(K,J),K.of=function(){return K(arguments)},K.prototype.toIndexedSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq [","]")},K.prototype.__iterate=function(e,t){return le(this,e,t,!1)},K.prototype.__iterator=function(e,t){return ce(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=oe,J.Keyed=Y,J.Set=G,J.Indexed=K;var $,Z,X,Q="@@__IMMUTABLE_SEQ__@@";function ee(e){this._array=e,this.size=e.length}function te(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function ne(e){this._iterable=e,this.size=e.length||e.size}function re(e){this._iterator=e,this._iteratorCache=[]}function oe(e){return!(!e||!e[Q])}function ie(){return $||($=new ee([]))}function ae(e){var t=Array.isArray(e)?new ee(e).fromEntrySeq():B(e)?new re(e).fromEntrySeq():z(e)?new ne(e).fromEntrySeq():"object"==typeof e?new te(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function ue(e){var t=se(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function se(e){return W(e)?new ee(e):B(e)?new re(e):z(e)?new ne(e):void 0}function le(e,t,n,r){var o=e._cache;if(o){for(var i=o.length-1,a=0;a<=i;a++){var u=o[n?i-a:a];if(!1===t(u[1],r?u[0]:a,e))return a+1}return a}return e.__iterateUncached(t,n)}function ce(e,t,n,r){var o=e._cache;if(o){var i=o.length-1,a=0;return new U(function(){var e=o[n?i-a:a];return a++>i?{value:void 0,done:!0}:q(t,r?e[0]:a-1,e[1])})}return e.__iteratorUncached(t,n)}function fe(e,t){return t?function e(t,n,r,o){if(Array.isArray(n))return t.call(o,r,K(n).map(function(r,o){return e(t,r,o,n)}));if(de(n))return t.call(o,r,Y(n).map(function(r,o){return e(t,r,o,n)}));return n}(t,e,"",{"":e}):pe(e)}function pe(e){return Array.isArray(e)?K(e).map(pe).toList():de(e)?Y(e).map(pe).toMap():e}function de(e){return e&&(e.constructor===Object||void 0===e.constructor)}function he(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ve(e,t){if(e===t)return!0;if(!a(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||u(e)!==u(t)||s(e)!==s(t)||c(e)!==c(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!l(e);if(c(e)){var r=e.entries();return t.every(function(e,t){var o=r.next().value;return o&&he(o[1],e)&&(n||he(o[0],t))})&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var i=e;e=t,t=i}var f=!0,p=t.__iterate(function(t,r){if(n?!e.has(t):o?!he(t,e.get(r,y)):!he(e.get(r,y),t))return f=!1,!1});return f&&e.size===p}function me(e,t){if(!(this instanceof me))return new me(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(Z)return Z;Z=this}}function ge(e,t){if(!e)throw new Error(t)}function ye(e,t,n){if(!(this instanceof ye))return new ye(e,t,n);if(ge(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?{value:void 0,done:!0}:q(e,o,n[t?r-o++:o++])})},t(te,Y),te.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},te.prototype.has=function(e){return this._object.hasOwnProperty(e)},te.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,i=0;i<=o;i++){var a=r[t?o-i:i];if(!1===e(n[a],a,this))return i+1}return i},te.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,i=0;return new U(function(){var a=r[t?o-i:i];return i++>o?{value:void 0,done:!0}:q(e,a,n[a])})},te.prototype[h]=!0,t(ne,K),ne.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=V(this._iterable),r=0;if(B(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},ne.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=V(this._iterable);if(!B(n))return new U(F);var r=0;return new U(function(){var t=n.next();return t.done?t:q(e,r++,t.value)})},t(re,K),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,i=0;i=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return q(e,o,r[o++])})},t(me,K),me.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},me.prototype.get=function(e,t){return this.has(e)?this._value:t},me.prototype.includes=function(e){return he(this._value,e)},me.prototype.slice=function(e,t){var n=this.size;return O(e,t,n)?this:new me(this._value,T(t,n)-P(e,n))},me.prototype.reverse=function(){return this},me.prototype.indexOf=function(e){return he(this._value,e)?0:-1},me.prototype.lastIndexOf=function(e){return he(this._value,e)?this.size:-1},me.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?{value:void 0,done:!0}:q(e,i++,a)})},ye.prototype.equals=function(e){return e instanceof ye?this._start===e._start&&this._end===e._end&&this._step===e._step:ve(this,e)},t(be,n),t(_e,be),t(we,be),t(Ee,be),be.Keyed=_e,be.Indexed=we,be.Set=Ee;var xe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function Se(e){return e>>>1&1073741824|3221225471&e}function Ce(e){if(!1===e||null===e||void 0===e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null===e||void 0===e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return Se(n)}if("string"===t)return e.length>je?function(e){var t=De[e];void 0===t&&(t=ke(e),Re===Ne&&(Re=0,De={}),Re++,De[e]=t);return t}(e):ke(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return function(e){var t;if(Te&&void 0!==(t=Pe.get(e)))return t;if(void 0!==(t=e[Ie]))return t;if(!Oe){if(void 0!==(t=e.propertyIsEnumerable&&e.propertyIsEnumerable[Ie]))return t;if(void 0!==(t=function(e){if(e&&e.nodeType>0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}(e)))return t}t=++Me,1073741824&Me&&(Me=0);if(Te)Pe.set(e,t);else{if(void 0!==Ae&&!1===Ae(e))throw new Error("Non-extensible objects are not allowed as keys.");if(Oe)Object.defineProperty(e,Ie,{enumerable:!1,configurable:!1,writable:!1,value:t});else if(void 0!==e.propertyIsEnumerable&&e.propertyIsEnumerable===e.constructor.prototype.propertyIsEnumerable)e.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},e.propertyIsEnumerable[Ie]=t;else{if(void 0===e.nodeType)throw new Error("Unable to set a non-enumerable property on object.");e[Ie]=t}}return t}(e);if("function"==typeof e.toString)return ke(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function ke(e){for(var t=0,n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}})},Ue.prototype.toString=function(){return this.__toString("Map {","}")},Ue.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Ue.prototype.set=function(e,t){return Qe(this,e,t)},Ue.prototype.setIn=function(e,t){return this.updateIn(e,y,function(){return t})},Ue.prototype.remove=function(e){return Qe(this,e,y)},Ue.prototype.deleteIn=function(e){return this.updateIn(e,function(){return y})},Ue.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Ue.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=function e(t,n,r,o){var i=t===y;var a=n.next();if(a.done){var u=i?r:t,s=o(u);return s===u?t:s}ge(i||t&&t.set,"invalid keyPath");var l=a.value;var c=i?y:t.get(l,y);var f=e(c,n,r,o);return f===c?t:f===y?t.remove(l):(i?Xe():t).set(l,f)}(this,nn(e),t,n);return r===y?void 0:r},Ue.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Xe()},Ue.prototype.merge=function(){return rt(this,void 0,arguments)},Ue.prototype.mergeWith=function(t){return rt(this,t,e.call(arguments,1))},Ue.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Xe(),function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]})},Ue.prototype.mergeDeep=function(){return rt(this,ot,arguments)},Ue.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return rt(this,it(t),n)},Ue.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,Xe(),function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]})},Ue.prototype.sort=function(e){return Pt(Wt(this,e))},Ue.prototype.sortBy=function(e,t){return Pt(Wt(this,t,e))},Ue.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Ue.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new x)},Ue.prototype.asImmutable=function(){return this.__ensureOwner()},Ue.prototype.wasAltered=function(){return this.__altered},Ue.prototype.__iterator=function(e,t){return new Ke(this,e,t)},Ue.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate(function(t){return r++,e(t[1],t[0],n)},t),r},Ue.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Ze(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Ue.isMap=qe;var Fe,ze="@@__IMMUTABLE_MAP__@@",Be=Ue.prototype;function Ve(e,t){this.ownerID=e,this.entries=t}function He(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function We(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Je(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Ye(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function Ke(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&$e(e._root)}function Ge(e,t){return q(e,t[0],t[1])}function $e(e,t){return{node:e,index:0,__prev:t}}function Ze(e,t,n,r){var o=Object.create(Be);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Xe(){return Fe||(Fe=Ze(0))}function Qe(e,t,n){var r,o;if(e._root){var i=w(b),a=w(_);if(r=et(e._root,e.__ownerID,0,void 0,t,n,i,a),!a.value)return e;o=e.size+(i.value?n===y?-1:1:0)}else{if(n===y)return e;o=1,r=new Ve(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?Ze(o,r):Xe()}function et(e,t,n,r,o,i,a,u){return e?e.update(t,n,r,o,i,a,u):i===y?e:(E(u),E(a),new Ye(t,r,[o,i]))}function tt(e){return e.constructor===Ye||e.constructor===Je}function nt(e,t,n,r,o){if(e.keyHash===r)return new Je(t,r,[e.entry,o]);var i,a=(0===n?e.keyHash:e.keyHash>>>n)&g,u=(0===n?r:r>>>n)&g;return new He(t,1<>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function st(e,t,n,r){var o=r?e:S(e);return o[t]=n,o}Be[ze]=!0,Be.delete=Be.remove,Be.removeIn=Be.deleteIn,Ve.prototype.get=function(e,t,n,r){for(var o=this.entries,i=0,a=o.length;i=lt)return function(e,t,n,r){e||(e=new x);for(var o=new Ye(e,Ce(n),[n,r]),i=0;i>>e)&g),i=this.bitmap;return 0==(i&o)?r:this.nodes[ut(i&o-1)].get(e+v,t,n,r)},He.prototype.update=function(e,t,n,r,o,i,a){void 0===n&&(n=Ce(r));var u=(0===t?n:n>>>t)&g,s=1<=ct)return function(e,t,n,r,o){for(var i=0,a=new Array(m),u=0;0!==n;u++,n>>>=1)a[u]=1&n?t[i++]:void 0;return a[r]=o,new We(e,i+1,a)}(e,p,l,u,h);if(c&&!h&&2===p.length&&tt(p[1^f]))return p[1^f];if(c&&h&&1===p.length&&tt(h))return h;var b=e&&e===this.ownerID,_=c?h?l:l^s:l|s,w=c?h?st(p,f,h,b):function(e,t,n){var r=e.length-1;if(n&&t===r)return e.pop(),e;for(var o=new Array(r),i=0,a=0;a>>e)&g,i=this.nodes[o];return i?i.get(e+v,t,n,r):r},We.prototype.update=function(e,t,n,r,o,i,a){void 0===n&&(n=Ce(r));var u=(0===t?n:n>>>t)&g,s=o===y,l=this.nodes,c=l[u];if(s&&!c)return this;var f=et(c,e,t+v,n,r,o,i,a);if(f===c)return this;var p=this.count;if(c){if(!f&&--p0&&r=0&&e=e.size||t<0)return e.withMutations(function(e){t<0?kt(e,t).set(0,n):kt(e,0,t+1).set(t,n)});t+=e._origin;var r=e._tail,o=e._root,i=w(_);t>=Ot(e._capacity)?r=xt(r,e.__ownerID,0,t,n,i):o=xt(o,e.__ownerID,e._level,t,n,i);if(!i.value)return e;if(e.__ownerID)return e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e;return wt(e._origin,e._capacity,e._level,o,r)}(this,e,t)},pt.prototype.remove=function(e){return this.has(e)?0===e?this.shift():e===this.size-1?this.pop():this.splice(e,1):this},pt.prototype.insert=function(e,t){return this.splice(e,0,t)},pt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=v,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):Et()},pt.prototype.push=function(){var e=arguments,t=this.size;return this.withMutations(function(n){kt(n,0,t+e.length);for(var r=0;r>>t&g;if(r>=this.array.length)return new mt([],e);var o,i=0===r;if(t>0){var a=this.array[r];if((o=a&&a.removeBefore(e,t-v,n))===a&&i)return this}if(i&&!o)return this;var u=St(this,e);if(!i)for(var s=0;s>>t&g;if(o>=this.array.length)return this;if(t>0){var i=this.array[o];if((r=i&&i.removeAfter(e,t-v,n))===i&&o===this.array.length-1)return this}var a=St(this,e);return a.array.splice(o+1),r&&(a.array[o]=r),a};var gt,yt,bt={};function _t(e,t){var n=e._origin,r=e._capacity,o=Ot(r),i=e._tail;return a(e._root,e._level,0);function a(e,u,s){return 0===u?function(e,a){var u=a===o?i&&i.array:e&&e.array,s=a>n?0:n-a,l=r-a;l>m&&(l=m);return function(){if(s===l)return bt;var e=t?--l:s++;return u&&u[e]}}(e,s):function(e,o,i){var u,s=e&&e.array,l=i>n?0:n-i>>o,c=1+(r-i>>o);c>m&&(c=m);return function(){for(;;){if(u){var e=u();if(e!==bt)return e;u=null}if(l===c)return bt;var n=t?--c:l++;u=a(s&&s[n],o-v,i+(n<>>n&g,s=e&&u0){var l=e&&e.array[u],c=xt(l,t,n-v,r,o,i);return c===l?e:((a=St(e,t)).array[u]=c,a)}return s&&e.array[u]===o?e:(E(i),a=St(e,t),void 0===o&&u===a.array.length-1?a.array.pop():a.array[u]=o,a)}function St(e,t){return t&&e&&t===e.ownerID?e:new mt(e?e.array.slice():[],t)}function Ct(e,t){if(t>=Ot(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&g],r-=v;return n}}function kt(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new x,o=e._origin,i=e._capacity,a=o+t,u=void 0===n?i:n<0?i+n:o+n;if(a===o&&u===i)return e;if(a>=u)return e.clear();for(var s=e._level,l=e._root,c=0;a+c<0;)l=new mt(l&&l.array.length?[void 0,l]:[],r),c+=1<<(s+=v);c&&(a+=c,o+=c,u+=c,i+=c);for(var f=Ot(i),p=Ot(u);p>=1<f?new mt([],r):d;if(d&&p>f&&av;y-=v){var b=f>>>y&g;m=m.array[b]=St(m.array[b],r)}m.array[f>>>v&g]=d}if(u=p)a-=p,u-=p,s=v,l=null,h=h&&h.removeBefore(r,0,a);else if(a>o||p>>s&g;if(_!==p>>>s&g)break;_&&(c+=(1<o&&(l=l.removeBefore(r,s,a-c)),l&&pi&&(i=l.size),a(s)||(l=l.map(function(e){return fe(e)})),r.push(l)}return i>e.size&&(e=e.setSize(i)),at(e,t,r)}function Ot(e){return e>>v<=m&&a.size>=2*i.size?(r=(o=a.filter(function(e,t){return void 0!==e&&u!==t})).toKeyedSeq().map(function(e){return e[0]}).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=i.remove(t),o=u===a.size-1?a.pop():a.set(u,void 0))}else if(s){if(n===a.get(u)[1])return e;r=i,o=a.set(u,[t,n])}else r=i.set(t,a.size),o=a.set(a.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Mt(r,o)}function Nt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Rt(e){this._iter=e,this.size=e.size}function Dt(e){this._iter=e,this.size=e.size}function Lt(e){this._iter=e,this.size=e.size}function Ut(e){var t=Qt(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=en,t.__iterateUncached=function(t,n){var r=this;return e.__iterate(function(e,n){return!1!==t(n,e,r)},n)},t.__iteratorUncached=function(t,n){if(t===N){var r=e.__iterator(t,n);return new U(function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e})}return e.__iterator(t===j?I:j,n)},t}function qt(e,t,n){var r=Qt(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var i=e.get(r,y);return i===y?o:t.call(n,i,r,e)},r.__iterateUncached=function(r,o){var i=this;return e.__iterate(function(e,o,a){return!1!==r(t.call(n,e,o,a),o,i)},o)},r.__iteratorUncached=function(r,o){var i=e.__iterator(N,o);return new U(function(){var o=i.next();if(o.done)return o;var a=o.value,u=a[0];return q(r,u,t.call(n,a[1],u,e),o)})},r}function Ft(e,t){var n=Qt(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Ut(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=en,n.__iterate=function(t,n){var r=this;return e.__iterate(function(e,n){return t(e,n,r)},!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function zt(e,t,n,r){var o=Qt(e);return r&&(o.has=function(r){var o=e.get(r,y);return o!==y&&!!t.call(n,o,r,e)},o.get=function(r,o){var i=e.get(r,y);return i!==y&&t.call(n,i,r,e)?i:o}),o.__iterateUncached=function(o,i){var a=this,u=0;return e.__iterate(function(e,i,s){if(t.call(n,e,i,s))return u++,o(e,r?i:u-1,a)},i),u},o.__iteratorUncached=function(o,i){var a=e.__iterator(N,i),u=0;return new U(function(){for(;;){var i=a.next();if(i.done)return i;var s=i.value,l=s[0],c=s[1];if(t.call(n,c,l,e))return q(o,r?l:u++,c,i)}})},o}function Bt(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),O(t,n,o))return e;var i=P(t,o),a=T(n,o);if(i!=i||a!=a)return Bt(e.toSeq().cacheResult(),t,n,r);var u,s=a-i;s==s&&(u=s<0?0:s);var l=Qt(e);return l.size=0===u?u:e.size&&u||void 0,!r&&oe(e)&&u>=0&&(l.get=function(t,n){return(t=k(this,t))>=0&&tu)return{value:void 0,done:!0};var e=o.next();return r||t===j?e:q(t,s-1,t===I?void 0:e.value[1],e)})},l}function Vt(e,t,n,r){var o=Qt(e);return o.__iterateUncached=function(o,i){var a=this;if(i)return this.cacheResult().__iterate(o,i);var u=!0,s=0;return e.__iterate(function(e,i,l){if(!u||!(u=t.call(n,e,i,l)))return s++,o(e,r?i:s-1,a)}),s},o.__iteratorUncached=function(o,i){var a=this;if(i)return this.cacheResult().__iterator(o,i);var u=e.__iterator(N,i),s=!0,l=0;return new U(function(){var e,i,c;do{if((e=u.next()).done)return r||o===j?e:q(o,l++,o===I?void 0:e.value[1],e);var f=e.value;i=f[0],c=f[1],s&&(s=t.call(n,c,i,a))}while(s);return o===N?e:q(o,i,c,e)})},o}function Ht(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,o){var i=0,u=!1;return function e(s,l){var c=this;s.__iterate(function(o,s){return(!t||l0}function Kt(e,t,r){var o=Qt(e);return o.size=new ee(r).map(function(e){return e.size}).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(j,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var i=r.map(function(e){return e=n(e),V(o?e.reverse():e)}),a=0,u=!1;return new U(function(){var n;return u||(n=i.map(function(e){return e.next()}),u=n.some(function(e){return e.done})),u?{value:void 0,done:!0}:q(e,a++,t.apply(null,n.map(function(e){return e.value})))})},o}function Gt(e,t){return oe(e)?t:e.constructor(t)}function $t(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function Zt(e){return Le(e.size),C(e)}function Xt(e){return u(e)?r:s(e)?o:i}function Qt(e){return Object.create((u(e)?Y:s(e)?K:G).prototype)}function en(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):J.prototype.cacheResult.call(this)}function tn(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):An(e,t)},En.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;Le(e.size);var t=this.size,n=this._head;return e.reverse().forEach(function(e){t++,n={value:e,next:n}}),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):An(t,n)},En.prototype.pop=function(){return this.slice(1)},En.prototype.unshift=function(){return this.push.apply(this,arguments)},En.prototype.unshiftAll=function(e){return this.pushAll(e)},En.prototype.shift=function(){return this.pop.apply(this,arguments)},En.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):On()},En.prototype.slice=function(e,t){if(O(e,t,this.size))return this;var n=P(e,this.size);if(T(t,this.size)!==this.size)return we.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):An(r,o)},En.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?An(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},En.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},En.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new U(function(){if(r){var t=r.value;return r=r.next,q(e,n++,t)}return{value:void 0,done:!0}})},En.isStack=xn;var Sn,Cn="@@__IMMUTABLE_STACK__@@",kn=En.prototype;function An(e,t,n,r){var o=Object.create(kn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function On(){return Sn||(Sn=An(0))}function Pn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}kn[Cn]=!0,kn.withMutations=Be.withMutations,kn.asMutable=Be.asMutable,kn.asImmutable=Be.asImmutable,kn.wasAltered=Be.wasAltered,n.Iterator=U,Pn(n,{toArray:function(){Le(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate(function(t,n){e[n]=t}),e},toIndexedSeq:function(){return new Rt(this)},toJS:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJS?e.toJS():e}).__toJS()},toJSON:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e}).__toJS()},toKeyedSeq:function(){return new Nt(this,!0)},toMap:function(){return Ue(this.toKeyedSeq())},toObject:function(){Le(this.size);var e={};return this.__iterate(function(t,n){e[n]=t}),e},toOrderedMap:function(){return Pt(this.toKeyedSeq())},toOrderedSet:function(){return mn(u(this)?this.valueSeq():this)},toSet:function(){return sn(u(this)?this.valueSeq():this)},toSetSeq:function(){return new Dt(this)},toSeq:function(){return s(this)?this.toIndexedSeq():u(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return En(u(this)?this.valueSeq():this)},toList:function(){return pt(u(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return Gt(this,function(e,t){var n=u(e),o=[e].concat(t).map(function(e){return a(e)?n&&(e=r(e)):e=n?ae(e):ue(Array.isArray(e)?e:[e]),e}).filter(function(e){return 0!==e.size});if(0===o.length)return e;if(1===o.length){var i=o[0];if(i===e||n&&u(i)||s(e)&&s(i))return i}var l=new ee(o);return n?l=l.toKeyedSeq():s(e)||(l=l.toSetSeq()),(l=l.flatten(!0)).size=o.reduce(function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}},0),l}(this,e.call(arguments,0)))},includes:function(e){return this.some(function(t){return he(t,e)})},entries:function(){return this.__iterator(N)},every:function(e,t){Le(this.size);var n=!0;return this.__iterate(function(r,o,i){if(!e.call(t,r,o,i))return n=!1,!1}),n},filter:function(e,t){return Gt(this,zt(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return Le(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){Le(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate(function(r){n?n=!1:t+=e,t+=null!==r&&void 0!==r?r.toString():""}),t},keys:function(){return this.__iterator(I)},map:function(e,t){return Gt(this,qt(this,e,t))},reduce:function(e,t,n){var r,o;return Le(this.size),arguments.length<2?o=!0:r=t,this.__iterate(function(t,i,a){o?(o=!1,r=t):r=e.call(n,r,t,i,a)}),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return Gt(this,Ft(this,!0))},slice:function(e,t){return Gt(this,Bt(this,e,t,!0))},some:function(e,t){return!this.every(Nn(e),t)},sort:function(e){return Gt(this,Wt(this,e))},values:function(){return this.__iterator(j)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some(function(){return!0})},count:function(e,t){return C(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return function(e,t,n){var r=Ue().asMutable();return e.__iterate(function(o,i){r.update(t.call(n,o,i,e),0,function(e){return e+1})}),r.asImmutable()}(this,e,t)},equals:function(e){return ve(this,e)},entrySeq:function(){var e=this;if(e._cache)return new ee(e._cache);var t=e.toSeq().map(jn).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(Nn(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate(function(n,o,i){if(e.call(t,n,o,i))return r=[o,n],!1}),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return Gt(this,function(e,t,n){var r=Xt(e);return e.toSeq().map(function(o,i){return r(t.call(n,o,i,e))}).flatten(!0)}(this,e,t))},flatten:function(e){return Gt(this,Ht(this,e,!0))},fromEntrySeq:function(){return new Lt(this)},get:function(e,t){return this.find(function(t,n){return he(n,e)},void 0,t)},getIn:function(e,t){for(var n,r=this,o=nn(e);!(n=o.next()).done;){var i=n.value;if((r=r&&r.get?r.get(i,y):y)===y)return t}return r},groupBy:function(e,t){return function(e,t,n){var r=u(e),o=(c(e)?Pt():Ue()).asMutable();e.__iterate(function(i,a){o.update(t.call(n,i,a,e),function(e){return(e=e||[]).push(r?[a,i]:i),e})});var i=Xt(e);return o.map(function(t){return Gt(e,i(t))})}(this,e,t)},has:function(e){return this.get(e,y)!==y},hasIn:function(e){return this.getIn(e,y)!==y},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every(function(t){return e.includes(t)})},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey(function(t){return he(t,e)})},keySeq:function(){return this.toSeq().map(In).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return Jt(this,e)},maxBy:function(e,t){return Jt(this,t,e)},min:function(e){return Jt(this,e?Rn(e):Un)},minBy:function(e,t){return Jt(this,t?Rn(t):Un,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return Gt(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return Gt(this,Vt(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(Nn(e),t)},sortBy:function(e,t){return Gt(this,Wt(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return Gt(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return Gt(this,function(e,t,n){var r=Qt(e);return r.__iterateUncached=function(r,o){var i=this;if(o)return this.cacheResult().__iterate(r,o);var a=0;return e.__iterate(function(e,o,u){return t.call(n,e,o,u)&&++a&&r(e,o,i)}),a},r.__iteratorUncached=function(r,o){var i=this;if(o)return this.cacheResult().__iterator(r,o);var a=e.__iterator(N,o),u=!0;return new U(function(){if(!u)return{value:void 0,done:!0};var e=a.next();if(e.done)return e;var o=e.value,s=o[0],l=o[1];return t.call(n,l,s,i)?r===N?e:q(r,s,l,e):(u=!1,{value:void 0,done:!0})})},r}(this,e,t))},takeUntil:function(e,t){return this.takeWhile(Nn(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=function(e){if(e.size===1/0)return 0;var t=c(e),n=u(e),r=t?1:0;return function(e,t){return t=xe(t,3432918353),t=xe(t<<15|t>>>-15,461845907),t=xe(t<<13|t>>>-13,5),t=xe((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=Se((t=xe(t^t>>>13,3266489909))^t>>>16)}(e.__iterate(n?t?function(e,t){r=31*r+qn(Ce(e),Ce(t))|0}:function(e,t){r=r+qn(Ce(e),Ce(t))|0}:t?function(e){r=31*r+Ce(e)|0}:function(e){r=r+Ce(e)|0}),r)}(this))}});var Tn=n.prototype;Tn[f]=!0,Tn[L]=Tn.values,Tn.__toJS=Tn.toArray,Tn.__toStringMapper=Dn,Tn.inspect=Tn.toSource=function(){return this.toString()},Tn.chain=Tn.flatMap,Tn.contains=Tn.includes,Pn(r,{flip:function(){return Gt(this,Ut(this))},mapEntries:function(e,t){var n=this,r=0;return Gt(this,this.toSeq().map(function(o,i){return e.call(t,[i,o],r++,n)}).fromEntrySeq())},mapKeys:function(e,t){var n=this;return Gt(this,this.toSeq().flip().map(function(r,o){return e.call(t,r,o,n)}).flip())}});var Mn=r.prototype;function In(e,t){return t}function jn(e,t){return[t,e]}function Nn(e){return function(){return!e.apply(this,arguments)}}function Rn(e){return function(){return-e.apply(this,arguments)}}function Dn(e){return"string"==typeof e?JSON.stringify(e):String(e)}function Ln(){return S(arguments)}function Un(e,t){return et?-1:0}function qn(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Mn[p]=!0,Mn[L]=Tn.entries,Mn.__toJS=Tn.toObject,Mn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+Dn(e)},Pn(o,{toKeyedSeq:function(){return new Nt(this,!1)},filter:function(e,t){return Gt(this,zt(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return Gt(this,Ft(this,!1))},slice:function(e,t){return Gt(this,Bt(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=P(e,e<0?this.count():this.size);var r=this.slice(0,e);return Gt(this,1===n?r:r.concat(S(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return Gt(this,Ht(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find(function(t,n){return n===e},void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e5e3)return e.textContent;return function(e){for(var n,r,o,i,a,u=e.textContent,s=0,l=u[0],c=1,f=e.innerHTML="",p=0;r=n,n=p<7&&"\\"==n?1:c;){if(c=l,l=u[++s],i=f.length>1,!c||p>8&&"\n"==c||[/\S/.test(c),1,1,!/[$\w]/.test(c),("/"==n||"\n"==n)&&i,'"'==n&&i,"'"==n&&i,u[s-4]+r+n=="--\x3e",r+n=="*/"][p])for(f&&(e.appendChild(a=t.createElement("span")).setAttribute("style",["color: #555; font-weight: bold;","","","color: #555;",""][p?p<3?2:p>6?4:p>3?3:+/^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/.test(f):0]),a.appendChild(t.createTextNode(f))),o=p&&p<7?p:o,f="",p=11;![1,/[\/{}[(\-+*=<>:;|\\.,?!&@~]/.test(c),/[\])]/.test(c),/[$\w]/.test(c),"/"==c&&o<2&&"<"!=n,'"'==c,"'"==c,c+l+u[s+1]+u[s+2]=="\x3c!--",c+l=="/*",c+l=="//","#"==c][--p];);f+=c}}(e)},t.mapToList=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"key";var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:l.default.Map();if(!l.default.Map.isMap(t)||!t.size)return l.default.List();Array.isArray(n)||(n=[n]);if(n.length<1)return t.merge(r);var a=l.default.List();var u=n[0];var s=!0;var c=!1;var f=void 0;try{for(var p,d=(0,i.default)(t.entries());!(s=(p=d.next()).done);s=!0){var h=p.value,v=(0,o.default)(h,2),m=v[0],g=v[1],y=e(g,n.slice(1),r.set(u,m));a=l.default.List.isList(y)?a.concat(y):a.push(y)}}catch(e){c=!0,f=e}finally{try{!s&&d.return&&d.return()}finally{if(c)throw f}}return a},t.extractFileNameFromContentDispositionHeader=function(e){var t=void 0;if([/filename\*=[^']+'\w*'"([^"]+)";?/i,/filename\*=[^']+'\w*'([^;]+);?/i,/filename="([^;]*);?"/i,/filename=([^;]*);?/i].some(function(n){return null!==(t=n.exec(e))}),null!==t&&t.length>1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null},t.pascalCase=C,t.pascalCaseFilename=function(e){return C(e.replace(/\.[^./]*$/,""))},t.sanitizeUrl=function(e){if("string"!=typeof e||""===e)return"";return(0,c.sanitizeUrl)(e)},t.getAcceptControllingResponse=function(e){if(!l.default.OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;var t=e.find(function(e,t){return t.startsWith("2")&&(0,u.default)(e.get("content")||{}).length>0}),n=e.get("default")||l.default.OrderedMap(),r=(n.get("content")||l.default.OrderedMap()).keySeq().toJS().length?n:null;return t||r},t.deeplyStripKey=function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("object"!==(void 0===t?"undefined":(0,s.default)(t))||Array.isArray(t)||null===t||!n)return t;var o=(0,a.default)({},t);(0,u.default)(o).forEach(function(t){t===n&&r(o[t],t)?delete o[t]:o[t]=e(o[t],n,r)});return o},t.stringify=function(e){if("string"==typeof e)return e;e.toJS&&(e=e.toJS());if("object"===(void 0===e?"undefined":(0,s.default)(e))&&null!==e)try{return(0,r.default)(e,null,2)}catch(t){return String(e)}return e.toString()},t.numberToString=function(e){if("number"==typeof e)return e.toString();return e},t.paramToIdentifier=q,t.paramToValue=function(e,t){return q(e,{returnAll:!0}).map(function(e){return t[e]}).filter(function(e){return void 0!==e})[0]};var l=_(n(7)),c=n(572),f=_(n(573)),p=_(n(281)),d=_(n(285)),h=_(n(288)),v=_(n(651)),m=_(n(105)),g=n(194),y=_(n(32)),b=_(n(724));function _(e){return e&&e.__esModule?e:{default:e}}var w="default",E=t.isImmutable=function(e){return l.default.Iterable.isIterable(e)};function x(e){return Array.isArray(e)?e:[e]}function S(e){return!!e&&"object"===(void 0===e?"undefined":(0,s.default)(e))}t.memoize=d.default;function C(e){return(0,p.default)((0,f.default)(e))}t.propChecker=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];return(0,u.default)(e).length!==(0,u.default)(t).length||((0,v.default)(e,function(e,n){if(r.includes(n))return!1;var o=t[n];return l.default.Iterable.isIterable(e)?!l.default.is(e,o):("object"!==(void 0===e?"undefined":(0,s.default)(e))||"object"!==(void 0===o?"undefined":(0,s.default)(o)))&&e!==o})||n.some(function(n){return!(0,m.default)(e[n],t[n])}))};var k=t.validateMaximum=function(e,t){if(e>t)return"Value must be less than Maximum"},A=t.validateMinimum=function(e,t){if(et)return"Value must be less than MaxLength"},D=t.validateMinLength=function(e,t){if(e.length2&&void 0!==arguments[2]?arguments[2]:{},r=n.isOAS3,o=void 0!==r&&r,i=n.bypassRequiredCheck,a=void 0!==i&&i,u=[],c=e.get("required"),f=o?e.get("schema"):e;if(!f)return u;var p=f.get("maximum"),d=f.get("minimum"),h=f.get("type"),v=f.get("format"),m=f.get("maxLength"),g=f.get("minLength"),b=f.get("pattern");if(h&&(c||t)){var _="string"===h&&t,w="array"===h&&Array.isArray(t)&&t.length,E="array"===h&&l.default.List.isList(t)&&t.count(),x="file"===h&&t instanceof y.default.File,S="boolean"===h&&(t||!1===t),C="number"===h&&(t||0===t),U="integer"===h&&(t||0===t),q=!1;if(o&&"object"===h)if("object"===(void 0===t?"undefined":(0,s.default)(t)))q=!0;else if("string"==typeof t)try{JSON.parse(t),q=!0}catch(e){return u.push("Parameter string value must be valid JSON"),u}var F=[_,w,E,x,S,C,U,q].some(function(e){return!!e});if(c&&!F&&!a)return u.push("Required field is not provided"),u;if(b){var z=L(t,b);z&&u.push(z)}if(m||0===m){var B=R(t,m);B&&u.push(B)}if(g){var V=D(t,g);V&&u.push(V)}if(p||0===p){var H=k(t,p);H&&u.push(H)}if(d||0===d){var W=A(t,d);W&&u.push(W)}if("string"===h){var J=void 0;if(!(J="date-time"===v?j(t):"uuid"===v?N(t):I(t)))return u;u.push(J)}else if("boolean"===h){var Y=M(t);if(!Y)return u;u.push(Y)}else if("number"===h){var K=O(t);if(!K)return u;u.push(K)}else if("integer"===h){var G=P(t);if(!G)return u;u.push(G)}else if("array"===h){var $;if(!E||!t.count())return u;$=f.getIn(["items","type"]),t.forEach(function(e,t){var n=void 0;"number"===$?n=O(e):"integer"===$?n=P(e):"string"===$&&(n=I(e)),n&&u.push({index:t,error:n})})}else if("file"===h){var Z=T(t);if(!Z)return u;u.push(Z)}}return u},t.getSampleSchema=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(/xml/.test(t)){if(!e.xml||!e.xml.name){if(e.xml=e.xml||{},!e.$$ref)return e.type||e.items||e.properties||e.additionalProperties?'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e':null;var o=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=o[1]}return(0,g.memoizedCreateXMLExample)(e,n)}var i=(0,g.memoizedSampleFromSchema)(e,n);return"object"===(void 0===i?"undefined":(0,s.default)(i))?(0,r.default)(i,null,2):i},t.parseSearch=function(){var e={},t=y.default.location.search;if(!t)return{};if(""!=t){var n=t.substr(1).split("&");for(var r in n)n.hasOwnProperty(r)&&(r=n[r].split("="),e[decodeURIComponent(r[0])]=r[1]&&decodeURIComponent(r[1])||"")}return e},t.serializeSearch=function(e){return(0,u.default)(e).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])}).join("&")},t.btoa=function(t){return(t instanceof e?t:new e(t.toString(),"utf-8")).toString("base64")},t.sorters={operationsSorter:{alpha:function(e,t){return e.get("path").localeCompare(t.get("path"))},method:function(e,t){return e.get("method").localeCompare(t.get("method"))}},tagsSorter:{alpha:function(e,t){return e.localeCompare(t)}}},t.buildFormData=function(e){var t=[];for(var n in e){var r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},t.shallowEqualKeys=function(e,t,n){return!!(0,h.default)(n,function(n){return(0,m.default)(e[n],t[n])})};var U=t.createDeepLinkPath=function(e){return"string"==typeof e||e instanceof String?e.trim().replace(/\s/g,"%20"):""};t.escapeDeepLinkPath=function(e){return(0,b.default)(U(e).replace(/%20/g,"_"))},t.getExtensions=function(e){return e.filter(function(e,t){return/^x-/.test(t)})},t.getCommonExtensions=function(e){return e.filter(function(e,t){return/^pattern|maxLength|minLength|maximum|minimum/.test(t)})};function q(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.returnAll,r=void 0!==n&&n,o=t.allowHashes,i=void 0===o||o;if(!l.default.Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");var a=e.get("name"),u=e.get("in"),s=[];return e&&e.hashCode&&u&&a&&i&&s.push(u+"."+a+".hash-"+e.hashCode()),u&&a&&s.push(u+"."+a),s.push(a),r?s:s[0]||""}}).call(t,n(54).Buffer)},function(e,t,n){"use strict";var r=n(34);e.exports=r},function(e,t,n){"use strict";e.exports=function(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r>",i={listOf:function(e){return l(e,"List",r.List.isList)},mapOf:function(e,t){return c(e,t,"Map",r.Map.isMap)},orderedMapOf:function(e,t){return c(e,t,"OrderedMap",r.OrderedMap.isOrderedMap)},setOf:function(e){return l(e,"Set",r.Set.isSet)},orderedSetOf:function(e){return l(e,"OrderedSet",r.OrderedSet.isOrderedSet)},stackOf:function(e){return l(e,"Stack",r.Stack.isStack)},iterableOf:function(e){return l(e,"Iterable",r.Iterable.isIterable)},recordOf:function(e){return u(function(t,n,o,i,u){for(var s=arguments.length,l=Array(s>5?s-5:0),c=5;c6?s-6:0),c=6;c5?l-5:0),f=5;f5?i-5:0),u=5;u key("+c[f]+")"].concat(a));if(d instanceof Error)return d}})).apply(void 0,i);var s})}function f(e){var t=void 0===arguments[1]?"Iterable":arguments[1],n=void 0===arguments[2]?r.Iterable.isIterable:arguments[2];return u(function(r,o,i,u,s){for(var l=arguments.length,c=Array(l>5?l-5:0),f=5;f?@[\]^_`{|}~-])/g;function a(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function u(e){if(e>65535){var t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}var s=/&([a-z#][a-z0-9]{1,31});/gi,l=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,c=n(417);function f(e,t){var n=0;return o(c,t)?c[t]:35===t.charCodeAt(0)&&l.test(t)&&a(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?u(n):e}var p=/[&<>"]/,d=/[&<>"]/g,h={"&":"&","<":"<",">":">",'"':"""};function v(e){return h[e]}t.assign=function(e){return[].slice.call(arguments,1).forEach(function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach(function(n){e[n]=t[n]})}}),e},t.isString=function(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)},t.has=o,t.unescapeMd=function(e){return e.indexOf("\\")<0?e:e.replace(i,"$1")},t.isValidEntityCode=a,t.fromCodePoint=u,t.replaceEntities=function(e){return e.indexOf("&")<0?e:e.replace(s,f)},t.escapeHtml=function(e){return p.test(e)?e.replace(d,v):e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(33),o=n(61),i=n(59),a=n(73),u=n(120),s=function(e,t,n){var l,c,f,p,d=e&s.F,h=e&s.G,v=e&s.S,m=e&s.P,g=e&s.B,y=h?r:v?r[t]||(r[t]={}):(r[t]||{}).prototype,b=h?o:o[t]||(o[t]={}),_=b.prototype||(b.prototype={});for(l in h&&(n=t),n)f=((c=!d&&y&&void 0!==y[l])?y:n)[l],p=g&&c?u(f,r):m&&"function"==typeof f?u(Function.call,f):f,y&&a(y,l,f,e&s.U),b[l]!=f&&i(b,l,p),m&&_[l]!=f&&(_[l]=f)};r.core=o,s.F=1,s.G=2,s.S=4,s.P=8,s.B=16,s.W=32,s.U=64,s.R=128,e.exports=s},function(e,t,n){var r=n(29),o=n(101),i=n(53),a=/"/g,u=function(e,t,n,r){var o=String(i(e)),u="<"+t;return""!==n&&(u+=" "+n+'="'+String(r).replace(a,""")+'"'),u+">"+o+""};e.exports=function(e,t){var n={};n[e]=t(u),r(r.P+r.F*o(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}),"String",n)}},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r,o=n(91),i=(r=o)&&r.__esModule?r:{default:r};e.exports=function(){var e={location:{},history:{},open:function(){},close:function(){},File:function(){}};if("undefined"==typeof window)return e;try{e=window;var t=!0,n=!1,r=void 0;try{for(var o,a=(0,i.default)(["File","Blob","FormData"]);!(t=(o=a.next()).done);t=!0){var u=o.value;u in window&&(e[u]=window[u])}}catch(e){n=!0,r=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw r}}}catch(e){console.error(e)}return e}()},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){"use strict";function r(e){return function(){return e}}var o=function(){};o.thatReturns=r,o.thatReturnsFalse=r(!1),o.thatReturnsTrue=r(!0),o.thatReturnsNull=r(null),o.thatReturnsThis=function(){return this},o.thatReturnsArgument=function(e){return e},e.exports=o},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=i(n(25));t.isOAS3=a,t.isSwagger2=function(e){var t=e.get("swagger");if("string"!=typeof t)return!1;return t.startsWith("2.0")},t.OAS3ComponentWrapFactory=function(e){return function(t,n){return function(i){if(n&&n.specSelectors&&n.specSelectors.specJson){var u=n.specSelectors.specJson();return a(u)?o.default.createElement(e,(0,r.default)({},i,n,{Ori:t})):o.default.createElement(t,i)}return console.warn("OAS3 wrapper: couldn't get spec"),null}}};var o=i(n(0));function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=e.get("openapi");return"string"==typeof t&&(t.startsWith("3.0.")&&t.length>4)}},function(e,t,n){var r=n(28);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var r=n(279),o="object"==typeof self&&self&&self.Object===Object&&self,i=r||o||Function("return this")();e.exports=i},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t,n){"use strict";var r=null;e.exports={debugTool:r}},function(e,t,n){var r=n(36),o=n(239),i=n(158),a=Object.defineProperty;t.f=n(44)?Object.defineProperty:function(e,t,n){if(r(e),t=i(t,!0),r(n),o)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){e.exports={default:n(517),__esModule:!0}},function(e,t,n){e.exports={default:n(518),__esModule:!0}},function(e,t,n){"use strict";var r=n(11),o=n(13),i=n(354),a=n(69),u=n(355),s=n(88),l=n(148),c=n(8),f=[],p=0,d=i.getPooled(),h=!1,v=null;function m(){E.ReactReconcileTransaction&&v||r("123")}var g=[{initialize:function(){this.dirtyComponentsLength=f.length},close:function(){this.dirtyComponentsLength!==f.length?(f.splice(0,this.dirtyComponentsLength),w()):f.length=0}},{initialize:function(){this.callbackQueue.reset()},close:function(){this.callbackQueue.notifyAll()}}];function y(){this.reinitializeTransaction(),this.dirtyComponentsLength=null,this.callbackQueue=i.getPooled(),this.reconcileTransaction=E.ReactReconcileTransaction.getPooled(!0)}function b(e,t){return e._mountOrder-t._mountOrder}function _(e){var t=e.dirtyComponentsLength;t!==f.length&&r("124",t,f.length),f.sort(b),p++;for(var n=0;n + * @license MIT + */ +var r=n(529),o=n(530),i=n(262);function a(){return s.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function u(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function h(e,t){if(s.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function v(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function m(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=s.from(t,r)),s.isBuffer(t))return 0===t.length?-1:g(e,t,n,r,o);if("number"==typeof t)return t&=255,s.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):g(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function g(e,t,n,r,o){var i,a=1,u=e.length,s=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,u/=2,s/=2,n/=2}function l(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(o){var c=-1;for(i=n;iu&&(n=u-s),i=n;i>=0;i--){for(var f=!0,p=0;po&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function S(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function C(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:l>223?3:l>191?2:1;if(o+f<=n)switch(f){case 1:l<128&&(c=l);break;case 2:128==(192&(i=e[o+1]))&&(s=(31&l)<<6|63&i)>127&&(c=s);break;case 3:i=e[o+1],a=e[o+2],128==(192&i)&&128==(192&a)&&(s=(15&l)<<12|(63&i)<<6|63&a)>2047&&(s<55296||s>57343)&&(c=s);break;case 4:i=e[o+1],a=e[o+2],u=e[o+3],128==(192&i)&&128==(192&a)&&128==(192&u)&&(s=(15&l)<<18|(63&i)<<12|(63&a)<<6|63&u)>65535&&s<1114112&&(c=s)}null===c?(c=65533,f=1):c>65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c),o+=f}return function(e){var t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return P(this,t,n);case"utf8":case"utf-8":return C(this,t,n);case"ascii":return A(this,t,n);case"latin1":case"binary":return O(this,t,n);case"base64":return S(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},s.prototype.equals=function(e){if(!s.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===s.compare(this,e)},s.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},s.prototype.compare=function(e,t,n,r,o){if(!s.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,o>>>=0,this===e)return 0;for(var i=o-r,a=n-t,u=Math.min(i,a),l=this.slice(r,o),c=e.slice(t,n),f=0;fo)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return y(this,e,t,n);case"utf8":case"utf-8":return b(this,e,t,n);case"ascii":return _(this,e,t,n);case"latin1":case"binary":return w(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return x(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},s.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function A(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function I(e,t,n,r,o,i){if(!s.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function j(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function N(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function R(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function D(e,t,n,r,i){return i||R(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function L(e,t,n,r,i){return i||R(e,0,n,8),o.write(e,t,n,r,52,8),n+8}s.prototype.slice=function(e,t){var n,r=this.length;if(e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},s.prototype.readUInt8=function(e,t){return t||M(e,1,this.length),this[e]},s.prototype.readUInt16LE=function(e,t){return t||M(e,2,this.length),this[e]|this[e+1]<<8},s.prototype.readUInt16BE=function(e,t){return t||M(e,2,this.length),this[e]<<8|this[e+1]},s.prototype.readUInt32LE=function(e,t){return t||M(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},s.prototype.readUInt32BE=function(e,t){return t||M(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},s.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||M(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},s.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||M(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},s.prototype.readInt8=function(e,t){return t||M(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},s.prototype.readInt16LE=function(e,t){t||M(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},s.prototype.readInt16BE=function(e,t){t||M(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},s.prototype.readInt32LE=function(e,t){return t||M(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},s.prototype.readInt32BE=function(e,t){return t||M(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},s.prototype.readFloatLE=function(e,t){return t||M(e,4,this.length),o.read(this,e,!0,23,4)},s.prototype.readFloatBE=function(e,t){return t||M(e,4,this.length),o.read(this,e,!1,23,4)},s.prototype.readDoubleLE=function(e,t){return t||M(e,8,this.length),o.read(this,e,!0,52,8)},s.prototype.readDoubleBE=function(e,t){return t||M(e,8,this.length),o.read(this,e,!1,52,8)},s.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||I(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},s.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,1,255,0),s.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},s.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):j(this,e,t,!0),t+2},s.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):j(this,e,t,!1),t+2},s.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):N(this,e,t,!0),t+4},s.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):N(this,e,t,!1),t+4},s.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);I(this,e,t,n,o-1,-o)}var i=0,a=1,u=0;for(this[t]=255&e;++i>0)-u&255;return t+n},s.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);I(this,e,t,n,o-1,-o)}var i=n-1,a=1,u=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===u&&0!==this[t+i+1]&&(u=1),this[t+i]=(e/a>>0)-u&255;return t+n},s.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,1,127,-128),s.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},s.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):j(this,e,t,!0),t+2},s.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):j(this,e,t,!1),t+2},s.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,4,2147483647,-2147483648),s.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):N(this,e,t,!0),t+4},s.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||I(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),s.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):N(this,e,t,!1),t+4},s.prototype.writeFloatLE=function(e,t,n){return D(this,e,t,!0,n)},s.prototype.writeFloatBE=function(e,t,n){return D(this,e,t,!1,n)},s.prototype.writeDoubleLE=function(e,t,n){return L(this,e,t,!0,n)},s.prototype.writeDoubleBE=function(e,t,n){return L(this,e,t,!1,n)},s.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!s.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function B(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(t,n(31))},function(e,t,n){var r=n(278);e.exports=function(e){return null==e?"":r(e)}},function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function u(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var s,l=[],c=!1,f=-1;function p(){c&&s&&(c=!1,s.length?l=s.concat(l):f=-1,l.length&&d())}function d(){if(!c){var e=u(p);c=!0;for(var t=l.length;t;){for(s=l,l=[];++f1)for(var n=1;n1?t-1:0),r=1;r2?n-2:0),o=2;o1){for(var h=Array(d),v=0;v1){for(var g=Array(m),y=0;y=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t,n){"use strict";function r(e){return void 0===e||null===e}e.exports.isNothing=r,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:r(e)?[]:[e]},e.exports.repeat=function(e,t){var n,r="";for(n=0;n=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){e.exports=!n(101)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){e.exports={}},function(e,t,n){var r=n(119),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},function(e,t,n){"use strict";e.exports=function(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r0?o(r(e),9007199254740991):0}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){var r=n(60),o=n(460),i=n(461),a=Object.defineProperty;t.f=n(100)?Object.defineProperty:function(e,t,n){if(r(e),t=i(t,!0),r(n),o)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(121);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(466),o=n(53);e.exports=function(e){return r(o(e))}},function(e,t,n){"use strict";var r=n(59),o=n(73),i=n(101),a=n(53),u=n(18);e.exports=function(e,t,n){var s=u(e),l=n(a,s,""[e]),c=l[0],f=l[1];i(function(){var t={};return t[s]=function(){return 7},7!=""[e](t)})&&(o(String.prototype,e,c),r(RegExp.prototype,s,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)}))}},function(e,t,n){var r=n(116)("meta"),o=n(28),i=n(52),a=n(40).f,u=0,s=Object.isExtensible||function(){return!0},l=!n(51)(function(){return s(Object.preventExtensions({}))}),c=function(e){a(e,r,{value:{i:"O"+ ++u,w:{}}})},f=e.exports={KEY:r,NEED:!1,fastKey:function(e,t){if(!o(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!i(e,r)){if(!s(e))return"F";if(!t)return"E";c(e)}return e[r].i},getWeak:function(e,t){if(!i(e,r)){if(!s(e))return!0;if(!t)return!1;c(e)}return e[r].w},onFreeze:function(e){return l&&f.NEED&&s(e)&&!i(e,r)&&c(e),e}}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){"use strict";var r={};e.exports=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CLEAR_BY=t.CLEAR=t.NEW_AUTH_ERR=t.NEW_SPEC_ERR_BATCH=t.NEW_SPEC_ERR=t.NEW_THROWN_ERR_BATCH=t.NEW_THROWN_ERR=void 0,t.newThrownErr=function(e){return{type:a,payload:(0,i.default)(e)}},t.newThrownErrBatch=function(e){return{type:u,payload:e}},t.newSpecErr=function(e){return{type:s,payload:e}},t.newSpecErrBatch=function(e){return{type:l,payload:e}},t.newAuthErr=function(e){return{type:c,payload:e}},t.clear=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{type:f,payload:e}},t.clearBy=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!0};return{type:p,payload:e}};var r,o=n(180),i=(r=o)&&r.__esModule?r:{default:r};var a=t.NEW_THROWN_ERR="err_new_thrown_err",u=t.NEW_THROWN_ERR_BATCH="err_new_thrown_err_batch",s=t.NEW_SPEC_ERR="err_new_spec_err",l=t.NEW_SPEC_ERR_BATCH="err_new_spec_err_batch",c=t.NEW_AUTH_ERR="err_new_auth_err",f=t.CLEAR="err_clear",p=t.CLEAR_BY="err_clear_by"},function(e,t,n){var r=n(62),o=n(47),i="[object Symbol]";e.exports=function(e){return"symbol"==typeof e||o(e)&&r(e)==i}},function(e,t,n){var r=n(63)(Object,"create");e.exports=r},function(e,t,n){var r=n(601),o=n(602),i=n(603),a=n(604),u=n(605);function s(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("in")===t})},t.parametersIncludeType=T,t.contentTypeValues=function(e,t){t=t||[];var n=d(e).getIn(["paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),r=e.getIn(["meta","paths"].concat((0,o.default)(t)),(0,u.fromJS)({})),i=M(e,t),a=n.get("parameters")||new u.List,s=r.get("consumes_value")?r.get("consumes_value"):T(a,"file")?"multipart/form-data":T(a,"formData")?"application/x-www-form-urlencoded":void 0;return(0,u.fromJS)({requestContentType:s,responseContentType:i})},t.currentProducesFor=M,t.producesOptionsFor=function(e,t){t=t||[];var n=d(e),i=n.getIn(["paths"].concat((0,o.default)(t)),null);if(null===i)return;var a=t,u=(0,r.default)(a,1)[0],s=i.get("produces",null),l=n.getIn(["paths",u,"produces"],null),c=n.getIn(["produces"],null);return s||l||c},t.consumesOptionsFor=function(e,t){t=t||[];var n=d(e),i=n.getIn(["paths"].concat((0,o.default)(t)),null);if(null===i)return;var a=t,u=(0,r.default)(a,1)[0],s=i.get("consumes",null),l=n.getIn(["paths",u,"consumes"],null),c=n.getIn(["consumes"],null);return s||l||c};var i=n(58),a=n(9),u=n(7);function s(e){return e&&e.__esModule?e:{default:e}}var l=["get","put","post","delete","options","head","patch","trace"],c=function(e){return e||(0,u.Map)()},f=(t.lastError=(0,i.createSelector)(c,function(e){return e.get("lastError")}),t.url=(0,i.createSelector)(c,function(e){return e.get("url")}),t.specStr=(0,i.createSelector)(c,function(e){return e.get("spec")||""}),t.specSource=(0,i.createSelector)(c,function(e){return e.get("specSource")||"not-editor"}),t.specJson=(0,i.createSelector)(c,function(e){return e.get("json",(0,u.Map)())})),p=(t.specResolved=(0,i.createSelector)(c,function(e){return e.get("resolved",(0,u.Map)())}),t.specResolvedSubtree=function(e,t){return e.getIn(["resolvedSubtrees"].concat((0,o.default)(t)),void 0)},function e(t,n){return u.Map.isMap(t)&&u.Map.isMap(n)?n.get("$$ref")?n:(0,u.OrderedMap)().mergeWith(e,t,n):n}),d=t.specJsonWithResolvedSubtrees=(0,i.createSelector)(c,function(e){return(0,u.OrderedMap)().mergeWith(p,e.get("json"),e.get("resolvedSubtrees"))}),h=t.spec=function(e){return f(e)},v=(t.isOAS3=(0,i.createSelector)(h,function(){return!1}),t.info=(0,i.createSelector)(h,function(e){return j(e&&e.get("info"))})),m=(t.externalDocs=(0,i.createSelector)(h,function(e){return j(e&&e.get("externalDocs"))}),t.version=(0,i.createSelector)(v,function(e){return e&&e.get("version")})),g=(t.semver=(0,i.createSelector)(m,function(e){return/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e).slice(1)}),t.paths=(0,i.createSelector)(d,function(e){return e.get("paths")})),y=t.operations=(0,i.createSelector)(g,function(e){if(!e||e.size<1)return(0,u.List)();var t=(0,u.List)();return e&&e.forEach?(e.forEach(function(e,n){if(!e||!e.forEach)return{};e.forEach(function(e,r){l.indexOf(r)<0||(t=t.push((0,u.fromJS)({path:n,method:r,operation:e,id:r+"-"+n})))})}),t):(0,u.List)()}),b=t.consumes=(0,i.createSelector)(h,function(e){return(0,u.Set)(e.get("consumes"))}),_=t.produces=(0,i.createSelector)(h,function(e){return(0,u.Set)(e.get("produces"))}),w=(t.security=(0,i.createSelector)(h,function(e){return e.get("security",(0,u.List)())}),t.securityDefinitions=(0,i.createSelector)(h,function(e){return e.get("securityDefinitions")}),t.findDefinition=function(e,t){var n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},t.definitions=(0,i.createSelector)(h,function(e){var t=e.get("definitions");return u.Map.isMap(t)?t:(0,u.Map)()}),t.basePath=(0,i.createSelector)(h,function(e){return e.get("basePath")}),t.host=(0,i.createSelector)(h,function(e){return e.get("host")}),t.schemes=(0,i.createSelector)(h,function(e){return e.get("schemes",(0,u.Map)())}),t.operationsWithRootInherited=(0,i.createSelector)(y,b,_,function(e,t,n){return e.map(function(e){return e.update("operation",function(e){if(e){if(!u.Map.isMap(e))return;return e.withMutations(function(e){return e.get("consumes")||e.update("consumes",function(e){return(0,u.Set)(e).merge(t)}),e.get("produces")||e.update("produces",function(e){return(0,u.Set)(e).merge(n)}),e})}return(0,u.Map)()})})})),E=t.tags=(0,i.createSelector)(h,function(e){var t=e.get("tags",(0,u.List)());return u.List.isList(t)?t.filter(function(e){return u.Map.isMap(e)}):(0,u.List)()}),x=t.tagDetails=function(e,t){return(E(e)||(0,u.List)()).filter(u.Map.isMap).find(function(e){return e.get("name")===t},(0,u.Map)())},S=t.operationsWithTags=(0,i.createSelector)(w,E,function(e,t){return e.reduce(function(e,t){var n=(0,u.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,u.List)(),function(e){return e.push(t)}):n.reduce(function(e,n){return e.update(n,(0,u.List)(),function(e){return e.push(t)})},e)},t.reduce(function(e,t){return e.set(t.get("name"),(0,u.List)())},(0,u.OrderedMap)()))}),C=(t.taggedOperations=function(e){return function(t){var n=(0,t.getConfigs)(),r=n.tagsSorter,o=n.operationsSorter;return S(e).sortBy(function(e,t){return t},function(e,t){var n="function"==typeof r?r:a.sorters.tagsSorter[r];return n?n(e,t):null}).map(function(t,n){var r="function"==typeof o?o:a.sorters.operationsSorter[o],i=r?t.sort(r):t;return(0,u.Map)({tagDetails:x(e,n),operations:i})})}},t.responses=(0,i.createSelector)(c,function(e){return e.get("responses",(0,u.Map)())})),k=t.requests=(0,i.createSelector)(c,function(e){return e.get("requests",(0,u.Map)())}),A=t.mutatedRequests=(0,i.createSelector)(c,function(e){return e.get("mutatedRequests",(0,u.Map)())}),O=(t.responseFor=function(e,t,n){return C(e).getIn([t,n],null)},t.requestFor=function(e,t,n){return k(e).getIn([t,n],null)},t.mutatedRequestFor=function(e,t,n){return A(e).getIn([t,n],null)},t.allowTryItOutFor=function(){return!0},t.parameterWithMetaByIdentity=function(e,t,n){var r=d(e).getIn(["paths"].concat((0,o.default)(t),["parameters"]),(0,u.OrderedMap)()),i=e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.OrderedMap)());return r.map(function(e){var t=i.get(n.get("in")+"."+n.get("name")),r=i.get(n.get("in")+"."+n.get("name")+".hash-"+n.hashCode());return(0,u.OrderedMap)().merge(e,t,r)}).find(function(e){return e.get("in")===n.get("in")&&e.get("name")===n.get("name")},(0,u.OrderedMap)())}),P=(t.parameterInclusionSettingFor=function(e,t,n,r){var i=r+"."+n;return e.getIn(["meta","paths"].concat((0,o.default)(t),["parameter_inclusions",i]),!1)},t.parameterWithMeta=function(e,t,n,r){var i=d(e).getIn(["paths"].concat((0,o.default)(t),["parameters"]),(0,u.OrderedMap)()).find(function(e){return e.get("in")===r&&e.get("name")===n},(0,u.OrderedMap)());return O(e,t,i)},t.operationWithMeta=function(e,t,n){var r=d(e).getIn(["paths",t,n],(0,u.OrderedMap)()),o=e.getIn(["meta","paths",t,n],(0,u.OrderedMap)()),i=r.get("parameters",(0,u.List)()).map(function(r){return O(e,[t,n],r)});return(0,u.OrderedMap)().merge(r,o).set("parameters",i)});t.hasHost=(0,i.createSelector)(h,function(e){var t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]});function T(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(u.List.isList(e))return e.some(function(e){return u.Map.isMap(e)&&e.get("type")===t})}function M(e,t){t=t||[];var n=d(e).getIn(["paths"].concat((0,o.default)(t)),null);if(null!==n){var r=e.getIn(["meta","paths"].concat((0,o.default)(t),["produces_value"]),null),i=n.getIn(["produces",0],null);return r||i||"application/json"}}var I=t.operationScheme=function(e,t,n){var r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=Array.isArray(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""};t.canExecuteScheme=function(e,t,n){return["http","https"].indexOf(I(e,t,n))>-1},t.validateBeforeExecute=function(e,t){t=t||[];var n=!0;return e.getIn(["meta","paths"].concat((0,o.default)(t),["parameters"]),(0,u.fromJS)([])).forEach(function(e){var t=e.get("errors");t&&t.count()&&(n=!1)}),n};function j(e){return u.Map.isMap(e)?e:new u.Map}},function(e,t,n){var r=n(49),o=n(329),i=n(330),a=n(36),u=n(115),s=n(165),l={},c={};(t=e.exports=function(e,t,n,f,p){var d,h,v,m,g=p?function(){return e}:s(e),y=r(n,f,t?2:1),b=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(i(g)){for(d=u(e.length);d>b;b++)if((m=t?y(a(h=e[b])[0],h[1]):y(e[b]))===l||m===c)return m}else for(v=g.call(e);!(h=v.next()).done;)if((m=o(v,y,h.value,t))===l||m===c)return m}).BREAK=l,t.RETURN=c},function(e,t,n){"use strict";var r=n(86);e.exports=r.DEFAULT=new r({include:[n(108)],explicit:[n(758),n(759),n(760)]})},function(e,t,n){var r=n(344),o=n(105),i=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var a=e[t];i.call(e,t)&&o(a,n)&&(void 0!==n||t in e)||r(e,t,n)}},function(e,t,n){"use strict";var r=n(11),o=(n(8),{}),i={reinitializeTransaction:function(){this.transactionWrappers=this.getTransactionWrappers(),this.wrapperInitData?this.wrapperInitData.length=0:this.wrapperInitData=[],this._isInTransaction=!1},_isInTransaction:!1,getTransactionWrappers:null,isInTransaction:function(){return!!this._isInTransaction},perform:function(e,t,n,o,i,a,u,s){var l,c;this.isInTransaction()&&r("27");try{this._isInTransaction=!0,l=!0,this.initializeAll(0),c=e.call(t,n,o,i,a,u,s),l=!1}finally{try{if(l)try{this.closeAll(0)}catch(e){}else this.closeAll(0)}finally{this._isInTransaction=!1}}return c},initializeAll:function(e){for(var t=this.transactionWrappers,n=e;n]/,s=n(219)(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{(r=r||document.createElement("div")).innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var l=document.createElement("div");l.innerHTML=" ",""===l.innerHTML&&(s=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&u.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),l=null}e.exports=s},function(e,t,n){"use strict";var r=/["'&<>]/;e.exports=function(e){return"boolean"==typeof e||"number"==typeof e?""+e:function(e){var t,n=""+e,o=r.exec(n);if(!o)return n;var i="",a=0,u=0;for(a=o.index;adocument.F=Object<\/script>"),e.close(),s=e.F;r--;)delete s.prototype[i[r]];return s()};e.exports=Object.create||function(e,t){var n;return null!==e?(u.prototype=r(e),n=new u,u.prototype=null,n[a]=e):n=s(),void 0===t?n:o(n,t)}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(163)("keys"),o=n(116);e.exports=function(e){return r[e]||(r[e]=o(e))}},function(e,t,n){var r=n(21),o=r["__core-js_shared__"]||(r["__core-js_shared__"]={});e.exports=function(e){return o[e]||(o[e]={})}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(166),o=n(19)("iterator"),i=n(70);e.exports=n(15).getIteratorMethod=function(e){if(void 0!=e)return e[o]||e["@@iterator"]||i[r(e)]}},function(e,t,n){var r=n(93),o=n(19)("toStringTag"),i="Arguments"==r(function(){return arguments}());e.exports=function(e){var t,n,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?n:i?r(t):"Object"==(a=r(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t,n){var r=n(99),o=n(18)("toStringTag"),i="Arguments"==r(function(){return arguments}());e.exports=function(e){var t,n,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?n:i?r(t):"Object"==(a=r(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){var r=n(74),o=n(33).document,i=r(o)&&r(o.createElement);e.exports=function(e){return i?o.createElement(e):{}}},function(e,t,n){var r=n(243)("keys"),o=n(168);e.exports=function(e){return r[e]||(r[e]=o(e))}},function(e,t,n){var r=n(117).f,o=n(118),i=n(18)("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},function(e,t,n){"use strict";var r=n(121);e.exports.f=function(e){return new function(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)}(e)}},function(e,t,n){var r=n(257),o=n(53);e.exports=function(e,t,n){if(r(t))throw TypeError("String#"+n+" doesn't accept regex!");return String(o(e))}},function(e,t,n){var r=n(18)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,!"/./"[e](t)}catch(e){}}return!0}},function(e,t,n){t.f=n(19)},function(e,t,n){var r=n(21),o=n(15),i=n(114),a=n(175),u=n(40).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=i?{}:r.Symbol||{});"_"==e.charAt(0)||e in t||u(t,e,{value:a.f(e)})}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t){},function(e,t,n){"use strict";(function(t){ +/*! + * @description Recursive object extending + * @author Viacheslav Lotsmanov + * @license MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2018 Viacheslav Lotsmanov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +function n(e){return e instanceof t||e instanceof Date||e instanceof RegExp}function r(e){if(e instanceof t){var n=t.alloc?t.alloc(e.length):new t(e.length);return e.copy(n),n}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function o(e,t){return"__proto__"===t?void 0:e[t]}var i=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,a=arguments[0];return Array.prototype.slice.call(arguments,1).forEach(function(u){"object"!=typeof u||null===u||Array.isArray(u)||Object.keys(u).forEach(function(s){return t=o(a,s),(e=o(u,s))===a?void 0:"object"!=typeof e||null===e?void(a[s]=e):Array.isArray(e)?void(a[s]=function e(t){var o=[];return t.forEach(function(t,a){"object"==typeof t&&null!==t?Array.isArray(t)?o[a]=e(t):n(t)?o[a]=r(t):o[a]=i({},t):o[a]=t}),o}(e)):n(e)?void(a[s]=r(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(a[s]=i({},e)):void(a[s]=i(t,e))})}),a}}).call(t,n(54).Buffer)},function(e,t,n){"use strict";e.exports=function(e){return"object"==typeof e?function e(t,n){var r;r=Array.isArray(t)?[]:{};n.push(t);Object.keys(t).forEach(function(o){var i=t[o];"function"!=typeof i&&(i&&"object"==typeof i?-1!==n.indexOf(t[o])?r[o]="[Circular]":r[o]=e(t[o],n.slice(0)):r[o]=i)});"string"==typeof t.name&&(r.name=t.name);"string"==typeof t.message&&(r.message=t.message);"string"==typeof t.stack&&(r.stack=t.stack);return r}(e,[]):"function"==typeof e?"[Function: "+(e.name||"anonymous")+"]":e}},function(e,t,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function o(e){return null===e?"null":void 0===e?"undefined":"object"===(void 0===e?"undefined":r(e))?Array.isArray(e)?"array":"object":void 0===e?"undefined":r(e)}function i(e){return"object"===o(e)?u(e):"array"===o(e)?a(e):e}function a(e){return e.map(i)}function u(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=i(e[n]));return t}function s(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n={arrayBehaviour:(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).arrayBehaviour||"replace"},r=t.map(function(e){return e||{}}),i=e||{},l=0;l1?t-1:0),r=1;r-1&&e%1==0&&e<=n}},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){(function(e){var r=n(279),o="object"==typeof t&&t&&!t.nodeType&&t,i=o&&"object"==typeof e&&e&&!e.nodeType&&e,a=i&&i.exports===o&&r.process,u=function(){try{var e=i&&i.require&&i.require("util").types;return e||a&&a.binding&&a.binding("util")}catch(e){}}();e.exports=u}).call(t,n(134)(e))},function(e,t,n){var r=n(24),o=n(128),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,a=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||a.test(e)||!i.test(e)||null!=t&&e in Object(t)}},function(e,t){e.exports=function(e){return e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.memoizedSampleFromSchema=t.memoizedCreateXMLExample=t.sampleXmlFromSchema=t.inferSchema=t.sampleFromSchema=void 0,t.createXMLExample=p;var r=n(9),o=u(n(657)),i=u(n(670)),a=u(n(181));function u(e){return e&&e.__esModule?e:{default:e}}var s={string:function(){return"string"},string_email:function(){return"user@example.com"},"string_date-time":function(){return(new Date).toISOString()},string_date:function(){return(new Date).toISOString().substring(0,10)},string_uuid:function(){return"3fa85f64-5717-4562-b3fc-2c963f66afa6"},string_hostname:function(){return"example.com"},string_ipv4:function(){return"198.51.100.42"},string_ipv6:function(){return"2001:0db8:5b96:0000:0000:426f:8e17:642a"},number:function(){return 0},number_float:function(){return 0},integer:function(){return 0},boolean:function(e){return"boolean"!=typeof e.default||e.default}},l=function(e){var t=e=(0,r.objectify)(e),n=t.type,o=t.format,i=s[n+"_"+o]||s[n];return(0,r.isFunc)(i)?i(e):"Unknown Type: "+e.type},c=t.sampleFromSchema=function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=(0,r.objectify)(t),i=o.type,a=o.example,u=o.properties,s=o.additionalProperties,c=o.items,f=n.includeReadOnly,p=n.includeWriteOnly;if(void 0!==a)return(0,r.deeplyStripKey)(a,"$$ref",function(e){return"string"==typeof e&&e.indexOf("#")>-1});if(!i)if(u)i="object";else{if(!c)return;i="array"}if("object"===i){var d=(0,r.objectify)(u),h={};for(var v in d)d[v]&&d[v].deprecated||d[v]&&d[v].readOnly&&!f||d[v]&&d[v].writeOnly&&!p||(h[v]=e(d[v],n));if(!0===s)h.additionalProp1={};else if(s)for(var m=(0,r.objectify)(s),g=e(m,n),y=1;y<4;y++)h["additionalProp"+y]=g;return h}return"array"===i?Array.isArray(c.anyOf)?c.anyOf.map(function(t){return e(t,n)}):Array.isArray(c.oneOf)?c.oneOf.map(function(t){return e(t,n)}):[e(c,n)]:t.enum?t.default?t.default:(0,r.normalizeArray)(t.enum)[0]:"file"!==i?l(t):void 0},f=(t.inferSchema=function(e){return e.schema&&(e=e.schema),e.properties&&(e.type="object"),e},t.sampleXmlFromSchema=function e(t){var n,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=(0,a.default)({},(0,r.objectify)(t)),u=i.type,s=i.properties,c=i.additionalProperties,f=i.items,p=i.example,d=o.includeReadOnly,h=o.includeWriteOnly,v=i.default,m={},g={},y=t.xml,b=y.name,_=y.prefix,w=y.namespace,E=i.enum,x=void 0;if(!u)if(s||c)u="object";else{if(!f)return;u="array"}(b=b||"notagname",n=(_?_+":":"")+b,w)&&(g[_?"xmlns:"+_:"xmlns"]=w);if("array"===u&&f){if(f.xml=f.xml||y||{},f.xml.name=f.xml.name||y.name,y.wrapped)return m[n]=[],Array.isArray(p)?p.forEach(function(t){f.example=t,m[n].push(e(f,o))}):Array.isArray(v)?v.forEach(function(t){f.default=t,m[n].push(e(f,o))}):m[n]=[e(f,o)],g&&m[n].push({_attr:g}),m;var S=[];return Array.isArray(p)?(p.forEach(function(t){f.example=t,S.push(e(f,o))}),S):Array.isArray(v)?(v.forEach(function(t){f.default=t,S.push(e(f,o))}),S):e(f,o)}if("object"===u){var C=(0,r.objectify)(s);for(var k in m[n]=[],p=p||{},C)if(C.hasOwnProperty(k)&&(!C[k].readOnly||d)&&(!C[k].writeOnly||h))if(C[k].xml=C[k].xml||{},C[k].xml.attribute){var A=Array.isArray(C[k].enum)&&C[k].enum[0],O=C[k].example,P=C[k].default;g[C[k].xml.name||k]=void 0!==O&&O||void 0!==p[k]&&p[k]||void 0!==P&&P||A||l(C[k])}else{C[k].xml.name=C[k].xml.name||k,void 0===C[k].example&&void 0!==p[k]&&(C[k].example=p[k]);var T=e(C[k]);Array.isArray(T)?m[n]=m[n].concat(T):m[n].push(T)}return!0===c?m[n].push({additionalProp:"Anything can be here"}):c&&m[n].push({additionalProp:l(c)}),g&&m[n].push({_attr:g}),m}return x=void 0!==p?p:void 0!==v?v:Array.isArray(E)?E[0]:l(t),m[n]=g?[{_attr:g},x]:x,m});function p(e,t){var n=f(e,t);if(n)return(0,o.default)(n,{declaration:!0,indent:"\t"})}t.memoizedCreateXMLExample=(0,i.default)(p),t.memoizedSampleFromSchema=(0,i.default)(c)},function(e,t){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function o(e){return"object"==typeof e&&null!==e}function i(e){return void 0===e}e.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,a,u,s,l;if(this._events||(this._events={}),"error"===e&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if((t=arguments[1])instanceof Error)throw t;var c=new Error('Uncaught, unspecified "error" event. ('+t+")");throw c.context=t,c}if(i(n=this._events[e]))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:u=Array.prototype.slice.call(arguments,1),n.apply(this,u)}else if(o(n))for(u=Array.prototype.slice.call(arguments,1),a=(l=n.slice()).length,s=0;s0&&this._events[e].length>a&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){if(!r(t))throw TypeError("listener must be a function");var n=!1;function o(){this.removeListener(e,o),n||(n=!0,t.apply(this,arguments))}return o.listener=t,this.on(e,o),this},n.prototype.removeListener=function(e,t){var n,i,a,u;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(a=(n=this._events[e]).length,i=-1,n===t||r(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(o(n)){for(u=a;u-- >0;)if(n[u]===t||n[u].listener&&n[u].listener===t){i=u;break}if(i<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(r(n=this._events[e]))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){return this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(r(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}},function(e,t,n){(t=e.exports=n(306)).Stream=t,t.Readable=t,t.Writable=n(197),t.Duplex=n(65),t.Transform=n(311),t.PassThrough=n(665)},function(e,t,n){"use strict";(function(t,r,o){var i=n(140);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=y;var u,s=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;y.WritableState=g;var l=n(106);l.inherits=n(81);var c={deprecate:n(664)},f=n(307),p=n(141).Buffer,d=o.Uint8Array||function(){};var h,v=n(308);function m(){}function g(e,t){u=u||n(65),e=e||{};var r=t instanceof u;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,l=e.writableHighWaterMark,c=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(l||0===l)?l:c,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var f=!1===e.decodeStrings;this.decodeStrings=!f,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(S,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),S(e,t))}(e,n,r,t,o);else{var a=E(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||w(e,n),r?s(_,e,n,a,o):_(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function y(e){if(u=u||n(65),!(h.call(y,this)||this instanceof u))return new y(e);this._writableState=new g(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),f.call(this)}function b(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function _(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),S(e,t)}function w(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var u=0,s=!0;n;)o[u]=n,n.isBuf||(s=!1),n=n.next,u+=1;o.allBuffers=s,b(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var l=n.chunk,c=n.encoding,f=n.callback;if(b(e,t,!1,t.objectMode?1:l.length,l,c,f),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function E(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function x(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),S(e,t)})}function S(e,t){var n=E(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(x,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}l.inherits(y,f),g.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(g.prototype,"buffer",{get:c.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(h=Function.prototype[Symbol.hasInstance],Object.defineProperty(y,Symbol.hasInstance,{value:function(e){return!!h.call(this,e)||this===y&&(e&&e._writableState instanceof g)}})):h=function(e){return e instanceof this},y.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},y.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,u=!o.objectMode&&(r=e,p.isBuffer(r)||r instanceof d);return u&&!p.isBuffer(e)&&(e=function(e){return p.from(e)}(e)),"function"==typeof t&&(n=t,t=null),u?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=m),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(u||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=p.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var u=t.objectMode?1:r.length;t.length+=u;var s=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(y.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),y.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},y.prototype._writev=null,y.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!==e&&void 0!==e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,S(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(y.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),y.prototype.destroy=v.destroy,y.prototype._undestroy=v.undestroy,y.prototype._destroy=function(e,t){this.end(),t(e)}}).call(t,n(56),n(309).setImmediate,n(31))},function(e,t,n){"use strict";e.exports=function(e){return"function"==typeof e}},function(e,t,n){"use strict";e.exports=n(691)()?Array.from:n(692)},function(e,t,n){"use strict";var r=n(705),o=n(67),i=n(82),a=Array.prototype.indexOf,u=Object.prototype.hasOwnProperty,s=Math.abs,l=Math.floor;e.exports=function(e){var t,n,c,f;if(!r(e))return a.apply(this,arguments);for(n=o(i(this).length),c=arguments[1],t=c=isNaN(c)?0:c>=0?l(c):o(this.length)-l(s(c));t1&&void 0!==arguments[1])||arguments[1];return e=(0,r.normalizeArray)(e),{type:u,payload:{thing:e,shown:t}}},t.changeMode=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.normalizeArray)(e),{type:a,payload:{thing:e,mode:t}}};var r=n(9),o=t.UPDATE_LAYOUT="layout_update_layout",i=t.UPDATE_FILTER="layout_update_filter",a=t.UPDATE_MODE="layout_update_mode",u=t.SHOW="layout_show"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.execute=t.executeRequest=t.logRequest=t.setMutatedRequest=t.setRequest=t.setResponse=t.updateEmptyParamInclusion=t.validateParams=t.invalidateResolvedSubtreeCache=t.updateResolvedSubtree=t.requestResolvedSubtree=t.resolveSpec=t.parseToJson=t.SET_SCHEME=t.UPDATE_RESOLVED_SUBTREE=t.UPDATE_RESOLVED=t.UPDATE_OPERATION_META_VALUE=t.CLEAR_VALIDATE_PARAMS=t.CLEAR_REQUEST=t.CLEAR_RESPONSE=t.LOG_REQUEST=t.SET_MUTATED_REQUEST=t.SET_REQUEST=t.SET_RESPONSE=t.VALIDATE_PARAMS=t.UPDATE_EMPTY_PARAM_INCLUSION=t.UPDATE_PARAM=t.UPDATE_JSON=t.UPDATE_URL=t.UPDATE_SPEC=void 0;var r=b(n(25)),o=b(n(84)),i=b(n(23)),a=b(n(42)),u=b(n(204)),s=b(n(338)),l=b(n(339)),c=b(n(45));t.updateSpec=function(e){var t=L(e).replace(/\t/g," ");if("string"==typeof e)return{type:_,payload:t}},t.updateResolved=function(e){return{type:N,payload:e}},t.updateUrl=function(e){return{type:w,payload:e}},t.updateJsonSpec=function(e){return{type:E,payload:e}},t.changeParam=function(e,t,n,r,o){return{type:x,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}},t.changeParamByIdentity=function(e,t,n,r){return{type:x,payload:{path:e,param:t,value:n,isXml:r}}},t.clearValidateParams=function(e){return{type:I,payload:{pathMethod:e}}},t.changeConsumesValue=function(e,t){return{type:j,payload:{path:e,value:t,key:"consumes_value"}}},t.changeProducesValue=function(e,t){return{type:j,payload:{path:e,value:t,key:"produces_value"}}},t.clearResponse=function(e,t){return{type:T,payload:{path:e,method:t}}},t.clearRequest=function(e,t){return{type:M,payload:{path:e,method:t}}},t.setScheme=function(e,t,n){return{type:D,payload:{scheme:e,path:t,method:n}}};var f=b(n(208)),p=n(7),d=b(n(210)),h=b(n(180)),v=b(n(342)),m=b(n(764)),g=b(n(766)),y=n(9);function b(e){return e&&e.__esModule?e:{default:e}}var _=t.UPDATE_SPEC="spec_update_spec",w=t.UPDATE_URL="spec_update_url",E=t.UPDATE_JSON="spec_update_json",x=t.UPDATE_PARAM="spec_update_param",S=t.UPDATE_EMPTY_PARAM_INCLUSION="spec_update_empty_param_inclusion",C=t.VALIDATE_PARAMS="spec_validate_param",k=t.SET_RESPONSE="spec_set_response",A=t.SET_REQUEST="spec_set_request",O=t.SET_MUTATED_REQUEST="spec_set_mutated_request",P=t.LOG_REQUEST="spec_log_request",T=t.CLEAR_RESPONSE="spec_clear_response",M=t.CLEAR_REQUEST="spec_clear_request",I=t.CLEAR_VALIDATE_PARAMS="spec_clear_validate_param",j=t.UPDATE_OPERATION_META_VALUE="spec_update_operation_meta_value",N=t.UPDATE_RESOLVED="spec_update_resolved",R=t.UPDATE_RESOLVED_SUBTREE="spec_update_resolved_subtree",D=t.SET_SCHEME="set_scheme",L=function(e){return(0,v.default)(e)?e:""};t.parseToJson=function(e){return function(t){var n=t.specActions,r=t.specSelectors,o=t.errActions,i=r.specStr,a=null;try{e=e||i(),o.clear({source:"parser"}),a=f.default.safeLoad(e)}catch(e){return console.error(e),o.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return a&&"object"===(void 0===a?"undefined":(0,c.default)(a))?n.updateJsonSpec(a):{}}};var U=!1,q=(t.resolveSpec=function(e,t){return function(n){var r=n.specActions,o=n.specSelectors,i=n.errActions,a=n.fn,u=a.fetch,s=a.resolve,l=a.AST,c=void 0===l?{}:l,f=n.getConfigs;U||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),U=!0);var p=f(),d=p.modelPropertyMacro,h=p.parameterMacro,v=p.requestInterceptor,m=p.responseInterceptor;void 0===e&&(e=o.specJson()),void 0===t&&(t=o.url());var g=c.getLineNumberForPath?c.getLineNumberForPath:function(){},y=o.specStr();return s({fetch:u,spec:e,baseDoc:t,modelPropertyMacro:d,parameterMacro:h,requestInterceptor:v,responseInterceptor:m}).then(function(e){var t=e.spec,n=e.errors;if(i.clear({type:"thrown"}),Array.isArray(n)&&n.length>0){var o=n.map(function(e){return console.error(e),e.line=e.fullPath?g(y,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e});i.newThrownErrBatch(o)}return r.updateResolved(t)})}},[]),F=(0,m.default)((0,l.default)(s.default.mark(function e(){var t,n,r,o,i,a,c,f,d,h,v,m,y,b,_,w,E;return s.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(t=q.system){e.next=4;break}return console.error("debResolveSubtrees: don't have a system to operate on, aborting."),e.abrupt("return");case 4:if(n=t.errActions,r=t.errSelectors,o=t.fn,i=o.resolveSubtree,a=o.AST,c=void 0===a?{}:a,f=t.specSelectors,d=t.specActions,i){e.next=8;break}return console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing."),e.abrupt("return");case 8:return h=c.getLineNumberForPath?c.getLineNumberForPath:function(){},v=f.specStr(),m=t.getConfigs(),y=m.modelPropertyMacro,b=m.parameterMacro,_=m.requestInterceptor,w=m.responseInterceptor,e.prev=11,e.next=14,q.reduce(function(){var e=(0,l.default)(s.default.mark(function e(t,o){var a,u,l,c,p,d,m;return s.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t;case 2:return a=e.sent,u=a.resultMap,l=a.specWithCurrentSubtrees,e.next=7,i(l,o,{baseDoc:f.url(),modelPropertyMacro:y,parameterMacro:b,requestInterceptor:_,responseInterceptor:w});case 7:return c=e.sent,p=c.errors,d=c.spec,r.allErrors().size&&n.clearBy(function(e){return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!e.get("fullPath").every(function(e,t){return e===o[t]||void 0===o[t]})}),Array.isArray(p)&&p.length>0&&(m=p.map(function(e){return e.line=e.fullPath?h(v,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",Object.defineProperty(e,"message",{enumerable:!0,value:e.message}),e}),n.newThrownErrBatch(m)),(0,g.default)(u,o,d),(0,g.default)(l,o,d),e.abrupt("return",{resultMap:u,specWithCurrentSubtrees:l});case 15:case"end":return e.stop()}},e,void 0)}));return function(t,n){return e.apply(this,arguments)}}(),u.default.resolve({resultMap:(f.specResolvedSubtree([])||(0,p.Map)()).toJS(),specWithCurrentSubtrees:f.specJson().toJS()}));case 14:E=e.sent,delete q.system,q=[],e.next=22;break;case 19:e.prev=19,e.t0=e.catch(11),console.error(e.t0);case 22:d.updateResolvedSubtree([],E.resultMap);case 23:case"end":return e.stop()}},e,void 0,[[11,19]])})),35);t.requestResolvedSubtree=function(e){return function(t){q.map(function(e){return e.join("@@")}).indexOf(e.join("@@"))>-1||(q.push(e),q.system=t,F())}};t.updateResolvedSubtree=function(e,t){return{type:R,payload:{path:e,value:t}}},t.invalidateResolvedSubtreeCache=function(){return{type:R,payload:{path:[],value:(0,p.Map)()}}},t.validateParams=function(e,t){return{type:C,payload:{pathMethod:e,isOAS3:t}}},t.updateEmptyParamInclusion=function(e,t,n,r){return{type:S,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}}};t.setResponse=function(e,t,n){return{payload:{path:e,method:t,res:n},type:k}},t.setRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:A}},t.setMutatedRequest=function(e,t,n){return{payload:{path:e,method:t,req:n},type:O}},t.logRequest=function(e){return{payload:e,type:P}},t.executeRequest=function(e){return function(t){var n=t.fn,r=t.specActions,o=t.specSelectors,u=t.getConfigs,s=t.oas3Selectors,l=e.pathName,c=e.method,f=e.operation,p=u(),v=p.requestInterceptor,m=p.responseInterceptor,g=f.toJS();if(f&&f.get("parameters")&&f.get("parameters").filter(function(e){return e&&!0===e.get("allowEmptyValue")}).forEach(function(t){if(o.parameterInclusionSettingFor([l,c],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};var n=(0,y.paramToValue)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}),e.contextUrl=(0,d.default)(o.url()).toString(),g&&g.operationId?e.operationId=g.operationId:g&&l&&c&&(e.operationId=n.opId(g,l,c)),o.isOAS3()){var b=l+":"+c;e.server=s.selectedServer(b)||s.selectedServer();var _=s.serverVariables({server:e.server,namespace:b}).toJS(),w=s.serverVariables({server:e.server}).toJS();e.serverVariables=(0,a.default)(_).length?_:w,e.requestContentType=s.requestContentType(l,c),e.responseContentType=s.responseContentType(l,c)||"*/*";var E=s.requestBodyValue(l,c);(0,y.isJSONObject)(E)?e.requestBody=JSON.parse(E):E&&E.toJS?e.requestBody=E.toJS():e.requestBody=E}var x=(0,i.default)({},e);x=n.buildRequest(x),r.setRequest(e.pathName,e.method,x);e.requestInterceptor=function(t){var n=v.apply(this,[t]),o=(0,i.default)({},n);return r.setMutatedRequest(e.pathName,e.method,o),n},e.responseInterceptor=m;var S=Date.now();return n.execute(e).then(function(t){t.duration=Date.now()-S,r.setResponse(e.pathName,e.method,t)}).catch(function(t){return r.setResponse(e.pathName,e.method,{error:!0,err:(0,h.default)(t)})})}};t.execute=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.path,n=e.method,i=(0,o.default)(e,["path","method"]);return function(e){var o=e.fn.fetch,a=e.specSelectors,u=e.specActions,s=a.specJsonWithResolvedSubtrees().toJS(),l=a.operationScheme(t,n),c=a.contentTypeValues([t,n]).toJS(),f=c.requestContentType,p=c.responseContentType,d=/xml/i.test(f),h=a.parameterValues([t,n],d).toJS();return u.executeRequest((0,r.default)({},i,{fetch:o,spec:s,pathName:t,method:n,parameters:h,requestContentType:f,scheme:l,responseContentType:p}))}}},function(e,t,n){e.exports={default:n(733),__esModule:!0}},function(e,t){e.exports=function(e,t,n,r){if(!(e instanceof t)||void 0!==r&&r in e)throw TypeError(n+": incorrect invocation!");return e}},function(e,t,n){"use strict";var r=n(94);e.exports.f=function(e){return new function(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)}(e)}},function(e,t,n){var r=n(50);e.exports=function(e,t,n){for(var o in t)n&&e[o]?e[o]=t[o]:r(e,o,t[o]);return e}},function(e,t,n){"use strict";var r=n(742);e.exports=r},function(e,t,n){"use strict";var r=n(86);e.exports=new r({explicit:[n(745),n(746),n(747)]})},function(e,t,n){"use strict";(function(t){var r=n(762),o=n(763),i=/^([a-z][a-z0-9.+-]*:)?(\/\/)?([\S\s]*)/i,a=/^[A-Za-z][A-Za-z0-9+-.]*:\/\//,u=[["#","hash"],["?","query"],function(e){return e.replace("\\","/")},["/","pathname"],["@","auth",1],[NaN,"host",void 0,1,1],[/:(\d+)$/,"port",void 0,1],[NaN,"hostname",void 0,1,1]],s={hash:1,query:1};function l(e){var n,r=t&&t.location||{},o={},i=typeof(e=e||r);if("blob:"===e.protocol)o=new f(unescape(e.pathname),{});else if("string"===i)for(n in o=new f(e,{}),s)delete o[n];else if("object"===i){for(n in e)n in s||(o[n]=e[n]);void 0===o.slashes&&(o.slashes=a.test(e.href))}return o}function c(e){var t=i.exec(e);return{protocol:t[1]?t[1].toLowerCase():"",slashes:!!t[2],rest:t[3]}}function f(e,t,n){if(!(this instanceof f))return new f(e,t,n);var i,a,s,p,d,h,v=u.slice(),m=typeof t,g=this,y=0;for("object"!==m&&"string"!==m&&(n=t,t=null),n&&"function"!=typeof n&&(n=o.parse),t=l(t),i=!(a=c(e||"")).protocol&&!a.slashes,g.slashes=a.slashes||i&&t.slashes,g.protocol=a.protocol||t.protocol||"",e=a.rest,a.slashes||(v[3]=[/(.*)/,"pathname"]);y-1||r("96",e),!l.plugins[n]){t.extractEvents||r("97",e),l.plugins[n]=t;var a=t.eventTypes;for(var s in a)u(a[s],t,s)||r("98",s,e)}}}function u(e,t,n){l.eventNameDispatchConfigs.hasOwnProperty(n)&&r("99",n),l.eventNameDispatchConfigs[n]=e;var o=e.phasedRegistrationNames;if(o){for(var i in o){if(o.hasOwnProperty(i))s(o[i],t,n)}return!0}return!!e.registrationName&&(s(e.registrationName,t,n),!0)}function s(e,t,n){l.registrationNameModules[e]&&r("100",e),l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){o&&r("101"),o=Array.prototype.slice.call(e),a()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];i.hasOwnProperty(n)&&i[n]===o||(i[n]&&r("102",n),i[n]=o,t=!0)}t&&a()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=l.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){for(var e in o=null,i)i.hasOwnProperty(e)&&delete i[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var a in r)r.hasOwnProperty(a)&&delete r[a]}};e.exports=l},function(e,t,n){"use strict";var r,o,i=n(11),a=n(213);n(8),n(10);function u(e,t,n,r){var o=e.type||"unknown-event";e.currentTarget=s.getNodeFromInstance(r),t?a.invokeGuardedCallbackWithCatch(o,n,e):a.invokeGuardedCallback(o,n,e),e.currentTarget=null}var s={isEndish:function(e){return"topMouseUp"===e||"topTouchEnd"===e||"topTouchCancel"===e},isMoveish:function(e){return"topMouseMove"===e||"topTouchMove"===e},isStartish:function(e){return"topMouseDown"===e||"topTouchStart"===e},executeDirectDispatch:function(e){var t=e._dispatchListeners,n=e._dispatchInstances;Array.isArray(t)&&i("103"),e.currentTarget=t?s.getNodeFromInstance(n):null;var r=t?t(e):null;return e.currentTarget=null,e._dispatchListeners=null,e._dispatchInstances=null,r},executeDispatchesInOrder:function(e,t){var n=e._dispatchListeners,r=e._dispatchInstances;if(Array.isArray(n))for(var o=0;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function s(e,t){var n=o.get(e);return n||null}var l={isMounted:function(e){var t=o.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){l.validateCallback(t,n);var r=s(e);if(!r)return null;r._pendingCallbacks?r._pendingCallbacks.push(t):r._pendingCallbacks=[t],a(r)},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],a(e)},enqueueForceUpdate:function(e){var t=s(e);t&&(t._pendingForceUpdate=!0,a(t))},enqueueReplaceState:function(e,t,n){var r=s(e);r&&(r._pendingStateQueue=[t],r._pendingReplaceState=!0,void 0!==n&&null!==n&&(l.validateCallback(n,"replaceState"),r._pendingCallbacks?r._pendingCallbacks.push(n):r._pendingCallbacks=[n]),a(r))},enqueueSetState:function(e,t){var n=s(e);n&&((n._pendingStateQueue||(n._pendingStateQueue=[])).push(t),a(n))},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,a(e)},validateCallback:function(e,t){e&&"function"!=typeof e&&r("122",t,u(e))}};e.exports=l},function(e,t,n){"use strict";n(13);var r=n(34),o=(n(10),r);e.exports=o},function(e,t,n){"use strict";e.exports=function(e){var t,n=e.keyCode;return"charCode"in e?0===(t=e.charCode)&&13===n&&(t=13):t=n,t>=32||13===t?t:0}},function(e,t,n){var r=n(62),o=n(229),i=n(47),a="[object Object]",u=Function.prototype,s=Object.prototype,l=u.toString,c=s.hasOwnProperty,f=l.call(Object);e.exports=function(e){if(!i(e)||r(e)!=a)return!1;var t=o(e);if(null===t)return!0;var n=c.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==f}},function(e,t,n){var r=n(298)(Object.getPrototypeOf,Object);e.exports=r},function(e,t,n){var r=n(292);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},function(e,t){var n=this&&this.__extends||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);function r(){this.constructor=e}e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)},r=Object.prototype.hasOwnProperty; +/*! + * https://github.com/Starcounter-Jack/JSON-Patch + * (c) 2017 Joachim Wester + * MIT license + */function o(e,t){return r.call(e,t)}function i(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0},t.escapePathComponent=a,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var n=u(e,t);if(""===n)throw new Error("Object not found in root");return"/"+n},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var n=0,r=t.length;nw;w++)if((p||w in y)&&(m=b(v=y[w],w,g),e))if(n)E[w]=m;else if(m)switch(e){case 3:return!0;case 5:return v;case 6:return w;case 2:E.push(v)}else if(c)return!1;return f?-1:l||c?c:E}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.authorizeRequest=t.authorizeAccessCodeWithBasicAuthentication=t.authorizeAccessCodeWithFormParams=t.authorizeApplication=t.authorizePassword=t.preAuthorizeImplicit=t.CONFIGURE_AUTH=t.VALIDATE=t.AUTHORIZE_OAUTH2=t.PRE_AUTHORIZE_OAUTH2=t.LOGOUT=t.AUTHORIZE=t.SHOW_AUTH_POPUP=void 0;var r=l(n(45)),o=l(n(23)),i=l(n(41));t.showDefinitions=function(e){return{type:c,payload:e}},t.authorize=function(e){return{type:f,payload:e}},t.logout=function(e){return{type:p,payload:e}},t.authorizeOauth2=function(e){return{type:d,payload:e}},t.configureAuth=function(e){return{type:h,payload:e}};var a=l(n(210)),u=l(n(32)),s=n(9);function l(e){return e&&e.__esModule?e:{default:e}}var c=t.SHOW_AUTH_POPUP="show_popup",f=t.AUTHORIZE="authorize",p=t.LOGOUT="logout",d=(t.PRE_AUTHORIZE_OAUTH2="pre_authorize_oauth2",t.AUTHORIZE_OAUTH2="authorize_oauth2"),h=(t.VALIDATE="validate",t.CONFIGURE_AUTH="configure_auth");t.preAuthorizeImplicit=function(e){return function(t){var n=t.authActions,r=t.errActions,o=e.auth,a=e.token,s=e.isValid,l=o.schema,c=o.name,f=l.get("flow");delete u.default.swaggerUIRedirectOauth2,"accessCode"===f||s||r.newAuthErr({authId:c,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),a.error?r.newAuthErr({authId:c,source:"auth",level:"error",message:(0,i.default)(a)}):n.authorizeOauth2({auth:o,token:a})}};t.authorizePassword=function(e){return function(t){var n=t.authActions,r=e.schema,i=e.name,a=e.username,u=e.password,l=e.passwordType,c=e.clientId,f=e.clientSecret,p={grant_type:"password",scope:e.scopes.join(" "),username:a,password:u},d={};switch(l){case"request-body":!function(e,t,n){t&&(0,o.default)(e,{client_id:t});n&&(0,o.default)(e,{client_secret:n})}(p,c,f);break;case"basic":d.Authorization="Basic "+(0,s.btoa)(c+":"+f);break;default:console.warn("Warning: invalid passwordType "+l+" was passed, not including client id and secret")}return n.authorizeRequest({body:(0,s.buildFormData)(p),url:r.get("tokenUrl"),name:i,headers:d,query:{},auth:e})}};t.authorizeApplication=function(e){return function(t){var n=t.authActions,r=e.schema,o=e.scopes,i=e.name,a=e.clientId,u=e.clientSecret,l={Authorization:"Basic "+(0,s.btoa)(a+":"+u)},c={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:(0,s.buildFormData)(c),name:i,url:r.get("tokenUrl"),auth:e,headers:l})}},t.authorizeAccessCodeWithFormParams=function(e){var t=e.auth,n=e.redirectUrl;return function(e){var r=e.authActions,o=t.schema,i=t.name,a=t.clientId,u=t.clientSecret,l={grant_type:"authorization_code",code:t.code,client_id:a,client_secret:u,redirect_uri:n};return r.authorizeRequest({body:(0,s.buildFormData)(l),name:i,url:o.get("tokenUrl"),auth:t})}},t.authorizeAccessCodeWithBasicAuthentication=function(e){var t=e.auth,n=e.redirectUrl;return function(e){var r=e.authActions,o=t.schema,i=t.name,a=t.clientId,u=t.clientSecret,l={Authorization:"Basic "+(0,s.btoa)(a+":"+u)},c={grant_type:"authorization_code",code:t.code,client_id:a,redirect_uri:n};return r.authorizeRequest({body:(0,s.buildFormData)(c),name:i,url:o.get("tokenUrl"),auth:t,headers:l})}},t.authorizeRequest=function(e){return function(t){var n=t.fn,u=t.getConfigs,s=t.authActions,l=t.errActions,c=t.oas3Selectors,f=t.specSelectors,p=t.authSelectors,d=e.body,h=e.query,v=void 0===h?{}:h,m=e.headers,g=void 0===m?{}:m,y=e.name,b=e.url,_=e.auth,w=(p.getConfigs()||{}).additionalQueryStringParams,E=void 0;E=f.isOAS3()?(0,a.default)(b,c.selectedServer(),!0):(0,a.default)(b,f.url(),!0),"object"===(void 0===w?"undefined":(0,r.default)(w))&&(E.query=(0,o.default)({},E.query,w));var x=E.toString(),S=(0,o.default)({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},g);n.fetch({url:x,method:"post",headers:S,query:v,body:d,requestInterceptor:u().requestInterceptor,responseInterceptor:u().responseInterceptor}).then(function(e){var t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?l.newAuthErr({authId:y,level:"error",source:"auth",message:(0,i.default)(t)}):s.authorizeOauth2({auth:_,token:t}):l.newAuthErr({authId:y,level:"error",source:"auth",message:e.statusText})}).catch(function(e){var t=new Error(e).message;if(e.response&&e.response.data){var n=e.response.data;try{var r="string"==typeof n?JSON.parse(n):n;r.error&&(t+=", error: "+r.error),r.error_description&&(t+=", description: "+r.error_description)}catch(e){}}l.newAuthErr({authId:y,level:"error",source:"auth",message:t})})}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parseYamlConfig=void 0;var r,o=n(208),i=(r=o)&&r.__esModule?r:{default:r};t.parseYamlConfig=function(e,t){try{return i.default.safeLoad(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loaded=t.TOGGLE_CONFIGS=t.UPDATE_CONFIGS=void 0;var r,o=n(22),i=(r=o)&&r.__esModule?r:{default:r};t.update=function(e,t){return{type:a,payload:(0,i.default)({},e,t)}},t.toggle=function(e){return{type:u,payload:e}};var a=t.UPDATE_CONFIGS="configs_update",u=t.TOGGLE_CONFIGS="configs_toggle";t.loaded=function(){return function(){}}},function(e,t,n){"use strict";function r(e,t,n,r,o){this.src=e,this.env=r,this.options=n,this.parser=t,this.tokens=o,this.pos=0,this.posMax=this.src.length,this.level=0,this.pending="",this.pendingLevel=0,this.cache=[],this.isInLabel=!1,this.linkLevel=0,this.linkContent="",this.labelUnmatchedScopes=0}r.prototype.pushPending=function(){this.tokens.push({type:"text",content:this.pending,level:this.pendingLevel}),this.pending=""},r.prototype.push=function(e){this.pending&&this.pushPending(),this.tokens.push(e),this.pendingLevel=this.level},r.prototype.cacheSet=function(e,t){for(var n=this.cache.length;n<=e;n++)this.cache.push(0);this.cache[e]=t},r.prototype.cacheGet=function(e){return es;)r(u,n=t[s++])&&(~i(l,n)||l.push(n));return l}},function(e,t,n){var r=n(21).document;e.exports=r&&r.documentElement},function(e,t,n){var r=n(52),o=n(72),i=n(162)("IE_PROTO"),a=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=o(e),r(e,i)?e[i]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?a:null}},function(e,t,n){var r=n(33),o=r["__core-js_shared__"]||(r["__core-js_shared__"]={});e.exports=function(e){return o[e]||(o[e]={})}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){"use strict";var r=n(246)(!0);n(247)(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){var r=n(119),o=n(53);e.exports=function(e){return function(t,n){var i,a,u=String(o(t)),s=r(n),l=u.length;return s<0||s>=l?e?"":void 0:(i=u.charCodeAt(s))<55296||i>56319||s+1===l||(a=u.charCodeAt(s+1))<56320||a>57343?e?u.charAt(s):i:e?u.slice(s,s+2):a-56320+(i-55296<<10)+65536}}},function(e,t,n){"use strict";var r=n(248),o=n(29),i=n(73),a=n(59),u=n(102),s=n(462),l=n(171),c=n(468),f=n(18)("iterator"),p=!([].keys&&"next"in[].keys()),d=function(){return this};e.exports=function(e,t,n,h,v,m,g){s(n,t,h);var y,b,_,w=function(e){if(!p&&e in C)return C[e];switch(e){case"keys":case"values":return function(){return new n(this,e)}}return function(){return new n(this,e)}},E=t+" Iterator",x="values"==v,S=!1,C=e.prototype,k=C[f]||C["@@iterator"]||v&&C[v],A=k||w(v),O=v?x?w("entries"):A:void 0,P="Array"==t&&C.entries||k;if(P&&(_=c(P.call(new e)))!==Object.prototype&&_.next&&(l(_,E,!0),r||"function"==typeof _[f]||a(_,f,d)),x&&k&&"values"!==k.name&&(S=!0,A=function(){return k.call(this)}),r&&!g||!p&&!S&&C[f]||a(C,f,A),u[t]=A,u[E]=d,v)if(y={values:x?A:w("values"),keys:m?A:w("keys"),entries:O},g)for(b in y)b in C||i(C,b,y[b]);else o(o.P+o.F*(p||S),t,y);return y}},function(e,t){e.exports=!1},function(e,t,n){var r=n(465),o=n(251);e.exports=Object.keys||function(e){return r(e,o)}},function(e,t,n){var r=n(119),o=Math.max,i=Math.min;e.exports=function(e,t){return(e=r(e))<0?o(e+t,0):i(e,t)}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(33).document;e.exports=r&&r.documentElement},function(e,t,n){var r=n(60),o=n(121),i=n(18)("species");e.exports=function(e,t){var n,a=r(e).constructor;return void 0===a||void 0==(n=r(a)[i])?t:o(n)}},function(e,t,n){var r,o,i,a=n(120),u=n(480),s=n(252),l=n(169),c=n(33),f=c.process,p=c.setImmediate,d=c.clearImmediate,h=c.MessageChannel,v=c.Dispatch,m=0,g={},y=function(){var e=+this;if(g.hasOwnProperty(e)){var t=g[e];delete g[e],t()}},b=function(e){y.call(e.data)};p&&d||(p=function(e){for(var t=[],n=1;arguments.length>n;)t.push(arguments[n++]);return g[++m]=function(){u("function"==typeof e?e:Function(e),t)},r(m),m},d=function(e){delete g[e]},"process"==n(99)(f)?r=function(e){f.nextTick(a(y,e,1))}:v&&v.now?r=function(e){v.now(a(y,e,1))}:h?(i=(o=new h).port2,o.port1.onmessage=b,r=a(i.postMessage,i,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(r=function(e){c.postMessage(e+"","*")},c.addEventListener("message",b,!1)):r="onreadystatechange"in l("script")?function(e){s.appendChild(l("script")).onreadystatechange=function(){s.removeChild(this),y.call(e)}}:function(e){setTimeout(a(y,e,1),0)}),e.exports={set:p,clear:d}},function(e,t){e.exports=function(e){try{return{e:!1,v:e()}}catch(e){return{e:!0,v:e}}}},function(e,t,n){var r=n(60),o=n(74),i=n(172);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e);return(0,n.resolve)(t),n.promise}},function(e,t,n){var r=n(74),o=n(99),i=n(18)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[i])?!!t:"RegExp"==o(e))}},function(e,t,n){var r=n(20),o=n(15),i=n(51);e.exports=function(e,t){var n=(o.Object||{})[e]||Object[e],a={};a[e]=t(n),r(r.S+r.F*i(function(){n(1)}),"Object",a)}},function(e,t,n){var r=n(93);e.exports=Array.isArray||function(e){return"Array"==r(e)}},function(e,t,n){var r=n(240),o=n(164).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},function(e,t,n){var r=n(125),o=n(95),i=n(71),a=n(158),u=n(52),s=n(239),l=Object.getOwnPropertyDescriptor;t.f=n(44)?l:function(e,t){if(e=i(e),t=a(t,!0),s)try{return l(e,t)}catch(e){}if(u(e,t))return o(!r.f.call(e,t),e[t])}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){e.exports={default:n(532),__esModule:!0}},function(e,t,n){"use strict";var r=n(96),o=n(177),i=n(125),a=n(72),u=n(155),s=Object.assign;e.exports=!s||n(51)(function(){var e={},t={},n=Symbol(),r="abcdefghijklmnopqrst";return e[n]=7,r.split("").forEach(function(e){t[e]=e}),7!=s({},e)[n]||Object.keys(s({},t)).join("")!=r})?function(e,t){for(var n=a(e),s=arguments.length,l=1,c=o.f,f=i.f;s>l;)for(var p,d=u(arguments[l++]),h=c?r(d).concat(c(d)):r(d),v=h.length,m=0;v>m;)f.call(d,p=h[m++])&&(n[p]=d[p]);return n}:s},function(e,t,n){"use strict";var r=n(104),o=n(13),i=n(266),a=(n(267),n(126));n(8),n(536);function u(e,t,n){this.props=e,this.context=t,this.refs=a,this.updater=n||i}function s(e,t,n){this.props=e,this.context=t,this.refs=a,this.updater=n||i}function l(){}u.prototype.isReactComponent={},u.prototype.setState=function(e,t){"object"!=typeof e&&"function"!=typeof e&&null!=e&&r("85"),this.updater.enqueueSetState(this,e),t&&this.updater.enqueueCallback(this,t,"setState")},u.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this),e&&this.updater.enqueueCallback(this,e,"forceUpdate")},l.prototype=u.prototype,s.prototype=new l,s.prototype.constructor=s,o(s.prototype,u.prototype),s.prototype.isPureReactComponent=!0,e.exports={Component:u,PureComponent:s}},function(e,t,n){"use strict";n(10);var r={isMounted:function(e){return!1},enqueueCallback:function(e,t){},enqueueForceUpdate:function(e){},enqueueReplaceState:function(e,t){},enqueueSetState:function(e,t){}};e.exports=r},function(e,t,n){"use strict";var r=!1;e.exports=r},function(e,t,n){"use strict";var r="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103;e.exports=r},function(e,t,n){"use strict";var r=n(544);e.exports=function(e){return r(e,!1)}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(272),o=n(561),i=n(562),a=n(563),u=n(276);n(275);n.d(t,"createStore",function(){return r.b}),n.d(t,"combineReducers",function(){return o.a}),n.d(t,"bindActionCreators",function(){return i.a}),n.d(t,"applyMiddleware",function(){return a.a}),n.d(t,"compose",function(){return u.a})},function(e,t,n){"use strict";n.d(t,"a",function(){return i}),t.b=function e(t,n,a){var u;"function"==typeof n&&void 0===a&&(a=n,n=void 0);if(void 0!==a){if("function"!=typeof a)throw new Error("Expected the enhancer to be a function.");return a(e)(t,n)}if("function"!=typeof t)throw new Error("Expected the reducer to be a function.");var s=t;var l=n;var c=[];var f=c;var p=!1;function d(){f===c&&(f=c.slice())}function h(){return l}function v(e){if("function"!=typeof e)throw new Error("Expected listener to be a function.");var t=!0;return d(),f.push(e),function(){if(t){t=!1,d();var n=f.indexOf(e);f.splice(n,1)}}}function m(e){if(!r.a(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(p)throw new Error("Reducers may not dispatch actions.");try{p=!0,l=s(l,e)}finally{p=!1}for(var t=c=f,n=0;no?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var i=Array(o);++rp))return!1;var h=c.get(e);if(h&&c.get(t))return h==t;var v=-1,m=!0,g=n&u?new r:void 0;for(c.set(e,t),c.set(t,e);++v0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===l.prototype||(t=function(e){return l.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):w(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?w(e,a,t,!1):k(e,a)):w(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=E?e=E:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function S(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(d("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(C,e):C(e))}function C(e){d("emit readable"),e.emit("readable"),T(e)}function k(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(A,e,t))}function A(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=l.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function I(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(j,t,e))}function j(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function N(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return d("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?I(this):S(this),null;if(0===(e=x(e,t))&&t.ended)return 0===t.length&&I(this),null;var r,o=t.needReadable;return d("need readable",o),(0===t.length||t.length-e0?M(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&I(this)),null!==r&&this.emit("data",r),r},b.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},b.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,d("pipe count=%d opts=%j",i.pipesCount,t);var s=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?c:b;function l(t,r){d("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,d("cleanup"),e.removeListener("close",g),e.removeListener("finish",y),e.removeListener("drain",f),e.removeListener("error",m),e.removeListener("unpipe",l),n.removeListener("end",c),n.removeListener("end",b),n.removeListener("data",v),p=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function c(){d("onend"),e.end()}i.endEmitted?o.nextTick(s):n.once("end",s),e.on("unpipe",l);var f=function(e){return function(){var t=e._readableState;d("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&u(e,"data")&&(t.flowing=!0,T(e))}}(n);e.on("drain",f);var p=!1;var h=!1;function v(t){d("ondata"),h=!1,!1!==e.write(t)||h||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==N(i.pipes,e))&&!p&&(d("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,h=!0),n.pause())}function m(t){d("onerror",t),b(),e.removeListener("error",m),0===u(e,"error")&&e.emit("error",t)}function g(){e.removeListener("finish",y),b()}function y(){d("onfinish"),e.removeListener("close",g),b()}function b(){d("unpipe"),n.unpipe(e)}return n.on("data",v),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",m),e.once("close",g),e.once("finish",y),e.emit("pipe",n),i.flowing||(d("pipe resume"),n.resume()),e},b.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n),this);if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(663),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(t,n(31))},function(e,t,n){"use strict";var r=n(141).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=s,this.end=l,t=4;break;case"utf8":this.fillLast=u,t=4;break;case"base64":this.text=c,this.end=f,t=3;break;default:return this.write=p,void(this.end=d)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function u(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function s(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function l(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function c(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function p(e){return e.toString(this.encoding)}function d(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";e.exports=i;var r=n(65),o=n(106);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e),this._transformState={afterTransform:function(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length=0?n&&o?o-1:o:1:!1!==e&&r(e)}},function(e,t,n){"use strict";e.exports=n(679)()?Object.assign:n(680)},function(e,t,n){"use strict";var r,o,i,a,u,s=n(67),l=function(e,t){return t};try{Object.defineProperty(l,"length",{configurable:!0,writable:!1,enumerable:!1,value:1})}catch(e){}1===l.length?(r={configurable:!0,writable:!1,enumerable:!1},o=Object.defineProperty,e.exports=function(e,t){return t=s(t),e.length===t?e:(r.value=t,o(e,"length",r))}):(a=n(317),u=[],i=function(e){var t,n=0;if(u[e])return u[e];for(t=[];e--;)t.push("a"+(++n).toString(36));return new Function("fn","return function ("+t.join(", ")+") { return fn.apply(this, arguments); };")},e.exports=function(e,t){var n;if(t=s(t),e.length===t)return e;n=i(t)(e);try{a(n,e)}catch(e){}return n})},function(e,t,n){"use strict";var r=n(82),o=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,u=Object.getOwnPropertySymbols;e.exports=function(e,t){var n,s=Object(r(t));if(e=Object(r(e)),a(s).forEach(function(r){try{o(e,r,i(t,r))}catch(e){n=e}}),"function"==typeof u&&u(s).forEach(function(r){try{o(e,r,i(t,r))}catch(e){n=e}}),void 0!==n)throw n;return e}},function(e,t,n){"use strict";var r=n(57),o=n(142),i=Function.prototype.call;e.exports=function(e,t){var n={},a=arguments[2];return r(t),o(e,function(e,r,o,u){n[r]=i.call(t,a,e,r,o,u)}),n}},function(e,t){e.exports=function(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return{statePlugins:{err:{reducers:(0,i.default)(e),actions:a,selectors:u}}}};var r,o=n(321),i=(r=o)&&r.__esModule?r:{default:r},a=s(n(127)),u=s(n(325));function s(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=s(n(22)),o=s(n(23));t.default=function(e){var t;return t={},(0,r.default)(t,i.NEW_THROWN_ERR,function(t,n){var r=n.payload,i=(0,o.default)(l,r,{type:"thrown"});return t.update("errors",function(e){return(e||(0,a.List)()).push((0,a.fromJS)(i))}).update("errors",function(t){return(0,u.default)(t,e.getSystem())})}),(0,r.default)(t,i.NEW_THROWN_ERR_BATCH,function(t,n){var r=n.payload;return r=r.map(function(e){return(0,a.fromJS)((0,o.default)(l,e,{type:"thrown"}))}),t.update("errors",function(e){return(e||(0,a.List)()).concat((0,a.fromJS)(r))}).update("errors",function(t){return(0,u.default)(t,e.getSystem())})}),(0,r.default)(t,i.NEW_SPEC_ERR,function(t,n){var r=n.payload,o=(0,a.fromJS)(r);return o=o.set("type","spec"),t.update("errors",function(e){return(e||(0,a.List)()).push((0,a.fromJS)(o)).sortBy(function(e){return e.get("line")})}).update("errors",function(t){return(0,u.default)(t,e.getSystem())})}),(0,r.default)(t,i.NEW_SPEC_ERR_BATCH,function(t,n){var r=n.payload;return r=r.map(function(e){return(0,a.fromJS)((0,o.default)(l,e,{type:"spec"}))}),t.update("errors",function(e){return(e||(0,a.List)()).concat((0,a.fromJS)(r))}).update("errors",function(t){return(0,u.default)(t,e.getSystem())})}),(0,r.default)(t,i.NEW_AUTH_ERR,function(t,n){var r=n.payload,i=(0,a.fromJS)((0,o.default)({},r));return i=i.set("type","auth"),t.update("errors",function(e){return(e||(0,a.List)()).push((0,a.fromJS)(i))}).update("errors",function(t){return(0,u.default)(t,e.getSystem())})}),(0,r.default)(t,i.CLEAR,function(e,t){var n=t.payload;if(!n||!e.get("errors"))return e;var r=e.get("errors").filter(function(e){return e.keySeq().every(function(t){var r=e.get(t),o=n[t];return!o||r!==o})});return e.merge({errors:r})}),(0,r.default)(t,i.CLEAR_BY,function(e,t){var n=t.payload;if(!n||"function"!=typeof n)return e;var r=e.get("errors").filter(function(e){return n(e)});return e.merge({errors:r})}),t};var i=n(127),a=n(7),u=s(n(322));function s(e){return e&&e.__esModule?e:{default:e}}var l={line:0,level:"error",message:"Unknown error"}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){var n={jsSpec:t.specSelectors.specJson().toJS()};return(0,i.default)(u,function(e,t){try{var r=t.transform(e,n);return r.filter(function(e){return!!e})}catch(t){return console.error("Transformer error:",t),e}},e).filter(function(e){return!!e}).map(function(e){return!e.get("line")&&e.get("path"),e})};var r,o=n(727),i=(r=o)&&r.__esModule?r:{default:r};function a(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}var u=[a(n(323)),a(n(324))]},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.transform=function(e){return e.map(function(e){var t=e.get("message").indexOf("is not of a type(s)");if(t>-1){var n=e.get("message").slice(t+"is not of a type(s)".length).split(",");return e.set("message",e.get("message").slice(0,t)+function(e){return e.reduce(function(e,t,n,r){return n===r.length-1&&r.length>1?e+"or "+t:r[n+1]&&r.length>2?e+t+", ":r[n+1]?e+t+" ":e+t},"should be a")}(n))}return e})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.transform=function(e,t){t.jsSpec;return e};var r,o=n(138);(r=o)&&r.__esModule,n(7)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.lastError=t.allErrors=void 0;var r=n(7),o=n(58),i=t.allErrors=(0,o.createSelector)(function(e){return e},function(e){return e.get("errors",(0,r.List)())});t.lastError=(0,o.createSelector)(i,function(e){return e.last()})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){return{statePlugins:{layout:{reducers:i.default,actions:a,selectors:u}}}};var r,o=n(327),i=(r=o)&&r.__esModule?r:{default:r},a=s(n(202)),u=s(n(328));function s(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o,i=n(22),a=(r=i)&&r.__esModule?r:{default:r},u=n(7),s=n(202);t.default=(o={},(0,a.default)(o,s.UPDATE_LAYOUT,function(e,t){return e.set("layout",t.payload)}),(0,a.default)(o,s.UPDATE_FILTER,function(e,t){return e.set("filter",t.payload)}),(0,a.default)(o,s.SHOW,function(e,t){var n=t.payload.shown,r=(0,u.fromJS)(t.payload.thing);return e.update("shown",(0,u.fromJS)({}),function(e){return e.set(r,n)})}),(0,a.default)(o,s.UPDATE_MODE,function(e,t){var n=t.payload.thing,r=t.payload.mode;return e.setIn(["modes"].concat(n),(r||"")+"")}),o)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.showSummary=t.whatMode=t.isShown=t.currentFilter=t.current=void 0;var r,o=n(83),i=(r=o)&&r.__esModule?r:{default:r},a=n(58),u=n(9),s=n(7);t.current=function(e){return e.get("layout")},t.currentFilter=function(e){return e.get("filter")};var l=t.isShown=function(e,t,n){return t=(0,u.normalizeArray)(t),e.get("shown",(0,s.fromJS)({})).get((0,s.fromJS)(t),n)};t.whatMode=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return t=(0,u.normalizeArray)(t),e.getIn(["modes"].concat((0,i.default)(t)),n)},t.showSummary=(0,a.createSelector)(function(e){return e},function(e){return!l(e,"editor")})},function(e,t,n){var r=n(36);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&r(i.call(e)),t}}},function(e,t,n){var r=n(70),o=n(19)("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(r.Array===e||i[o]===e)}},function(e,t,n){var r=n(19)("iterator"),o=!1;try{var i=[7][r]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var i=[7],a=i[r]();a.next=function(){return{done:n=!0}},i[r]=function(){return a},e(i)}catch(e){}return n}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){return{statePlugins:{spec:{wrapActions:s,reducers:i.default,actions:a,selectors:u}}}};var r,o=n(333),i=(r=o)&&r.__esModule?r:{default:r},a=l(n(203)),u=l(n(144)),s=l(n(346));function l(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o=p(n(22)),i=p(n(23)),a=p(n(83)),u=n(7),s=n(9),l=p(n(32)),c=n(144),f=n(203);function p(e){return e&&e.__esModule?e:{default:e}}t.default=(r={},(0,o.default)(r,f.UPDATE_SPEC,function(e,t){return"string"==typeof t.payload?e.set("spec",t.payload):e}),(0,o.default)(r,f.UPDATE_URL,function(e,t){return e.set("url",t.payload+"")}),(0,o.default)(r,f.UPDATE_JSON,function(e,t){return e.set("json",(0,s.fromJSOrdered)(t.payload))}),(0,o.default)(r,f.UPDATE_RESOLVED,function(e,t){return e.setIn(["resolved"],(0,s.fromJSOrdered)(t.payload))}),(0,o.default)(r,f.UPDATE_RESOLVED_SUBTREE,function(e,t){var n=t.payload,r=n.value,o=n.path;return e.setIn(["resolvedSubtrees"].concat((0,a.default)(o)),(0,s.fromJSOrdered)(r))}),(0,o.default)(r,f.UPDATE_PARAM,function(e,t){var n=t.payload,r=n.path,o=n.paramName,i=n.paramIn,u=n.param,l=n.value,c=n.isXml,f=u?(0,s.paramToIdentifier)(u):i+"."+o,p=c?"value_xml":"value";return e.setIn(["meta","paths"].concat((0,a.default)(r),["parameters",f,p]),l)}),(0,o.default)(r,f.UPDATE_EMPTY_PARAM_INCLUSION,function(e,t){var n=t.payload,r=n.pathMethod,o=n.paramName,i=n.paramIn,u=n.includeEmptyValue;if(!o||!i)return console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey."),e;var s=i+"."+o;return e.setIn(["meta","paths"].concat((0,a.default)(r),["parameter_inclusions",s]),u)}),(0,o.default)(r,f.VALIDATE_PARAMS,function(e,t){var n=t.payload,r=n.pathMethod,o=n.isOAS3,i=(0,c.specJsonWithResolvedSubtrees)(e).getIn(["paths"].concat((0,a.default)(r))),l=(0,c.parameterValues)(e,r).toJS();return e.updateIn(["meta","paths"].concat((0,a.default)(r),["parameters"]),(0,u.fromJS)({}),function(t){return i.get("parameters",(0,u.List)()).reduce(function(t,n){var i=(0,s.paramToValue)(n,l),a=(0,c.parameterInclusionSettingFor)(e,r,n.get("name"),n.get("in")),f=(0,s.validateParam)(n,i,{bypassRequiredCheck:a,isOAS3:o});return t.setIn([(0,s.paramToIdentifier)(n),"errors"],(0,u.fromJS)(f))},t)})}),(0,o.default)(r,f.CLEAR_VALIDATE_PARAMS,function(e,t){var n=t.payload.pathMethod;return e.updateIn(["meta","paths"].concat((0,a.default)(n),["parameters"]),(0,u.fromJS)([]),function(e){return e.map(function(e){return e.set("errors",(0,u.fromJS)([]))})})}),(0,o.default)(r,f.SET_RESPONSE,function(e,t){var n=t.payload,r=n.res,o=n.path,a=n.method,u=void 0;(u=r.error?(0,i.default)({error:!0,name:r.err.name,message:r.err.message,statusCode:r.err.statusCode},r.err.response):r).headers=u.headers||{};var c=e.setIn(["responses",o,a],(0,s.fromJSOrdered)(u));return l.default.Blob&&r.data instanceof l.default.Blob&&(c=c.setIn(["responses",o,a,"text"],r.data)),c}),(0,o.default)(r,f.SET_REQUEST,function(e,t){var n=t.payload,r=n.req,o=n.path,i=n.method;return e.setIn(["requests",o,i],(0,s.fromJSOrdered)(r))}),(0,o.default)(r,f.SET_MUTATED_REQUEST,function(e,t){var n=t.payload,r=n.req,o=n.path,i=n.method;return e.setIn(["mutatedRequests",o,i],(0,s.fromJSOrdered)(r))}),(0,o.default)(r,f.UPDATE_OPERATION_META_VALUE,function(e,t){var n=t.payload,r=n.path,o=n.value,i=n.key,s=["paths"].concat((0,a.default)(r)),l=["meta","paths"].concat((0,a.default)(r));return e.getIn(["json"].concat((0,a.default)(s)))||e.getIn(["resolved"].concat((0,a.default)(s)))||e.getIn(["resolvedSubtrees"].concat((0,a.default)(s)))?e.setIn([].concat((0,a.default)(l),[i]),(0,u.fromJS)(o)):e}),(0,o.default)(r,f.CLEAR_RESPONSE,function(e,t){var n=t.payload,r=n.path,o=n.method;return e.deleteIn(["responses",r,o])}),(0,o.default)(r,f.CLEAR_REQUEST,function(e,t){var n=t.payload,r=n.path,o=n.method;return e.deleteIn(["requests",r,o])}),(0,o.default)(r,f.SET_SCHEME,function(e,t){var n=t.payload,r=n.scheme,o=n.path,i=n.method;return o&&i?e.setIn(["scheme",o,i],r):o||i?void 0:e.setIn(["scheme","_defaultScheme"],r)}),r)},function(e,t,n){var r=n(36),o=n(94),i=n(19)("species");e.exports=function(e,t){var n,a=r(e).constructor;return void 0===a||void 0==(n=r(a)[i])?t:o(n)}},function(e,t,n){var r,o,i,a=n(49),u=n(735),s=n(241),l=n(157),c=n(21),f=c.process,p=c.setImmediate,d=c.clearImmediate,h=c.MessageChannel,v=c.Dispatch,m=0,g={},y=function(){var e=+this;if(g.hasOwnProperty(e)){var t=g[e];delete g[e],t()}},b=function(e){y.call(e.data)};p&&d||(p=function(e){for(var t=[],n=1;arguments.length>n;)t.push(arguments[n++]);return g[++m]=function(){u("function"==typeof e?e:Function(e),t)},r(m),m},d=function(e){delete g[e]},"process"==n(93)(f)?r=function(e){f.nextTick(a(y,e,1))}:v&&v.now?r=function(e){v.now(a(y,e,1))}:h?(i=(o=new h).port2,o.port1.onmessage=b,r=a(i.postMessage,i,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(r=function(e){c.postMessage(e+"","*")},c.addEventListener("message",b,!1)):r="onreadystatechange"in l("script")?function(e){s.appendChild(l("script")).onreadystatechange=function(){s.removeChild(this),y.call(e)}}:function(e){setTimeout(a(y,e,1),0)}),e.exports={set:p,clear:d}},function(e,t){e.exports=function(e){try{return{e:!1,v:e()}}catch(e){return{e:!0,v:e}}}},function(e,t,n){var r=n(36),o=n(28),i=n(206);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e);return(0,n.resolve)(t),n.promise}},function(e,t,n){e.exports=n(740)},function(e,t,n){"use strict";t.__esModule=!0;var r,o=n(204),i=(r=o)&&r.__esModule?r:{default:r};t.default=function(e){return function(){var t=e.apply(this,arguments);return new i.default(function(e,n){return function r(o,a){try{var u=t[o](a),s=u.value}catch(e){return void n(e)}if(!u.done)return i.default.resolve(s).then(function(e){r("next",e)},function(e){r("throw",e)});e(s)}("next")})}}},function(e,t,n){"use strict";var r=n(86);e.exports=new r({include:[n(341)]})},function(e,t,n){"use strict";var r=n(86);e.exports=new r({include:[n(209)],implicit:[n(748),n(749),n(750),n(751)]})},function(e,t,n){var r=n(62),o=n(24),i=n(47),a="[object String]";e.exports=function(e){return"string"==typeof e||!o(e)&&i(e)&&r(e)==a}},function(e,t,n){var r=n(147),o=n(79),i=n(135),a=n(38),u=n(80);e.exports=function(e,t,n,s){if(!a(e))return e;for(var l=-1,c=(t=o(t,e)).length,f=c-1,p=e;null!=p&&++l.":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var i,u=a.createElement(D,{child:t});if(e){var s=p.get(e);i=s._processChildContext(s._context)}else i=g;var l=N(n);if(l){var c=l._currentElement.props.child;if(_(c,t)){var f=l._renderedComponent.getPublicInstance(),d=o&&function(){o.call(f)};return L._updateRootComponent(l,u,i,n,d),f}L.unmountComponentAtNode(n)}var h=A(n),m=h&&!!O(h),y=I(n),b=m&&!l&&!y,w=L._renderNewRootComponent(u,n,b,i)._renderedComponent.getPublicInstance();return o&&o.call(w),w},render:function(e,t,n){return L._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){j(e)||r("40");var t=N(e);if(!t){I(e),1===e.nodeType&&e.hasAttribute(E);return!1}return delete k[t._instance.rootID],m.batchedUpdates(M,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,i,a){if(j(t)||r("41"),i){var u=A(t);if(d.canReuseMarkup(e,u))return void s.precacheNode(n,u);var l=u.getAttribute(d.CHECKSUM_ATTR_NAME);u.removeAttribute(d.CHECKSUM_ATTR_NAME);var c=u.outerHTML;u.setAttribute(d.CHECKSUM_ATTR_NAME,l);var f=e,p=function(e,t){for(var n=Math.min(e.length,t.length),r=0;r1?r-1:0),a=1;a=o&&(t=console)[e].apply(t,i)}return i.warn=i.bind(null,"warn"),i.error=i.bind(null,"error"),i.info=i.bind(null,"info"),i.debug=i.bind(null,"debug"),{rootInjects:{log:i}}}},function(e,t,n){"use strict";var r,o=n(387),i=(r=o)&&r.__esModule?r:{default:r},a=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}(n(393));e.exports=function(e){var t=e.configs,n=e.getConfigs;return{fn:{fetch:i.default.makeHttp(t.preFetch,t.postFetch),buildRequest:i.default.buildRequest,execute:i.default.execute,resolve:i.default.resolve,resolveSubtree:function(e,t,r){for(var o=arguments.length,a=Array(o>3?o-3:0),u=3;u2&&void 0!==arguments[2]?arguments[2]:"",r=(arguments.length>3&&void 0!==arguments[3]?arguments[3]:{}).v2OperationIdCompatibilityMode;return e&&"object"===(void 0===e?"undefined":(0,c.default)(e))?(e.operationId||"").replace(/\s/g,"").length?h(e.operationId):i(t,n,{v2OperationIdCompatibilityMode:r}):null}function i(e,t){if((arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).v2OperationIdCompatibilityMode){var n=(t.toLowerCase()+"_"+e).replace(/[\s!@#$%^&*()_+=[{\]};:<>|.\/?,\\'""-]/g,"_");return(n=n||e.substring(1)+"_"+t).replace(/((_){2,})/g,"_").replace(/^(_)*/g,"").replace(/([_])*$/g,"")}return""+d(t)+h(e)}function a(e,t){return d(t)+"-"+e}function u(e,t){return s(e,t,!0)||null}function s(e,t,n){if(!e||"object"!==(void 0===e?"undefined":(0,c.default)(e))||!e.paths||"object"!==(0,c.default)(e.paths))return null;var r=e.paths;for(var o in r)for(var i in r[o])if("PARAMETERS"!==i.toUpperCase()){var a=r[o][i];if(a&&"object"===(void 0===a?"undefined":(0,c.default)(a))){var u={spec:e,pathName:o,method:i.toUpperCase(),operation:a},s=t(u);if(n&&s)return u}}}Object.defineProperty(t,"__esModule",{value:!0});var l=r(n(18)),c=r(n(1));t.isOAS3=function(e){var t=e.openapi;return!!t&&(0,p.default)(t,"3")},t.isSwagger2=function(e){var t=e.swagger;return!!t&&(0,p.default)(t,"2")},t.opId=o,t.idFromPathMethod=i,t.legacyIdFromPathMethod=a,t.getOperationRaw=function(e,t){return e&&e.paths?u(e,function(e){var n=e.pathName,r=e.method,i=e.operation;if(!i||"object"!==(void 0===i?"undefined":(0,c.default)(i)))return!1;var u=i.operationId;return[o(i,n,r),a(n,r),u].some(function(e){return e&&e===t})}):null},t.findOperation=u,t.eachOperation=s,t.normalizeSwagger=function(e){var t=e.spec,n=t.paths,r={};if(!n||t.$$normalized)return e;for(var i in n){var a=n[i];if((0,f.default)(a)){var u=a.parameters;for(var s in a)!function(e){var n=a[e];if(!(0,f.default)(n))return"continue";var s=o(n,i,e);if(s){r[s]?r[s].push(n):r[s]=[n];var c=r[s];if(c.length>1)c.forEach(function(e,t){e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=""+s+(t+1)});else if(void 0!==n.operationId){var p=c[0];p.__originalOperationId=p.__originalOperationId||n.operationId,p.operationId=s}}if("parameters"!==e){var d=[],h={};for(var v in t)"produces"!==v&&"consumes"!==v&&"security"!==v||(h[v]=t[v],d.push(h));if(u&&(h.parameters=u,d.push(h)),d.length){var m=!0,g=!1,y=void 0;try{for(var b,_=(0,l.default)(d);!(m=(b=_.next()).done);m=!0){var w=b.value;for(var E in w)if(n[E]){if("parameters"===E){var x=!0,S=!1,C=void 0;try{for(var k,A=(0,l.default)(w[E]);!(x=(k=A.next()).done);x=!0)!function(){var e=k.value;n[E].some(function(t){return t.name&&t.name===e.name||t.$ref&&t.$ref===e.$ref||t.$$ref&&t.$$ref===e.$$ref||t===e})||n[E].push(e)}()}catch(e){S=!0,C=e}finally{try{!x&&A.return&&A.return()}finally{if(S)throw C}}}}else n[E]=w[E]}}catch(e){g=!0,y=e}finally{try{!m&&_.return&&_.return()}finally{if(g)throw y}}}}}(s)}}return t.$$normalized=!0,e};var f=r(n(47)),p=r(n(14)),d=function(e){return String.prototype.toLowerCase.call(e)},h=function(e){return e.replace(/[^\w]/gi,"_")}},function(e,t){e.exports=n(893)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).loadSpec,r=void 0!==n&&n,o={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:i(e.headers)},a=o.headers["content-type"],u=r||_(a);return(u?e.text:e.blob||e.buffer).call(e).then(function(e){if(o.text=e,o.data=e,u)try{var t=function(e,t){return t&&(0===t.indexOf("application/json")||t.indexOf("+json")>0)?JSON.parse(e):g.default.safeLoad(e)}(e,a);o.body=t,o.obj=t}catch(e){o.parseError=e}return o})}function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t={};return"function"==typeof e.forEach?(e.forEach(function(e,n){void 0!==t[n]?(t[n]=Array.isArray(t[n])?t[n]:[t[n]],t[n].push(e)):t[n]=e}),t):t}function a(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!==(void 0===e?"undefined":(0,h.default)(e))||"string"!=typeof e.uri):"undefined"!=typeof File?e instanceof File:null!==e&&"object"===(void 0===e?"undefined":(0,h.default)(e))&&"function"==typeof e.pipe}function u(e,t){var n=e.collectionFormat,r=e.allowEmptyValue,o="object"===(void 0===e?"undefined":(0,h.default)(e))?e.value:e;if(void 0===o&&r)return"";if(a(o)||"boolean"==typeof o)return o;var i=encodeURIComponent;return t&&(i=(0,y.default)(o)?function(e){return e}:function(e){return(0,p.default)(e)}),"object"!==(void 0===o?"undefined":(0,h.default)(o))||Array.isArray(o)?Array.isArray(o)?Array.isArray(o)&&!n?o.map(i).join(","):"multi"===n?o.map(i):o.map(i).join({csv:",",ssv:"%20",tsv:"%09",pipes:"|"}[n]):i(o):""}function s(e){var t=(0,f.default)(e).reduce(function(t,n){var r=e[n],o=!!r.skipEncoding,i=o?n:encodeURIComponent(n),a=function(e){return e&&"object"===(void 0===e?"undefined":(0,h.default)(e))}(r)&&!Array.isArray(r);return t[i]=u(a?r:{value:r},o),t},{});return m.default.stringify(t,{encode:!1,indices:!1})||""}function l(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.url,r=void 0===t?"":t,o=e.query,i=e.form;if(i){var l=(0,f.default)(i).some(function(e){return a(i[e].value)}),p=e.headers["content-type"]||e.headers["Content-Type"];if(l||/multipart\/form-data/i.test(p)){var d=n(30);e.body=new d,(0,f.default)(i).forEach(function(t){e.body.append(t,u(i[t],!0))})}else e.body=s(i);delete e.form}if(o){var h=r.split("?"),v=(0,c.default)(h,2),g=v[0],y=v[1],b="";if(y){var _=m.default.parse(y);(0,f.default)(o).forEach(function(e){return delete _[e]}),b=m.default.stringify(_,{encode:!0})}var w=function(){for(var e=arguments.length,t=Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:{};return d.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if("object"===(void 0===t?"undefined":(0,h.default)(t))&&(t=(a=t).url),a.headers=a.headers||{},b.mergeInQueryOrForm(a),!a.requestInterceptor){e.next=10;break}return e.next=6,a.requestInterceptor(a);case 6:if(e.t0=e.sent,e.t0){e.next=9;break}e.t0=a;case 9:a=e.t0;case 10:return n=a.headers["content-type"]||a.headers["Content-Type"],/multipart\/form-data/i.test(n)&&(delete a.headers["content-type"],delete a.headers["Content-Type"]),r=void 0,e.prev=13,e.next=16,(a.userFetch||fetch)(a.url,a);case 16:return r=e.sent,e.next=19,b.serializeRes(r,t,a);case 19:if(r=e.sent,!a.responseInterceptor){e.next=27;break}return e.next=23,a.responseInterceptor(r);case 23:if(e.t1=e.sent,e.t1){e.next=26;break}e.t1=r;case 26:r=e.t1;case 27:e.next=37;break;case 29:if(e.prev=29,e.t2=e.catch(13),r){e.next=33;break}throw e.t2;case 33:throw(o=new Error(r.statusText)).statusCode=o.status=r.status,o.responseError=e.t2,o;case 37:if(r.ok){e.next=42;break}throw(i=new Error(r.statusText)).statusCode=i.status=r.status,i.response=r,i;case 42:return e.abrupt("return",r);case 43:case"end":return e.stop()}},e,this,[[13,29]])}));return function(t){return e.apply(this,arguments)}}();var _=t.shouldDownloadAsText=function(){return/(json|xml|yaml|text)\b/.test(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")}},function(e,t){e.exports=n(41)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){return Array.isArray(e)?e.length<1?"":"/"+e.map(function(e){return(e+"").replace(/~/g,"~0").replace(/\//g,"~1")}).join("/"):e}function i(e,t,n){return{op:"replace",path:e,value:t,meta:n}}function a(e,t,n){return f(c(e.filter(m).map(function(e){return t(e.value,n,e.path)})||[]))}function u(e,t,n){return n=n||[],Array.isArray(e)?e.map(function(e,r){return u(e,t,n.concat(r))}):p(e)?(0,w.default)(e).map(function(r){return u(e[r],t,n.concat(r))}):t(e,n[n.length-1],n)}function s(e,t,n){var r=[];if((n=n||[]).length>0){var o=t(e,n[n.length-1],n);o&&(r=r.concat(o))}if(Array.isArray(e)){var i=e.map(function(e,r){return s(e,t,n.concat(r))});i&&(r=r.concat(i))}else if(p(e)){var a=(0,w.default)(e).map(function(r){return s(e[r],t,n.concat(r))});a&&(r=r.concat(a))}return c(r)}function l(e){return Array.isArray(e)?e:[e]}function c(e){var t;return(t=[]).concat.apply(t,(0,_.default)(e.map(function(e){return Array.isArray(e)?c(e):e})))}function f(e){return e.filter(function(e){return void 0!==e})}function p(e){return e&&"object"===(void 0===e?"undefined":(0,b.default)(e))}function d(e){return e&&"function"==typeof e}function h(e){if(g(e)){var t=e.op;return"add"===t||"remove"===t||"replace"===t}return!1}function v(e){return h(e)||g(e)&&"mutation"===e.type}function m(e){return v(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function g(e){return e&&"object"===(void 0===e?"undefined":(0,b.default)(e))}function y(e,t){try{return S.default.getValueByPointer(e,t)}catch(e){return console.error(e),{}}}Object.defineProperty(t,"__esModule",{value:!0});var b=r(n(1)),_=r(n(34)),w=r(n(0)),E=r(n(35)),x=r(n(2)),S=r(n(36)),C=r(n(4)),k=r(n(37)),A=r(n(38));t.default={add:function(e,t){return{op:"add",path:e,value:t}},replace:i,remove:function(e,t){return{op:"remove",path:e}},merge:function(e,t){return{type:"mutation",op:"merge",path:e,value:t}},mergeDeep:function(e,t){return{type:"mutation",op:"mergeDeep",path:e,value:t}},context:function(e,t){return{type:"context",path:e,value:t}},getIn:function(e,t){return t.reduce(function(e,t){return void 0!==t&&e?e[t]:e},e)},applyPatch:function(e,t,n){if(n=n||{},"merge"===(t=(0,x.default)({},t,{path:t.path&&o(t.path)})).op){var r=y(e,t.path);(0,x.default)(r,t.value),S.default.applyPatch(e,[i(t.path,r)])}else if("mergeDeep"===t.op){var a=y(e,t.path);for(var u in t.value){var s=t.value[u],l=Array.isArray(s);if(l){var c=a[u]||[];a[u]=c.concat(s)}else if(p(s)&&!l){var f=(0,x.default)({},a[u]);for(var d in s){if(Object.prototype.hasOwnProperty.call(f,d)){f=(0,k.default)((0,A.default)({},f),s);break}(0,x.default)(f,(0,E.default)({},d,s[d]))}a[u]=f}else a[u]=s}}else if("add"===t.op&&""===t.path&&p(t.value)){var h=(0,w.default)(t.value).reduce(function(e,n){return e.push({op:"add",path:"/"+o(n),value:t.value[n]}),e},[]);S.default.applyPatch(e,h)}else if("replace"===t.op&&""===t.path){var v=t.value;n.allowMetaPatches&&t.meta&&m(t)&&(Array.isArray(t.value)||p(t.value))&&(v=(0,x.default)({},v,t.meta)),e=v}else if(S.default.applyPatch(e,[t]),n.allowMetaPatches&&t.meta&&m(t)&&(Array.isArray(t.value)||p(t.value))){var g=y(e,t.path),b=(0,x.default)({},g,t.meta);S.default.applyPatch(e,[i(t.path,b)])}return e},parentPathMatch:function(e,t){if(!Array.isArray(t))return!1;for(var n=0,r=t.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.requestInterceptor,r=t.responseInterceptor,o=e.withCredentials?"include":"same-origin";return function(t){return e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:"application/json"},credentials:o}).then(function(e){return e.body})}}Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(4)),a=r(n(11));t.makeFetchJSON=o,t.clearCache=function(){s.plugins.refs.clearCache()},t.default=function(e){function t(e){var t=this;E&&(s.plugins.refs.docCache[E]=e),s.plugins.refs.fetchJSON=o(w,{requestInterceptor:y,responseInterceptor:b});var n=[s.plugins.refs];return"function"==typeof g&&n.push(s.plugins.parameters),"function"==typeof m&&n.push(s.plugins.properties),"strict"!==p&&n.push(s.plugins.allOf),(0,l.default)({spec:e,context:{baseDoc:E},plugins:n,allowMetaPatches:h,pathDiscriminator:v,parameterMacro:g,modelPropertyMacro:m}).then(_?function(){var e=(0,a.default)(i.default.mark(function e(n){return i.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",n);case 1:case"end":return e.stop()}},e,t)}));return function(t){return e.apply(this,arguments)}}():c.normalizeSwagger)}var n=e.fetch,r=e.spec,f=e.url,p=e.mode,d=e.allowMetaPatches,h=void 0===d||d,v=e.pathDiscriminator,m=e.modelPropertyMacro,g=e.parameterMacro,y=e.requestInterceptor,b=e.responseInterceptor,_=e.skipNormalization,w=e.http,E=e.baseDoc;return E=E||f,w=n||w||u.default,r?t(r):o(w,{requestInterceptor:y,responseInterceptor:b})(E).then(t)};var u=r(n(7)),s=n(31),l=r(s),c=n(5)},function(e,t){e.exports=n(204)},function(e,t){e.exports=n(91)},function(e,t){e.exports=n(2)},function(e,t){e.exports=n(3)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=Array(e),r=0;r-1&&-1===o.indexOf(n)||i.indexOf(u)>-1||a.some(function(e){return u.indexOf(e)>-1})};var r=["properties"],o=["properties"],i=["definitions","parameters","responses","securityDefinitions","components/schemas","components/responses","components/parameters","components/securitySchemes"],a=["schema/example","items/example"]},function(e,t,n){e.exports=n(24)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if("string"==typeof e?n.url=e:n=e,!(this instanceof o))return new o(n);(0,a.default)(this,n);var r=this.resolve().then(function(){return t.disableInterfaces||(0,a.default)(t,o.makeApisTagOperation(t)),t});return r.client=this,r}Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(3)),a=r((r(n(25)),n(6))),u=r(n(14)),s=r(n(10)),l=n(7),c=r(l),f=n(16),p=r(f),d=r(n(48)),h=n(49),v=n(51),m=n(5);o.http=c.default,o.makeHttp=l.makeHttp.bind(null,o.http),o.resolve=p.default,o.resolveSubtree=d.default,o.execute=v.execute,o.serializeRes=l.serializeRes,o.serializeHeaders=l.serializeHeaders,o.clearCache=f.clearCache,o.parameterBuilders=v.PARAMETER_BUILDERS,o.makeApisTagOperation=h.makeApisTagOperation,o.buildRequest=v.buildRequest,o.helpers={opId:m.opId},o.prototype={http:c.default,execute:function(e){return this.applyDefaults(),o.execute((0,i.default)({spec:this.spec,http:this.http,securities:{authorized:this.authorizations},contextUrl:"string"==typeof this.url?this.url:void 0},e))},resolve:function(){var e=this;return o.resolve({spec:this.spec,url:this.url,allowMetaPatches:this.allowMetaPatches,requestInterceptor:this.requestInterceptor||null,responseInterceptor:this.responseInterceptor||null}).then(function(t){return e.originalSpec=e.spec,e.spec=t.spec,e.errors=t.errors,e})}},o.prototype.applyDefaults=function(){var e=this.spec,t=this.url;if(t&&(0,u.default)(t,"http")){var n=s.default.parse(t);e.host||(e.host=n.host),e.schemes||(e.schemes=[n.protocol.replace(":","")]),e.basePath||(e.basePath="/")}},t.default=o,e.exports=t.default},function(e,t){e.exports=n(905)},function(e,t){e.exports=n(17)},function(e,t){e.exports=n(906)},function(e,t){e.exports=n(907)},function(e,t){e.exports=n(342)},function(e,t){e.exports=n(910)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.plugins=t.SpecMap=void 0;var o=r(n(8)),i=r(n(1)),a=r(n(17)),u=r(n(4)),s=r(n(0)),l=r(n(18)),c=r(n(32)),f=r(n(2)),p=r(n(19)),d=r(n(20));t.default=function(e){return new w(e).dispatch()};var h=r(n(33)),v=r(n(9)),m=r(n(39)),g=r(n(43)),y=r(n(44)),b=r(n(45)),_=r(n(46)),w=function(){function e(t){(0,p.default)(this,e),(0,f.default)(this,{spec:"",debugLevel:"info",plugins:[],pluginHistory:{},errors:[],mutations:[],promisedPatches:[],state:{},patches:[],context:{},contextTree:new _.default,showDebug:!1,allPatches:[],pluginProp:"specMap",libMethods:(0,f.default)((0,c.default)(this),v.default),allowMetaPatches:!1},t),this.get=this._get.bind(this),this.getContext=this._getContext.bind(this),this.hasRun=this._hasRun.bind(this),this.wrappedPlugins=this.plugins.map(this.wrapPlugin.bind(this)).filter(v.default.isFunction),this.patches.push(v.default.add([],this.spec)),this.patches.push(v.default.context([],this.context)),this.updatePatches(this.patches)}return(0,d.default)(e,[{key:"debug",value:function(e){if(this.debugLevel===e){for(var t,n=arguments.length,r=Array(n>1?n-1:0),o=1;o1?n-1:0),o=1;o0})}},{key:"nextPromisedPatch",value:function(){if(this.promisedPatches.length>0)return a.default.race(this.promisedPatches.map(function(e){return e.value}))}},{key:"getPluginHistory",value:function(e){var t=this.getPluginName(e);return this.pluginHistory[t]||[]}},{key:"getPluginRunCount",value:function(e){return this.getPluginHistory(e).length}},{key:"getPluginHistoryTip",value:function(e){var t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}},{key:"getPluginMutationIndex",value:function(e){var t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}},{key:"getPluginName",value:function(e){return e.pluginName}},{key:"updatePluginHistory",value:function(e,t){var n=this.getPluginName(e);(this.pluginHistory[n]=this.pluginHistory[n]||[]).push(t)}},{key:"updatePatches",value:function(e,t){var n=this;v.default.normalizeArray(e).forEach(function(e){if(e instanceof Error)n.errors.push(e);else try{if(!v.default.isObject(e))return void n.debug("updatePatches","Got a non-object patch",e);if(n.showDebug&&n.allPatches.push(e),v.default.isPromise(e.value))return n.promisedPatches.push(e),void n.promisedPatchThen(e);if(v.default.isContextPatch(e))return void n.setContext(e.path,e.value);if(v.default.isMutation(e))return void n.updateMutations(e)}catch(e){console.error(e),n.errors.push(e)}})}},{key:"updateMutations",value:function(e){"object"===(0,i.default)(e.value)&&!Array.isArray(e.value)&&this.allowMetaPatches&&(e.value=(0,f.default)({},e.value));var t=v.default.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}},{key:"removePromisedPatch",value:function(e){var t=this.promisedPatches.indexOf(e);t<0?this.debug("Tried to remove a promisedPatch that isn't there!"):this.promisedPatches.splice(t,1)}},{key:"promisedPatchThen",value:function(e){var t=this;return e.value=e.value.then(function(n){var r=(0,f.default)({},e,{value:n});t.removePromisedPatch(e),t.updatePatches(r)}).catch(function(n){t.removePromisedPatch(e),t.updatePatches(n)})}},{key:"getMutations",value:function(e,t){return e=e||0,"number"!=typeof t&&(t=this.mutations.length),this.mutations.slice(e,t)}},{key:"getCurrentMutations",value:function(){return this.getMutationsForPlugin(this.getCurrentPlugin())}},{key:"getMutationsForPlugin",value:function(e){var t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}},{key:"getCurrentPlugin",value:function(){return this.currentPlugin}},{key:"getPatchesOfType",value:function(e,t){return e.filter(t)}},{key:"getLib",value:function(){return this.libMethods}},{key:"_get",value:function(e){return v.default.getIn(this.state,e)}},{key:"_getContext",value:function(e){return this.contextTree.get(e)}},{key:"setContext",value:function(e,t){return this.contextTree.set(e,t)}},{key:"_hasRun",value:function(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}},{key:"_clone",value:function(e){return JSON.parse((0,o.default)(e))}},{key:"dispatch",value:function(){function e(e){e&&(e=v.default.fullyNormalizeArray(e),n.updatePatches(e,r))}var t=this,n=this,r=this.nextPlugin();if(!r){var o=this.nextPromisedPatch();if(o)return o.then(function(){return t.dispatch()}).catch(function(){return t.dispatch()});var i={spec:this.state,errors:this.errors};return this.showDebug&&(i.patches=this.allPatches),a.default.resolve(i)}if(n.pluginCount=n.pluginCount||{},n.pluginCount[r]=(n.pluginCount[r]||0)+1,n.pluginCount[r]>100)return a.default.resolve({spec:n.state,errors:n.errors.concat(new Error("We've reached a hard limit of 100 plugin runs"))});if(r!==this.currentPlugin&&this.promisedPatches.length){var u=this.promisedPatches.map(function(e){return e.value});return a.default.all(u.map(function(e){return e.then(Function,Function)})).then(function(){return t.dispatch()})}return function(){n.currentPlugin=r;var t=n.getCurrentMutations(),o=n.mutations.length-1;try{if(r.isGenerator){var i=!0,a=!1,u=void 0;try{for(var s,p=(0,l.default)(r(t,n.getLib()));!(i=(s=p.next()).done);i=!0)e(s.value)}catch(e){a=!0,u=e}finally{try{!i&&p.return&&p.return()}finally{if(a)throw u}}}else e(r(t,n.getLib()))}catch(t){console.error(t),e([(0,f.default)((0,c.default)(t),{plugin:r})])}finally{n.updatePluginHistory(r,{mutationIndex:o})}return n.dispatch()}()}}]),e}(),E={refs:m.default,allOf:g.default,parameters:y.default,properties:b.default};t.SpecMap=w,t.plugins=E},function(e,t){e.exports=n(349)},function(e,t){e.exports=n(288)},function(e,t){e.exports=n(83)},function(e,t){e.exports=n(22)},function(e,t){e.exports=n(911)},function(e,t){e.exports=n(179)},function(e,t){e.exports=n(181)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!A.test(e)){if(!t)throw new O("Tried to resolve a relative URL, without having a basePath. path: '"+e+"' basePath: '"+t+"'");return x.default.resolve(t,e)}return e}function i(e,t){var n;return n=e&&e.response&&e.response.body?e.response.body.code+" "+e.response.body.message:e.message,new O("Could not resolve reference: "+n,t,e)}function a(e){return(e+"").split("#")}function u(e,t){var n=P[e];if(n&&!S.default.isPromise(n))try{var r=l(t,n);return(0,b.default)(g.default.resolve(r),{__value:r})}catch(e){return g.default.reject(e)}return s(e).then(function(e){return l(t,e)})}function s(e){var t=P[e];return t?S.default.isPromise(t)?t:g.default.resolve(t):(P[e]=I.fetchJSON(e).then(function(t){return P[e]=t,t}),P[e])}function l(e,t){var n=c(e);if(n.length<1)return t;var r=S.default.getIn(t,n);if(void 0===r)throw new O("Could not resolve pointer: "+e+" does not exist in document",{pointer:e});return r}function c(e){if("string"!=typeof e)throw new TypeError("Expected a string, got a "+(void 0===e?"undefined":(0,v.default)(e)));return"/"===e[0]&&(e=e.substr(1)),""===e?[]:e.split("/").map(f)}function f(e){return"string"!=typeof e?e:E.default.unescape(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function p(e){return E.default.escape(e.replace(/~/g,"~0").replace(/\//g,"~1"))}function d(e,t){if(j(t))return!0;var n=e.charAt(t.length),r=t.slice(-1);return 0===e.indexOf(t)&&(!n||"/"===n||"#"===n)&&"#"!==r}function h(e,t,n,r){var o=T.get(r);o||(o={},T.set(r,o));var i=function(e){return 0===e.length?"":"/"+e.map(p).join("/")}(n),a=(t||"")+"#"+e;if(t==r.contextTree.get([]).baseDoc&&d(i,e))return!0;var u="";if(n.some(function(e){return u=u+"/"+p(e),o[u]&&o[u].some(function(e){return d(e,a)||d(a,e)})}))return!0;o[i]=(o[i]||[]).concat(a)}Object.defineProperty(t,"__esModule",{value:!0});var v=r(n(1)),m=r(n(0)),g=r(n(17)),y=r(n(40)),b=r(n(2)),_=n(41),w=r(n(15)),E=r(n(42)),x=r(n(10)),S=r(n(9)),C=r(n(21)),k=n(22),A=new RegExp("^([a-z]+://|//)","i"),O=(0,C.default)("JSONRefError",function(e,t,n){this.originalError=n,(0,b.default)(this,t||{})}),P={},T=new y.default,M={key:"$ref",plugin:function(e,t,n,r){var s=n.slice(0,-1);if(!(0,k.isFreelyNamed)(s)){var l=r.getContext(n).baseDoc;if("string"!=typeof e)return new O("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:l,fullPath:n});var f=a(e),p=f[0],d=f[1]||"",v=void 0;try{v=l||p?o(p,l):null}catch(t){return i(t,{pointer:d,$ref:e,basePath:v,fullPath:n})}var g=void 0,y=void 0;if(!h(d,v,s,r)){if(null==v?(y=c(d),void 0===(g=r.get(y))&&(g=new O("Could not resolve reference: "+e,{pointer:d,$ref:e,baseDoc:l,fullPath:n}))):g=null!=(g=u(v,d)).__value?g.__value:g.catch(function(t){throw i(t,{pointer:d,$ref:e,baseDoc:l,fullPath:n})}),g instanceof Error)return[S.default.remove(n),g];var b=S.default.replace(s,g,{$$ref:e});if(v&&v!==l)return[b,S.default.context(s,{baseDoc:v})];try{if(!function(e,t){var n=[e];return t.path.reduce(function(e,t){return n.push(e[t]),e[t]},e),function e(t){return S.default.isObject(t)&&(n.indexOf(t)>=0||(0,m.default)(t).some(function(n){return e(t[n])}))}(t.value)}(r.state,b))return b}catch(e){return null}}}}},I=(0,b.default)(M,{docCache:P,absoluteify:o,clearCache:function(e){void 0!==e?delete P[e]:(0,m.default)(P).forEach(function(e){delete P[e]})},JSONRefError:O,wrapError:i,getDoc:s,split:a,extractFromDoc:u,fetchJSON:function(e){return(0,_.fetch)(e,{headers:{Accept:"application/json, application/yaml"},loadSpec:!0}).then(function(e){return e.text()}).then(function(e){return w.default.safeLoad(e)})},extract:l,jsonPointerToArray:c,unescapeJsonPointerToken:f});t.default=I;var j=function(e){return!e||"/"===e||"#"===e};e.exports=t.default},function(e,t){e.exports=n(914)},function(e,t){e.exports=n(925)},function(e,t){e.exports=n(926)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(e){return e&&e.__esModule?e:{default:e}}(n(2)),o=n(22);t.default={key:"allOf",plugin:function(e,t,n,i,a){if(!a.meta||!a.meta.$$ref){var u=n.slice(0,-1);if(!(0,o.isFreelyNamed)(u)){if(!Array.isArray(e)){var s=new TypeError("allOf must be an array");return s.fullPath=n,s}var l=!1,c=a.value;u.forEach(function(e){c&&(c=c[e])}),delete(c=(0,r.default)({},c)).allOf;var f=[i.replace(u,{})].concat(e.map(function(e,t){if(!i.isObject(e)){if(l)return null;l=!0;var r=new TypeError("Elements in allOf must be objects");return r.fullPath=n,r}return i.mergeDeep(u,e)}));return f.push(i.mergeDeep(u,c)),c.$$ref||f.push(i.remove([].concat(u,"$$ref"))),f}}}},e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(n(2)),i=r(n(9));t.default={key:"parameters",plugin:function(e,t,n,r,a){if(Array.isArray(e)&&e.length){var u=(0,o.default)([],e),s=n.slice(0,-1),l=(0,o.default)({},i.default.getIn(r.spec,s));return e.forEach(function(e,t){try{u[t].default=r.parameterMacro(l,e)}catch(e){var o=new Error(e);return o.fullPath=n,o}}),i.default.replace(n,u)}return i.default.replace(n,e)}},e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(n(2)),i=r(n(9));t.default={key:"properties",plugin:function(e,t,n,r){var a=(0,o.default)({},e);for(var u in e)try{a[u].default=r.modelPropertyMacro(a[u])}catch(e){var s=new Error(e);return s.fullPath=n,s}return i.default.replace(n,a)}},e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){return i({children:{}},e,t)}function i(e,t,n){return e.value=t||{},e.protoValue=n?(0,u.default)({},n.protoValue,e.value):e.value,(0,a.default)(e.children).forEach(function(t){var n=e.children[t];e.children[t]=i(n,n.value,e)}),e}Object.defineProperty(t,"__esModule",{value:!0});var a=r(n(0)),u=r(n(3)),s=r(n(19)),l=r(n(20)),c=function(){function e(t){(0,s.default)(this,e),this.root=o(t||{})}return(0,l.default)(e,[{key:"set",value:function(e,t){var n=this.getParent(e,!0);if(n){var r=e[e.length-1],a=n.children;a[r]?i(a[r],t,n):a[r]=o(t,n)}else i(this.root,t,null)}},{key:"get",value:function(e){if((e=e||[]).length<1)return this.root.value;for(var t=this.root,n=void 0,r=void 0,o=0;o2&&void 0!==arguments[2]?arguments[2]:{};return o.default.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return r=y.returnEntireTree,a=y.baseDoc,c=y.requestInterceptor,f=y.responseInterceptor,p=y.parameterMacro,d=y.modelPropertyMacro,h={pathDiscriminator:n,baseDoc:a,requestInterceptor:c,responseInterceptor:f,parameterMacro:p,modelPropertyMacro:d},v=(0,l.normalizeSwagger)({spec:t}),m=v.spec,e.next=5,(0,s.default)((0,i.default)({},h,{spec:m,allowMetaPatches:!0,skipNormalization:!0}));case 5:return g=e.sent,!r&&Array.isArray(n)&&n.length&&(g.spec=(0,u.default)(g.spec,n)||null),e.abrupt("return",g);case 8:case"end":return e.stop()}},e,this)}));return function(t,n){return e.apply(this,arguments)}}(),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return function(t){var n=t.pathName,r=t.method,o=t.operationId;return function(t){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.execute((0,a.default)({spec:e.spec},(0,u.default)(e,"requestInterceptor","responseInterceptor","userFetch"),{pathName:n,method:r,parameters:t,operationId:o},i))}}}function i(e){var t=e.spec,n=e.cb,r=void 0===n?l:n,o=e.defaultTag,i=void 0===o?"default":o,a=e.v2OperationIdCompatibilityMode,u={},f={};return(0,s.eachOperation)(t,function(e){var n=e.pathName,o=e.method,l=e.operation;(l.tags?c(l.tags):[i]).forEach(function(e){if("string"==typeof e){var i=f[e]=f[e]||{},c=(0,s.opId)(l,n,o,{v2OperationIdCompatibilityMode:a}),p=r({spec:t,pathName:n,method:o,operation:l,operationId:c});if(u[c])u[c]++,i[""+c+u[c]]=p;else if(void 0!==i[c]){var d=u[c]||1;u[c]=d+1,i[""+c+u[c]]=p;var h=i[c];delete i[c],i[""+c+d]=h}else i[c]=p}})}),f}Object.defineProperty(t,"__esModule",{value:!0}),t.self=void 0;var a=r(n(3));t.makeExecute=o,t.makeApisTagOperationsOperationExecute=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=f.makeExecute(e),n=f.mapTagOperations({v2OperationIdCompatibilityMode:e.v2OperationIdCompatibilityMode,spec:e.spec,cb:t}),r={};for(var o in n)for(var i in r[o]={operations:{}},n[o])r[o].operations[i]={execute:n[o][i]};return{apis:r}},t.makeApisTagOperation=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=f.makeExecute(e);return{apis:f.mapTagOperations({v2OperationIdCompatibilityMode:e.v2OperationIdCompatibilityMode,spec:e.spec,cb:t})}},t.mapTagOperations=i;var u=r(n(50)),s=n(5),l=function(){return null},c=function(e){return Array.isArray(e)?e:[e]},f=t.self={mapTagOperations:i,makeExecute:o}},function(e,t){e.exports=n(927)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=e.spec,n=e.operationId,r=(e.securities,e.requestContentType,e.responseContentType),o=e.scheme,a=e.requestInterceptor,s=e.responseInterceptor,c=e.contextUrl,f=e.userFetch,p=(e.requestBody,e.server),d=e.serverVariables,h=e.http,g=e.parameters,y=e.parameterBuilders,O=(0,x.isOAS3)(t);y||(y=O?_.default:b.default);var P={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};a&&(P.requestInterceptor=a),s&&(P.responseInterceptor=s),f&&(P.userFetch=f);var T=(0,x.getOperationRaw)(t,n);if(!T)throw new C("Operation "+n+" not found");var M=T.operation,I=void 0===M?{}:M,j=T.method,N=T.pathName;if(P.url+=i({spec:t,scheme:o,contextUrl:c,server:p,serverVariables:d,pathName:N,method:j}),!n)return delete P.cookies,P;P.url+=N,P.method=(""+j).toUpperCase(),g=g||{};var R=t.paths[N]||{};r&&(P.headers.accept=r);var D=A([].concat(S(I.parameters)).concat(S(R.parameters)));D.forEach(function(e){var n=y[e.in],r=void 0;if("body"===e.in&&e.schema&&e.schema.properties&&(r=g),void 0===(r=e&&e.name&&g[e.name])?r=e&&e.name&&g[e.in+"."+e.name]:k(e.name,D).length>1&&console.warn("Parameter '"+e.name+"' is ambiguous because the defined spec has more than one parameter with the name: '"+e.name+"' and the passed-in parameter values did not define an 'in' value."),null!==r){if(void 0!==e.default&&void 0===r&&(r=e.default),void 0===r&&e.required&&!e.allowEmptyValue)throw new Error("Required parameter "+e.name+" is not provided");if(O&&e.schema&&"object"===e.schema.type&&"string"==typeof r)try{r=JSON.parse(r)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}n&&n({req:P,parameter:e,value:r,operation:I,spec:t})}});var L=(0,u.default)({},e,{operation:I});if((P=O?(0,w.default)(L,P):(0,E.default)(L,P)).cookies&&(0,l.default)(P.cookies).length){var U=(0,l.default)(P.cookies).reduce(function(e,t){var n=P.cookies[t];return e+(e?"&":"")+v.default.serialize(t,n)},"");P.headers.Cookie=U}return P.cookies&&delete P.cookies,(0,m.mergeInQueryOrForm)(P),P}function i(e){return(0,x.isOAS3)(e.spec)?function(e){var t=e.spec,n=e.pathName,r=e.method,o=e.server,i=e.contextUrl,a=e.serverVariables,u=void 0===a?{}:a,s=(0,f.default)(t,["paths",n,(r||"").toLowerCase(),"servers"])||(0,f.default)(t,["paths",n,"servers"])||(0,f.default)(t,["servers"]),l="",c=null;if(o&&s&&s.length){var p=s.map(function(e){return e.url});p.indexOf(o)>-1&&(l=o,c=s[p.indexOf(o)])}!l&&s&&s.length&&(l=s[0].url,c=s[0]),l.indexOf("{")>-1&&function(e){for(var t=[],n=/{([^}]+)}/g,r=void 0;r=n.exec(e);)t.push(r[1]);return t}(l).forEach(function(e){if(c.variables&&c.variables[e]){var t=c.variables[e],n=u[e]||t.default,r=new RegExp("{"+e+"}","g");l=l.replace(r,n)}});return function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=h.default.parse(e),r=h.default.parse(t),o=P(n.protocol)||P(r.protocol)||"",i=n.host||r.host,a=n.pathname||"",u=void 0;return"/"===(u=o&&i?o+"://"+(i+a):a)[u.length-1]?u.slice(0,-1):u}(l,i)}(e):function(e){var t=e.spec,n=e.scheme,r=e.contextUrl,o=void 0===r?"":r,i=h.default.parse(o),a=Array.isArray(t.schemes)?t.schemes[0]:null,u=n||a||P(i.protocol)||"http",s=t.host||i.host||"",l=t.basePath||"",c=void 0;return"/"===(c=u&&s?u+"://"+(s+l):l)[c.length-1]?c.slice(0,-1):c}(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.self=void 0;var a=r(n(8)),u=r(n(3)),s=r(n(52)),l=r(n(0)),c=r(n(2));t.execute=function(e){var t=e.http,n=e.fetch,r=e.spec,o=e.operationId,i=e.pathName,l=e.method,c=e.parameters,f=e.securities,h=(0,s.default)(e,["http","fetch","spec","operationId","pathName","method","parameters","securities"]),v=t||n||g.default;i&&l&&!o&&(o=(0,x.legacyIdFromPathMethod)(i,l));var m=O.buildRequest((0,u.default)({spec:r,operationId:o,parameters:c,securities:f,http:v},h));return m.body&&((0,p.default)(m.body)||(0,d.default)(m.body))&&(m.body=(0,a.default)(m.body)),v(m)},t.buildRequest=o,t.baseUrl=i;var f=r((r(n(6)),n(12))),p=r(n(53)),d=r(n(54)),h=r((r(n(13)),n(10))),v=r(n(55)),m=n(7),g=r(m),y=r(n(21)),b=r(n(56)),_=r(n(57)),w=r(n(62)),E=r(n(64)),x=n(5),S=function(e){return Array.isArray(e)?e:[]},C=(0,y.default)("OperationNotFoundError",function(e,t,n){this.originalError=n,(0,c.default)(this,t||{})}),k=function(e,t){return t.filter(function(t){return t.name===e})},A=function(e){var t={};e.forEach(function(e){t[e.in]||(t[e.in]={}),t[e.in][e.name]=e});var n=[];return(0,l.default)(t).forEach(function(e){(0,l.default)(t[e]).forEach(function(r){n.push(t[e][r])})}),n},O=t.self={buildRequest:o},P=function(e){return e?e.replace(/\W/g,""):null}},function(e,t){e.exports=n(84)},function(e,t){e.exports=n(228)},function(e,t){e.exports=n(24)},function(e,t){e.exports=n(930)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={body:function(e){var t=e.req,n=e.value;t.body=n},header:function(e){var t=e.req,n=e.parameter,r=e.value;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){var t=e.req,n=e.value,r=e.parameter;if(t.query=t.query||{},!1===n&&"boolean"===r.type&&(n="false"),0===n&&["number","integer"].indexOf(r.type)>-1&&(n="0"),n)t.query[r.name]={collectionFormat:r.collectionFormat,value:n};else if(r.allowEmptyValue&&void 0!==n){var o=r.name;t.query[o]=t.query[o]||{},t.query[o].allowEmptyValue=!0}},path:function(e){var t=e.req,n=e.value,r=e.parameter;t.url=t.url.split("{"+r.name+"}").join(encodeURIComponent(n))},formData:function(e){var t=e.req,n=e.value,r=e.parameter;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}},e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(n(0)),i=r(n(1)),a=r(n(58));t.default={path:function(e){var t=e.req,n=e.value,r=e.parameter,o=r.name,i=r.style,u=r.explode,s=(0,a.default)({key:r.name,value:n,style:i||"simple",explode:u||!1,escape:!0});t.url=t.url.split("{"+o+"}").join(s)},query:function(e){var t=e.req,n=e.value,r=e.parameter;if(t.query=t.query||{},!1===n&&(n="false"),0===n&&(n="0"),n){var u=void 0===n?"undefined":(0,i.default)(n);"deepObject"===r.style?(0,o.default)(n).forEach(function(e){var o=n[e];t.query[r.name+"["+e+"]"]={value:(0,a.default)({key:e,value:o,style:"deepObject",escape:r.allowReserved?"unsafe":"reserved"}),skipEncoding:!0}}):"object"!==u||Array.isArray(n)||"form"!==r.style&&r.style||!r.explode&&void 0!==r.explode?t.query[r.name]={value:(0,a.default)({key:r.name,value:n,style:r.style||"form",explode:void 0===r.explode||r.explode,escape:r.allowReserved?"unsafe":"reserved"}),skipEncoding:!0}:(0,o.default)(n).forEach(function(e){var o=n[e];t.query[e]={value:(0,a.default)({key:e,value:o,style:r.style||"form",escape:r.allowReserved?"unsafe":"reserved"}),skipEncoding:!0}})}else if(r.allowEmptyValue&&void 0!==n){var s=r.name;t.query[s]=t.query[s]||{},t.query[s].allowEmptyValue=!0}},header:function(e){var t=e.req,n=e.parameter,r=e.value;t.headers=t.headers||{},u.indexOf(n.name.toLowerCase())>-1||void 0!==r&&(t.headers[n.name]=(0,a.default)({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))},cookie:function(e){var t=e.req,n=e.parameter,r=e.value;t.headers=t.headers||{};var o=void 0===r?"undefined":(0,i.default)(r);if("undefined"!==o){var u="object"===o&&!Array.isArray(r)&&n.explode?"":n.name+"=";t.headers.Cookie=u+(0,a.default)({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}};var u=["accept","authorization","content-type"];e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).escape,n=arguments[2];return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&t?n?JSON.parse(e):(0,s.stringToCharArray)(e).map(function(e){return c(e)?e:l(e)&&"unsafe"===t?e:((0,u.default)(e)||[]).map(function(e){return("0"+e.toString(16).toUpperCase()).slice(-2)}).map(function(e){return"%"+e}).join("")}).join(""):e}Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(0)),a=r(n(1));t.encodeDisallowedCharacters=o,t.default=function(e){var t=e.value;return Array.isArray(t)?function(e){var t=e.key,n=e.value,r=e.style,i=e.explode,a=e.escape,u=function(e){return o(e,{escape:a})};if("simple"===r)return n.map(function(e){return u(e)}).join(",");if("label"===r)return"."+n.map(function(e){return u(e)}).join(".");if("matrix"===r)return n.map(function(e){return u(e)}).reduce(function(e,n){return!e||i?(e||"")+";"+t+"="+n:e+","+n},"");if("form"===r){var s=i?"&"+t+"=":",";return n.map(function(e){return u(e)}).join(s)}if("spaceDelimited"===r){var l=i?t+"=":"";return n.map(function(e){return u(e)}).join(" "+l)}if("pipeDelimited"===r){var c=i?t+"=":"";return n.map(function(e){return u(e)}).join("|"+c)}}(e):"object"===(void 0===t?"undefined":(0,a.default)(t))?function(e){var t=e.key,n=e.value,r=e.style,a=e.explode,u=e.escape,s=function(e){return o(e,{escape:u})},l=(0,i.default)(n);return"simple"===r?l.reduce(function(e,t){var r=s(n[t]);return(e?e+",":"")+t+(a?"=":",")+r},""):"label"===r?l.reduce(function(e,t){var r=s(n[t]);return(e?e+".":".")+t+(a?"=":".")+r},""):"matrix"===r&&a?l.reduce(function(e,t){var r=s(n[t]);return(e?e+";":";")+t+"="+r},""):"matrix"===r?l.reduce(function(e,r){var o=s(n[r]);return(e?e+",":";"+t+"=")+r+","+o},""):"form"===r?l.reduce(function(e,t){var r=s(n[t]);return(e?e+(a?"&":","):"")+t+(a?"=":",")+r},""):void 0}(e):function(e){var t=e.key,n=e.value,r=e.style,i=e.escape,a=function(e){return o(e,{escape:i})};return"simple"===r?a(n):"label"===r?"."+a(n):"matrix"===r?";"+t+"="+a(n):"form"===r?a(n):"deepObject"===r?a(n):void 0}(e)};var u=r((r(n(59)),n(60))),s=n(61),l=function(e){return":/?#[]@!$&'()*+,;=".indexOf(e)>-1},c=function(e){return/^[a-z0-9\-._~]+$/i.test(e)}},function(e,t){e.exports=n(931)},function(e,t){e.exports=n(932)},function(e,t){e.exports=n(933)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=e.request,n=e.securities,r=void 0===n?{}:n,o=e.operation,i=void 0===o?{}:o,a=e.spec,f=(0,s.default)({},t),p=r.authorized,d=void 0===p?{}:p,h=i.security||a.security||[],v=d&&!!(0,u.default)(d).length,m=(0,l.default)(a,["components","securitySchemes"])||{};return f.headers=f.headers||{},f.query=f.query||{},(0,u.default)(r).length&&v&&h&&(!Array.isArray(i.security)||i.security.length)?(h.forEach(function(e,t){for(var n in e){var r=d[n],o=m[n];if(r){var i=r.value||r,a=o.type;if(r)if("apiKey"===a)"query"===o.in&&(f.query[o.name]=i),"header"===o.in&&(f.headers[o.name]=i),"cookie"===o.in&&(f.cookies[o.name]=i);else if("http"===a){if("basic"===o.scheme){var u=i.username,s=i.password,l=(0,c.default)(u+":"+s);f.headers.Authorization="Basic "+l}"bearer"===o.scheme&&(f.headers.Authorization="Bearer "+i)}else if("oauth2"===a){var p=r.token||{},h=p.access_token,v=p.token_type;v&&"bearer"!==v.toLowerCase()||(v="Bearer"),f.headers.Authorization=v+" "+h}}}}),f):t}Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(8)),a=r(n(1)),u=r(n(0));t.default=function(e,t){var n=e.operation,r=e.requestBody,s=e.securities,l=e.spec,c=e.attachContentTypeForEmptyPayload,p=e.requestContentType;t=o({request:t,securities:s,operation:n,spec:l});var d=n.requestBody||{},h=(0,u.default)(d.content||{}),v=p&&h.indexOf(p)>-1;if(r||c){if(p&&v)t.headers["Content-Type"]=p;else if(!p){var m=h[0];m&&(t.headers["Content-Type"]=m,p=m)}}else p&&v&&(t.headers["Content-Type"]=p);return r&&(p?h.indexOf(p)>-1&&("application/x-www-form-urlencoded"===p||0===p.indexOf("multipart/")?"object"===(void 0===r?"undefined":(0,a.default)(r))?(t.form={},(0,u.default)(r).forEach(function(e){var n,o=r[e],u=void 0;"undefined"!=typeof File&&(u=o instanceof File),"undefined"!=typeof Blob&&(u=u||o instanceof Blob),void 0!==f.Buffer&&(u=u||f.Buffer.isBuffer(o)),n="object"!==(void 0===o?"undefined":(0,a.default)(o))||u?o:Array.isArray(o)?o.toString():(0,i.default)(o),t.form[e]={value:n}})):t.form=r:t.body=r):t.body=r),t},t.applySecurities=o;var s=r(n(6)),l=r(n(12)),c=r(n(13)),f=n(63)},function(e,t){e.exports=n(54)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=e.request,n=e.securities,r=void 0===n?{}:n,o=e.operation,s=void 0===o?{}:o,l=e.spec,c=(0,u.default)({},t),f=r.authorized,p=void 0===f?{}:f,d=r.specSecurity,h=void 0===d?[]:d,v=s.security||h,m=p&&!!(0,i.default)(p).length,g=l.securityDefinitions;return c.headers=c.headers||{},c.query=c.query||{},(0,i.default)(r).length&&m&&v&&(!Array.isArray(s.security)||s.security.length)?(v.forEach(function(e,t){for(var n in e){var r=p[n];if(r){var o=r.token,i=r.value||r,u=g[n],s=u.type,l=u["x-tokenName"]||"access_token",f=o&&o[l],d=o&&o.token_type;if(r)if("apiKey"===s){var h="query"===u.in?"query":"headers";c[h]=c[h]||{},c[h][u.name]=i}else"basic"===s?i.header?c.headers.authorization=i.header:(i.base64=(0,a.default)(i.username+":"+i.password),c.headers.authorization="Basic "+i.base64):"oauth2"===s&&f&&(d=d&&"bearer"!==d.toLowerCase()?d:"Bearer",c.headers.authorization=d+" "+f)}}}),c):t}Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(0));t.default=function(e,t){var n=e.spec,r=e.operation,i=e.securities,a=e.requestContentType,u=e.attachContentTypeForEmptyPayload;if((t=o({request:t,securities:i,operation:r,spec:n})).body||t.form||u)a?t.headers["Content-Type"]=a:Array.isArray(r.consumes)?t.headers["Content-Type"]=r.consumes[0]:Array.isArray(n.consumes)?t.headers["Content-Type"]=n.consumes[0]:r.parameters&&r.parameters.filter(function(e){return"file"===e.type}).length?t.headers["Content-Type"]="multipart/form-data":r.parameters&&r.parameters.filter(function(e){return"formData"===e.in}).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(a){var s=r.parameters&&r.parameters.filter(function(e){return"body"===e.in}).length>0,l=r.parameters&&r.parameters.filter(function(e){return"formData"===e.in}).length>0;(s||l)&&(t.headers["Content-Type"]=a)}return t},t.applySecurities=o;var a=r(n(13)),u=r(n(6));r(n(7))}])},function(e,t,n){"use strict";var r=Object.prototype.hasOwnProperty,o=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}();t.arrayToObject=function(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r=48&&i<=57||i>=65&&i<=90||i>=97&&i<=122?n+=t.charAt(r):i<128?n+=o[i]:i<2048?n+=o[192|i>>6]+o[128|63&i]:i<55296||i>=57344?n+=o[224|i>>12]+o[128|i>>6&63]+o[128|63&i]:(r+=1,i=65536+((1023&i)<<10|1023&t.charCodeAt(r)),n+=o[240|i>>18]+o[128|i>>12&63]+o[128|i>>6&63]+o[128|63&i])}return n},t.compact=function(e){for(var t=[{obj:{o:e},prop:"o"}],n=[],r=0;r=0;l--)if(f[l]!=p[l])return!1;for(l=f.length-1;l>=0;l--)if(c=f[l],!a(e[c],t[c],n))return!1;return typeof e==typeof t}(e,t,n))};function u(e){return null===e||void 0===e}function s(e){return!(!e||"object"!=typeof e||"number"!=typeof e.length)&&("function"==typeof e.copy&&"function"==typeof e.slice&&!(e.length>0&&"number"!=typeof e[0]))}},function(e,t,n){var r={strict:!0},o=n(390),i=function(e,t){return o(e,t,r)},a=n(231);t.JsonPatchError=a.PatchError,t.deepClone=a._deepClone;var u={add:function(e,t,n){return e[t]=this.value,{newDocument:n}},remove:function(e,t,n){var r=e[t];return delete e[t],{newDocument:n,removed:r}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:function(e,t,n){var r=l(n,this.path);r&&(r=a._deepClone(r));var o=c(n,{op:"remove",path:this.from}).removed;return c(n,{op:"add",path:this.path,value:o}),{newDocument:n,removed:r}},copy:function(e,t,n){var r=l(n,this.from);return c(n,{op:"add",path:this.path,value:a._deepClone(r)}),{newDocument:n}},test:function(e,t,n){return{newDocument:n,test:i(e[t],this.value)}},_get:function(e,t,n){return this.value=e[t],{newDocument:n}}},s={add:function(e,t,n){return a.isInteger(t)?e.splice(t,0,this.value):e[t]=this.value,{newDocument:n,index:t}},remove:function(e,t,n){return{newDocument:n,removed:e.splice(t,1)[0]}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:u.move,copy:u.copy,test:u.test,_get:u._get};function l(e,t){if(""==t)return e;var n={op:"_get",path:t};return c(e,n),n.value}function c(e,n,r,o){if(void 0===r&&(r=!1),void 0===o&&(o=!0),r&&("function"==typeof r?r(n,0,e,n.path):p(n,0)),""===n.path){var c={newDocument:e};if("add"===n.op)return c.newDocument=n.value,c;if("replace"===n.op)return c.newDocument=n.value,c.removed=e,c;if("move"===n.op||"copy"===n.op)return c.newDocument=l(e,n.from),"move"===n.op&&(c.removed=e),c;if("test"===n.op){if(c.test=i(e,n.value),!1===c.test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",0,n,e);return c.newDocument=e,c}if("remove"===n.op)return c.removed=e,c.newDocument=null,c;if("_get"===n.op)return n.value=e,c;if(r)throw new t.JsonPatchError("Operation `op` property is not one of operations defined in RFC-6902","OPERATION_OP_INVALID",0,n,e);return c}o||(e=a._deepClone(e));var f=(n.path||"").split("/"),d=e,h=1,v=f.length,m=void 0,g=void 0,y=void 0;for(y="function"==typeof r?r:p;;){if(g=f[h],r&&void 0===m&&(void 0===d[g]?m=f.slice(0,h).join("/"):h==v-1&&(m=n.path),void 0!==m&&y(n,0,e,m)),h++,Array.isArray(d)){if("-"===g)g=d.length;else{if(r&&!a.isInteger(g))throw new t.JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",0,n.path,n);a.isInteger(g)&&(g=~~g)}if(h>=v){if(r&&"add"===n.op&&g>d.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",0,n.path,n);if(!1===(c=s[n.op].call(n,d,g,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",0,n,e);return c}}else if(g&&-1!=g.indexOf("~")&&(g=a.unescapePathComponent(g)),h>=v){if(!1===(c=u[n.op].call(n,d,g,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",0,n,e);return c}d=d[g]}}function f(e,n,r,o){if(void 0===o&&(o=!0),r&&!Array.isArray(n))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");o||(e=a._deepClone(e));for(var i=new Array(n.length),u=0,s=n.length;u0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",n,e,r);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",n,e,r);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",n,e,r);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&a.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",n,e,r);if(r)if("add"==e.op){var i=e.path.split("/").length,s=o.split("/").length;if(i!==s+1&&i!==s)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",n,e,r)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==o)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",n,e,r)}else if("move"===e.op||"copy"===e.op){var l=d([{op:"_get",path:e.from,value:void 0}],r);if(l&&"OPERATION_PATH_UNRESOLVABLE"===l.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",n,e,r)}}function d(e,n,r){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(n)f(a._deepClone(n),a._deepClone(e),r||!0);else{r=r||p;for(var o=0;o1&&void 0!==arguments[1]?arguments[1]:(0,a.List)();return function(e){return(e.authSelectors.definitionsToAuthorize()||(0,a.List)()).filter(function(e){return t.some(function(t){return t.get(e.keySeq().first())})})}},t.authorized=(0,i.createSelector)(s,function(e){return e.get("authorized")||(0,a.Map)()}),t.isAuthorized=function(e,t){return function(e){var n=e.authSelectors.authorized();return a.List.isList(t)?!!t.toJS().filter(function(e){return-1===(0,r.default)(e).map(function(e){return!!n.get(e)}).indexOf(!1)}).length:null}},t.getConfigs=(0,i.createSelector)(s,function(e){return e.get("configs")})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.execute=void 0;var r,o=n(25),i=(r=o)&&r.__esModule?r:{default:r};t.execute=function(e,t){var n=t.authSelectors,r=t.specSelectors;return function(t){var o=t.path,a=t.method,u=t.operation,s=t.extras,l={authorized:n.authorized()&&n.authorized().toJS(),definitions:r.securityDefinitions()&&r.securityDefinitions().toJS(),specSecurity:r.security()&&r.security().toJS()};return e((0,i.default)({path:o,method:a,operation:u,securities:l},s))}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){return{fn:{shallowEqualKeys:r.shallowEqualKeys}}};var r=n(9)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=s(n(41)),o=s(n(23));t.default=function(e){var t=e.fn,n={download:function(e){return function(n){var r=n.errActions,i=n.specSelectors,a=n.specActions,s=n.getConfigs,l=t.fetch,c=s();function f(t){if(t instanceof Error||t.status>=400)return a.updateLoadingStatus("failed"),r.newThrownErr((0,o.default)(new Error((t.message||t.statusText)+" "+e),{source:"fetch"})),void(!t.status&&t instanceof Error&&function(){try{var t=void 0;if("URL"in u.default?t=new URL(e):(t=document.createElement("a")).href=e,"https:"!==t.protocol&&"https:"===u.default.location.protocol){var n=(0,o.default)(new Error("Possible mixed-content issue? The page was loaded over https:// but a "+t.protocol+"// URL was specified. Check that you are not attempting to load mixed content."),{source:"fetch"});return void r.newThrownErr(n)}if(t.origin!==u.default.location.origin){var i=(0,o.default)(new Error("Possible cross-origin (CORS) issue? The URL origin ("+t.origin+") does not match the page ("+u.default.location.origin+"). Check the server returns the correct 'Access-Control-Allow-*' headers."),{source:"fetch"});r.newThrownErr(i)}}catch(e){return}}());a.updateLoadingStatus("success"),a.updateSpec(t.text),i.url()!==e&&a.updateUrl(e)}e=e||i.url(),a.updateLoadingStatus("loading"),r.clear({source:"fetch"}),l({url:e,loadSpec:!0,requestInterceptor:c.requestInterceptor||function(e){return e},responseInterceptor:c.responseInterceptor||function(e){return e},credentials:"same-origin",headers:{Accept:"application/json,*/*"}}).then(f,f)}},updateLoadingStatus:function(e){var t=[null,"loading","failed","success","failedConfig"];return-1===t.indexOf(e)&&console.error("Error: "+e+" is not one of "+(0,r.default)(t)),{type:"spec_update_loading_status",payload:e}}},s={loadingStatus:(0,i.createSelector)(function(e){return e||(0,a.Map)()},function(e){return e.get("loadingStatus")||null})};return{statePlugins:{spec:{actions:n,reducers:{spec_update_loading_status:function(e,t){return"string"==typeof t.payload?e.set("loadingStatus",t.payload):e}},selectors:s}}}};var i=n(58),a=n(7),u=s(n(32));function s(e){return e&&e.__esModule?e:{default:e}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){return{statePlugins:{spec:{actions:a,selectors:f},configs:{reducers:s.default,actions:i,selectors:u}}}};var r=c(n(934)),o=n(234),i=l(n(235)),a=l(n(401)),u=l(n(402)),s=c(n(403));function l(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}function c(e){return e&&e.__esModule?e:{default:e}}var f={getLocalConfig:function(){return(0,o.parseYamlConfig)(r.default)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getConfigByUrl=t.downloadConfig=void 0;var r=n(234);t.downloadConfig=function(e){return function(t){return(0,t.fn.fetch)(e)}},t.getConfigByUrl=function(e,t){return function(n){var o=n.specActions;if(e)return o.downloadConfig(e).then(i,i);function i(n){n instanceof Error||n.status>=400?(o.updateLoadingStatus("failedConfig"),o.updateLoadingStatus("failedConfig"),o.updateUrl(""),console.error(n.statusText+" "+e.url),t(null)):t((0,r.parseYamlConfig)(n.text))}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.get=function(e,t){return e.getIn(Array.isArray(t)?t:[t])}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o,i=n(22),a=(r=i)&&r.__esModule?r:{default:r},u=n(7),s=n(235);t.default=(o={},(0,a.default)(o,s.UPDATE_CONFIGS,function(e,t){return e.merge((0,u.fromJS)(t.payload))}),(0,a.default)(o,s.TOGGLE_CONFIGS,function(e,t){var n=t.payload,r=e.get(n);return e.set(n,!r)}),o)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){return[r.default,{statePlugins:{configs:{wrapActions:{loaded:function(e,t){return function(){e.apply(void 0,arguments);var n=decodeURIComponent(window.location.hash);t.layoutActions.parseDeepLinkHash(n)}}}}},wrapComponents:{operation:o.default,OperationTag:i.default}}]};var r=a(n(405)),o=a(n(407)),i=a(n(408));function a(e){return e&&e.__esModule?e:{default:e}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.clearScrollTo=t.scrollToElement=t.readyToScroll=t.parseDeepLinkHash=t.scrollTo=t.show=void 0;var r,o=f(n(22)),i=f(n(17)),a=n(406),u=f(n(935)),s=n(9),l=n(7),c=f(l);function f(e){return e&&e.__esModule?e:{default:e}}var p=t.show=function(e,t){var n=t.getConfigs,r=t.layoutSelectors;return function(){for(var t=arguments.length,o=Array(t),u=0;u-1&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(h.map(function(e){return e.replace(/_/g," ")}),!0)),n.show(h,!0)}(f.indexOf("_")>-1||d.indexOf("_")>-1)&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(u.map(function(e){return e.replace(/_/g," ")}),!0)),n.show(u,!0),n.scrollTo(u)}}},v=t.readyToScroll=function(e,t){return function(n){var r=n.layoutSelectors.getScrollToKey();c.default.is(r,(0,l.fromJS)(e))&&(n.layoutActions.scrollToElement(t),n.layoutActions.clearScrollTo())}},m=t.scrollToElement=function(e,t){return function(n){try{t=t||n.fn.getScrollParent(e),u.default.createScroller(t).to(e)}catch(e){console.error(e)}}},g=t.clearScrollTo=function(){return{type:"layout_clear_scroll"}};t.default={fn:{getScrollParent:function(e,t){var n=document.documentElement,r=getComputedStyle(e),o="absolute"===r.position,i=t?/(auto|scroll|hidden)/:/(auto|scroll)/;if("fixed"===r.position)return n;for(var a=e;a=a.parentElement;)if(r=getComputedStyle(a),(!o||"static"!==r.position)&&i.test(r.overflow+r.overflowY+r.overflowX))return a;return n}},statePlugins:{layout:{actions:{scrollToElement:m,scrollTo:d,clearScrollTo:g,readyToScroll:v,parseDeepLinkHash:h},selectors:{getScrollToKey:function(e){return e.get("scrollToKey")},isShownKeyFromUrlHashArray:function(e,t){var n=(0,i.default)(t,2),r=n[0],o=n[1];return o?["operations",r,o]:r?["operations-tag",r]:[]},urlHashArrayFromIsShownKey:function(e,t){var n=(0,i.default)(t,3),r=n[0],o=n[1],a=n[2];return"operations"==r?[o,a]:"operations-tag"==r?[o]:[]}},reducers:(r={},(0,o.default)(r,"layout_scroll_to",function(e,t){return e.set("scrollToKey",c.default.fromJS(t.payload))}),(0,o.default)(r,"layout_clear_scroll",function(e){return e.delete("scrollToKey")}),r),wrapActions:{show:p}}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.setHash=function(e){return e?history.pushState(null,null,"#"+e):window.location.hash=""}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=l(n(4)),o=l(n(2)),i=l(n(3)),a=l(n(5)),u=l(n(6)),s=l(n(0));l(n(12));function l(e){return e&&e.__esModule?e:{default:e}}t.default=function(e,t){return function(n){function l(){var e,n,i,u;(0,o.default)(this,l);for(var s=arguments.length,c=Array(s),f=0;f",Gt:"≫",gt:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",HARDcy:"Ъ",hardcy:"ъ",hArr:"⇔",harr:"↔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",Hcirc:"Ĥ",hcirc:"ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",Hfr:"ℌ",hfr:"𝔥",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",Hopf:"ℍ",hopf:"𝕙",horbar:"―",HorizontalLine:"─",Hscr:"ℋ",hscr:"𝒽",hslash:"ℏ",Hstrok:"Ħ",hstrok:"ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",Iacute:"Í",iacute:"í",ic:"⁣",Icirc:"Î",icirc:"î",Icy:"И",icy:"и",Idot:"İ",IEcy:"Е",iecy:"е",iexcl:"¡",iff:"⇔",Ifr:"ℑ",ifr:"𝔦",Igrave:"Ì",igrave:"ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",IJlig:"IJ",ijlig:"ij",Im:"ℑ",Imacr:"Ī",imacr:"ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",Int:"∬",int:"∫",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",IOcy:"Ё",iocy:"ё",Iogon:"Į",iogon:"į",Iopf:"𝕀",iopf:"𝕚",Iota:"Ι",iota:"ι",iprod:"⨼",iquest:"¿",Iscr:"ℐ",iscr:"𝒾",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",Itilde:"Ĩ",itilde:"ĩ",Iukcy:"І",iukcy:"і",Iuml:"Ï",iuml:"ï",Jcirc:"Ĵ",jcirc:"ĵ",Jcy:"Й",jcy:"й",Jfr:"𝔍",jfr:"𝔧",jmath:"ȷ",Jopf:"𝕁",jopf:"𝕛",Jscr:"𝒥",jscr:"𝒿",Jsercy:"Ј",jsercy:"ј",Jukcy:"Є",jukcy:"є",Kappa:"Κ",kappa:"κ",kappav:"ϰ",Kcedil:"Ķ",kcedil:"ķ",Kcy:"К",kcy:"к",Kfr:"𝔎",kfr:"𝔨",kgreen:"ĸ",KHcy:"Х",khcy:"х",KJcy:"Ќ",kjcy:"ќ",Kopf:"𝕂",kopf:"𝕜",Kscr:"𝒦",kscr:"𝓀",lAarr:"⇚",Lacute:"Ĺ",lacute:"ĺ",laemptyv:"⦴",lagran:"ℒ",Lambda:"Λ",lambda:"λ",Lang:"⟪",lang:"⟨",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",Larr:"↞",lArr:"⇐",larr:"←",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",lAtail:"⤛",latail:"⤙",late:"⪭",lates:"⪭︀",lBarr:"⤎",lbarr:"⤌",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",Lcaron:"Ľ",lcaron:"ľ",Lcedil:"Ļ",lcedil:"ļ",lceil:"⌈",lcub:"{",Lcy:"Л",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",lE:"≦",le:"≤",LeftAngleBracket:"⟨",LeftArrow:"←",Leftarrow:"⇐",leftarrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",LeftRightArrow:"↔",Leftrightarrow:"⇔",leftrightarrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",lEg:"⪋",leg:"⋚",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",Lfr:"𝔏",lfr:"𝔩",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",LJcy:"Љ",ljcy:"љ",Ll:"⋘",ll:"≪",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",Lmidot:"Ŀ",lmidot:"ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lnE:"≨",lne:"⪇",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",LongLeftArrow:"⟵",Longleftarrow:"⟸",longleftarrow:"⟵",LongLeftRightArrow:"⟷",Longleftrightarrow:"⟺",longleftrightarrow:"⟷",longmapsto:"⟼",LongRightArrow:"⟶",Longrightarrow:"⟹",longrightarrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",Lopf:"𝕃",lopf:"𝕝",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",Lscr:"ℒ",lscr:"𝓁",Lsh:"↰",lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",Lstrok:"Ł",lstrok:"ł",LT:"<",Lt:"≪",lt:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",Map:"⤅",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",Mcy:"М",mcy:"м",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",Mfr:"𝔐",mfr:"𝔪",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",Mopf:"𝕄",mopf:"𝕞",mp:"∓",Mscr:"ℳ",mscr:"𝓂",mstpos:"∾",Mu:"Μ",mu:"μ",multimap:"⊸",mumap:"⊸",nabla:"∇",Nacute:"Ń",nacute:"ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",Ncaron:"Ň",ncaron:"ň",Ncedil:"Ņ",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",Ncy:"Н",ncy:"н",ndash:"–",ne:"≠",nearhk:"⤤",neArr:"⇗",nearr:"↗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",Nfr:"𝔑",nfr:"𝔫",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",nGt:"≫⃒",ngt:"≯",ngtr:"≯",nGtv:"≫̸",nhArr:"⇎",nharr:"↮",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",NJcy:"Њ",njcy:"њ",nlArr:"⇍",nlarr:"↚",nldr:"‥",nlE:"≦̸",nle:"≰",nLeftarrow:"⇍",nleftarrow:"↚",nLeftrightarrow:"⇎",nleftrightarrow:"↮",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nLt:"≪⃒",nlt:"≮",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",Nopf:"ℕ",nopf:"𝕟",Not:"⫬",not:"¬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrArr:"⇏",nrarr:"↛",nrarrc:"⤳̸",nrarrw:"↝̸",nRightarrow:"⇏",nrightarrow:"↛",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",Nscr:"𝒩",nscr:"𝓃",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",Ntilde:"Ñ",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",Nu:"Ν",nu:"ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nVDash:"⊯",nVdash:"⊮",nvDash:"⊭",nvdash:"⊬",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwArr:"⇖",nwarr:"↖",nwarrow:"↖",nwnear:"⤧",Oacute:"Ó",oacute:"ó",oast:"⊛",ocir:"⊚",Ocirc:"Ô",ocirc:"ô",Ocy:"О",ocy:"о",odash:"⊝",Odblac:"Ő",odblac:"ő",odiv:"⨸",odot:"⊙",odsold:"⦼",OElig:"Œ",oelig:"œ",ofcir:"⦿",Ofr:"𝔒",ofr:"𝔬",ogon:"˛",Ograve:"Ò",ograve:"ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",Omacr:"Ō",omacr:"ō",Omega:"Ω",omega:"ω",Omicron:"Ο",omicron:"ο",omid:"⦶",ominus:"⊖",Oopf:"𝕆",oopf:"𝕠",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",Or:"⩔",or:"∨",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",Oscr:"𝒪",oscr:"ℴ",Oslash:"Ø",oslash:"ø",osol:"⊘",Otilde:"Õ",otilde:"õ",Otimes:"⨷",otimes:"⊗",otimesas:"⨶",Ouml:"Ö",ouml:"ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",Pcy:"П",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",Pfr:"𝔓",pfr:"𝔭",Phi:"Φ",phi:"φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",Pi:"Π",pi:"π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",Popf:"ℙ",popf:"𝕡",pound:"£",Pr:"⪻",pr:"≺",prap:"⪷",prcue:"≼",prE:"⪳",pre:"⪯",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",Prime:"″",prime:"′",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",Pscr:"𝒫",pscr:"𝓅",Psi:"Ψ",psi:"ψ",puncsp:" ",Qfr:"𝔔",qfr:"𝔮",qint:"⨌",Qopf:"ℚ",qopf:"𝕢",qprime:"⁗",Qscr:"𝒬",qscr:"𝓆",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",QUOT:'"',quot:'"',rAarr:"⇛",race:"∽̱",Racute:"Ŕ",racute:"ŕ",radic:"√",raemptyv:"⦳",Rang:"⟫",rang:"⟩",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",Rarr:"↠",rArr:"⇒",rarr:"→",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",Rarrtl:"⤖",rarrtl:"↣",rarrw:"↝",rAtail:"⤜",ratail:"⤚",ratio:"∶",rationals:"ℚ",RBarr:"⤐",rBarr:"⤏",rbarr:"⤍",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",Rcaron:"Ř",rcaron:"ř",Rcedil:"Ŗ",rcedil:"ŗ",rceil:"⌉",rcub:"}",Rcy:"Р",rcy:"р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",REG:"®",reg:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",Rfr:"ℜ",rfr:"𝔯",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",Rho:"Ρ",rho:"ρ",rhov:"ϱ",RightAngleBracket:"⟩",RightArrow:"→",Rightarrow:"⇒",rightarrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",Ropf:"ℝ",ropf:"𝕣",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",Rscr:"ℛ",rscr:"𝓇",Rsh:"↱",rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",Sacute:"Ś",sacute:"ś",sbquo:"‚",Sc:"⪼",sc:"≻",scap:"⪸",Scaron:"Š",scaron:"š",sccue:"≽",scE:"⪴",sce:"⪰",Scedil:"Ş",scedil:"ş",Scirc:"Ŝ",scirc:"ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",Scy:"С",scy:"с",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",seArr:"⇘",searr:"↘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",Sfr:"𝔖",sfr:"𝔰",sfrown:"⌢",sharp:"♯",SHCHcy:"Щ",shchcy:"щ",SHcy:"Ш",shcy:"ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",Sigma:"Σ",sigma:"σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",SOFTcy:"Ь",softcy:"ь",sol:"/",solb:"⧄",solbar:"⌿",Sopf:"𝕊",sopf:"𝕤",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",Square:"□",square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",Sscr:"𝒮",sscr:"𝓈",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",Star:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",Sub:"⋐",sub:"⊂",subdot:"⪽",subE:"⫅",sube:"⊆",subedot:"⫃",submult:"⫁",subnE:"⫋",subne:"⊊",subplus:"⪿",subrarr:"⥹",Subset:"⋐",subset:"⊂",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",Sum:"∑",sum:"∑",sung:"♪",Sup:"⋑",sup:"⊃",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supE:"⫆",supe:"⊇",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supnE:"⫌",supne:"⊋",supplus:"⫀",Supset:"⋑",supset:"⊃",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swArr:"⇙",swarr:"↙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",Tau:"Τ",tau:"τ",tbrk:"⎴",Tcaron:"Ť",tcaron:"ť",Tcedil:"Ţ",tcedil:"ţ",Tcy:"Т",tcy:"т",tdot:"⃛",telrec:"⌕",Tfr:"𝔗",tfr:"𝔱",there4:"∴",Therefore:"∴",therefore:"∴",Theta:"Θ",theta:"θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",THORN:"Þ",thorn:"þ",Tilde:"∼",tilde:"˜",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",Topf:"𝕋",topf:"𝕥",topfork:"⫚",tosa:"⤩",tprime:"‴",TRADE:"™",trade:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",Tscr:"𝒯",tscr:"𝓉",TScy:"Ц",tscy:"ц",TSHcy:"Ћ",tshcy:"ћ",Tstrok:"Ŧ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",Uacute:"Ú",uacute:"ú",Uarr:"↟",uArr:"⇑",uarr:"↑",Uarrocir:"⥉",Ubrcy:"Ў",ubrcy:"ў",Ubreve:"Ŭ",ubreve:"ŭ",Ucirc:"Û",ucirc:"û",Ucy:"У",ucy:"у",udarr:"⇅",Udblac:"Ű",udblac:"ű",udhar:"⥮",ufisht:"⥾",Ufr:"𝔘",ufr:"𝔲",Ugrave:"Ù",ugrave:"ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",Umacr:"Ū",umacr:"ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",uogon:"ų",Uopf:"𝕌",uopf:"𝕦",UpArrow:"↑",Uparrow:"⇑",uparrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",UpDownArrow:"↕",Updownarrow:"⇕",updownarrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",Upsi:"ϒ",upsi:"υ",upsih:"ϒ",Upsilon:"Υ",upsilon:"υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",Uring:"Ů",uring:"ů",urtri:"◹",Uscr:"𝒰",uscr:"𝓊",utdot:"⋰",Utilde:"Ũ",utilde:"ũ",utri:"▵",utrif:"▴",uuarr:"⇈",Uuml:"Ü",uuml:"ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",vArr:"⇕",varr:"↕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",Vbar:"⫫",vBar:"⫨",vBarv:"⫩",Vcy:"В",vcy:"в",VDash:"⊫",Vdash:"⊩",vDash:"⊨",vdash:"⊢",Vdashl:"⫦",Vee:"⋁",vee:"∨",veebar:"⊻",veeeq:"≚",vellip:"⋮",Verbar:"‖",verbar:"|",Vert:"‖",vert:"|",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"𝔙",vfr:"𝔳",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",Vopf:"𝕍",vopf:"𝕧",vprop:"∝",vrtri:"⊳",Vscr:"𝒱",vscr:"𝓋",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",Vvdash:"⊪",vzigzag:"⦚",Wcirc:"Ŵ",wcirc:"ŵ",wedbar:"⩟",Wedge:"⋀",wedge:"∧",wedgeq:"≙",weierp:"℘",Wfr:"𝔚",wfr:"𝔴",Wopf:"𝕎",wopf:"𝕨",wp:"℘",wr:"≀",wreath:"≀",Wscr:"𝒲",wscr:"𝓌",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",Xfr:"𝔛",xfr:"𝔵",xhArr:"⟺",xharr:"⟷",Xi:"Ξ",xi:"ξ",xlArr:"⟸",xlarr:"⟵",xmap:"⟼",xnis:"⋻",xodot:"⨀",Xopf:"𝕏",xopf:"𝕩",xoplus:"⨁",xotime:"⨂",xrArr:"⟹",xrarr:"⟶",Xscr:"𝒳",xscr:"𝓍",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",Yacute:"Ý",yacute:"ý",YAcy:"Я",yacy:"я",Ycirc:"Ŷ",ycirc:"ŷ",Ycy:"Ы",ycy:"ы",yen:"¥",Yfr:"𝔜",yfr:"𝔶",YIcy:"Ї",yicy:"ї",Yopf:"𝕐",yopf:"𝕪",Yscr:"𝒴",yscr:"𝓎",YUcy:"Ю",yucy:"ю",Yuml:"Ÿ",yuml:"ÿ",Zacute:"Ź",zacute:"ź",Zcaron:"Ž",zcaron:"ž",Zcy:"З",zcy:"з",Zdot:"Ż",zdot:"ż",zeetrf:"ℨ",ZeroWidthSpace:"​",Zeta:"Ζ",zeta:"ζ",Zfr:"ℨ",zfr:"𝔷",ZHcy:"Ж",zhcy:"ж",zigrarr:"⇝",Zopf:"ℤ",zopf:"𝕫",Zscr:"𝒵",zscr:"𝓏",zwj:"‍",zwnj:"‌"}},function(e,t,n){"use strict";var r=n(419),o=n(27).unescapeMd;e.exports=function(e,t){var n,i,a,u=t,s=e.posMax;if(60===e.src.charCodeAt(t)){for(t++;t8&&n<14);)if(92===n&&t+11)break;if(41===n&&--i<0)break;t++}return u!==t&&(a=o(e.src.slice(u,t)),!!e.parser.validateLink(a)&&(e.linkContent=a,e.pos=t,!0))}},function(e,t,n){"use strict";var r=n(27).replaceEntities;e.exports=function(e){var t=r(e);try{t=decodeURI(t)}catch(e){}return encodeURI(t)}},function(e,t,n){"use strict";var r=n(27).unescapeMd;e.exports=function(e,t){var n,o=t,i=e.posMax,a=e.src.charCodeAt(t);if(34!==a&&39!==a&&40!==a)return!1;for(t++,40===a&&(a=41);t1?r-1:0),i=1;i1?t-1:0),r=1;r0){var S=a("JsonSchemaForm"),C=a("ParameterExt"),k=w.get("properties",(0,o.OrderedMap)());return n=o.Map.isMap(n)?n:(0,o.OrderedMap)(),r.default.createElement("div",{className:"table-container"},y&&r.default.createElement(h,{source:y}),r.default.createElement("table",null,r.default.createElement("tbody",null,k.map(function(e,t){var u=g?(0,i.getCommonExtensions)(e):null,s=w.get("required",(0,o.List)()).includes(t),c=e.get("type"),p=e.get("format"),v=e.get("description"),m=n.get(t),y=e.get("default")||e.get("example")||"";""===y&&"object"===c&&(y=(0,i.getSampleSchema)(e,!1,{includeWriteOnly:!0})),"string"!=typeof y&&"object"===c&&(y=(0,i.stringify)(y));var b="string"===c&&("binary"===p||"base64"===p);return r.default.createElement("tr",{key:t,className:"parameters","data-property-name":t},r.default.createElement("td",{className:"col parameters-col_name"},r.default.createElement("div",{className:s?"parameter__name required":"parameter__name"},t,s?r.default.createElement("span",{style:{color:"red"}}," *"):null),r.default.createElement("div",{className:"parameter__type"},c,p&&r.default.createElement("span",{className:"prop-format"},"($",p,")"),g&&u.size?u.map(function(e,t){return r.default.createElement(C,{key:t+"-"+e,xKey:t,xVal:e})}):null),r.default.createElement("div",{className:"parameter__deprecated"},e.get("deprecated")?"deprecated":null)),r.default.createElement("td",{className:"col parameters-col_description"},r.default.createElement(h,{source:v}),f?r.default.createElement("div",null,r.default.createElement(S,{fn:l,dispatchInitialValue:!b,schema:e,description:t,getComponent:a,value:void 0===m?y:m,onChange:function(e){d(e,[t])}})):null))}))))}return r.default.createElement("div",null,y&&r.default.createElement(h,{source:y}),r.default.createElement(v,{getComponent:a,getConfigs:u,specSelectors:s,expandDepth:1,isExecute:f,schema:_.get("schema"),specPath:p.push("content",c),example:r.default.createElement(m,{requestBody:t,onChange:d,mediaType:c,getComponent:a,isExecute:f,specSelectors:s})}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=f(n(41)),o=f(n(4)),i=f(n(2)),a=f(n(3)),u=f(n(5)),s=f(n(6)),l=n(0),c=f(l);f(n(1)),f(n(12));function f(e){return e&&e.__esModule?e:{default:e}}var p=function(e){function t(){return(0,i.default)(this,t),(0,u.default)(this,(t.__proto__||(0,o.default)(t)).apply(this,arguments))}return(0,s.default)(t,e),(0,a.default)(t,[{key:"render",value:function(){var e=this.props,t=e.link,n=e.name,o=(0,e.getComponent)("Markdown"),i=t.get("operationId")||t.get("operationRef"),a=t.get("parameters")&&t.get("parameters").toJS(),u=t.get("description");return c.default.createElement("div",{style:{marginBottom:"1.5em"}},c.default.createElement("div",{style:{marginBottom:".5em"}},c.default.createElement("b",null,c.default.createElement("code",null,n)),u?c.default.createElement(o,{source:u}):null),c.default.createElement("pre",null,"Operation `",i,"`",c.default.createElement("br",null),c.default.createElement("br",null),"Parameters ",function(e,t){if("string"!=typeof t)return"";return t.split("\n").map(function(t,n){return n>0?Array(e+1).join(" ")+t:t}).join("\n")}(0,(0,r.default)(a,null,2))||"{}",c.default.createElement("br",null)))}}]),t}(l.Component);t.default=p},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=c(n(4)),o=c(n(2)),i=c(n(3)),a=c(n(5)),u=c(n(6)),s=c(n(0)),l=n(7);c(n(1)),c(n(12));function c(e){return e&&e.__esModule?e:{default:e}}var f=function(e){function t(){var e,n,i,u;(0,o.default)(this,t);for(var s=arguments.length,l=Array(s),c=0;c=e.length?(this._t=void 0,o(1)):o(0,"keys"==t?n:"values"==t?e[n]:[n,e[n]])},"values"),i.Arguments=i.Array,r("keys"),r("values"),r("entries")},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){"use strict";var r=n(160),o=n(95),i=n(97),a={};n(50)(a,n(19)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=r(a,{next:o(1,n)}),i(e,t+" Iterator")}},function(e,t,n){var r=n(40),o=n(36),i=n(96);e.exports=n(44)?Object.defineProperties:function(e,t){o(e);for(var n,a=i(t),u=a.length,s=0;u>s;)r.f(e,n=a[s++],t[n]);return e}},function(e,t,n){var r=n(71),o=n(115),i=n(455);e.exports=function(e){return function(t,n,a){var u,s=r(t),l=o(s.length),c=i(a,l);if(e&&n!=n){for(;l>c;)if((u=s[c++])!=u)return!0}else for(;l>c;c++)if((e||c in s)&&s[c]===n)return e||c||0;return!e&&-1}}},function(e,t,n){var r=n(161),o=Math.max,i=Math.min;e.exports=function(e,t){return(e=r(e))<0?o(e+t,0):i(e,t)}},function(e,t,n){var r=n(161),o=n(156);e.exports=function(e){return function(t,n){var i,a,u=String(o(t)),s=r(n),l=u.length;return s<0||s>=l?e?"":void 0:(i=u.charCodeAt(s))<55296||i>56319||s+1===l||(a=u.charCodeAt(s+1))<56320||a>57343?e?u.charAt(s):i:e?u.slice(s,s+2):a-56320+(i-55296<<10)+65536}}},function(e,t,n){var r=n(36),o=n(165);e.exports=n(15).getIterator=function(e){var t=o(e);if("function"!=typeof t)throw TypeError(e+" is not iterable!");return r(t.call(e))}},function(e,t,n){n(459),n(245),n(470),n(474),n(485),n(486),e.exports=n(61).Promise},function(e,t,n){"use strict";var r=n(167),o={};o[n(18)("toStringTag")]="z",o+""!="[object z]"&&n(73)(Object.prototype,"toString",function(){return"[object "+r(this)+"]"},!0)},function(e,t,n){e.exports=!n(100)&&!n(101)(function(){return 7!=Object.defineProperty(n(169)("div"),"a",{get:function(){return 7}}).a})},function(e,t,n){var r=n(74);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){"use strict";var r=n(463),o=n(244),i=n(171),a={};n(59)(a,n(18)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=r(a,{next:o(1,n)}),i(e,t+" Iterator")}},function(e,t,n){var r=n(60),o=n(464),i=n(251),a=n(170)("IE_PROTO"),u=function(){},s=function(){var e,t=n(169)("iframe"),r=i.length;for(t.style.display="none",n(252).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + + diff --git a/ygot-modified-files/ygot.patch b/ygot-modified-files/ygot.patch new file mode 100644 index 0000000000..01aaa1a5fb --- /dev/null +++ b/ygot-modified-files/ygot.patch @@ -0,0 +1,752 @@ +diff -ruN ygot-dir-orig/ygot/util/debug.go ygot-dir/ygot/util/debug.go +--- ygot-dir-orig/ygot/util/debug.go 2019-10-24 12:30:06.378629000 -0700 ++++ ygot-dir/ygot/util/debug.go 2019-10-24 12:31:25.059277000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -53,6 +56,14 @@ + fmt.Println(globalIndent + out) + } + ++func IsDebugLibraryEnabled () bool { ++ return debugLibrary ++} ++ ++func IsDebugSchemaEnabled () bool { ++ return debugSchema ++} ++ + // DbgSchema prints v if the package global variable debugSchema is set. + // v has the same format as Printf. + func DbgSchema(v ...interface{}) { +@@ -177,6 +188,9 @@ + + // YangTypeToDebugString returns a debug string representation of a YangType. + func YangTypeToDebugString(yt *yang.YangType) string { ++ if !debugLibrary { ++ return "" ++ } + out := fmt.Sprintf("(TypeKind: %s", yang.TypeKindToName[yt.Kind]) + if len(yt.Pattern) != 0 { + out += fmt.Sprintf(", Pattern: %s", strings.Join(yt.Pattern, " or ")) +diff -ruN ygot-dir-orig/ygot/util/reflect.go ygot-dir/ygot/util/reflect.go +--- ygot-dir-orig/ygot/util/reflect.go 2019-10-24 12:30:06.403914000 -0700 ++++ ygot-dir/ygot/util/reflect.go 2019-10-24 12:31:25.063424000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -196,8 +199,10 @@ + + // InsertIntoMap inserts value with key into parent which must be a map. + func InsertIntoMap(parentMap interface{}, key interface{}, value interface{}) error { +- DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", +- parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ if debugLibrary { ++ DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", ++ parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ } + + v := reflect.ValueOf(parentMap) + t := reflect.TypeOf(parentMap) +@@ -288,7 +293,7 @@ + n = reflect.Zero(ft.Type) + } + +- if !isFieldTypeCompatible(ft, n) { ++ if !isFieldTypeCompatible(ft, n) && !IsValueTypeCompatible(ft.Type, v) { + return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) + } + +diff -ruN ygot-dir-orig/ygot/util/schema.go ygot-dir/ygot/util/schema.go +--- ygot-dir-orig/ygot/util/schema.go 2019-10-24 12:30:06.417942000 -0700 ++++ ygot-dir/ygot/util/schema.go 2019-10-24 12:31:25.069042000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -22,6 +25,8 @@ + "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 +73,22 @@ + + // 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 +@@ -233,7 +243,9 @@ + found := true + DbgSchema("traversing schema Dirs...") + for ; len(p) > 0; p = p[1:] { +- DbgSchema("/%s", p[0]) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("/%s", p[0]) ++ } + var ok bool + s, ok = s.Dir[p[0]] + if !ok { +@@ -261,10 +273,13 @@ + return nil, nil + } + entries := FindFirstNonChoiceOrCase(schema) +- +- DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for pe, entry := range entries { +- DbgSchema("%s ? ", pe) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("%s ? ", pe) ++ } + if pe == p[0] { + DbgSchema(" - match\n") + return entry, nil +diff -ruN ygot-dir-orig/ygot/ytypes/container.go ygot-dir/ygot/ytypes/container.go +--- ygot-dir-orig/ygot/ytypes/container.go 2019-10-24 12:30:07.700737000 -0700 ++++ ygot-dir/ygot/ytypes/container.go 2019-10-24 12:31:26.682226000 -0700 +@@ -12,12 +12,15 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( + "fmt" + "reflect" +- ++ + "github.com/kylelemons/godebug/pretty" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/util" +@@ -71,7 +74,7 @@ + if errs := Validate(cschema, fieldValue); errs != nil { + errors = util.AppendErrs(errors, util.PrefixErrors(errs, cschema.Path())) + } +- case !util.IsValueNilOrDefault(structElems.Field(i).Interface()): ++ case !structElems.Field(i).IsNil(): + // Either an element in choice schema subtree, or bad field. + // If the former, it will be found in the choice check below. + extraFields[fieldName] = nil +@@ -217,7 +220,10 @@ + } + } + +- util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ } ++ + return nil + } + +diff -ruN ygot-dir-orig/ygot/ytypes/leaf.go ygot-dir/ygot/ytypes/leaf.go +--- ygot-dir-orig/ygot/ytypes/leaf.go 2019-10-24 12:30:07.705496000 -0700 ++++ ygot-dir/ygot/ytypes/leaf.go 2019-10-24 12:31:26.691433000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -79,7 +82,7 @@ + + switch ykind { + case yang.Ybinary: +- return util.NewErrs(validateBinary(schema, value)) ++ return util.NewErrs(validateBinary(schema, rv)) + case yang.Ybits: + return nil + // TODO(mostrowski): restore when representation is decided. +@@ -252,7 +255,7 @@ + // during validation against each matching schema otherwise. + func validateMatchingSchemas(schema *yang.Entry, value interface{}) util.Errors { + var errors []error +- ss := findMatchingSchemasInUnion(schema.Type, value) ++ ss := findMatchingSchemasInUnion(schema, schema.Type, value) + var kk []yang.TypeKind + for _, s := range ss { + kk = append(kk, s.Type.Kind) +@@ -283,17 +286,25 @@ + // findMatchingSchemasInUnion returns all schemas in the given union type, + // including those within nested unions, that match the Go type of value. + // value must not be nil. +-func findMatchingSchemasInUnion(ytype *yang.YangType, value interface{}) []*yang.Entry { ++func findMatchingSchemasInUnion(schema *yang.Entry, ytype *yang.YangType, value interface{}) []*yang.Entry { + var matches []*yang.Entry + + util.DbgPrint("findMatchingSchemasInUnion for type %T, kind %s", value, reflect.TypeOf(value).Kind()) + for _, t := range ytype.Type { + if t.Kind == yang.Yunion { + // Recursively check all union types within this union. +- matches = append(matches, findMatchingSchemasInUnion(t, value)...) ++ matches = append(matches, findMatchingSchemasInUnion(schema, t, value)...) + continue + } + ++ if t.Kind == yang.Yleafref { ++ ns, err := findLeafRefSchema(schema, t.Path) ++ if err != nil { ++ log.Warningf("not found base Go type for type %v in union value %s", t.Kind, util.ValueStr(value)) ++ continue ++ } ++ t = ns.Type ++ } + ybt := yangBuiltinTypeToGoType(t.Kind) + if reflect.ValueOf(value).Kind() == reflect.Ptr { + ybt = ygot.ToPtr(yangBuiltinTypeToGoType(t.Kind)) +@@ -418,12 +429,10 @@ + return nil + } + +-// YANGEmpty is a derived type which is used to represent the YANG empty type. ++// YANGEmpty is a derived type which is used to represent the YANG ++// empty type. + type YANGEmpty bool + +-// Binary is a derived type which is used to represent the YANG binary type. +-type Binary []byte +- + // unmarshalLeaf unmarshals a scalar value (determined by json.Unmarshal) into + // the parent containing the leaf. + // schema points to the schema for the leaf type. +@@ -720,7 +729,9 @@ + return nil, fmt.Errorf("%s ΛEnumTypes function returned wrong type %T, want map[string][]reflect.Type", t, ei) + } + +- util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ } + + return enumTypesMap[absoluteSchemaDataPath(schema)], nil + } +diff -ruN ygot-dir-orig/ygot/ytypes/list.go ygot-dir/ygot/ytypes/list.go +--- ygot-dir-orig/ygot/ytypes/list.go 2019-10-24 12:30:07.712731000 -0700 ++++ ygot-dir/ygot/ytypes/list.go 2019-10-24 12:31:26.696852000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -217,6 +220,9 @@ + 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 +238,7 @@ + } + } + ++ schema.IsSchemaValidated = true + return nil + } + +@@ -282,10 +289,10 @@ + 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) + +@@ -350,7 +357,9 @@ + return err + } + } +- util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ } + + return nil + } +@@ -388,17 +397,96 @@ + if err != nil { + return err + } +- + fv := val.Elem().FieldByName(fn) + ft := fv.Type() + if util.IsValuePtr(fv) { + ft = ft.Elem() + } +- +- nv, err := StringToType(ft, fieldVal) ++ sf, ok := val.Elem().Type().FieldByName(fn) ++ if ok == false { ++ return fmt.Errorf("Field %s not present in the struct %s", fn, val.Elem()) ++ } ++ cschema, err := childSchema(schema, sf) + if err != nil { + return err + } ++ keyLeafKind := cschema.Type.Kind ++ if keyLeafKind == yang.Yleafref { ++ lrfschema, err := resolveLeafRef(cschema) ++ if err != nil { ++ return err ++ } ++ keyLeafKind = lrfschema.Type.Kind ++ } ++ ++ var nv reflect.Value ++ if keyLeafKind == yang.Yunion && strings.HasSuffix(keyT.Name(), "_Union") { ++ sks, err := getUnionKindsNotEnums(cschema) ++ if err != nil { ++ return err ++ } ++ for _, sk := range sks { ++ gv, err := StringToType(reflect.TypeOf(yangBuiltinTypeToGoType(sk)), fieldVal) ++ if err == nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{gv}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ } ++ ++ if nv.IsValid() == false { ++ ets, err := schemaToEnumTypes(cschema, elmT) ++ if err != nil { ++ return err ++ } ++ for _, et := range ets { ++ ev, err := castToEnumValue(et, fieldVal) ++ if err != nil { ++ return err ++ } ++ if ev != nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{reflect.ValueOf(ev)}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ fmt.Errorf("could not unmarshal %v into enum type: %s\n", fieldVal, err) ++ } ++ if nv.IsValid() == false { ++ return fmt.Errorf("could not create the value type for the field name %s with the value %s", fn, fieldVal) ++ } ++ } ++ } else { ++ nv, err = StringToType(ft, fieldVal) ++ if err != nil { ++ return err ++ } ++ } + return util.InsertIntoStruct(val.Interface(), fn, nv.Interface()) + } + +@@ -494,6 +582,9 @@ + } + + // TODO(yusufsn): When the key is a leafref, its target should be filled out. ++ if (len(keys) == 0) { ++ return nil, nil ++ } + mapVal, err := makeValForInsert(schema, root, keys) + if err != nil { + return nil, fmt.Errorf("failed to create map value for insert, root %T, keys %v: %v", root, keys, err) +diff -ruN ygot-dir-orig/ygot/ytypes/node.go ygot-dir/ygot/ytypes/node.go +--- ygot-dir-orig/ygot/ytypes/node.go 2019-10-24 12:30:07.727365000 -0700 ++++ ygot-dir/ygot/ytypes/node.go 2019-10-24 12:31:26.701328000 -0700 +@@ -12,17 +12,19 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + 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 +131,16 @@ + 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 +@@ -286,6 +298,11 @@ + if err != nil { + return nil, err + } ++ ++ if (key == nil) { ++ return []*TreeNode{{Path: traversedPath,Schema: schema,Data: root,}}, nil ++ } ++ + nodes, err := retrieveNode(schema, rv.MapIndex(reflect.ValueOf(key)).Interface(), util.PopGNMIPath(path), appendElem(traversedPath, path.GetElem()[0]), args) + if err != nil { + return nil, err +diff -ruN ygot-dir-orig/ygot/ytypes/string_type.go ygot-dir/ygot/ytypes/string_type.go +--- ygot-dir-orig/ygot/ytypes/string_type.go 2019-10-24 12:30:07.734288000 -0700 ++++ ygot-dir/ygot/ytypes/string_type.go 2019-10-24 12:31:26.705649000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -23,6 +26,8 @@ + "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 +@@ -48,10 +53,18 @@ + + // Check that the value satisfies any regex patterns. + for _, p := range schema.Type.Pattern { +- r, err := regexp.Compile(fixYangRegexp(p)) +- if err != nil { +- return err ++ 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) { +@@ -105,13 +118,29 @@ + 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 { +- if _, err := regexp.Compile(fixYangRegexp(p)); err != nil { +- return fmt.Errorf("error generating regexp %s %v for schema %s", p, err, schema.Name) +- } ++ _, 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 ++ } ++ } + } + +- return validateLengthSchema(schema) ++ if err = validateLengthSchema(schema); err == nil { ++ schema.IsSchemaValidated = true ++ } ++ ++ return err + } + + // fixYangRegexp takes a pattern regular expression from a YANG module and +diff -ruN ygot-dir-orig/ygot/ytypes/unmarshal.go ygot-dir/ygot/ytypes/unmarshal.go +--- ygot-dir-orig/ygot/ytypes/unmarshal.go 2019-10-24 12:30:07.753024000 -0700 ++++ ygot-dir/ygot/ytypes/unmarshal.go 2019-10-24 12:31:26.710027000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -73,7 +76,10 @@ + if schema == nil { + return fmt.Errorf("nil schema for parent type %T, value %v (%T)", parent, value, value) + } +- util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ ++ if (util.IsDebugLibraryEnabled()) { ++ util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ } + + if enc == GNMIEncoding && !(schema.IsLeaf() || schema.IsLeafList()) { + return errors.New("unmarshalling a non leaf node isn't supported in GNMIEncoding mode") +diff -ruN ygot-dir-orig/ygot/ytypes/util_schema.go ygot-dir/ygot/ytypes/util_schema.go +--- ygot-dir-orig/ygot/ytypes/util_schema.go 2019-10-24 12:30:07.763728000 -0700 ++++ ygot-dir/ygot/ytypes/util_schema.go 2019-10-24 12:31:26.715104000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -23,6 +26,8 @@ + "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,8 +142,16 @@ + // 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) { +- pathTag, _ := f.Tag.Lookup("path") +- util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) ++ 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) ++ } + p, err := pathToSchema(f) + if err != nil { + return nil, err +@@ -168,6 +181,7 @@ + } + if foundSchema { + util.DbgSchema(" - found\n") ++ schema.ChildSchemaCache[f.Tag] = childSchema + return childSchema, nil + } + util.DbgSchema(" - not found\n") +@@ -183,21 +197,25 @@ + // 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) +- +- util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if util.IsDebugSchemaEnabled() { ++ util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for name, entry := range entries { + util.DbgSchema("%s ? ", name) + + 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 + } + +@@ -239,25 +257,32 @@ + // 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 +diff -ruN ygot-dir-orig/ygot/ytypes/validate.go ygot-dir/ygot/ytypes/validate.go +--- ygot-dir-orig/ygot/ytypes/validate.go 2019-10-24 12:30:07.778829000 -0700 ++++ ygot-dir/ygot/ytypes/validate.go 2019-10-24 12:31:26.719650000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -74,7 +77,7 @@ + errs = ValidateLeafRefData(schema, value, leafrefOpt) + } + +- util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStr(value), value, schema.Name) ++ util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStrDebug(value), value, schema.Name) + + switch { + case schema.IsLeaf():