Skip to content

Commit bb9dc4b

Browse files
authored
Merge pull request #46 from cisco-ie/actual-doc-examples
Add several examples
2 parents de478bf + 43e08d7 commit bb9dc4b

File tree

10 files changed

+495
-3
lines changed

10 files changed

+495
-3
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ cisco-gnmi --help
4444

4545
This library covers the gNMI defined `Capabilities`, `Get`, `Set`, and `Subscribe` RPCs, and helper clients provide OS-specific recommendations. A CLI (`cisco-gnmi`) is also available upon installation. As commonalities and differences are identified between OS functionality this library will be refactored as necessary.
4646

47+
Several examples of library usage are available in [`examples/`](examples/). The `cisco-gnmi` CLI script found at [`src/cisco_gnmi/cli.py`](src/cisco_gnmi/cli.py) may also be useful.
48+
4749
It is *highly* recommended that users of the library learn [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) syntax to significantly ease usage. Understanding how to read Protocol Buffers, and reference [`gnmi.proto`](https://github.com/openconfig/gnmi/blob/master/proto/gnmi/gnmi.proto), will be immensely useful for utilizing gNMI and any other gRPC interface.
4850

4951
### cisco-gnmi CLI

examples/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gnmi_sub.json

examples/custom.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env python
2+
"""Copyright 2020 Cisco Systems
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are
7+
met:
8+
9+
* Redistributions of source code must retain the above copyright
10+
notice, this list of conditions and the following disclaimer.
11+
12+
The contents of this file are licensed under the Apache License, Version 2.0
13+
(the "License"); you may not use this file except in compliance with the
14+
License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
20+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
21+
License for the specific language governing permissions and limitations under
22+
the License.
23+
"""
24+
25+
"""Custom usage, no wrapper.
26+
Because we're not using a wrapper, we are going to need to build our own protos.
27+
"""
28+
29+
import json
30+
from getpass import getpass
31+
from cisco_gnmi import ClientBuilder, proto
32+
33+
"""First let's build a Client. We are not going to specify an OS
34+
name here resulting in just the base Client returned without any OS
35+
convenience methods. Client does have some level of "convenience" built-in
36+
insofar as it doesn't take direct <RPC>Requests (SubscribeRequest) etc.
37+
To directly use the gNMI RPCs access via client.service.<RPC>().
38+
So - either:
39+
* Pass args to the client.<RPC>() methods.
40+
* Pass full <RPC>Request protos to client.service.<RPC>()
41+
This code passes args to the client.<RPC>() methods.
42+
"""
43+
target = input("Host/Port: ")
44+
username = input("Username: ")
45+
password = getpass()
46+
client = (
47+
ClientBuilder(target)
48+
.set_secure_from_target()
49+
.set_ssl_target_override()
50+
.set_call_authentication(username, password)
51+
.construct()
52+
)
53+
"""Capabilities is an easy RPC to test."""
54+
input("Press Enter for Capabilities...")
55+
capabilities = client.capabilities()
56+
print(capabilities)
57+
"""Let's build a Get!
58+
client.get() expects a list of Paths as the primary method of interaction.
59+
client.parse_xpath_to_gnmi_path is a convenience method to..parse an XPath to a Path.
60+
Generally OS wrappers will override this function to specialize on origins, etc.
61+
But we are not using a wrapper, and if using OpenConfig pathing we don't need an origin.
62+
"""
63+
input("Press Enter for Get...")
64+
get_path = client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters")
65+
get_response = client.get([get_path], data_type="STATE", encoding="JSON_IETF")
66+
print(get_response)
67+
"""Let's build a sampled Subscribe!
68+
client.subscribe() accepts an iterable of SubscriptionLists
69+
"""
70+
input("Press Enter for Subscribe SAMPLE...")
71+
subscription_list = proto.gnmi_pb2.SubscriptionList()
72+
subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM")
73+
subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO")
74+
sampled_subscription = proto.gnmi_pb2.Subscription()
75+
sampled_subscription.path.CopyFrom(
76+
client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters")
77+
)
78+
sampled_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("SAMPLE")
79+
sampled_subscription.sample_interval = 10 * int(1e9)
80+
subscription_list.subscription.extend([sampled_subscription])
81+
for subscribe_response in client.subscribe([subscription_list]):
82+
print(subscribe_response)
83+
break
84+
"""Now let's do ON_CHANGE. Just have to put SubscriptionMode to ON_CHANGE."""
85+
input("Press Enter for Subscribe ON_CHANGE...")
86+
subscription_list = proto.gnmi_pb2.SubscriptionList()
87+
subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM")
88+
subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO")
89+
onchange_subscription = proto.gnmi_pb2.Subscription()
90+
onchange_subscription.path.CopyFrom(
91+
client.parse_xpath_to_gnmi_path(
92+
"/syslog/messages/message", origin="Cisco-IOS-XR-infra-syslog-oper"
93+
)
94+
)
95+
onchange_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE")
96+
subscription_list.subscription.extend([onchange_subscription])
97+
synced = False
98+
for subscribe_response in client.subscribe([subscription_list]):
99+
if subscribe_response.sync_response:
100+
synced = True
101+
print("Synced. Now perform action that will create a changed value.")
102+
print("If using XR syslog as written, just try SSH'ing to device.")
103+
continue
104+
if not synced:
105+
continue
106+
print(subscribe_response)
107+
break
108+
"""Let's build a Set!
109+
client.set() expects updates, replaces, and/or deletes to be provided.
110+
updates is a list of Updates
111+
replaces is a list of Updates
112+
deletes is a list of Paths
113+
Let's do an update.
114+
"""
115+
input("Press Enter for Set update...")
116+
set_update = proto.gnmi_pb2.Update()
117+
# This is the fully modeled JSON we want to update with
118+
update_json = json.loads(
119+
"""
120+
{
121+
"openconfig-interfaces:interfaces": {
122+
"interface": [
123+
{
124+
"name": "Loopback9339"
125+
}
126+
]
127+
}
128+
}
129+
"""
130+
)
131+
# Let's just do an update from the very top element
132+
top_element = next(iter(update_json.keys()))
133+
set_update.path.CopyFrom(client.parse_xpath_to_gnmi_path(top_element))
134+
# Remove the top element from the config since it's now in Path
135+
update_json = update_json.pop(top_element)
136+
# Set our update payload
137+
set_update.val.json_ietf_val = json.dumps(update_json).encode("utf-8")
138+
set_result = client.set(updates=[set_update])
139+
print(set_result)
140+
# This may all seem somewhat obtuse, and that's what the client wrappers are for.

examples/load_subscribe_dump.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python
2+
"""Copyright 2020 Cisco Systems
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are
7+
met:
8+
9+
* Redistributions of source code must retain the above copyright
10+
notice, this list of conditions and the following disclaimer.
11+
12+
The contents of this file are licensed under the Apache License, Version 2.0
13+
(the "License"); you may not use this file except in compliance with the
14+
License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
20+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
21+
License for the specific language governing permissions and limitations under
22+
the License.
23+
"""
24+
25+
"""This is effectively just demo code to load the output of subscribe_dump.py
26+
"""
27+
import argparse
28+
import os
29+
import logging
30+
import json
31+
import cisco_gnmi
32+
from google.protobuf import json_format, text_format
33+
34+
35+
def main():
36+
logging.basicConfig(level=logging.INFO)
37+
logging.info("Demo of loading protobufs from files.")
38+
args = setup_args()
39+
src_proto_array = load_proto_file(args.protos_file)
40+
parsed_proto_array = []
41+
for proto_msg in src_proto_array:
42+
parsed_proto = None
43+
if args.text_format is True:
44+
parsed_proto = text_format.Parse(
45+
proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse()
46+
)
47+
else:
48+
if args.raw_json:
49+
parsed_proto = json_format.Parse(
50+
proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse()
51+
)
52+
else:
53+
parsed_proto = json_format.ParseDict(
54+
proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse()
55+
)
56+
parsed_proto_array.append(parsed_proto)
57+
logging.info("Parsed %i formatted messages into objects!", len(parsed_proto_array))
58+
59+
60+
def load_proto_file(filename):
61+
if not filename.endswith(".json"):
62+
raise Exception("Expected JSON file (array of messages) from proto_dump.py")
63+
proto_array = None
64+
with open(filename, "r") as protos_fd:
65+
proto_array = json.load(protos_fd)
66+
if not isinstance(proto_array, (list)):
67+
raise Exception("Expected array of messages from file!")
68+
return proto_array
69+
70+
71+
def setup_args():
72+
parser = argparse.ArgumentParser(description="Proto Load Example")
73+
parser.add_argument("protos_file", help="File containing protos.", type=str)
74+
parser.add_argument(
75+
"-text_format",
76+
help="Protos are in text format instead of JSON.",
77+
action="store_true",
78+
)
79+
parser.add_argument(
80+
"-raw_json",
81+
help="Do not serialize to dict, but directly to JSON.",
82+
action="store_true",
83+
)
84+
return parser.parse_args()
85+
86+
87+
if __name__ == "__main__":
88+
main()

examples/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cisco_gnmi

examples/subscribe_dump.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python
2+
"""Copyright 2020 Cisco Systems
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are
7+
met:
8+
9+
* Redistributions of source code must retain the above copyright
10+
notice, this list of conditions and the following disclaimer.
11+
12+
The contents of this file are licensed under the Apache License, Version 2.0
13+
(the "License"); you may not use this file except in compliance with the
14+
License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
20+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
21+
License for the specific language governing permissions and limitations under
22+
the License.
23+
"""
24+
25+
"""This demoes a gNMI subscription and dumping messages to a file."""
26+
import json
27+
import logging
28+
import argparse
29+
from getpass import getpass
30+
from google.protobuf import json_format, text_format
31+
from cisco_gnmi import ClientBuilder
32+
33+
34+
def main():
35+
logging.basicConfig(level=logging.INFO)
36+
args = setup_args()
37+
username = input("Username: ")
38+
password = getpass()
39+
logging.info("Connecting to %s as %s ...", args.netloc, args.os)
40+
client = (
41+
ClientBuilder(args.netloc)
42+
.set_os(args.os)
43+
.set_secure_from_target()
44+
.set_ssl_target_override()
45+
.set_call_authentication(username, password)
46+
.construct()
47+
)
48+
formatted_messages = []
49+
try:
50+
logging.info("Subscribing to %s ...", args.xpath)
51+
sub_args = {"xpath_subscriptions": args.xpath}
52+
if args.encoding:
53+
sub_args["encoding"] = args.encoding
54+
for message in client.subscribe_xpaths(**sub_args):
55+
if message.sync_response and not args.no_stop:
56+
logging.warning("Stopping on sync_response.")
57+
break
58+
formatted_message = None
59+
if args.text_format is True:
60+
formatted_message = text_format.MessageToString(message)
61+
else:
62+
if args.raw_json:
63+
formatted_message = json_format.MessageToJson(message)
64+
else:
65+
formatted_message = json_format.MessageToDict(message)
66+
logging.info(formatted_message)
67+
formatted_messages.append(formatted_message)
68+
except KeyboardInterrupt:
69+
logging.warning("Stopping on interrupt.")
70+
except Exception:
71+
logging.exception("Stopping due to exception!")
72+
finally:
73+
logging.info("Writing to %s ...", args.protos_file)
74+
with open(args.protos_file, "w") as protos_fd:
75+
json.dump(
76+
formatted_messages,
77+
protos_fd,
78+
sort_keys=True,
79+
indent=4,
80+
separators=(",", ": "),
81+
)
82+
83+
84+
def setup_args():
85+
parser = argparse.ArgumentParser(description="gNMI Proto Dump Example")
86+
parser.add_argument("netloc", help="<host>:<port>", type=str)
87+
parser.add_argument(
88+
"-os",
89+
help="OS to use.",
90+
type=str,
91+
default="IOS XR",
92+
choices=list(ClientBuilder.os_class_map.keys()),
93+
)
94+
parser.add_argument(
95+
"-xpath",
96+
help="XPath to subscribe to.",
97+
type=str,
98+
default="/interfaces/interface/state/counters",
99+
)
100+
parser.add_argument(
101+
"-protos_file", help="File to write protos.", type=str, default="gnmi_sub.json"
102+
)
103+
parser.add_argument(
104+
"-no_stop", help="Do not stop on sync_response.", action="store_true"
105+
)
106+
parser.add_argument(
107+
"-encoding", help="gNMI subscription encoding.", type=str, nargs="?"
108+
)
109+
parser.add_argument(
110+
"-text_format",
111+
help="Protos are in text format instead of JSON.",
112+
action="store_true",
113+
)
114+
parser.add_argument(
115+
"-raw_json",
116+
help="Do not serialize to dict, but directly to JSON.",
117+
action="store_true",
118+
)
119+
return parser.parse_args()
120+
121+
122+
if __name__ == "__main__":
123+
main()

0 commit comments

Comments
 (0)