Skip to content

Commit

Permalink
Add gnmi-cli support with Bazel (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhwang408 authored and Yi Tseng committed Oct 9, 2019
1 parent 8289bc5 commit 4b48d5a
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 40 deletions.
12 changes: 8 additions & 4 deletions bazel/external/gnmi.BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
licenses(["notice"]) # Apache v2

load("@com_github_grpc_grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library")
load("@build_stack_rules_proto//python:python_grpc_library.bzl", "python_grpc_library")

package(
default_visibility = [ "//visibility:public" ],
Expand Down Expand Up @@ -50,8 +51,11 @@ cc_proto_library(
cc_grpc_library(
name = "gnmi_cc_grpc",
srcs = [":gnmi_proto"],
deps = [
":gnmi_cc_proto"
],
deps = [":gnmi_cc_proto"],
grpc_only = True
)
)

python_grpc_library(
name = "gnmi_py_grpc",
deps = [":gnmi_proto"],
)
12 changes: 6 additions & 6 deletions tools/gnmi/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ package(
default_visibility = ["//visibility:public"],
)

# FIXME: Wait until python support
# py_binary(
# name = "gnmi-cli",
# srcs = ["gnmi-cli.py"],
# deps = ["@com_github_openconfig_gnmi//:gnmi_proto_py"]
# )
py_binary(
name = "gnmi-cli",
srcs = ["gnmi-cli.py"],
deps = ["@com_github_openconfig_gnmi_proto//:gnmi_py_grpc"],
python_version = "PY2"
)
25 changes: 15 additions & 10 deletions tools/gnmi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ gnmi-cli.py is a script for user to send gNMI **Get**, **Set** request and **Sub
## Usage

```
usage: gnmi-cli.py [-h] [--grpc-addr GRPC_ADDR] [--bool-val BOOL_VAL]
[--int-val INT_VAL] [--uint-val UINT_VAL]
[--string-val STRING_VAL] [--float-val FLOAT_VAL]
{get,set,sub} path
usage: bazel run //tools/gnmi:gnmi-cli -- [-h] [--grpc-addr GRPC_ADDR]
[--bool-val BOOL_VAL] [--int-val INT_VAL] [--uint-val UINT_VAL]
[--string-val STRING_VAL] [--float-val FLOAT_VAL]
[--bytes-val BYTES_VAL] [--interval INTERVAL] [--replace]
{get,set,sub-onchange,sub-sample,cap,del} path
Test gNMI subscription
positional arguments:
{get,set,sub} gNMI command
path gNMI Path
{get,set,sub-onchange,sub-sample,cap,del} gNMI command
path gNMI Path
optional arguments:
-h, --help show this help message and exit
Expand All @@ -28,17 +29,21 @@ optional arguments:
[SetRequest only] Set string value
--float-val FLOAT_VAL
[SetRequest only] Set float value
--bytes-val BYTES_VAL
[SetRequest only] Set bytes value
--interval INTERVAL [Sample subscribe only] Sample subscribe poll interval in ms
--replace [SetRequest only] Use replace instead of update
```

## Examples

```
# To get port index
./gnmi-cli.py get /interfaces/interface[name=1/1/1]/state/ifindex
bazel run //tools/gnmi:gnmi-cli -- get /interfaces/interface[name=1/1/1]/state/ifindex
# To set port health indicator
./gnmi-cli.py set /interfaces/interface[name=1/1/1]/config/health-indicator --string-val GOOD
bazel run //tools/gnmi:gnmi-cli -- set /interfaces/interface[name=1/1/1]/config/health-indicator --string-val GOOD
# To subscribe port operation status
./gnmi-cli.py sub /interfaces/interface[name=1/1/1]/state/oper-status
# To subscribe one sample of port operation status per second
bazel run //tools/gnmi:gnmi-cli -- sub-sample /interfaces/interface[name=1/1/1]/state/oper-status --inverval 1000
```
96 changes: 76 additions & 20 deletions tools/gnmi/gnmi-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
import re
import argparse
from time import sleep
import ast

import grpc
from gnmi import gnmi_pb2
import google.protobuf.text_format
import struct
import signal
import sys
from gnmi import gnmi_pb2_grpc
import threading
import Queue

Expand All @@ -35,7 +33,7 @@ def str2bool(v):
parser = argparse.ArgumentParser(description='Test gNMI subscription')
parser.add_argument('--grpc-addr', help='gNMI server address',
type=str, action="store", default='localhost:28000')
parser.add_argument('cmd', help='gNMI command', type=str, choices=['get', 'set', 'sub'])
parser.add_argument('cmd', help='gNMI command', type=str, choices=['get', 'set', 'sub-onchange', 'sub-sample', 'cap', 'del'])
parser.add_argument('path', help='gNMI Path', type=str)

# gNMI options for SetRequest
Expand All @@ -49,6 +47,12 @@ def str2bool(v):
type=str, action="store", required=False)
parser.add_argument('--float-val', help='[SetRequest only] Set float value',
type=float, action="store", required=False)
parser.add_argument('--bytes-val', help='[SetRequest only] Set bytes value',
type=str, action="store", required=False)
parser.add_argument('--interval', help='[Sample subscribe only] Sample subscribe poll interval in ms',
type=int, action="store", default=5000)
parser.add_argument('--replace', help='[SetRequest only] Use replace instead of update',
action="store_true", required=False)

args = parser.parse_args()

Expand All @@ -60,8 +64,7 @@ def parse_key_val(key_val_str):
# parse path_str string and add elements to path (gNMI Path class)
def build_path(path_str, path):
if path_str == '/':
pe = path.elem.add()
pe.name = '/'
# the root path should be an empty path
return

path_elem_info_list = re.findall(r'/([^/\[]+)(\[([^=]+=[^\]]+)\])?', path_str)
Expand All @@ -77,23 +80,30 @@ def build_path(path_str, path):
pe.key[kv[0]] = kv[1]

def print_msg(msg, prompt):
print "***************************"
print prompt
print msg
print "***************************"
print("***************************")
print(prompt)
print(msg)
print("***************************")

def build_gnmi_get_req():
req = gnmi_pb2.GetRequest()
req.encoding = gnmi_pb2.PROTO
path = req.path.add()
build_path(args.path, path)
if args.path == '/':
# Special case
req.type = gnmi_pb2.GetRequest.CONFIG
return req

def build_gnmi_set_req():
req = gnmi_pb2.SetRequest()
update = req.update.add()
if (args.replace):
update = req.replace.add()
else:
update = req.update.add()
path = update.path
build_path(args.path, path)
if (args.path != '/'):
build_path(args.path, path)
if (args.bool_val is not None):
update.val.bool_val = args.bool_val
elif (args.int_val is not None):
Expand All @@ -104,17 +114,25 @@ def build_gnmi_set_req():
update.val.string_val = args.string_val
elif (args.float_val is not None):
update.val.float_val = args.float_val
elif (args.bytes_val is not None):
update.val.bytes_val = ast.literal_eval("b'" + args.bytes_val + "'")
else:
print "No typed value set"
print("No typed value set")
return None
return req

def build_gnmi_del_req():
req = gnmi_pb2.SetRequest()
delete = req.delete.add()
build_path(args.path, delete)
return req

# for subscrption
stream_out_q = Queue.Queue()
stream_in_q = Queue.Queue()
stream = None

def build_gnmi_sub():
def build_gnmi_sub_onchange():
req = gnmi_pb2.SubscribeRequest()
subList = req.subscribe
subList.mode = gnmi_pb2.SubscriptionList.STREAM
Expand All @@ -125,6 +143,19 @@ def build_gnmi_sub():
build_path(args.path, path)
return req

def build_gnmi_sub_sample():
req = gnmi_pb2.SubscribeRequest()
subList = req.subscribe
subList.mode = gnmi_pb2.SubscriptionList.STREAM
subList.updates_only = True
sub = subList.subscription.add()
sub.mode = gnmi_pb2.SAMPLE
sub.sample_interval = args.interval
path = sub.path
build_path(args.path, path)
return req


def req_iterator():
while True:
req = stream_out_q.get()
Expand All @@ -140,9 +171,8 @@ def stream_recv(stream):

def main():
channel = grpc.insecure_channel(args.grpc_addr)
stub = gnmi_pb2.gNMIStub(channel)
stub = gnmi_pb2_grpc.gNMIStub(channel)
req = None
path = None

if args.cmd == 'get':
req = build_gnmi_get_req()
Expand All @@ -154,8 +184,27 @@ def main():
print_msg(req, "REQUEST")
resp = stub.Set(req)
print_msg(resp, "RESPONSE")
elif args.cmd == 'sub':
req = build_gnmi_sub()
elif args.cmd == 'del':
req = build_gnmi_del_req()
print_msg(req, "REQUEST")
resp = stub.Set(req)
print_msg(resp, "RESPONSE")
elif args.cmd == 'sub-onchange':
req = build_gnmi_sub_onchange()
stream_out_q.put(req)
stream = stub.Subscribe(req_iterator())
stream_recv_thread = threading.Thread(
target=stream_recv, args=(stream,))
stream_recv_thread.start()

try:
while True:
sleep(1)
except KeyboardInterrupt:
stream_out_q.put(None)
stream_recv_thread.join()
elif args.cmd == 'sub-sample':
req = build_gnmi_sub_sample()
stream_out_q.put(req)
stream = stub.Subscribe(req_iterator())
stream_recv_thread = threading.Thread(
Expand All @@ -168,9 +217,16 @@ def main():
except KeyboardInterrupt:
stream_out_q.put(None)
stream_recv_thread.join()

elif args.cmd == 'cap':
req = gnmi_pb2.CapabilityRequest()
print_msg(req, "REQUEST")
resp = stub.Capabilities(req)
print_msg(resp, "RESPONSE")
else:
print 'Unknown command %s', args.cmd
print('Unknown command %s', args.cmd)
return

if __name__ == '__main__':
main()

0 comments on commit 4b48d5a

Please sign in to comment.