From 60b4aebebf0e3afbe72a4fe5b702f9709c698611 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 23:28:47 -0700 Subject: [PATCH 01/10] Add arbitrary examples --- examples/custom.py | 44 ++++++++++ examples/load_subscribe_dump.py | 88 ++++++++++++++++++++ examples/requirements.txt | 1 + examples/subscribe_dump.py | 123 ++++++++++++++++++++++++++++ examples/subscribe_onchange.py | 139 ++++++++++++++++++++++++++++++++ 5 files changed, 395 insertions(+) create mode 100644 examples/custom.py create mode 100644 examples/load_subscribe_dump.py create mode 100644 examples/requirements.txt create mode 100644 examples/subscribe_dump.py create mode 100644 examples/subscribe_onchange.py diff --git a/examples/custom.py b/examples/custom.py new file mode 100644 index 0000000..d7695bf --- /dev/null +++ b/examples/custom.py @@ -0,0 +1,44 @@ +"""Copyright 2020 Cisco Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +The contents of this file are 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. +""" + +"""Custom usage, no wrapper.""" + +from cisco_gnmi import ClientBuilder, proto + +client = ClientBuilder( + '127.0.0.1:9339' +).set_secure_from_target().set_ssl_target_override().set_authentication( + 'admin', + 'its_a_secret' +).construct() +capabilities = client.capabilities() +print(capabilities) +subscription_list = proto.gnmi_pb2.SubscriptionList() +subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") +subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO") +subscription = proto.gnmi_pb2.Subscription() +subscription.path.CopyFrom(client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters")) +subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") +subscription_list.subscription.append(subscription) +for message in client.subscribe([subscription_list]): + print(message) \ No newline at end of file diff --git a/examples/load_subscribe_dump.py b/examples/load_subscribe_dump.py new file mode 100644 index 0000000..5c4cdfb --- /dev/null +++ b/examples/load_subscribe_dump.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +"""Copyright 2020 Cisco Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +The contents of this file are 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 is effectively just demo code to load the output of subscribe_dump.py +""" +import argparse +import os +import logging +import json +import cisco_gnmi +from google.protobuf import json_format, text_format + + +def main(): + logging.basicConfig(level=logging.INFO) + logging.info("Demo of loading protobufs from files.") + args = setup_args() + src_proto_array = load_proto_file(args.protos_file) + parsed_proto_array = [] + for proto_msg in src_proto_array: + parsed_proto = None + if args.text_format is True: + parsed_proto = text_format.Parse( + proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse() + ) + else: + if args.raw_json: + parsed_proto = json_format.Parse( + proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse() + ) + else: + parsed_proto = json_format.ParseDict( + proto_msg, cisco_gnmi.proto.gnmi_pb2.SubscribeResponse() + ) + parsed_proto_array.append(parsed_proto) + logging.info("Parsed %i formatted messages into objects!", len(parsed_proto_array)) + + +def load_proto_file(filename): + if not filename.endswith(".json"): + raise Exception("Expected JSON file (array of messages) from proto_dump.py") + proto_array = None + with open(filename, "r") as protos_fd: + proto_array = json.load(protos_fd) + if not isinstance(proto_array, (list)): + raise Exception("Expected array of messages from file!") + return proto_array + + +def setup_args(): + parser = argparse.ArgumentParser(description="Proto Load Example") + parser.add_argument("protos_file", help="File containing protos.", type=str) + parser.add_argument( + "-text_format", + help="Protos are in text format instead of JSON.", + action="store_true", + ) + parser.add_argument( + "-raw_json", + help="Do not serialize to dict, but directly to JSON.", + action="store_true", + ) + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 0000000..34dc4c7 --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1 @@ +cisco_gnmi \ No newline at end of file diff --git a/examples/subscribe_dump.py b/examples/subscribe_dump.py new file mode 100644 index 0000000..2eaaa2e --- /dev/null +++ b/examples/subscribe_dump.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +"""Copyright 2020 Cisco Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +The contents of this file are 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 demoes a gNMI subscription and dumping messages to a file.""" +import json +import logging +import argparse +from getpass import getpass +from google.protobuf import json_format, text_format +from cisco_gnmi import ClientBuilder + + +def main(): + logging.basicConfig(level=logging.INFO) + args = setup_args() + username = input("Username: ") + password = getpass() + logging.info("Connecting to %s as %s ...", args.netloc, args.os) + client = ( + ClientBuilder(args.netloc) + .set_os(args.os) + .set_secure_from_target() + .set_ssl_target_override() + .set_call_authentication(username, password) + .construct() + ) + formatted_messages = [] + try: + logging.info("Subscribing to %s ...", args.xpath) + sub_args = {"xpath_subscriptions": args.xpath} + if args.encoding: + sub_args["encoding"] = args.encoding + for message in client.subscribe_xpaths(**sub_args): + if message.sync_response and not args.no_stop: + logging.warning("Stopping on sync_response.") + break + formatted_message = None + if args.text_format is True: + formatted_message = text_format.MessageToString(message) + else: + if args.raw_json: + formatted_message = json_format.MessageToJson(message) + else: + formatted_message = json_format.MessageToDict(message) + logging.info(formatted_message) + formatted_messages.append(formatted_message) + except KeyboardInterrupt: + logging.warning("Stopping on interrupt.") + except Exception: + logging.exception("Stopping due to exception!") + finally: + logging.info("Writing to %s ...", args.protos_file) + with open(args.protos_file, "w") as protos_fd: + json.dump( + formatted_messages, + protos_fd, + sort_keys=True, + indent=4, + separators=(",", ": "), + ) + + +def setup_args(): + parser = argparse.ArgumentParser(description="gNMI Proto Dump Example") + parser.add_argument("netloc", help=":", type=str) + parser.add_argument( + "-os", + help="OS to use.", + type=str, + default="IOS XR", + choices=list(ClientBuilder.os_class_map.keys()), + ) + parser.add_argument( + "-xpath", + help="XPath to subscribe to.", + type=str, + default="/interfaces/interface/state/counters", + ) + parser.add_argument( + "-protos_file", help="File to write protos.", type=str, default="gnmi_sub.json" + ) + parser.add_argument( + "-no_stop", help="Do not stop on sync_response.", action="store_true" + ) + parser.add_argument( + "-encoding", help="gNMI subscription encoding.", type=str, nargs="?" + ) + parser.add_argument( + "-text_format", + help="Protos are in text format instead of JSON.", + action="store_true", + ) + parser.add_argument( + "-raw_json", + help="Do not serialize to dict, but directly to JSON.", + action="store_true", + ) + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/examples/subscribe_onchange.py b/examples/subscribe_onchange.py new file mode 100644 index 0000000..0483f50 --- /dev/null +++ b/examples/subscribe_onchange.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +"""Copyright 2020 Cisco Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +The contents of this file are 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 demoes a gNMI subscription and dumping messages to a file. +Targets IOS XR syslog as demo. +TODO: Refactor library so ON_CHANGE is functionally simpler. +""" +import json +import logging +import argparse +from getpass import getpass +from google.protobuf import json_format, text_format +from cisco_gnmi import ClientBuilder, proto + + +def main(): + logging.basicConfig(level=logging.INFO) + args = setup_args() + username = input("Username: ") + password = getpass() + logging.info("Connecting to %s as %s ...", args.netloc, args.os) + client = ( + ClientBuilder(args.netloc) + .set_os(args.os) + .set_secure_from_target() + .set_ssl_target_override() + .set_call_authentication(username, password) + .construct() + ) + formatted_messages = [] + try: + logging.info("Subscribing to %s ...", args.xpath) + subscription_list = proto.gnmi_pb2.SubscriptionList() + subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") + if args.encoding: + subscription_list.encoding = proto.gnmi_pb2.Encoding.Value(args.encoding) + subscription = proto.gnmi_pb2.Subscription() + subscription.path.CopyFrom(client.parse_xpath_to_gnmi_path(args.xpath)) + subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") + subscription_list.subscription.append(subscription) + synced = False + if not args.process_all: + logging.info("Ignoring messages before sync_response.") + for message in client.subscribe([subscription_list]): + if message.sync_response: + synced = True + logging.info("Synced with latest state.") + continue + if not synced and not args.process_all: + continue + formatted_message = None + if args.text_format is True: + formatted_message = text_format.MessageToString(message) + else: + if args.raw_json: + formatted_message = json_format.MessageToJson(message) + else: + formatted_message = json_format.MessageToDict(message) + logging.info(formatted_message) + formatted_messages.append(formatted_message) + except KeyboardInterrupt: + logging.warning("Stopping on interrupt.") + except Exception: + logging.exception("Stopping due to exception!") + finally: + logging.info("Writing to %s ...", args.protos_file) + with open(args.protos_file, "w") as protos_fd: + json.dump( + formatted_messages, + protos_fd, + sort_keys=True, + indent=4, + separators=(",", ": "), + ) + + +def setup_args(): + parser = argparse.ArgumentParser(description="gNMI Subscribe Dump Example") + parser.add_argument("netloc", help=":", type=str) + parser.add_argument( + "-os", + help="OS to use.", + type=str, + default="IOS XR", + choices=list(ClientBuilder.os_class_map.keys()), + ) + parser.add_argument( + "-xpath", + help="XPath to subscribe to.", + type=str, + default="Cisco-IOS-XR-infra-syslog-oper:syslog/messages/message", + ) + parser.add_argument( + "-protos_file", help="File to write protos.", type=str, default="gnmi_sub.json" + ) + parser.add_argument( + "-process_all", + help="Process all the way through sync_response.", + action="store_true", + ) + parser.add_argument( + "-encoding", help="gNMI subscription encoding.", type=str, default="PROTO" + ) + parser.add_argument( + "-text_format", + help="Protos are in text format instead of JSON.", + action="store_true", + ) + parser.add_argument( + "-raw_json", + help="Do not serialize to dict, but directly to JSON.", + action="store_true", + ) + return parser.parse_args() + + +if __name__ == "__main__": + main() From 53fecf87f57b3e0266098978625dd35671e5bdf3 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Mon, 30 Mar 2020 14:52:44 -0700 Subject: [PATCH 02/10] Custom example of Capabilities/Get/Subscribe --- examples/custom.py | 91 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index d7695bf..408bf0d 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """Copyright 2020 Cisco Systems All rights reserved. @@ -21,24 +22,88 @@ the License. """ -"""Custom usage, no wrapper.""" +"""Custom usage, no wrapper. +Because we're not using a wrapper, we are going to need to build our own protos. +""" from cisco_gnmi import ClientBuilder, proto -client = ClientBuilder( - '127.0.0.1:9339' -).set_secure_from_target().set_ssl_target_override().set_authentication( - 'admin', - 'its_a_secret' -).construct() +"""First let's build a Client. We are not going to specify an OS +name here resulting in just the base Client returned without any OS +convenience methods. Client does have some level of "convenience" built-in +insofar as it doesn't take direct Requests (SubscribeRequest) etc. +To directly use the gNMI RPCs access via client.service.(). +So - either: + * Pass args to the client.() methods. + * Pass full Request protos to client.service.() +This code passes args to the client.() methods. +""" +client = ( + ClientBuilder("127.0.0.1:9339") + .set_secure_from_target() + .set_ssl_target_override() + .set_authentication("admin", "its_a_secret") + .construct() +) +"""Capabilities is an easy RPC to test.""" capabilities = client.capabilities() print(capabilities) +"""Let's build a Get! +client.get() expects a list of Paths as the primary method of interaction. +client.parse_xpath_to_gnmi_path is a convenience method to..parse an XPath to a Path. +Generally OS wrappers will override this function to specialize on origins, etc. +But we are not using a wrapper, and if using OpenConfig pathing we don't need an origin. +""" +get_path = client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters") +get_response = client.get([get_path], data_type="STATE", encoding="JSON_IETF") +print(get_response) +"""Let's build a sampled Subscribe! +client.subscribe() accepts an iterable of SubscriptionLists +""" subscription_list = proto.gnmi_pb2.SubscriptionList() subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO") -subscription = proto.gnmi_pb2.Subscription() -subscription.path.CopyFrom(client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters")) -subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") -subscription_list.subscription.append(subscription) -for message in client.subscribe([subscription_list]): - print(message) \ No newline at end of file +sampled_subscription = proto.gnmi_pb2.Subscription() +sampled_subscription.path.CopyFrom( + client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters") +) +sampled_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("SAMPLE") +sampled_subscription.sample_interval = 10 * int(1e9) +subscription_list.subscription.append(sampled_subscription) +# Only print 2 responses +for msg_idx, subscribe_response in enumerate(client.subscribe([subscription_list])): + print(subscribe_response) + if msg_idx + 1 == 2: + break +"""Now let's do ON_CHANGE. Just have to put SubscriptionMode to ON_CHANGE.""" +subscription_list = proto.gnmi_pb2.SubscriptionList() +subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") +subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO") +onchange_subscription = proto.gnmi_pb2.Subscription() +onchange_subscription.path.CopyFrom( + client.parse_xpath_to_gnmi_path( + "/syslog/messages/message", origin="Cisco-IOS-XR-infra-syslog-oper" + ) +) +onchange_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") +subscription_list.subscription.append(onchange_subscription) +# Only print 2 responses +for msg_idx, subscribe_response in enumerate(client.subscribe([subscription_list])): + print(subscribe_response) + if msg_idx + 1 == 2: + break +"""Let's build a Set! +client.set() expects updates, replaces, and/or deletes to be provided. +updates is a list of Updates +replaces is a list of Updates +deletes is a list of Paths +Let's do an update, and then a delete. +""" +set_update = proto.gnmi_pb2.Update() +set_json = """ +{ + "config": { + "login-banner": "Hello, gNMI!" + } +} +""" From fbc050d89e63af82b897158545fd1406ba6b3725 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Tue, 31 Mar 2020 22:33:05 -0700 Subject: [PATCH 03/10] Add Set/better flow --- examples/custom.py | 59 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index 408bf0d..4ec2067 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -26,6 +26,8 @@ Because we're not using a wrapper, we are going to need to build our own protos. """ +import json +from getpass import getpass from cisco_gnmi import ClientBuilder, proto """First let's build a Client. We are not going to specify an OS @@ -38,14 +40,18 @@ * Pass full Request protos to client.service.() This code passes args to the client.() methods. """ +target = input("Host/Port: ") +username = input("Username: ") +password = getpass() client = ( - ClientBuilder("127.0.0.1:9339") + ClientBuilder(target) .set_secure_from_target() .set_ssl_target_override() - .set_authentication("admin", "its_a_secret") + .set_call_authentication(username, password) .construct() ) """Capabilities is an easy RPC to test.""" +input("Press Enter for Capabilities...") capabilities = client.capabilities() print(capabilities) """Let's build a Get! @@ -54,12 +60,14 @@ Generally OS wrappers will override this function to specialize on origins, etc. But we are not using a wrapper, and if using OpenConfig pathing we don't need an origin. """ +input("Press Enter for Get...") get_path = client.parse_xpath_to_gnmi_path("/interfaces/interface/state/counters") get_response = client.get([get_path], data_type="STATE", encoding="JSON_IETF") print(get_response) """Let's build a sampled Subscribe! client.subscribe() accepts an iterable of SubscriptionLists """ +input("Press Enter for Subscribe SAMPLE...") subscription_list = proto.gnmi_pb2.SubscriptionList() subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO") @@ -70,12 +78,11 @@ sampled_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("SAMPLE") sampled_subscription.sample_interval = 10 * int(1e9) subscription_list.subscription.append(sampled_subscription) -# Only print 2 responses -for msg_idx, subscribe_response in enumerate(client.subscribe([subscription_list])): +for subscribe_response in client.subscribe([subscription_list]): print(subscribe_response) - if msg_idx + 1 == 2: - break + break """Now let's do ON_CHANGE. Just have to put SubscriptionMode to ON_CHANGE.""" +input("Press Enter for Subscribe ON_CHANGE...") subscription_list = proto.gnmi_pb2.SubscriptionList() subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") subscription_list.encoding = proto.gnmi_pb2.Encoding.Value("PROTO") @@ -87,23 +94,45 @@ ) onchange_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") subscription_list.subscription.append(onchange_subscription) -# Only print 2 responses -for msg_idx, subscribe_response in enumerate(client.subscribe([subscription_list])): +synced = False +for subscribe_response in client.subscribe([subscription_list]): + if subscribe_response.sync_response: + synced = True + print("Synced. Now perform action that will create a changed value.") + print("If using XR syslog as written, just try SSH'ing to device.") + continue + if not synced: + continue print(subscribe_response) - if msg_idx + 1 == 2: - break + break """Let's build a Set! client.set() expects updates, replaces, and/or deletes to be provided. updates is a list of Updates replaces is a list of Updates deletes is a list of Paths -Let's do an update, and then a delete. +Let's do an update. """ +input("Press Enter for Set update...") set_update = proto.gnmi_pb2.Update() -set_json = """ +# This is the fully modeled JSON we want to update with +update_json = json.loads(""" { - "config": { - "login-banner": "Hello, gNMI!" + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "Loopback9339" + } + ] } } -""" +""") +# Let's just do an update from the very top element +top_element = next(iter(update_json.keys())) +set_update.path.CopyFrom(client.parse_xpath_to_gnmi_path(top_element)) +# Remove the top element from the config since it's now in Path +update_json = update_json.pop(top_element) +# Set our update payload +set_update.val.json_ietf_val = json.dumps(update_json).encode("utf-8") +set_result = client.set(updates=[set_update]) +print(set_result) +# This may all seem somewhat obtuse, and that's what the client wrappers are for. \ No newline at end of file From dbac1e81b8f241dc18bcd643bddf863ee39d51f4 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 3 Apr 2020 12:22:47 -0700 Subject: [PATCH 04/10] Proto append -> extend --- examples/custom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index 4ec2067..d7a6407 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -77,7 +77,7 @@ ) sampled_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("SAMPLE") sampled_subscription.sample_interval = 10 * int(1e9) -subscription_list.subscription.append(sampled_subscription) +subscription_list.subscription.extend([sampled_subscription]) for subscribe_response in client.subscribe([subscription_list]): print(subscribe_response) break @@ -93,7 +93,7 @@ ) ) onchange_subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") -subscription_list.subscription.append(onchange_subscription) +subscription_list.subscription.extend([onchange_subscription]) synced = False for subscribe_response in client.subscribe([subscription_list]): if subscribe_response.sync_response: From c67570510a0d19dd03a284770df4c87a46330037 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 3 Apr 2020 12:24:10 -0700 Subject: [PATCH 05/10] Add more OS aliases --- src/cisco_gnmi/__init__.py | 2 +- src/cisco_gnmi/builder.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cisco_gnmi/__init__.py b/src/cisco_gnmi/__init__.py index cb961fe..edd58b4 100644 --- a/src/cisco_gnmi/__init__.py +++ b/src/cisco_gnmi/__init__.py @@ -30,4 +30,4 @@ from .xe import XEClient from .builder import ClientBuilder -__version__ = "1.0.5" +__version__ = "1.0.6" diff --git a/src/cisco_gnmi/builder.py b/src/cisco_gnmi/builder.py index db1d0d6..bfd9039 100644 --- a/src/cisco_gnmi/builder.py +++ b/src/cisco_gnmi/builder.py @@ -76,9 +76,13 @@ class ClientBuilder(object): os_class_map = { None: Client, + "None": Client, "IOS XR": XRClient, + "XR": XRClient, "NX-OS": NXClient, + "NX": NXClient, "IOS XE": XEClient, + "XE": XEClient } def __init__(self, target): From 8bc952850306f07ab77f07ba58970a3394354fd0 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 16 Apr 2020 16:10:17 -0700 Subject: [PATCH 06/10] Bump version to 1.0.7 --- src/cisco_gnmi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cisco_gnmi/__init__.py b/src/cisco_gnmi/__init__.py index edd58b4..60f494f 100644 --- a/src/cisco_gnmi/__init__.py +++ b/src/cisco_gnmi/__init__.py @@ -30,4 +30,4 @@ from .xe import XEClient from .builder import ClientBuilder -__version__ = "1.0.6" +__version__ = "1.0.7" From 408679e72c0d010420727feae2ae129f5e64db20 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 17 Apr 2020 11:54:03 -0700 Subject: [PATCH 07/10] Mention examples in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0482a13..c0f6dfb 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ cisco-gnmi --help 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. +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. + 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. ### cisco-gnmi CLI From ac0658fa3e00532a664879b07b6d4d6bdf005fec Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 17 Apr 2020 12:11:35 -0700 Subject: [PATCH 08/10] No need for manual protos for ON_CHANGE --- examples/subscribe_onchange.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/subscribe_onchange.py b/examples/subscribe_onchange.py index 0483f50..4bcd0c9 100644 --- a/examples/subscribe_onchange.py +++ b/examples/subscribe_onchange.py @@ -51,18 +51,13 @@ def main(): formatted_messages = [] try: logging.info("Subscribing to %s ...", args.xpath) - subscription_list = proto.gnmi_pb2.SubscriptionList() - subscription_list.mode = proto.gnmi_pb2.SubscriptionList.Mode.Value("STREAM") + sub_args = {"xpath_subscriptions": args.xpath, "sub_mode": "ON_CHANGE"} if args.encoding: - subscription_list.encoding = proto.gnmi_pb2.Encoding.Value(args.encoding) - subscription = proto.gnmi_pb2.Subscription() - subscription.path.CopyFrom(client.parse_xpath_to_gnmi_path(args.xpath)) - subscription.mode = proto.gnmi_pb2.SubscriptionMode.Value("ON_CHANGE") - subscription_list.subscription.append(subscription) - synced = False + sub_args["encoding"] = args.encoding if not args.process_all: logging.info("Ignoring messages before sync_response.") - for message in client.subscribe([subscription_list]): + synced = False + for message in client.subscribe_xpaths(**sub_args): if message.sync_response: synced = True logging.info("Synced with latest state.") From 9a60bf98f9424e0dcae970d608f454dc39ce7a90 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 17 Apr 2020 12:11:57 -0700 Subject: [PATCH 09/10] Black formatting --- examples/custom.py | 8 +++++--- src/cisco_gnmi/builder.py | 2 +- src/cisco_gnmi/cli.py | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index d7a6407..988e057 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -115,7 +115,8 @@ input("Press Enter for Set update...") set_update = proto.gnmi_pb2.Update() # This is the fully modeled JSON we want to update with -update_json = json.loads(""" +update_json = json.loads( + """ { "openconfig-interfaces:interfaces": { "interface": [ @@ -125,7 +126,8 @@ ] } } -""") +""" +) # Let's just do an update from the very top element top_element = next(iter(update_json.keys())) set_update.path.CopyFrom(client.parse_xpath_to_gnmi_path(top_element)) @@ -135,4 +137,4 @@ set_update.val.json_ietf_val = json.dumps(update_json).encode("utf-8") set_result = client.set(updates=[set_update]) print(set_result) -# This may all seem somewhat obtuse, and that's what the client wrappers are for. \ No newline at end of file +# This may all seem somewhat obtuse, and that's what the client wrappers are for. diff --git a/src/cisco_gnmi/builder.py b/src/cisco_gnmi/builder.py index bfd9039..a74a219 100644 --- a/src/cisco_gnmi/builder.py +++ b/src/cisco_gnmi/builder.py @@ -82,7 +82,7 @@ class ClientBuilder(object): "NX-OS": NXClient, "NX": NXClient, "IOS XE": XEClient, - "XE": XEClient + "XE": XEClient, } def __init__(self, target): diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 623d380..ac2bf46 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -64,8 +64,7 @@ def main(): See --help for RPC options. """.format( - version=__version__, - supported_rpcs="\n".join(sorted(list(rpc_map.keys()))) + version=__version__, supported_rpcs="\n".join(sorted(list(rpc_map.keys()))) ), ) parser.add_argument("rpc", help="gNMI RPC to perform against network element.") From 43e08d70a0c41f384e16a8ace07c8e2f5d9b2548 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Fri, 17 Apr 2020 12:13:11 -0700 Subject: [PATCH 10/10] Ignore example outputs --- examples/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/.gitignore diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..d30c83a --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +gnmi_sub.json