diff --git a/.gitignore b/.gitignore index d70506f2..049e7ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ vendor src cvl translib +__pycache__ diff --git a/Makefile b/Makefile index a1aed09c..0d370d8a 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,23 @@ export PATH := $(PATH):$(GOPATH)/bin INSTALL := /usr/bin/install DBDIR := /var/run/redis/sonic-db/ GO ?= /usr/local/go/bin/go -TOP_DIR := $(abspath ..) -MGMT_COMMON_DIR := $(TOP_DIR)/sonic-mgmt-common +TOPDIR := $(abspath .) +MGMT_COMMON_DIR := $(TOPDIR)/../sonic-mgmt-common +BUILD_BASE := build BUILD_DIR := build/bin +BUILD_GNOI_YANG_DIR := $(BUILD_BASE)/gnoi_yang +BUILD_GNOI_YANG_PROTO_DIR := $(BUILD_GNOI_YANG_DIR)/proto +BUILD_GNOI_YANG_SERVER_DIR := $(BUILD_GNOI_YANG_DIR)/server +BUILD_GNOI_YANG_CLIENT_DIR := $(BUILD_GNOI_YANG_DIR)/client +GNOI_YANG := $(BUILD_GNOI_YANG_PROTO_DIR)/.gnoi_yang_done +TOOLS_DIR := $(TOPDIR)/tools +PYANG_PLUGIN_DIR := $(TOOLS_DIR)/pyang_plugins +PYANG ?= pyang export CVL_SCHEMA_PATH := $(MGMT_COMMON_DIR)/build/cvl/schema + +API_YANGS=$(shell find $(MGMT_COMMON_DIR)/build/yang -name '*.yang' -not -path '*/sonic/*' -not -path '*/annotations/*') +SONIC_YANGS=$(shell find $(MGMT_COMMON_DIR)/models/yang/sonic -name '*.yang') + export GOBIN := $(abspath $(BUILD_DIR)) export PATH := $(PATH):$(GOBIN):$(shell dirname $(GO)) export CGO_LDFLAGS := -lswsscommon -lhiredis @@ -40,7 +53,7 @@ all: sonic-gnmi go.mod: $(GO) mod init github.com/sonic-net/sonic-gnmi -$(GO_DEPS): go.mod $(PATCHES) swsscommon_wrap +$(GO_DEPS): go.mod $(PATCHES) swsscommon_wrap $(GNOI_YANG) $(GO) mod vendor $(GO) mod download golang.org/x/crypto@v0.0.0-20191206172530-e9b2fee46413 $(GO) mod download github.com/jipanyang/gnxi@v0.0.0-20181221084354-f0a90cca6fd0 @@ -82,6 +95,9 @@ endif $(GO) install -mod=vendor github.com/openconfig/gnmi/cmd/gnmi_cli $(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/gnoi_client $(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/gnmi_dump + $(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/build/gnoi_yang/client/gnoi_openconfig_client + $(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/build/gnoi_yang/client/gnoi_sonic_client + endif swsscommon_wrap: @@ -91,11 +107,46 @@ swsscommon_wrap: PROTOC_PATH := $(PATH):$(GOBIN) PROTOC_OPTS := -I$(CURDIR)/vendor -I/usr/local/include -I/usr/include +PROTOC_OPTS_WITHOUT_VENDOR := -I/usr/local/include -I/usr/include # Generate following go & grpc bindings using teh legacy protoc-gen-go PROTO_GO_BINDINGS += proto/sonic_internal.pb.go PROTO_GO_BINDINGS += proto/gnoi/sonic_debug.pb.go +$(BUILD_GNOI_YANG_PROTO_DIR)/.proto_api_done: $(API_YANGS) + @echo "+++++ Generating PROTOBUF files for API Yang modules; +++++" + $(PYANG) \ + -f proto \ + --proto-outdir $(BUILD_GNOI_YANG_PROTO_DIR) \ + --plugindir $(PYANG_PLUGIN_DIR) \ + --server-rpc-outdir $(BUILD_GNOI_YANG_SERVER_DIR) \ + --client-rpc-outdir $(BUILD_GNOI_YANG_CLIENT_DIR) \ + -p $(MGMT_COMMON_DIR)/build/yang/common:$(MGMT_COMMON_DIR)/build/yang/extensions \ + $(MGMT_COMMON_DIR)/build/yang/*.yang $(MGMT_COMMON_DIR)/build/yang/extensions/*.yang + @echo "+++++ Generation of protobuf files for API Yang modules completed +++++" + touch $@ + +$(BUILD_GNOI_YANG_PROTO_DIR)/.proto_sonic_done: $(SONIC_YANGS) + @echo "+++++ Generating PROTOBUF files for SONiC Yang modules; +++++" + $(PYANG) \ + -f proto \ + --proto-outdir $(BUILD_GNOI_YANG_PROTO_DIR) \ + --plugindir $(PYANG_PLUGIN_DIR) \ + --server-rpc-outdir $(BUILD_GNOI_YANG_SERVER_DIR) \ + --client-rpc-outdir $(BUILD_GNOI_YANG_CLIENT_DIR) \ + -p $(MGMT_COMMON_DIR)/build/yang/common:$(MGMT_COMMON_DIR)/build/yang/sonic/common \ + $(MGMT_COMMON_DIR)/build/yang/sonic/*.yang + @echo "+++++ Generation of protobuf files for SONiC Yang modules completed +++++" + touch $@ + +$(GNOI_YANG): $(BUILD_GNOI_YANG_PROTO_DIR)/.proto_api_done $(BUILD_GNOI_YANG_PROTO_DIR)/.proto_sonic_done + @echo "+++++ Compiling PROTOBUF files; +++++" + $(GO) install github.com/gogo/protobuf/protoc-gen-gofast + @mkdir -p $(@D) + $(foreach file, $(wildcard $(BUILD_GNOI_YANG_PROTO_DIR)/*/*.proto), PATH=$(PROTOC_PATH) protoc -I$(@D) $(PROTOC_OPTS_WITHOUT_VENDOR) --gofast_out=plugins=grpc,Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types:$(BUILD_GNOI_YANG_PROTO_DIR) $(file);) + @echo "+++++ PROTOBUF completion completed; +++++" + touch $@ + $(PROTO_GO_BINDINGS): $$(patsubst %.pb.go,%.proto,$$@) | $(GOBIN)/protoc-gen-go PATH=$(PROTOC_PATH) protoc -I$(@D) $(PROTOC_OPTS) --go_out=plugins=grpc:$(@D) $< @@ -145,6 +196,8 @@ endif $(INSTALL) -D $(BUILD_DIR)/gnmi_set $(DESTDIR)/usr/sbin/gnmi_set $(INSTALL) -D $(BUILD_DIR)/gnmi_cli $(DESTDIR)/usr/sbin/gnmi_cli $(INSTALL) -D $(BUILD_DIR)/gnoi_client $(DESTDIR)/usr/sbin/gnoi_client + $(INSTALL) -D $(BUILD_DIR)/gnoi_openconfig_client $(DESTDIR)/usr/sbin/gnoi_openconfig_client + $(INSTALL) -D $(BUILD_DIR)/gnoi_sonic_client $(DESTDIR)/usr/sbin/gnoi_sonic_client $(INSTALL) -D $(BUILD_DIR)/gnmi_dump $(DESTDIR)/usr/sbin/gnmi_dump @@ -156,6 +209,8 @@ endif rm $(DESTDIR)/usr/sbin/gnmi_get rm $(DESTDIR)/usr/sbin/gnmi_set rm $(DESTDIR)/usr/sbin/gnoi_client + rm $(DESTDIR)/usr/sbin/gnoi_openconfig_client + rm $(DESTDIR)/usr/sbin/gnoi_sonic_client rm $(DESTDIR)/usr/sbin/gnmi_dump diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5cdae841..989726c3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -137,6 +137,11 @@ stages: sudo dpkg -i python3-swsscommon_1.0.0_amd64.deb workingDirectory: $(Pipeline.Workspace)/ displayName: 'Install libswsscommon package' + + - script: | + sudo apt-get install -y protobuf-compiler + protoc --version + displayName: 'Install protoc' - script: | set -ex diff --git a/gnmi_server/server.go b/gnmi_server/server.go index 4ae32c18..c4ad1bdd 100644 --- a/gnmi_server/server.go +++ b/gnmi_server/server.go @@ -4,26 +4,29 @@ import ( "bytes" "errors" "fmt" + "net" + "strings" + "sync" + "github.com/Azure/sonic-mgmt-common/translib" - "github.com/sonic-net/sonic-gnmi/common_utils" - spb "github.com/sonic-net/sonic-gnmi/proto" - spb_gnoi "github.com/sonic-net/sonic-gnmi/proto/gnoi" - spb_jwt_gnoi "github.com/sonic-net/sonic-gnmi/proto/gnoi/jwt" - sdc "github.com/sonic-net/sonic-gnmi/sonic_data_client" log "github.com/golang/glog" "github.com/golang/protobuf/proto" gnmipb "github.com/openconfig/gnmi/proto/gnmi" gnmi_extpb "github.com/openconfig/gnmi/proto/gnmi_ext" gnoi_system_pb "github.com/openconfig/gnoi/system" + + //gnoi_yang "github.com/sonic-net/sonic-gnmi/build/gnoi_yang/server" + "github.com/sonic-net/sonic-gnmi/common_utils" + spb "github.com/sonic-net/sonic-gnmi/proto" + spb_gnoi "github.com/sonic-net/sonic-gnmi/proto/gnoi" + spb_jwt_gnoi "github.com/sonic-net/sonic-gnmi/proto/gnoi/jwt" + sdc "github.com/sonic-net/sonic-gnmi/sonic_data_client" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" - "net" - "strings" - "sync" ) var ( @@ -50,14 +53,14 @@ type AuthTypes map[string]bool type Config struct { // Port for the Server to listen on. If 0 or unset the Server will pick a port // for this Server. - Port int64 - LogLevel int - Threshold int - UserAuth AuthTypes + Port int64 + LogLevel int + Threshold int + UserAuth AuthTypes EnableTranslibWrite bool - EnableNativeWrite bool - ZmqAddress string - IdleConnDuration int + EnableNativeWrite bool + ZmqAddress string + IdleConnDuration int } var AuthLock sync.Mutex @@ -160,10 +163,12 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) { if srv.config.EnableTranslibWrite || srv.config.EnableNativeWrite { gnoi_system_pb.RegisterSystemServer(srv.s, srv) } - if srv.config.EnableTranslibWrite { + if srv.config.EnableTranslibWrite { spb_gnoi.RegisterSonicServiceServer(srv.s, srv) } spb_gnoi.RegisterDebugServer(srv.s, srv) + //gnoi_yang.RegisterGnoiopenconfigYangServer(srv.s, srv) + //gnoi_yang.RegisterGnoisonicYangServer(srv.s, srv) log.V(1).Infof("Created Server on %s, read-only: %t", srv.Address(), !srv.config.EnableTranslibWrite) return srv, nil } @@ -188,6 +193,11 @@ func (srv *Server) Port() int64 { return srv.config.Port } +// Auth - Authenticate +func (srv *Server) Auth(ctx context.Context) (context.Context, error) { + return authenticate(srv.config.UserAuth, ctx) +} + func authenticate(UserAuth AuthTypes, ctx context.Context) (context.Context, error) { var err error success := false @@ -591,7 +601,7 @@ func ReqFromMasterEnabledMA(req *gnmipb.SetRequest, masterEID *uint128) error { // Role will be implemented later. return status.Errorf(codes.Unimplemented, "MA: Role is not implemented") } - + reqEID = uint128{High: ma.ElectionId.High, Low: ma.ElectionId.Low} // Use the election ID that is in the last extension, so, no 'break' here. } diff --git a/tools/pyang_plugins/protobuf.py b/tools/pyang_plugins/protobuf.py new file mode 100644 index 00000000..94bc9322 --- /dev/null +++ b/tools/pyang_plugins/protobuf.py @@ -0,0 +1,641 @@ +################################################################################ +# # +# Copyright 2022 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. # +# # +################################################################################ + +"""pyang plugin to convert a yang schema to a protobuf schema +""" +import optparse +import os +import sys +from io import StringIO +from collections import OrderedDict +from re import sub +from pyang import plugin, statements, util +from jinja2 import Environment, FileSystemLoader + +# Register the Protobuf plugin +def pyang_plugin_init(): + plugin.register_plugin(ProtobufPlugin()) + +# Globals + +def snake_to_camel(word): + return sub(r"(_|-)+", " ", word).title().replace(" ", "") + +current_proto = None +pyang_plugin_dir = os.path.dirname(os.path.realpath(__file__)) +server_template_dir = os.path.join(pyang_plugin_dir, 'templates', 'server') +client_template_dir = os.path.join(pyang_plugin_dir, 'templates', 'client') + +server_template_env = Environment(loader=FileSystemLoader(server_template_dir), trim_blocks=True, lstrip_blocks=True) +client_template_env = Environment(loader=FileSystemLoader(client_template_dir), trim_blocks=True, lstrip_blocks=True) + +rpc_server_template = server_template_env.get_template('rpc.j2') +rpc_server_imports_template = server_template_env.get_template('rpc_imports.j2') +rpc_register_template = server_template_env.get_template('register.j2') +gnoiyang_template = server_template_env.get_template('gnoiyang.j2') + +rpc_client_template = stub = client_template_env.get_template('main.j2') + +class Protobuf(object): + def __init__(self, module_name): + module_name = module_name.replace('-','_') + self.module_name = snake_to_camel(module_name) + self.module_name_plain = module_name + self.tree = OrderedDict() + self.containers = [] + self.ylist = [] + self.leafs = [] + self.enums = [] + self.headers = [] + self.services = [] + self.rpcs = [] + self.has_empty = False + self.has_value_type = False + + def set_headers(self): + self.headers.append('syntax = "proto3";') + self.headers.append(f'\npackage gnoi.{self.module_name};\n') + if self.has_value_type: + self.headers.append('import "google/protobuf/struct.proto";') + # if self.has_empty: + # self.headers.append('import "google/protobuf/empty.proto";') + + def _print_rpc_server_stub(self, ctx): + out = StringIO() + out.write(rpc_server_imports_template.render(module_name=self.module_name,module_name_plain=self.module_name_plain)) + for rpc in self.rpcs: + out.write(rpc_server_template.render(rpc_name=rpc.name_without_parent, rpc_url=rpc.rpc_url, rpc_input_empty=rpc.input_empty, rpc_output_empty=rpc.output_empty)) + fndir = os.path.join(ctx.opts.server_stub_outdir, self.module_name_plain) + if fndir and not os.path.exists(fndir): + os.makedirs(fndir) + fn = os.path.join(fndir, self.module_name_plain+'.go') + with open(fn, "w") as fh: + fh.write(out.getvalue()) + + def _print_rpc(self, out, level=0): + spaces = ' ' * level + out.write(''.join([spaces, f"service {self.module_name}Service "])) + out.write(''.join('{\n')) + + rpc_space = spaces + ' ' + for rpc in self.rpcs: + out.write(''.join([rpc_space, f"rpc {rpc.name_without_parent.replace('-','_')}({rpc.input.replace('-','_')}) returns({rpc.output.replace('-','_')})"])) + out.write(''.join(' {}\n')) + + out.write(''.join([spaces, '}\n'])) + + def _print_container(self, container, out, level=0): + spaces = ' ' * level + out.write(''.join([spaces, f"message {container.name.replace('-','_')} "])) + out.write(''.join('{\n')) + + for l in container.ylist: + self._print_list(l, out, level + 1) + + for inner in container.containers: + self._print_container(inner, out, level + 1) + + self._print_leaf(container.leafs, out, spaces=spaces) + + out.write(''.join([spaces, '}\n'])) + + def _print_list(self, ylist, out, level=0): + spaces = ' ' * level + out.write(''.join([spaces, f"message {ylist.name.replace('-', '_')} "])) + out.write(''.join('{\n')) + + for l in ylist.ylist: + self._print_list(l, out, level + 1) + + for inner in ylist.containers: + self._print_container(inner, out, level + 1) + + self._print_leaf(ylist.leafs, out, spaces=spaces) + + out.write(''.join([spaces, '}\n'])) + + + def _print_leaf(self, leafs, out, spaces='', include_message=False): + leafspaces = ''.join([spaces, ' ']) + for idx, l in enumerate(leafs): + if l.type == "enum": + out.write(''.join([leafspaces, f"enum {l.name.replace('-','_').capitalize()}\n"])) + out.write(''.join([leafspaces, '{\n'])) + self._print_enumeration(l.enumeration, out, leafspaces) + out.write(''.join([leafspaces, '}\n'])) + l.type = l.name.replace('-','_').capitalize() + if include_message: + out.write(''.join([spaces, f"message {l.name.replace('-','_')} "])) + out.write(''.join([spaces, '{\n'])) + out.write(''.join([leafspaces, f'{"repeated " if l.leaf_list else ""}{l.type} {l.name.replace("-", "_")} = {idx + 1} [json_name = "{l.json_name}"];\n'])) + if include_message: + out.write(''.join([spaces, '}\n'])) + + def _print_enumeration(self, yang_enum, out, spaces): + enumspaces = ''.join([spaces, ' ']) + for _, e in enumerate(yang_enum): + out.write(''.join([enumspaces, f'{e}\n'])) + + def print_proto(self): + out = StringIO() + for h in self.headers: + out.write(f"{h}\n") + out.write('\n') + + if self.leafs: + self._print_leaf(self.leafs, out, spaces='', include_message=True) + out.write('\n') + + if self.ylist: + for l in self.ylist: + self._print_list(l, out) + out.write('\n') + + if self.containers: + for c in self.containers: + self._print_container(c, out) + + out.write('\n') + + if self.rpcs: + self._print_rpc(out) + + return out + + +class YangContainer(object): + def __init__(self): + self.name = None + self.containers = [] + self.enums = [] + self.leafs = [] + self.ylist = [] + +class YangList(object): + def __init__(self): + self.name = None + self.leafs = [] + self.containers = [] + self.ylist = [] + + +class YangLeaf(object): + def __init__(self): + self.name = None + self.type = None + self.json_name = None + self.leaf_list = False + self.enumeration = [] + self.enumeration_names = set() + self.description = None + + +class YangEnumeration(object): + def __init__(self): + self.value = [] + + +class YangRpc(object): + def __init__(self): + self.mod_name = None + self.name = None + self.name_without_parent = None + self.input = 'google.protobuf.Empty' + self.output = 'google.protobuf.Empty' + self.input_empty = False + self.output_empty = False + self.url = None + + +class ProtobufPlugin(plugin.PyangPlugin): + def add_output_format(self, fmts): + self.multiple_modules = True + fmts['proto'] = self + + def setup_fmt(self, ctx): + ctx.implicit_errors = False + + def add_opts(self, optparser): + optlist = [ + optparse.make_option("--proto-outdir", + type="string", + dest="outdir", + help="Output directory for protobuffs"), + optparse.make_option("--server-rpc-outdir", + type="string", + dest="server_stub_outdir", + help="Output directory for server stubs"), + optparse.make_option("--client-rpc-outdir", + type="string", + dest="client_outdir", + help="Output directory for server stubs"), + ] + g = optparser.add_option_group("OpenApiPlugin options") + g.add_options(optlist) + + def write_file(self, fn, content): + fileChanged = True + if os.path.isfile(fn): + with open(fn) as fp: + fileChanged = (fp.read() != content) + if fileChanged: + with open(fn, "w") as fp: + print(f"writing file: {fn}") + fp.write(content) + else: + print(f"file {fn} unchanged, skipped writing...") + return fileChanged + + def emit(self, ctx, modules, fd): + """Main control function. + """ + global current_proto + self.ctx = ctx + for idx, d in enumerate([ctx.opts.outdir, ctx.opts.server_stub_outdir, ctx.opts.client_outdir]): + if not d: + if idx == 0: + print("--proto-outdir cannot be empty") + elif idx == 1: + print("--server-rpc-outdir cannot be empty") + elif idx == 2: + print("--client-rpc-outdir cannot be empty") + else: + pass + sys.exit(2) + if d and not os.path.exists(d): + os.makedirs(d) + + server_mods = [] + mod_name_map = OrderedDict() + rpcs_list = [] + mods_rpc_map = OrderedDict() + prefix = "openconfig" + for module in modules: + if module.keyword == "submodule": + continue + proto = Protobuf(module.i_modulename) + current_proto = proto + # Only looking for RPCs as of now. + # Can be extended to other statements if required. + rpcs = module.search('rpc', children=module.i_children) + if len(rpcs) < 1: + continue + print("===> processing %s ..." % (module.i_modulename)) + if module.i_modulename.startswith("sonic") or module.i_modulename.startswith("Sonic"): + prefix = "sonic" + for rpc in rpcs: + self.process_rpc(rpc, proto) + proto.set_headers() + proto_content = proto.print_proto().getvalue() + if proto.module_name not in mods_rpc_map: + mods_rpc_map[proto.module_name] = list() + for rpc in proto.rpcs: + rpcs_list.append(rpc) + mods_rpc_map[proto.module_name].append(rpc) + server_mods.append(proto.module_name) + mod_name_map[proto.module_name] = proto.module_name_plain + # check if file is same + protoFnDir = os.path.join(ctx.opts.outdir, proto.module_name_plain) + if protoFnDir and not os.path.exists(protoFnDir): + os.makedirs(protoFnDir) + protoFn = os.path.join(protoFnDir, proto.module_name_plain + ".proto") + protoChanged = self.write_file(protoFn, proto_content) + if protoChanged: + proto._print_rpc_server_stub(ctx) + else: + print("skip unchanged module: " + module.i_modulename) + + stub = rpc_register_template.render(modules=server_mods, prefix=prefix, mod_name_map=mod_name_map) + fn = os.path.join(ctx.opts.server_stub_outdir, f'{prefix}_register.go') + self.write_file(fn, stub) + + stub = gnoiyang_template.render() + fn = os.path.join(ctx.opts.server_stub_outdir, 'gnoiyang.go') + self.write_file(fn, stub) + + # if len(server_mods) == 0: + # return # NO RPCs to process + + fndir = os.path.join(ctx.opts.client_outdir, 'gnoi_'+prefix+'_client') + if fndir and not os.path.exists(fndir): + os.makedirs(fndir) + fn = os.path.join(fndir, 'main.go') + client_stub = rpc_client_template.render(rpcs=rpcs_list, prefix=prefix, mods_rpc_map=mods_rpc_map, mod_name_map=mod_name_map) + self.write_file(fn, client_stub) + + def process_children(self, node, parent, pmod): + """Process all children of `node`, except "rpc" and "notification". + """ + for ch in node.i_children: + if ch.keyword in ["rpc"]: + self.process_rpc(ch, parent) + if ch.keyword in ["notification"]: + continue + if ch.keyword in ["choice", "case"]: + self.process_children(ch, parent, pmod) + continue + if ch.i_module.i_modulename == pmod: + nmod = pmod + else: + nmod = ch.i_module.i_modulename + xpath = mk_path_str(ch, prefix_onchange=True, prefix_to_module=True) + node_name = xpath.split("/")[-1] + if ch.keyword in ["container", "grouping"]: + c = YangContainer() + c.name = snake_to_camel(ch.arg) + self.process_children(ch, c, nmod) + parent.containers.append(c) + lc = YangLeaf() + lc.type = c.name + lc.name = ch.arg.replace('-','_') + lc.json_name = node_name + parent.leafs.append(lc) + # self.process_container(ch, p, nmod) + elif ch.keyword == "list": + l = YangList() + l.name = snake_to_camel(ch.arg) + self.process_children(ch, l, nmod) + parent.ylist.append(l) + lc = YangLeaf() + lc.leaf_list = True + lc.type = l.name + lc.name = ch.arg.replace('-','_') + lc.json_name = node_name + parent.leafs.append(lc) + elif ch.keyword in ["leaf", "leaf-list"]: + self.process_leaf(ch, parent, ch.keyword == "leaf-list", node_name) + + def process_leaf(self, node, parent, leaf_list=False, node_name=None): + global current_proto + # Leaf have specific sub statements + p_type, stmt = self.get_protobuf_type(node) + if p_type == "google.protobuf.Value": + current_proto.has_value_type = True + leaf = YangLeaf() + leaf.name = node.arg.replace('-','-') + leaf.json_name = node_name + leaf.type = p_type + if leaf.type == "enum": + if not self.process_enumeration(stmt, leaf, parent): + print(f"[INFO] - Due to protobuf limitation changing type to string from enum for leaf-{node_name}") + leaf.type = "string" + leaf.description = node.search_one("description") + leaf.leaf_list = leaf_list + parent.leafs.append(leaf) + + def process_enumeration(self, node, leaf, leaf_parent): + enumeration_dict = OrderedDict() + enums = node.search('enum') + for enum in enums: + if enum.arg[0].isdigit() or '-' in enum.arg: + return False + for sibling_leaf in leaf_parent.leafs: + if sibling_leaf.type == "enum": + if enum.arg in sibling_leaf.enumeration_names: + return False + val = enum.search_one('value') + if val is not None: + enumeration_dict[enum.arg] = int(val.arg) + else: + enumeration_dict[enum.arg] = '0' + + for key, value in enumerate(enumeration_dict): + leaf.enumeration.append(f'{value} = {key} ;') + leaf.enumeration_names.add(value) + return True + + def process_rpc(self, node, parent): + yrpc = YangRpc() + yrpc.rpc_url = mk_path_str(node, prefix_onchange=True, prefix_to_module=True) + yrpc.name = snake_to_camel(parent.module_name_plain + '_' + node.arg) # name of rpc call + yrpc.mod_name = parent.module_name + yrpc.name_without_parent = snake_to_camel(node.arg) # name of rpc call in plain form + yrpc.input = snake_to_camel(node.arg + '_request') + c_input = YangContainer() + c_input.name = yrpc.input + parent.containers.append(c_input) + # look for input node + input_node = node.search_one("input") + if input_node and input_node.substmts: + input = YangContainer() + input.name = "Input" + self.process_children(input_node, input, None) + c_input.containers.append(input) + leaf = YangLeaf() + leaf.name = "input" + xpath = mk_path_str(input_node, prefix_onchange=True, prefix_to_module=True) + mod_name = xpath.split("/")[-1].split(":")[0] + leaf.json_name = f"{mod_name}:input" + leaf.type = input.name + c_input.leafs.append(leaf) + else: + parent.has_empty = True + yrpc.input_empty = True + + yrpc.output = snake_to_camel(node.arg + '_response') + c_output = YangContainer() + c_output.name = yrpc.output + parent.containers.append(c_output) + output_node = node.search_one("output") + if output_node and output_node.substmts: + output = YangContainer() + output.name = "Output" + self.process_children(output_node, output, None) + c_output.containers.append(output) + leaf = YangLeaf() + leaf.name = "output" + xpath = mk_path_str(output_node, prefix_onchange=True, prefix_to_module=True) + mod_name = xpath.split("/")[-1].split(":")[0] + leaf.json_name = f"{mod_name}:output" + leaf.type = output.name + c_output.leafs.append(leaf) + else: + parent.has_empty = True + yrpc.output_empty = True + parent.rpcs.append(yrpc) + + def get_protobuf_type(self, node): + yang_type, stmt = self.get_type(node) + yang_type = yang_type.replace('-','_') + if yang_type in self.protobuf_types_map.keys(): + return self.protobuf_types_map[yang_type], stmt + else: + print(f"Error - No Proto type mapping for yang type {yang_type}") + sys.exit(2) + + def get_type(self, node): + + def resolveType(stmt, node_type): + + if node_type == "leafref": + return self.handle_leafref(node) + + return node_type, stmt + + 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') + + if node.keyword == "type": + t = node + + 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 = t.i_module + typedef = statements.search_typedef(pmodule, name) # typedef is defined at module level + if typedef is None: + # typedef is defined in local hierarchy + typedef = statements.search_typedef(t, name) + else: + # this is a prefixed name, check the imported modules + err = [] + pmodule = util.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) + + def handle_leafref(self, node): + target_node = None + if target_node is None: + target_node = statements.validate_leafref_path(self.ctx, node, node.i_leafref.path_spec, node.i_leafref.path_)[0] + if target_node.keyword in ["leaf", "leaf-list"]: + return self.get_type(target_node) + else: + print("leafref not pointing to leaf/leaflist") + sys.exit(2) + + protobuf_types_map = dict( + binary='bytes', + bits='bytes', + boolean='bool', + decimal64='sint64', + empty='string', + int8='int32', + int16='int32', + int32='int32', + int64='int64', + string='string', + uint8='uint32', + uint16='uint32', + uint32='uint32', + uint64='uint64', + union='google.protobuf.Value', + enumeration='enum', + identityref='string', + instance_identifier='string' + ) + +def mk_path_list(stmt): + """Derives a list of tuples containing + (module name, prefix, xpath, keys) + per node in the statement. + """ + resolved_names = [] + def resolve_stmt(stmt, resolved_names): + if stmt.keyword in ['case', 'input', 'output']: + resolve_stmt(stmt.parent, resolved_names) + return + def qualified_name_elements(stmt): + """(module name, prefix, name, keys)""" + return ( + stmt.i_module.i_modulename, + stmt.i_module.i_prefix, + stmt.arg, + get_keys(stmt) + ) + if stmt.parent.keyword in ['module', 'submodule']: + resolved_names.append(qualified_name_elements(stmt)) + return + else: + resolve_stmt(stmt.parent, resolved_names) + resolved_names.append(qualified_name_elements(stmt)) + return + resolve_stmt(stmt, resolved_names) + return resolved_names + +def get_keys(stmt): + """Gets the key names for the node if present. + Returns a list of key name strings. + """ + key_obj = stmt.search_one('key') + key_names = [] + keys = getattr(key_obj, 'arg', None) + if keys: + key_names = keys.split() + return key_names + +def mk_path_str(stmt, + with_prefixes=False, + prefix_onchange=False, + prefix_to_module=False, + resolve_top_prefix_to_module=False, + with_keys=False): + """Returns the XPath path of the node. + with_prefixes indicates whether or not to prefix every node. + + prefix_onchange modifies the behavior of with_prefixes and + only adds prefixes when the prefix changes mid-XPath. + + prefix_to_module replaces prefixes with the module name of the prefix. + + resolve_top_prefix_to_module resolves the module-level prefix + to the module name. + + with_keys will include "[key]" to indicate the key names in the XPath. + + Prefixes may be included in the path if the prefix changes mid-path. + """ + resolved_names = mk_path_list(stmt) + xpath_elements = [] + last_prefix = None + for index, resolved_name in enumerate(resolved_names): + module_name, prefix, node_name, node_keys = resolved_name + xpath_element = node_name + if with_prefixes or (prefix_onchange and prefix != last_prefix): + new_prefix = prefix + if (prefix_to_module or + (index == 0 and resolve_top_prefix_to_module)): + new_prefix = module_name + xpath_element = '%s:%s' % (new_prefix, node_name) + if with_keys and node_keys: + for node_key in node_keys: + xpath_element = '%s[%s]' % (xpath_element, node_key) + xpath_elements.append(xpath_element) + last_prefix = prefix + return '/%s' % '/'.join(xpath_elements) diff --git a/tools/pyang_plugins/templates/client/main.j2 b/tools/pyang_plugins/templates/client/main.j2 new file mode 100644 index 00000000..4a4e3e88 --- /dev/null +++ b/tools/pyang_plugins/templates/client/main.j2 @@ -0,0 +1,136 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "strings" + + {% for mod in mods_rpc_map %} + {{ mod }} "github.com/sonic-net/sonic-gnmi/build/gnoi_yang/proto/{{ mod_name_map[mod] }}" + {% endfor %} + {% if mods_rpc_map|length > 0 %} + "encoding/json" + "github.com/gogo/protobuf/jsonpb" + "github.com/gogo/protobuf/proto" + {% endif %} + "github.com/google/gnxi/utils/credentials" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +var ( + module = flag.String("module", "System", "gNOI Module") + rpc = flag.String("rpc", "Time", "rpc call in specified module to call") + target = flag.String("target", "localhost:8080", "Address:port of gNOI Server") + args = flag.String("jsonin", "", "RPC Arguments in json format") + jwtToken = flag.String("jwt_token", "", "JWT Token if required") + targetName = flag.String("target_name", "hostname.com", "The target name use to verify the hostname returned by TLS handshake") +) + +// RPC holds name, handler func and argument info (optional) for an rpc. +type RPC struct { + Name string + Func func(conn *grpc.ClientConn, ctx context.Context) + Args string +} + +func (r *RPC) args() string { + if len(r.Args) == 0 { + return "" + } + firstWord := strings.ToLower(strings.Fields(r.Args)[0]) + if strings.HasSuffix(firstWord, "_json") || + strings.HasSuffix(firstWord, "-json") || + strings.HasPrefix(firstWord, "{") { + return "-jsonin " + r.Args + } + return r.Args +} + +var rpcMap = map[string][]RPC{ + {% for mod in mods_rpc_map %} + "{{ mod }}": { + {% for rpc in mods_rpc_map[mod] %} + RPC{Name: "{{ rpc.name_without_parent }}", Func: {{ rpc.name }}}, + {% endfor %} + }, + {% endfor %} +} + +// getRPCInfo returns the registered RPC object matching the given module and +// rpc names; or nil. Names are not case sensitive. +func getRPCInfo(mod, rpc string) *RPC { + for m, rpcs := range rpcMap { + if !strings.EqualFold(m, mod) { + continue + } + for _, r := range rpcs { + if strings.EqualFold(r.Name, rpc) { + return &r + } + } + } + return nil +} + +func main() { + flag.Parse() + rpcInfo := getRPCInfo(*module, *rpc) + if rpcInfo == nil { + fmt.Printf("error: unknown module '%s' or rpc '%s'\n", *module, *rpc) + return + } + + opts := credentials.ClientCredentials(*targetName) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + cancel() + }() + + if len(*jwtToken) > 0 { + ctx = metadata.AppendToOutgoingContext(ctx, "access_token", *jwtToken) + } + + conn, err := grpc.Dial(*target, opts...) + if err != nil { + panic(err.Error()) + } + + rpcInfo.Func(conn, ctx) +} + +func usageError(message string) { + fmt.Printf("error: %s\n\n", message) + flag.Usage() + os.Exit(1) +} + +{% for rpc in rpcs %} +func {{ rpc.name }}(conn *grpc.ClientConn, ctx context.Context) { + fmt.Println("Sonic {{ rpc.name }} Client") + sc := {{ rpc.mod_name }}.New{{ rpc.mod_name }}ServiceClient(conn) + req := &{{ rpc.mod_name }}.{{ rpc.name_without_parent }}Request{ + {% if not rpc.input_empty %}Input: &{{ rpc.mod_name }}.{{ rpc.name_without_parent }}Request_Input{},{% endif %} + } + fmt.Printf("%+v\n", *args) + jsonpb.UnmarshalString(*args, req) + fmt.Printf("%+v\n", proto.MarshalTextString(req)) + resp, err := sc.{{ rpc.name_without_parent }}(ctx, req) + + if err != nil { + panic(err.Error()) + } + respstr, err := json.Marshal(resp) + if err != nil { + panic(err.Error()) + } + fmt.Println(string(respstr)) +} +{% endfor %} diff --git a/tools/pyang_plugins/templates/server/gnoiyang.j2 b/tools/pyang_plugins/templates/server/gnoiyang.j2 new file mode 100644 index 00000000..a2a0017e --- /dev/null +++ b/tools/pyang_plugins/templates/server/gnoiyang.j2 @@ -0,0 +1,10 @@ +package gnoiyang + +import ( + "context" +) + +// ServerHandle - interface for gnmi_server +type ServerHandle interface { + Auth(context.Context) (context.Context, error) +} \ No newline at end of file diff --git a/tools/pyang_plugins/templates/server/register.j2 b/tools/pyang_plugins/templates/server/register.j2 new file mode 100644 index 00000000..01b4fb78 --- /dev/null +++ b/tools/pyang_plugins/templates/server/register.j2 @@ -0,0 +1,17 @@ +package gnoiyang + +import ( + "google.golang.org/grpc" + {% for mod in modules %} + {{ mod }}_proto "github.com/sonic-net/sonic-gnmi/build/gnoi_yang/proto/{{ mod_name_map[mod] }}" + {{ mod }} "github.com/sonic-net/sonic-gnmi/build/gnoi_yang/server/{{ mod_name_map[mod] }}" + {% endfor %} +) + +//RegisterGnoi{{ prefix }}YangServer - Registers GnoiYangServer +func RegisterGnoi{{ prefix }}YangServer(s *grpc.Server, srv ServerHandle) { + {% for mod in modules %} + {{ mod }}_yg := {{ mod }}.Server{srv} + {{ mod }}_proto.Register{{ mod }}ServiceServer(s, &{{ mod }}_yg) + {% endfor %} +} diff --git a/tools/pyang_plugins/templates/server/rpc.j2 b/tools/pyang_plugins/templates/server/rpc.j2 new file mode 100644 index 00000000..cf2bb57e --- /dev/null +++ b/tools/pyang_plugins/templates/server/rpc.j2 @@ -0,0 +1,29 @@ +//{{ rpc_name }} -- RPC Implementation +func (srv *Server) {{ rpc_name }}(ctx context.Context, req *spb.{{ rpc_name }}Request) (*spb.{{ rpc_name }}Response, error) { + ctx, err := srv.Handle.Auth(ctx) + if err != nil { + return nil, err + } + + resp := &spb.{{ rpc_name }}Response{ + {% if not rpc_output_empty %}Output: &spb.{{ rpc_name }}Response_Output{},{% endif %} + } + + m := jsonpb.Marshaler{EmitDefaults: true} + reqstr, err := m.MarshalToString(req) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + jsresp, err := transutil.TranslProcessAction("{{ rpc_url }}", []byte(reqstr), ctx) + if err != nil { + return nil, status.Error(codes.Unknown, err.Error()) + } + + r := jsonpb.Unmarshaler{} + err = r.Unmarshal(bytes.NewReader(jsresp), resp) + if err != nil { + return nil, transutil.ToStatus(err).Err() + } + + return resp, nil +} \ No newline at end of file diff --git a/tools/pyang_plugins/templates/server/rpc_imports.j2 b/tools/pyang_plugins/templates/server/rpc_imports.j2 new file mode 100644 index 00000000..84a5c87c --- /dev/null +++ b/tools/pyang_plugins/templates/server/rpc_imports.j2 @@ -0,0 +1,24 @@ +package {{ module_name }} + +import ( + "bytes" + "context" + + spb "github.com/sonic-net/sonic-gnmi/build/gnoi_yang/proto/{{ module_name_plain }}" + transutil "github.com/sonic-net/sonic-gnmi/transl_utils" + + "github.com/gogo/protobuf/jsonpb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ServerHandle - interface for gnmi_server +type ServerHandle interface { + Auth(context.Context) (context.Context, error) +} + +//Server - proxy +type Server struct { + Handle ServerHandle +} + diff --git a/transl_utils/transl_utils.go b/transl_utils/transl_utils.go index 2160fdbf..dcc5b813 100644 --- a/transl_utils/transl_utils.go +++ b/transl_utils/transl_utils.go @@ -15,36 +15,95 @@ import ( gnmipb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" "github.com/sonic-net/sonic-gnmi/common_utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var ( - Writer *syslog.Writer + Writer *syslog.Writer ) func __log_audit_msg(ctx context.Context, reqType string, uriPath string, err error) { - var err1 error - username := "invalid" - statusMsg := "failure" - errMsg := "None" - if (err == nil) { - statusMsg = "success" - } else { - errMsg = err.Error() - } - - if Writer == nil { - Writer, err1 = syslog.Dial("", "", (syslog.LOG_LOCAL4), "") - if (err1 != nil) { - log.V(2).Infof("Could not open connection to syslog with error =%v", err1.Error()) - return - } - } - - common_utils.GetUsername(ctx, &username) - - auditMsg := fmt.Sprintf("User \"%s\" request \"%s %s\" status - %s error - %s", - username, reqType, uriPath, statusMsg, errMsg) - Writer.Info(auditMsg) + var err1 error + username := "invalid" + statusMsg := "failure" + errMsg := "None" + if err == nil { + statusMsg = "success" + } else { + errMsg = err.Error() + } + + if Writer == nil { + Writer, err1 = syslog.Dial("", "", (syslog.LOG_LOCAL4), "") + if err1 != nil { + log.V(2).Infof("Could not open connection to syslog with error =%v", err1.Error()) + return + } + } + + common_utils.GetUsername(ctx, &username) + + auditMsg := fmt.Sprintf("User \"%s\" request \"%s %s\" status - %s error - %s", + username, reqType, uriPath, statusMsg, errMsg) + Writer.Info(auditMsg) +} + +// ToStatus returns a gRPC status object for a translib error. +func ToStatus(err error) *status.Status { + if err == nil { + return nil + } + + log.V(3).Infof("Translib error type=%T; value=%v", err, err) + code := codes.Unknown + data := "Operation failed" + var s *status.Status + + switch err := err.(type) { + case tlerr.TranslibSyntaxValidationError: + code = codes.InvalidArgument + data = err.ErrorStr.Error() + case tlerr.TranslibUnsupportedClientVersion, tlerr.InvalidArgsError, tlerr.NotSupportedError: + code = codes.InvalidArgument + data = err.Error() + case tlerr.InternalError: + code = codes.Internal + data = err.Error() + case tlerr.NotFoundError: + code = codes.NotFound + data = err.Error() + case tlerr.AlreadyExistsError: + code = codes.AlreadyExists + data = err.Error() + case tlerr.TranslibCVLFailure: + code = codes.InvalidArgument + data = err.CVLErrorInfo.ConstraintErrMsg + if len(data) == 0 { + data = "Validation failed" + } + case tlerr.TranslibTransactionFail: + code = codes.Aborted + data = "Transaction failed. Please try again" + case tlerr.TranslibRedisClientEntryNotExist: + code = codes.NotFound + data = "Resource not found" + case tlerr.AuthorizationError: + code = codes.PermissionDenied + data = err.Error() + case interface{ GRPCStatus() *status.Status }: + s = err.GRPCStatus() + default: + s = status.FromContextError(err) + } + + if s == nil { + s = status.New(code, data) + } + if log.V(3) { + log.Infof("gRPC status code=%v; msg=%v", s.Code(), s.Message()) + } + return s } func GnmiTranslFullPath(prefix, path *gnmipb.Path) *gnmipb.Path { @@ -102,7 +161,7 @@ func TranslProcessGet(uriPath string, op *string, ctx context.Context) (*gnmipb. var data []byte rc, _ := common_utils.GetContext(ctx) - req := translib.GetRequest{Path:uriPath, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} + req := translib.GetRequest{Path: uriPath, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} if rc.BundleVersion != nil { nver, err := translib.NewVersion(*rc.BundleVersion) if err != nil { @@ -127,11 +186,10 @@ func TranslProcessGet(uriPath string, op *string, ctx context.Context) (*gnmipb. json.Compact(dst, data) jv = dst.Bytes() - /* Fill the values into GNMI data structures . */ return &gnmipb.TypedValue{ Value: &gnmipb.TypedValue_JsonIetfVal{ - JsonIetfVal: jv, + JsonIetfVal: jv, }}, nil } @@ -144,7 +202,7 @@ func TranslProcessDelete(prefix, delPath *gnmipb.Path, ctx context.Context) erro } rc, _ := common_utils.GetContext(ctx) - req := translib.SetRequest{Path:uri, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} + req := translib.SetRequest{Path: uri, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} if rc.BundleVersion != nil { nver, err := translib.NewVersion(*rc.BundleVersion) if err != nil { @@ -157,7 +215,7 @@ func TranslProcessDelete(prefix, delPath *gnmipb.Path, ctx context.Context) erro req.AuthEnabled = true } resp, err := translib.Delete(req) - if err != nil{ + if err != nil { log.V(2).Infof("DELETE operation failed with error =%v, %v", resp.ErrSrc, err.Error()) return err } @@ -174,7 +232,7 @@ func TranslProcessReplace(prefix *gnmipb.Path, entry *gnmipb.Update, ctx context payload := entry.GetVal().GetJsonIetfVal() rc, _ := common_utils.GetContext(ctx) - req := translib.SetRequest{Path:uri, Payload:payload, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} + req := translib.SetRequest{Path: uri, Payload: payload, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} if rc.BundleVersion != nil { nver, err := translib.NewVersion(*rc.BundleVersion) if err != nil { @@ -188,12 +246,11 @@ func TranslProcessReplace(prefix *gnmipb.Path, entry *gnmipb.Update, ctx context } resp, err1 := translib.Replace(req) - if err1 != nil{ + if err1 != nil { log.V(2).Infof("REPLACE operation failed with error =%v, %v", resp.ErrSrc, err1.Error()) return err1 } - return nil } @@ -206,7 +263,7 @@ func TranslProcessUpdate(prefix *gnmipb.Path, entry *gnmipb.Update, ctx context. payload := entry.GetVal().GetJsonIetfVal() rc, _ := common_utils.GetContext(ctx) - req := translib.SetRequest{Path:uri, Payload:payload, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} + req := translib.SetRequest{Path: uri, Payload: payload, User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}} if rc.BundleVersion != nil { nver, err := translib.NewVersion(*rc.BundleVersion) if err != nil { @@ -219,7 +276,7 @@ func TranslProcessUpdate(prefix *gnmipb.Path, entry *gnmipb.Update, ctx context. req.AuthEnabled = true } resp, err := translib.Update(req) - if err != nil{ + if err != nil { switch err.(type) { case tlerr.NotFoundError: //If Update fails, it may be due to object not existing in this case use Replace to create and update the object. @@ -229,7 +286,7 @@ func TranslProcessUpdate(prefix *gnmipb.Path, entry *gnmipb.Update, ctx context. return err } } - if err != nil{ + if err != nil { log.V(2).Infof("UPDATE operation failed with error =%v, %v", resp.ErrSrc, err.Error()) return err } @@ -240,9 +297,9 @@ func TranslProcessBulk(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ var br translib.BulkRequest var uri string - var deleteUri []string - var replaceUri []string - var updateUri []string + var deleteUri []string + var replaceUri []string + var updateUri []string rc, ctx := common_utils.GetContext(ctx) log.V(2).Info("TranslProcessBulk Called") @@ -255,7 +312,7 @@ func TranslProcessBulk(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ return err } } - for _,d := range delete { + for _, d := range delete { if uri, err = ConvertToURI(prefix, d); err != nil { return err } @@ -270,17 +327,17 @@ func TranslProcessBulk(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ req.AuthEnabled = true } br.DeleteRequest = append(br.DeleteRequest, req) - deleteUri = append(deleteUri, uri) + deleteUri = append(deleteUri, uri) } - for _,r := range replace { + for _, r := range replace { if uri, err = ConvertToURI(prefix, r.GetPath()); err != nil { return err } payload := r.GetVal().GetJsonIetfVal() req := translib.SetRequest{ - Path: uri, + Path: uri, Payload: payload, - User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, + User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, } if rc.BundleVersion != nil { req.ClientVersion = nver @@ -289,17 +346,17 @@ func TranslProcessBulk(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ req.AuthEnabled = true } br.ReplaceRequest = append(br.ReplaceRequest, req) - replaceUri = append(replaceUri, uri) + replaceUri = append(replaceUri, uri) } - for _,u := range update { + for _, u := range update { if uri, err = ConvertToURI(prefix, u.GetPath()); err != nil { return err } payload := u.GetVal().GetJsonIetfVal() req := translib.SetRequest{ - Path: uri, + Path: uri, Payload: payload, - User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, + User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, } if rc.BundleVersion != nil { req.ClientVersion = nver @@ -308,43 +365,43 @@ func TranslProcessBulk(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ req.AuthEnabled = true } br.UpdateRequest = append(br.UpdateRequest, req) - updateUri = append(updateUri, uri) - } - - resp,err := translib.Bulk(br) - - i := 0 - for _,d := range resp.DeleteResponse { - __log_audit_msg(ctx, "DELETE", deleteUri[i], d.Err) - i++ - } - i = 0 - for _,r := range resp.ReplaceResponse { - __log_audit_msg(ctx, "REPLACE", replaceUri[i], r.Err) - i++ - } - i = 0 - for _,u := range resp.UpdateResponse { - __log_audit_msg(ctx, "UPDATE", updateUri[i], u.Err) - i++ - } + updateUri = append(updateUri, uri) + } + + resp, err := translib.Bulk(br) + + i := 0 + for _, d := range resp.DeleteResponse { + __log_audit_msg(ctx, "DELETE", deleteUri[i], d.Err) + i++ + } + i = 0 + for _, r := range resp.ReplaceResponse { + __log_audit_msg(ctx, "REPLACE", replaceUri[i], r.Err) + i++ + } + i = 0 + for _, u := range resp.UpdateResponse { + __log_audit_msg(ctx, "UPDATE", updateUri[i], u.Err) + i++ + } var errors []string - if err != nil{ + if err != nil { log.V(2).Info("BULK SET operation failed with error(s):") - for _,d := range resp.DeleteResponse { + for _, d := range resp.DeleteResponse { if d.Err != nil { log.V(2).Infof("%s=%v", d.Err.Error(), d.ErrSrc) errors = append(errors, d.Err.Error()) } } - for _,r := range resp.ReplaceResponse { + for _, r := range resp.ReplaceResponse { if r.Err != nil { log.V(2).Infof("%s=%v", r.Err.Error(), r.ErrSrc) errors = append(errors, r.Err.Error()) } } - for _,u := range resp.UpdateResponse { + for _, u := range resp.UpdateResponse { if u.Err != nil { log.V(2).Infof("%s=%v", u.Err.Error(), u.ErrSrc) errors = append(errors, u.Err.Error()) @@ -375,9 +432,9 @@ func TranslProcessAction(uri string, payload []byte, ctx context.Context) ([]byt req.Payload = payload resp, err := translib.Action(req) - __log_audit_msg(ctx, "ACTION", uri, err) + __log_audit_msg(ctx, "ACTION", uri, err) - if err != nil{ + if err != nil { log.V(2).Infof("Action operation failed with error =%v, %v", resp.ErrSrc, err.Error()) return nil, err } @@ -389,21 +446,20 @@ func GetModels() []gnmipb.ModelData { gnmiModels := make([]gnmipb.ModelData, 0, 1) supportedModels, _ := translib.GetModels() - for _,model := range supportedModels { + for _, model := range supportedModels { gnmiModels = append(gnmiModels, gnmipb.ModelData{ - Name: model.Name, + Name: model.Name, Organization: model.Org, - Version: model.Ver, - + Version: model.Ver, }) } return gnmiModels } func isTranslibSuccess(err error) bool { - if err != nil && err.Error() != "Success" { - return false - } + if err != nil && err.Error() != "Success" { + return false + } - return true + return true }