From 45b5a60a30fdbb8d26c8f3015608e478786d46ff Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 12:28:55 -0700 Subject: [PATCH 01/17] Scaffold CLI --- src/cisco_gnmi/__init__.py | 2 +- src/cisco_gnmi/cli.py | 106 +++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/cisco_gnmi/cli.py diff --git a/src/cisco_gnmi/__init__.py b/src/cisco_gnmi/__init__.py index 0005330..cb961fe 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.4" +__version__ = "1.0.5" diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py new file mode 100644 index 0000000..ac9ba2e --- /dev/null +++ b/src/cisco_gnmi/cli.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +""" +Command parsing sourced from this wonderful blog by Chase Seibert +https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html +""" +import json +import logging +import argparse +from getpass import getpass +from google.protobuf import json_format, text_format +from . import ClientBuilder +import sys + + +def main(): + # Using a map so we don't have function overlap e.g. set() + rpc_map = { + "capabilities": gnmi_capabilities, + "subscribe": gnmi_subscribe, + "get": gnmi_get, + "set": gnmi_set + } + parser = argparse.ArgumentParser(description="gNMI CLI demonstrating library usage.", usage=""" + gnmcli [] + + Supported RPCs: + %s + + See --help for RPC options. + """.format('\n'.join(rpc_map.keys()))) + parser.add_argument("rpc", help="gNMI RPC to perform against network element.") + if len(sys.argv) < 2: + logging.error("Must at minimum provide RPC and required arguments!") + parser.print_help() + exit(1) + args = parser.parse_args(sys.argv[1:2]) + if args.rpc not in rpc_map.keys(): + logging.error("%s not in supported RPCs: %s!", args.rpc, ', '.join(rpc_map.keys())) + parser.print_help() + exit(1) + rpc_map[args.rpc]() + +def gnmi_capabilities(): + parser = argparse.ArgumentParser( + description="Performs Capabilities RPC against network element." + ) + args = __common_args_handler(parser) + +def gnmi_subscribe(): + parser = argparse.ArgumentParser( + description="Performs Subscribe RPC against network element." + ) + args = __common_args_handler(parser) + pass + +def gnmi_get(): + parser = argparse.ArgumentParser( + description="Performs Get RPC against network element." + ) + args = __common_args_handler(parser) + +def gnmi_set(): + parser = argparse.ArgumentParser( + description="Performs Set RPC against network element." + ) + args = __common_args_handler(parser) + +def __gen_client(netloc, os_name, username, password, root_certificates=None, private_key=None, certificate_chain=None, ssl_target_override=None, auto_ssl_target_override=False): + builder = ClientBuilder(netloc) + builder.set_os(os_name) + builder.set_call_authentication(username, password) + if not any([root_certificates, private_key, certificate_chain]): + builder.set_secure_from_target() + else: + builder.set_secure_from_file(root_certificates, private_key, certificate_chain) + if ssl_target_override: + builder.set_ssl_target_override(ssl_target_override) + elif auto_ssl_target_override: + builder.set_ssl_target_override() + return builder.construct() + +def __common_args_handler(parser): + """Ideally would be a decorator.""" + 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("-root_certificates", description="Root certificates for secure connection.") + parser.add_argument("-private_key", description="Private key for secure connection.") + parser.add_argument("-certificate_chain", description="Certificate chain for secure connection.") + parser.add_argument("-ssl_target_override", description="gRPC SSL target override option.") + parser.add_argument("-auto_ssl_target_override", description="Root certificates for secure connection.", action="store_true") + parser.add_argument("-debug", help="Print debug messages", action="store_true") + args = parser.parse_args(sys.argv[2:]) + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + args.username = input("Username: ") + args.password = getpass() + return args + + +if __name__ == "__main__": + main() \ No newline at end of file From 6bfc312ea5ca449a5ea6a9e86b1d1fd2f0acc5ea Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 14:12:45 -0700 Subject: [PATCH 02/17] Add Subscribe RPC --- src/cisco_gnmi/cli.py | 85 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index ac9ba2e..5fe4091 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -45,37 +45,102 @@ def gnmi_capabilities(): description="Performs Capabilities RPC against network element." ) args = __common_args_handler(parser) + client = __gen_client(args) + logging.info(client.capabilities()) def gnmi_subscribe(): + """Performs a sampled Subscribe against network element. + TODO: ON_CHANGE + """ parser = argparse.ArgumentParser( description="Performs Subscribe RPC against network element." ) + parser.add_argument( + "-xpath", + help="XPath to subscribe to.", + type=str, + action="append" + ) + parser.add_argument( + "-interval", + help="Sample interval in seconds for Subscription.", + type=int, + default=10*int(1e9) + ) + parser.add_argument( + "-dump_file", + help="Filename to dump to.", + type=str, + default="stdout" + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true" + ) + parser.add_argument( + "-sync_stop", help="Stop on sync_response.", action="store_true" + ) + parser.add_argument( + "-encoding", help="gNMI subscription encoding.", type=str, nargs="?" + ) args = __common_args_handler(parser) - pass + # Set default XPath outside of argparse + if not args.xpath: + args.xpath = ["/interfaces/interface/state/counters"] + client = __gen_client(args) + kwargs = {} + if args.encoding: + kwargs["encoding"] = args.encoding + if args.sample_interval: + kwargs["sample_interval"] = args.sample_interval + try: + logging.info("Subscribing to %s ...", args.xpath) + for subscribe_response in client.subscribe_xpaths(args.xpath, **kwargs): + if subscribe_response.sync_response and args.sync_stop: + logging.warning("Stopping on sync_response.") + break + formatted_message = None + if args.dump_json: + formatted_message = json_format.MessageToJson(subscribe_response, sort_keys=True) + else: + formatted_message = text_format.MessageToString(subscribe_response) + if args.dump_file == "stdout": + logging.info(formatted_message) + else: + with open(args.dump_file, "a") as dump_fd: + dump_fd.write(formatted_message) + except KeyboardInterrupt: + logging.warning("Stopping on interrupt.") + except Exception: + logging.exception("Stopping due to exception!") def gnmi_get(): parser = argparse.ArgumentParser( description="Performs Get RPC against network element." ) args = __common_args_handler(parser) + client = __gen_client(args) def gnmi_set(): parser = argparse.ArgumentParser( description="Performs Set RPC against network element." ) args = __common_args_handler(parser) + client = __gen_client(args) + -def __gen_client(netloc, os_name, username, password, root_certificates=None, private_key=None, certificate_chain=None, ssl_target_override=None, auto_ssl_target_override=False): - builder = ClientBuilder(netloc) - builder.set_os(os_name) - builder.set_call_authentication(username, password) - if not any([root_certificates, private_key, certificate_chain]): +def __gen_client(args): + builder = ClientBuilder(args.netloc) + builder.set_os(args.os) + builder.set_call_authentication(args.username, args.password) + if not any([args.root_certificates, args.private_key, args.certificate_chain]): builder.set_secure_from_target() else: - builder.set_secure_from_file(root_certificates, private_key, certificate_chain) - if ssl_target_override: - builder.set_ssl_target_override(ssl_target_override) - elif auto_ssl_target_override: + builder.set_secure_from_file(args.root_certificates, args.private_key, args.certificate_chain) + if args.ssl_target_override: + builder.set_ssl_target_override(args.ssl_target_override) + elif args.auto_ssl_target_override: builder.set_ssl_target_override() return builder.construct() From 049a51244e89a0b936136b55adf01e62893d2006 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 14:39:02 -0700 Subject: [PATCH 03/17] Add Get RPC --- src/cisco_gnmi/cli.py | 56 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 5fe4091..8f082fc 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -1,5 +1,8 @@ #!/usr/bin/env python """ +Wraps gNMI RPCs with a reasonably useful CLI for interacting with network elements. + + Command parsing sourced from this wonderful blog by Chase Seibert https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html """ @@ -8,7 +11,8 @@ import argparse from getpass import getpass from google.protobuf import json_format, text_format -from . import ClientBuilder +from . import ClientBuilder, proto +from google.protobuf.internal import enum_type_wrapper import sys @@ -69,7 +73,7 @@ def gnmi_subscribe(): ) parser.add_argument( "-dump_file", - help="Filename to dump to.", + help="Filename to dump to. Defaults to stdout.", type=str, default="stdout" ) @@ -82,24 +86,28 @@ def gnmi_subscribe(): "-sync_stop", help="Stop on sync_response.", action="store_true" ) parser.add_argument( - "-encoding", help="gNMI subscription encoding.", type=str, nargs="?" + "-encoding", help="gNMI subscription encoding.", type=str, nargs="?", choices=list(proto.gnmi_pb2.Encoding.keys()) ) args = __common_args_handler(parser) - # Set default XPath outside of argparse + # Set default XPath outside of argparse due to default being persistent in argparse. if not args.xpath: args.xpath = ["/interfaces/interface/state/counters"] client = __gen_client(args) + # Take care not to override options unnecessarily. kwargs = {} if args.encoding: kwargs["encoding"] = args.encoding if args.sample_interval: kwargs["sample_interval"] = args.sample_interval try: - logging.info("Subscribing to %s ...", args.xpath) + logging.info("Dumping responses to %s as %s ...", args.dump_file, "JSON" if args.dump_json else "textual proto") + logging.info("Subscribing to:\n%s", '\n'.join(args.xpath)) for subscribe_response in client.subscribe_xpaths(args.xpath, **kwargs): - if subscribe_response.sync_response and args.sync_stop: - logging.warning("Stopping on sync_response.") - break + if subscribe_response.sync_response: + logging.debug("sync_response received.") + if args.sync_stop: + logging.warning("Stopping on sync_response.") + break formatted_message = None if args.dump_json: formatted_message = json_format.MessageToJson(subscribe_response, sort_keys=True) @@ -119,8 +127,40 @@ def gnmi_get(): parser = argparse.ArgumentParser( description="Performs Get RPC against network element." ) + parser.add_argument( + "-xpath", + help="XPaths to Get.", + type=str, + action="append" + ) + parser.add_argument( + "-encoding", help="gNMI subscription encoding.", type=str, nargs="?", choices=list(proto.gnmi_pb2.Encoding.keys()) + ) + parser.add_argument( + "-data_type", help="gNMI GetRequest DataType", type=str, nargs="?", choices=list(enum_type_wrapper.EnumTypeWrapper(proto.gnmi_pb2._GETREQUEST_DATATYPE).keys()) + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true" + ) args = __common_args_handler(parser) + # Set default XPath outside of argparse due to default being persistent in argparse. + if not args.xpath: + args.xpath = ["/interfaces/interface/state/counters"] client = __gen_client(args) + kwargs = {} + if args.encoding: + kwargs["encoding"] = args.encoding + if args.data_type: + kwargs["data_type"] = args.data_type + get_response = client.get_xpaths(args.xpath, **kwargs) + formatted_message = None + if args.dump_json: + formatted_message = json_format.MessageToJson(get_response, sort_keys=True) + else: + formatted_message = text_format.MessageToString(get_response) + logging.info(formatted_message) def gnmi_set(): parser = argparse.ArgumentParser( From dadfd60f1b3c1958e68ac082af784e1f7a2178f7 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 15:00:30 -0700 Subject: [PATCH 04/17] Add Set RPC --- src/cisco_gnmi/cli.py | 70 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 8f082fc..74624b7 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -42,7 +42,11 @@ def main(): logging.error("%s not in supported RPCs: %s!", args.rpc, ', '.join(rpc_map.keys())) parser.print_help() exit(1) - rpc_map[args.rpc]() + try: + rpc_map[args.rpc]() + except: + logging.exception("Error during usage!") + exit(1) def gnmi_capabilities(): parser = argparse.ArgumentParser( @@ -124,6 +128,9 @@ def gnmi_subscribe(): logging.exception("Stopping due to exception!") def gnmi_get(): + """Provides Get RPC usage. Assumes JSON or JSON_IETF style configurations. + TODO: This is the least well understood/implemented. Need to validate if there is an OOO for update/replace/delete. + """ parser = argparse.ArgumentParser( description="Performs Get RPC against network element." ) @@ -163,12 +170,71 @@ def gnmi_get(): logging.info(formatted_message) def gnmi_set(): + """Provides Set RPC usage. Assumes JSON or JSON_IETF style configurations. + Applies update/replace operations, and then delete operations. + TODO: This is the least well understood/implemented. Need to validate if there is an OOO for update/replace/delete. + """ parser = argparse.ArgumentParser( description="Performs Set RPC against network element." ) + parser.add_argument( + "-update_json_config", + description="JSON-modeled config to apply as an update." + ) + parser.add_argument( + "-replace_json_config", + description="JSON-modeled config to apply as an update." + ) + parser.add_argument( + "-delete_xpath", + help="XPaths to delete.", + type=str, + action="append" + ) + parser.add_argument( + "-no_ietf", + help="JSON is not IETF conformant.", + action="store_true" + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true" + ) args = __common_args_handler(parser) + if not any([args.update_json_config, args.replace_json_config, args.delete_xpath]): + raise Exception("Must specify update, replace, or delete parameters!") + def load_json_file(filename): + config = None + with open(filename, "r") as config_fd: + config = json.load(config_fd) + return config + kwargs = {} + if args.update_json_config: + kwargs["update_json_configs"] = load_json_file(args.update_json_config) + if args.replace_json_config: + kwargs["replace_json_configs"] = load_json_file(args.replace_json_config) + if args.no_ietf: + kwargs["ietf"] = False client = __gen_client(args) - + set_response = client.set_json(**kwargs) + formatted_message = None + if args.dump_json: + formatted_message = json_format.MessageToJson(set_response, sort_keys=True) + else: + formatted_message = text_format.MessageToString(set_response) + logging.info(formatted_message) + if args.delete_xpath: + if getattr(client, "delete_xpaths", None) is not None: + delete_response = client.delete_xpaths(args.xpath) + formatted_message = None + if args.dump_json: + formatted_message = json_format.MessageToJson(delete_response, sort_keys=True) + else: + formatted_message = text_format.MessageToString(delete_response) + logging.info(formatted_message) + else: + raise Exception("Convenience delete_xpaths is not supported in the client library!") def __gen_client(args): builder = ClientBuilder(args.netloc) From f2cda6b54534f0dc4920e2079ca12df2f6e5c7a6 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 15:06:30 -0700 Subject: [PATCH 05/17] Black formatting --- src/cisco_gnmi/cli.py | 159 ++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 66 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 74624b7..19ff73d 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -22,16 +22,21 @@ def main(): "capabilities": gnmi_capabilities, "subscribe": gnmi_subscribe, "get": gnmi_get, - "set": gnmi_set + "set": gnmi_set, } - parser = argparse.ArgumentParser(description="gNMI CLI demonstrating library usage.", usage=""" + parser = argparse.ArgumentParser( + description="gNMI CLI demonstrating library usage.", + usage=""" gnmcli [] Supported RPCs: %s See --help for RPC options. - """.format('\n'.join(rpc_map.keys()))) + """.format( + "\n".join(rpc_map.keys()) + ), + ) parser.add_argument("rpc", help="gNMI RPC to perform against network element.") if len(sys.argv) < 2: logging.error("Must at minimum provide RPC and required arguments!") @@ -39,7 +44,9 @@ def main(): exit(1) args = parser.parse_args(sys.argv[1:2]) if args.rpc not in rpc_map.keys(): - logging.error("%s not in supported RPCs: %s!", args.rpc, ', '.join(rpc_map.keys())) + logging.error( + "%s not in supported RPCs: %s!", args.rpc, ", ".join(rpc_map.keys()) + ) parser.print_help() exit(1) try: @@ -48,13 +55,16 @@ def main(): logging.exception("Error during usage!") exit(1) + def gnmi_capabilities(): parser = argparse.ArgumentParser( description="Performs Capabilities RPC against network element." ) args = __common_args_handler(parser) client = __gen_client(args) - logging.info(client.capabilities()) + capability_response = client.capabilities() + logging.info(__format_message(capability_response)) + def gnmi_subscribe(): """Performs a sampled Subscribe against network element. @@ -64,33 +74,34 @@ def gnmi_subscribe(): description="Performs Subscribe RPC against network element." ) parser.add_argument( - "-xpath", - help="XPath to subscribe to.", - type=str, - action="append" + "-xpath", help="XPath to subscribe to.", type=str, action="append" ) parser.add_argument( "-interval", help="Sample interval in seconds for Subscription.", type=int, - default=10*int(1e9) + default=10 * int(1e9), ) parser.add_argument( "-dump_file", help="Filename to dump to. Defaults to stdout.", type=str, - default="stdout" + default="stdout", ) parser.add_argument( "-dump_json", help="Dump as JSON instead of textual protos.", - action="store_true" + action="store_true", ) parser.add_argument( "-sync_stop", help="Stop on sync_response.", action="store_true" ) parser.add_argument( - "-encoding", help="gNMI subscription encoding.", type=str, nargs="?", choices=list(proto.gnmi_pb2.Encoding.keys()) + "-encoding", + help="gNMI subscription encoding.", + type=str, + nargs="?", + choices=list(proto.gnmi_pb2.Encoding.keys()), ) args = __common_args_handler(parser) # Set default XPath outside of argparse due to default being persistent in argparse. @@ -104,19 +115,19 @@ def gnmi_subscribe(): if args.sample_interval: kwargs["sample_interval"] = args.sample_interval try: - logging.info("Dumping responses to %s as %s ...", args.dump_file, "JSON" if args.dump_json else "textual proto") - logging.info("Subscribing to:\n%s", '\n'.join(args.xpath)) + logging.info( + "Dumping responses to %s as %s ...", + args.dump_file, + "JSON" if args.dump_json else "textual proto", + ) + logging.info("Subscribing to:\n%s", "\n".join(args.xpath)) for subscribe_response in client.subscribe_xpaths(args.xpath, **kwargs): if subscribe_response.sync_response: logging.debug("sync_response received.") if args.sync_stop: logging.warning("Stopping on sync_response.") break - formatted_message = None - if args.dump_json: - formatted_message = json_format.MessageToJson(subscribe_response, sort_keys=True) - else: - formatted_message = text_format.MessageToString(subscribe_response) + formatted_message = __format_message(subscribe_response) if args.dump_file == "stdout": logging.info(formatted_message) else: @@ -127,6 +138,7 @@ def gnmi_subscribe(): except Exception: logging.exception("Stopping due to exception!") + def gnmi_get(): """Provides Get RPC usage. Assumes JSON or JSON_IETF style configurations. TODO: This is the least well understood/implemented. Need to validate if there is an OOO for update/replace/delete. @@ -134,22 +146,29 @@ def gnmi_get(): parser = argparse.ArgumentParser( description="Performs Get RPC against network element." ) + parser.add_argument("-xpath", help="XPaths to Get.", type=str, action="append") parser.add_argument( - "-xpath", - help="XPaths to Get.", + "-encoding", + help="gNMI subscription encoding.", type=str, - action="append" - ) - parser.add_argument( - "-encoding", help="gNMI subscription encoding.", type=str, nargs="?", choices=list(proto.gnmi_pb2.Encoding.keys()) + nargs="?", + choices=list(proto.gnmi_pb2.Encoding.keys()), ) parser.add_argument( - "-data_type", help="gNMI GetRequest DataType", type=str, nargs="?", choices=list(enum_type_wrapper.EnumTypeWrapper(proto.gnmi_pb2._GETREQUEST_DATATYPE).keys()) + "-data_type", + help="gNMI GetRequest DataType", + type=str, + nargs="?", + choices=list( + enum_type_wrapper.EnumTypeWrapper( + proto.gnmi_pb2._GETREQUEST_DATATYPE + ).keys() + ), ) parser.add_argument( "-dump_json", help="Dump as JSON instead of textual protos.", - action="store_true" + action="store_true", ) args = __common_args_handler(parser) # Set default XPath outside of argparse due to default being persistent in argparse. @@ -162,12 +181,8 @@ def gnmi_get(): if args.data_type: kwargs["data_type"] = args.data_type get_response = client.get_xpaths(args.xpath, **kwargs) - formatted_message = None - if args.dump_json: - formatted_message = json_format.MessageToJson(get_response, sort_keys=True) - else: - formatted_message = text_format.MessageToString(get_response) - logging.info(formatted_message) + logging.info(__format_message(get_response)) + def gnmi_set(): """Provides Set RPC usage. Assumes JSON or JSON_IETF style configurations. @@ -178,37 +193,32 @@ def gnmi_set(): description="Performs Set RPC against network element." ) parser.add_argument( - "-update_json_config", - description="JSON-modeled config to apply as an update." + "-update_json_config", description="JSON-modeled config to apply as an update." ) parser.add_argument( - "-replace_json_config", - description="JSON-modeled config to apply as an update." + "-replace_json_config", description="JSON-modeled config to apply as an update." ) parser.add_argument( - "-delete_xpath", - help="XPaths to delete.", - type=str, - action="append" + "-delete_xpath", help="XPaths to delete.", type=str, action="append" ) parser.add_argument( - "-no_ietf", - help="JSON is not IETF conformant.", - action="store_true" + "-no_ietf", help="JSON is not IETF conformant.", action="store_true" ) parser.add_argument( "-dump_json", help="Dump as JSON instead of textual protos.", - action="store_true" + action="store_true", ) args = __common_args_handler(parser) if not any([args.update_json_config, args.replace_json_config, args.delete_xpath]): raise Exception("Must specify update, replace, or delete parameters!") + def load_json_file(filename): config = None with open(filename, "r") as config_fd: config = json.load(config_fd) return config + kwargs = {} if args.update_json_config: kwargs["update_json_configs"] = load_json_file(args.update_json_config) @@ -218,23 +228,16 @@ def load_json_file(filename): kwargs["ietf"] = False client = __gen_client(args) set_response = client.set_json(**kwargs) - formatted_message = None - if args.dump_json: - formatted_message = json_format.MessageToJson(set_response, sort_keys=True) - else: - formatted_message = text_format.MessageToString(set_response) - logging.info(formatted_message) + logging.info(__format_message(set_response)) if args.delete_xpath: if getattr(client, "delete_xpaths", None) is not None: delete_response = client.delete_xpaths(args.xpath) - formatted_message = None - if args.dump_json: - formatted_message = json_format.MessageToJson(delete_response, sort_keys=True) - else: - formatted_message = text_format.MessageToString(delete_response) - logging.info(formatted_message) + logging.info(__format_message(delete_response)) else: - raise Exception("Convenience delete_xpaths is not supported in the client library!") + raise Exception( + "Convenience delete_xpaths is not supported in the client library!" + ) + def __gen_client(args): builder = ClientBuilder(args.netloc) @@ -243,13 +246,25 @@ def __gen_client(args): if not any([args.root_certificates, args.private_key, args.certificate_chain]): builder.set_secure_from_target() else: - builder.set_secure_from_file(args.root_certificates, args.private_key, args.certificate_chain) + builder.set_secure_from_file( + args.root_certificates, args.private_key, args.certificate_chain + ) if args.ssl_target_override: builder.set_ssl_target_override(args.ssl_target_override) elif args.auto_ssl_target_override: builder.set_ssl_target_override() return builder.construct() + +def __format_message(message, as_json=False): + formatted_message = None + if as_json: + formatted_message = json_format.MessageToJson(message, sort_keys=True) + else: + formatted_message = text_format.MessageToString(message) + return formatted_message + + def __common_args_handler(parser): """Ideally would be a decorator.""" parser.add_argument("netloc", help=":", type=str) @@ -260,11 +275,23 @@ def __common_args_handler(parser): default="IOS XR", choices=list(ClientBuilder.os_class_map.keys()), ) - parser.add_argument("-root_certificates", description="Root certificates for secure connection.") - parser.add_argument("-private_key", description="Private key for secure connection.") - parser.add_argument("-certificate_chain", description="Certificate chain for secure connection.") - parser.add_argument("-ssl_target_override", description="gRPC SSL target override option.") - parser.add_argument("-auto_ssl_target_override", description="Root certificates for secure connection.", action="store_true") + parser.add_argument( + "-root_certificates", description="Root certificates for secure connection." + ) + parser.add_argument( + "-private_key", description="Private key for secure connection." + ) + parser.add_argument( + "-certificate_chain", description="Certificate chain for secure connection." + ) + parser.add_argument( + "-ssl_target_override", description="gRPC SSL target override option." + ) + parser.add_argument( + "-auto_ssl_target_override", + description="Root certificates for secure connection.", + action="store_true", + ) parser.add_argument("-debug", help="Print debug messages", action="store_true") args = parser.parse_args(sys.argv[2:]) logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) @@ -274,4 +301,4 @@ def __common_args_handler(parser): if __name__ == "__main__": - main() \ No newline at end of file + main() From be3e58f44d4b5ccd2ac29e18a3e37930de876e26 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 15:07:52 -0700 Subject: [PATCH 06/17] Add copyright header --- src/cisco_gnmi/cli.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 19ff73d..0d6aa22 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -1,7 +1,30 @@ #!/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. """ -Wraps gNMI RPCs with a reasonably useful CLI for interacting with network elements. +""" +Wraps gNMI RPCs with a reasonably useful CLI for interacting with network elements. +Supports Capabilities, Subscribe, Get, and Set. Command parsing sourced from this wonderful blog by Chase Seibert https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html From 025d778a9db93884635625939293b3d3e7cf0aae Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 15:50:42 -0700 Subject: [PATCH 07/17] Fix CLI handlings/ergonomics --- src/cisco_gnmi/cli.py | 49 ++++++++++++++++------------------------ src/cisco_gnmi/client.py | 2 +- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 0d6aa22..e18fd98 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -50,21 +50,17 @@ def main(): parser = argparse.ArgumentParser( description="gNMI CLI demonstrating library usage.", usage=""" - gnmcli [] +gnmcli [] - Supported RPCs: - %s +Supported RPCs: +{supported_rpcs} - See --help for RPC options. +See --help for RPC options. """.format( - "\n".join(rpc_map.keys()) + supported_rpcs="\n".join(list(rpc_map.keys())) ), ) parser.add_argument("rpc", help="gNMI RPC to perform against network element.") - if len(sys.argv) < 2: - logging.error("Must at minimum provide RPC and required arguments!") - parser.print_help() - exit(1) args = parser.parse_args(sys.argv[1:2]) if args.rpc not in rpc_map.keys(): logging.error( @@ -74,7 +70,7 @@ def main(): exit(1) try: rpc_map[args.rpc]() - except: + except Exception: logging.exception("Error during usage!") exit(1) @@ -103,7 +99,7 @@ def gnmi_subscribe(): "-interval", help="Sample interval in seconds for Subscription.", type=int, - default=10 * int(1e9), + default=10, ) parser.add_argument( "-dump_file", @@ -124,7 +120,7 @@ def gnmi_subscribe(): help="gNMI subscription encoding.", type=str, nargs="?", - choices=list(proto.gnmi_pb2.Encoding.keys()), + choices=proto.gnmi_pb2.Encoding.keys(), ) args = __common_args_handler(parser) # Set default XPath outside of argparse due to default being persistent in argparse. @@ -135,8 +131,8 @@ def gnmi_subscribe(): kwargs = {} if args.encoding: kwargs["encoding"] = args.encoding - if args.sample_interval: - kwargs["sample_interval"] = args.sample_interval + if args.interval: + kwargs["sample_interval"] = args.interval * int(1e9) try: logging.info( "Dumping responses to %s as %s ...", @@ -145,6 +141,7 @@ def gnmi_subscribe(): ) logging.info("Subscribing to:\n%s", "\n".join(args.xpath)) for subscribe_response in client.subscribe_xpaths(args.xpath, **kwargs): + logging.debug("SubscribeResponse received.") if subscribe_response.sync_response: logging.debug("sync_response received.") if args.sync_stop: @@ -175,18 +172,16 @@ def gnmi_get(): help="gNMI subscription encoding.", type=str, nargs="?", - choices=list(proto.gnmi_pb2.Encoding.keys()), + choices=proto.gnmi_pb2.Encoding.keys(), ) parser.add_argument( "-data_type", help="gNMI GetRequest DataType", type=str, nargs="?", - choices=list( - enum_type_wrapper.EnumTypeWrapper( - proto.gnmi_pb2._GETREQUEST_DATATYPE - ).keys() - ), + choices=enum_type_wrapper.EnumTypeWrapper( + proto.gnmi_pb2._GETREQUEST_DATATYPE + ).keys(), ) parser.add_argument( "-dump_json", @@ -299,20 +294,16 @@ def __common_args_handler(parser): choices=list(ClientBuilder.os_class_map.keys()), ) parser.add_argument( - "-root_certificates", description="Root certificates for secure connection." - ) - parser.add_argument( - "-private_key", description="Private key for secure connection." - ) - parser.add_argument( - "-certificate_chain", description="Certificate chain for secure connection." + "-root_certificates", help="Root certificates for secure connection." ) + parser.add_argument("-private_key", help="Private key for secure connection.") parser.add_argument( - "-ssl_target_override", description="gRPC SSL target override option." + "-certificate_chain", help="Certificate chain for secure connection." ) + parser.add_argument("-ssl_target_override", help="gRPC SSL target override option.") parser.add_argument( "-auto_ssl_target_override", - description="Root certificates for secure connection.", + help="Root certificates for secure connection.", action="store_true", ) parser.add_argument("-debug", help="Print debug messages", action="store_true") diff --git a/src/cisco_gnmi/client.py b/src/cisco_gnmi/client.py index a46dcc0..7b7ca40 100755 --- a/src/cisco_gnmi/client.py +++ b/src/cisco_gnmi/client.py @@ -152,7 +152,7 @@ def get( "encoding", encoding, "Encoding", proto.gnmi_pb2.Encoding ) request = proto.gnmi_pb2.GetRequest() - if not isinstance(paths, (list, set)): + if not isinstance(paths, (list, set, map)): raise Exception("paths must be an iterable containing Path(s)!") request.path.extend(paths) request.type = data_type From d14b909ddac4c721fdac16065a45572416c8bed5 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 15:52:07 -0700 Subject: [PATCH 08/17] Add gnmcli as console script --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 08867d5..c33837b 100644 --- a/setup.py +++ b/setup.py @@ -69,4 +69,5 @@ "coverage", ], }, + entry_points={"console_scripts": ["gnmcli = cisco_gnmi.cli:main"]}, ) From 0cd6cbe16d372f3b0c475a19c8bdee72b7df6b9c Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 16:07:04 -0700 Subject: [PATCH 09/17] Fix Set argparse usage --- src/cisco_gnmi/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index e18fd98..97589be 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -211,10 +211,10 @@ def gnmi_set(): description="Performs Set RPC against network element." ) parser.add_argument( - "-update_json_config", description="JSON-modeled config to apply as an update." + "-update_json_config", help="JSON-modeled config to apply as an update." ) parser.add_argument( - "-replace_json_config", description="JSON-modeled config to apply as an update." + "-replace_json_config", help="JSON-modeled config to apply as an update." ) parser.add_argument( "-delete_xpath", help="XPaths to delete.", type=str, action="append" From d582257e24ca9d7f8a640173c77b0391f76de22c Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 16:07:15 -0700 Subject: [PATCH 10/17] Add gnmcli README section --- README.md | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27980e4..bb7dc93 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,16 @@ This library wraps gNMI functionality to ease usage with Cisco implementations i ```bash pip install cisco-gnmi python -c "import cisco_gnmi; print(cisco_gnmi)" +gnmcli --help ``` -This library covers the gNMI defined `capabilities`, `get`, `set`, and `subscribe` RPCs, and helper clients provide OS-specific recommendations. As commonalities and differences are identified this library will be refactored as necessary. +This library covers the gNMI defined `Capabilities`, `Get`, `Set`, and `Subscribe` RPCs, and helper clients provide OS-specific recommendations. A CLI is also available. As commonalities and differences are identified between OS functionality this library will be refactored as necessary. 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. +### gnmcli +Since `v1.0.5` a gNMI CLI is available when this module is installed. `Capabilities`, `Subscribe`, `Get`, and rudimentary `Set` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [gnmcli Usage](#gnmcli-usage). + ### ClientBuilder Since `v1.0.0` a builder pattern is available with `ClientBuilder`. `ClientBuilder` provides several `set_*` methods which define the intended `Client` connectivity and a `construct` method to construct and return the desired `Client`. There are several major methods involved here: @@ -181,6 +185,195 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ./update_protos.sh ``` +### gnmicli Usage +The below details the current `gnmcli` usage options. + +``` +gnmcli --help +usage: +gnmcli [] + +Supported RPCs: +capabilities +subscribe +get +set + +See --help for RPC options. + + +gNMI CLI demonstrating library usage. + +positional arguments: + rpc gNMI RPC to perform against network element. + +optional arguments: + -h, --help show this help message and exit +``` + +#### Capabilities +``` +gnmcli capabilities --help +usage: gnmcli [-h] [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Capabilities RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -os {None,IOS XR,NX-OS,IOS XE} + OS to use. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Root certificates for secure connection. + -debug Print debug messages +``` + +#### Get +``` +gnmcli get --help +usage: gnmcli [-h] [-xpath XPATH] + [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] + [-data_type [{ALL,CONFIG,STATE,OPERATIONAL}]] [-dump_json] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Get RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -xpath XPATH XPaths to Get. + -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] + gNMI subscription encoding. + -data_type [{ALL,CONFIG,STATE,OPERATIONAL}] + gNMI GetRequest DataType + -dump_json Dump as JSON instead of textual protos. + -os {None,IOS XR,NX-OS,IOS XE} + OS to use. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Root certificates for secure connection. + -debug Print debug messages +``` + +#### Subscribe +Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) +``` +gnmcli subscribe --help +usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] + [-dump_json] [-sync_stop] + [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Subscribe RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -xpath XPATH XPath to subscribe to. + -interval INTERVAL Sample interval in seconds for Subscription. + -dump_file DUMP_FILE Filename to dump to. Defaults to stdout. + -dump_json Dump as JSON instead of textual protos. + -sync_stop Stop on sync_response. + -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] + gNMI subscription encoding. + -os {None,IOS XR,NX-OS,IOS XE} + OS to use. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Root certificates for secure connection. + -debug Print debug messages +``` + +#### Set +``` +gnmcli set --help +usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] + [-replace_json_config REPLACE_JSON_CONFIG] + [-delete_xpath DELETE_XPATH] [-no_ietf] [-dump_json] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Set RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -update_json_config UPDATE_JSON_CONFIG + JSON-modeled config to apply as an update. + -replace_json_config REPLACE_JSON_CONFIG + JSON-modeled config to apply as an update. + -delete_xpath DELETE_XPATH + XPaths to delete. + -no_ietf JSON is not IETF conformant. + -dump_json Dump as JSON instead of textual protos. + -os {None,IOS XR,NX-OS,IOS XE} + OS to use. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Root certificates for secure connection. + -debug Print debug messages +``` + ## Licensing `cisco-gnmi-python` is licensed as [Apache License, Version 2.0](LICENSE). From 9fb52177909209e510cc9235ad2d02e8ee729931 Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 16:18:00 -0700 Subject: [PATCH 11/17] Add Capabilities, Get, Subscribe usage examples to README --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb7dc93..802650a 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ./update_protos.sh ``` -### gnmicli Usage +### gnmcli Usage The below details the current `gnmcli` usage options. ``` @@ -212,6 +212,11 @@ optional arguments: ``` #### Capabilities +This command will output the `CapabilitiesResponse` to `stdout`. +``` +gnmcli capabilities 127.0.0.1:57500 -auto_ssl_target_override +``` + ``` gnmcli capabilities --help usage: gnmcli [-h] [-os {None,IOS XR,NX-OS,IOS XE}] @@ -245,6 +250,11 @@ optional arguments: ``` #### Get +This command will output the `GetResponse` to `stdout`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. +``` +gnmcli get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +``` + ``` gnmcli get --help usage: gnmcli [-h] [-xpath XPATH] @@ -287,7 +297,11 @@ optional arguments: ``` #### Subscribe -Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) +This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) +``` +gnmcli subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +``` + ``` gnmcli subscribe --help usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] @@ -331,6 +345,7 @@ optional arguments: ``` #### Set +This command has not been validated. ``` gnmcli set --help usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] From 8238edab0e5aba223be47edbd2b0d795c0a27fae Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Thu, 26 Mar 2020 16:39:50 -0700 Subject: [PATCH 12/17] Improve doc --- README.md | 102 +++++++++++++++++++++++------------------- src/cisco_gnmi/cli.py | 40 ++++++++++------- 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 802650a..8f5af54 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This library covers the gNMI defined `Capabilities`, `Get`, `Set`, and `Subscrib 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. ### gnmcli -Since `v1.0.5` a gNMI CLI is available when this module is installed. `Capabilities`, `Subscribe`, `Get`, and rudimentary `Set` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [gnmcli Usage](#gnmcli-usage). +Since `v1.0.5` a gNMI CLI is available when this module is installed. `Capabilities`, `Get`, rudimentary `Set`, and `Subscribe` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [gnmcli Usage](#gnmcli-usage). ### ClientBuilder Since `v1.0.0` a builder pattern is available with `ClientBuilder`. `ClientBuilder` provides several `set_*` methods which define the intended `Client` connectivity and a `construct` method to construct and return the desired `Client`. There are several major methods involved here: @@ -186,7 +186,7 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ``` ### gnmcli Usage -The below details the current `gnmcli` usage options. +The below details the current `gnmcli` usage options. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. ``` gnmcli --help @@ -199,6 +199,11 @@ subscribe get set +gnmcli capabilities 127.0.0.1:57500 +gnmcli get 127.0.0.1:57500 +gnmcli set 127.0.0.1:57500 -delete_xpath Cisco-IOS-XR-shellutil-cfg:host-names/host-name +gnmcli subscribe 127.0.0.1:57500 -debug -auto_ssl_target_override -dump_file intfcounters.proto.txt + See --help for RPC options. @@ -235,7 +240,7 @@ positional arguments: optional arguments: -h, --help show this help message and exit -os {None,IOS XR,NX-OS,IOS XE} - OS to use. + OS wrapper to utilize. Defaults to IOS XR. -root_certificates ROOT_CERTIFICATES Root certificates for secure connection. -private_key PRIVATE_KEY @@ -245,8 +250,9 @@ optional arguments: -ssl_target_override SSL_TARGET_OVERRIDE gRPC SSL target override option. -auto_ssl_target_override - Root certificates for secure connection. - -debug Print debug messages + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. ``` #### Get @@ -256,7 +262,6 @@ gnmcli get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/count ``` ``` -gnmcli get --help usage: gnmcli [-h] [-xpath XPATH] [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] [-data_type [{ALL,CONFIG,STATE,OPERATIONAL}]] [-dump_json] @@ -277,12 +282,12 @@ optional arguments: -h, --help show this help message and exit -xpath XPATH XPaths to Get. -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] - gNMI subscription encoding. + gNMI Encoding. -data_type [{ALL,CONFIG,STATE,OPERATIONAL}] gNMI GetRequest DataType -dump_json Dump as JSON instead of textual protos. -os {None,IOS XR,NX-OS,IOS XE} - OS to use. + OS wrapper to utilize. Defaults to IOS XR. -root_certificates ROOT_CERTIFICATES Root certificates for secure connection. -private_key PRIVATE_KEY @@ -292,21 +297,17 @@ optional arguments: -ssl_target_override SSL_TARGET_OVERRIDE gRPC SSL target override option. -auto_ssl_target_override - Root certificates for secure connection. - -debug Print debug messages -``` - -#### Subscribe -This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) -``` -gnmcli subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. ``` +#### Set +This command has not been validated. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. ``` -gnmcli subscribe --help -usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] - [-dump_json] [-sync_stop] - [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] +usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] + [-replace_json_config REPLACE_JSON_CONFIG] + [-delete_xpath DELETE_XPATH] [-no_ietf] [-dump_json] [-os {None,IOS XR,NX-OS,IOS XE}] [-root_certificates ROOT_CERTIFICATES] [-private_key PRIVATE_KEY] @@ -315,22 +316,23 @@ usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] [-auto_ssl_target_override] [-debug] netloc -Performs Subscribe RPC against network element. +Performs Set RPC against network element. positional arguments: netloc : optional arguments: -h, --help show this help message and exit - -xpath XPATH XPath to subscribe to. - -interval INTERVAL Sample interval in seconds for Subscription. - -dump_file DUMP_FILE Filename to dump to. Defaults to stdout. + -update_json_config UPDATE_JSON_CONFIG + JSON-modeled config to apply as an update. + -replace_json_config REPLACE_JSON_CONFIG + JSON-modeled config to apply as a replace. + -delete_xpath DELETE_XPATH + XPaths to delete. + -no_ietf JSON is not IETF conformant. -dump_json Dump as JSON instead of textual protos. - -sync_stop Stop on sync_response. - -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] - gNMI subscription encoding. -os {None,IOS XR,NX-OS,IOS XE} - OS to use. + OS wrapper to utilize. Defaults to IOS XR. -root_certificates ROOT_CERTIFICATES Root certificates for secure connection. -private_key PRIVATE_KEY @@ -340,17 +342,22 @@ optional arguments: -ssl_target_override SSL_TARGET_OVERRIDE gRPC SSL target override option. -auto_ssl_target_override - Root certificates for secure connection. - -debug Print debug messages + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. ``` -#### Set -This command has not been validated. +#### Subscribe +This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) ``` -gnmcli set --help -usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] - [-replace_json_config REPLACE_JSON_CONFIG] - [-delete_xpath DELETE_XPATH] [-no_ietf] [-dump_json] +gnmcli subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +``` + +``` +gnmcli subscribe --help +usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] + [-dump_json] [-sync_stop] + [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] [-os {None,IOS XR,NX-OS,IOS XE}] [-root_certificates ROOT_CERTIFICATES] [-private_key PRIVATE_KEY] @@ -359,23 +366,23 @@ usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] [-auto_ssl_target_override] [-debug] netloc -Performs Set RPC against network element. +Performs Subscribe RPC against network element. positional arguments: netloc : optional arguments: -h, --help show this help message and exit - -update_json_config UPDATE_JSON_CONFIG - JSON-modeled config to apply as an update. - -replace_json_config REPLACE_JSON_CONFIG - JSON-modeled config to apply as an update. - -delete_xpath DELETE_XPATH - XPaths to delete. - -no_ietf JSON is not IETF conformant. + -xpath XPATH XPath to subscribe to. + -interval INTERVAL Sample interval in seconds for Subscription. Defaults + to 10. + -dump_file DUMP_FILE Filename to dump to. Defaults to stdout. -dump_json Dump as JSON instead of textual protos. + -sync_stop Stop on sync_response. + -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] + gNMI Encoding. -os {None,IOS XR,NX-OS,IOS XE} - OS to use. + OS wrapper to utilize. Defaults to IOS XR. -root_certificates ROOT_CERTIFICATES Root certificates for secure connection. -private_key PRIVATE_KEY @@ -385,8 +392,9 @@ optional arguments: -ssl_target_override SSL_TARGET_OVERRIDE gRPC SSL target override option. -auto_ssl_target_override - Root certificates for secure connection. - -debug Print debug messages + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. ``` ## Licensing diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 97589be..c0cabaf 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -55,6 +55,11 @@ def main(): Supported RPCs: {supported_rpcs} +gnmcli capabilities 127.0.0.1:57500 +gnmcli get 127.0.0.1:57500 -xpath /interfaces/interface/state/counters +gnmcli set 127.0.0.1:57500 -update_json_config newconfig.json +gnmcli subscribe 127.0.0.1:57500 -xpath /interfaces/interface/state/counters -dump_file intfcounters.proto.txt + See --help for RPC options. """.format( supported_rpcs="\n".join(list(rpc_map.keys())) @@ -97,7 +102,7 @@ def gnmi_subscribe(): ) parser.add_argument( "-interval", - help="Sample interval in seconds for Subscription.", + help="Sample interval in seconds for Subscription. Defaults to 10.", type=int, default=10, ) @@ -117,7 +122,7 @@ def gnmi_subscribe(): ) parser.add_argument( "-encoding", - help="gNMI subscription encoding.", + help="gNMI Encoding.", type=str, nargs="?", choices=proto.gnmi_pb2.Encoding.keys(), @@ -169,7 +174,7 @@ def gnmi_get(): parser.add_argument("-xpath", help="XPaths to Get.", type=str, action="append") parser.add_argument( "-encoding", - help="gNMI subscription encoding.", + help="gNMI Encoding.", type=str, nargs="?", choices=proto.gnmi_pb2.Encoding.keys(), @@ -214,7 +219,7 @@ def gnmi_set(): "-update_json_config", help="JSON-modeled config to apply as an update." ) parser.add_argument( - "-replace_json_config", help="JSON-modeled config to apply as an update." + "-replace_json_config", help="JSON-modeled config to apply as a replace." ) parser.add_argument( "-delete_xpath", help="XPaths to delete.", type=str, action="append" @@ -237,16 +242,17 @@ def load_json_file(filename): config = json.load(config_fd) return config - kwargs = {} - if args.update_json_config: - kwargs["update_json_configs"] = load_json_file(args.update_json_config) - if args.replace_json_config: - kwargs["replace_json_configs"] = load_json_file(args.replace_json_config) - if args.no_ietf: - kwargs["ietf"] = False - client = __gen_client(args) - set_response = client.set_json(**kwargs) - logging.info(__format_message(set_response)) + if args.update_json_config or args.replace_json_config: + kwargs = {} + if args.update_json_config: + kwargs["update_json_configs"] = load_json_file(args.update_json_config) + if args.replace_json_config: + kwargs["replace_json_configs"] = load_json_file(args.replace_json_config) + if args.no_ietf: + kwargs["ietf"] = False + client = __gen_client(args) + set_response = client.set_json(**kwargs) + logging.info(__format_message(set_response)) if args.delete_xpath: if getattr(client, "delete_xpaths", None) is not None: delete_response = client.delete_xpaths(args.xpath) @@ -288,7 +294,7 @@ def __common_args_handler(parser): parser.add_argument("netloc", help=":", type=str) parser.add_argument( "-os", - help="OS to use.", + help="OS wrapper to utilize. Defaults to IOS XR.", type=str, default="IOS XR", choices=list(ClientBuilder.os_class_map.keys()), @@ -303,10 +309,10 @@ def __common_args_handler(parser): parser.add_argument("-ssl_target_override", help="gRPC SSL target override option.") parser.add_argument( "-auto_ssl_target_override", - help="Root certificates for secure connection.", + help="Use root_certificates first CN as grpc.ssl_target_name_override.", action="store_true", ) - parser.add_argument("-debug", help="Print debug messages", action="store_true") + parser.add_argument("-debug", help="Print debug messages.", action="store_true") args = parser.parse_args(sys.argv[2:]) logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) args.username = input("Username: ") From afd782f9e09593bbb5955295c948dab9875a0b0d Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Mon, 30 Mar 2020 15:03:14 -0700 Subject: [PATCH 13/17] Change gnmcli to cisco-gnmi --- README.md | 42 +++++++++++++++++++++--------------------- setup.py | 2 +- src/cisco_gnmi/cli.py | 12 ++++++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 8f5af54..88d4e50 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,15 @@ This library wraps gNMI functionality to ease usage with Cisco implementations i ```bash pip install cisco-gnmi python -c "import cisco_gnmi; print(cisco_gnmi)" -gnmcli --help +cisco-gnmi --help ``` -This library covers the gNMI defined `Capabilities`, `Get`, `Set`, and `Subscribe` RPCs, and helper clients provide OS-specific recommendations. A CLI is also available. As commonalities and differences are identified between OS functionality this library will be refactored as necessary. +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. 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. -### gnmcli -Since `v1.0.5` a gNMI CLI is available when this module is installed. `Capabilities`, `Get`, rudimentary `Set`, and `Subscribe` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [gnmcli Usage](#gnmcli-usage). +### cisco-gnmi CLI +Since `v1.0.5` a gNMI CLI is available as `cisco-gnmi` when this module is installed. `Capabilities`, `Get`, rudimentary `Set`, and `Subscribe` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [CLI Usage](#cli-usage). ### ClientBuilder Since `v1.0.0` a builder pattern is available with `ClientBuilder`. `ClientBuilder` provides several `set_*` methods which define the intended `Client` connectivity and a `construct` method to construct and return the desired `Client`. There are several major methods involved here: @@ -185,13 +185,13 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ./update_protos.sh ``` -### gnmcli Usage -The below details the current `gnmcli` usage options. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. +### CLI Usage +The below details the current `cisco-gnmi` usage options. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. ``` -gnmcli --help +cisco-gnmi --help usage: -gnmcli [] +cisco-gnmi [] Supported RPCs: capabilities @@ -199,10 +199,10 @@ subscribe get set -gnmcli capabilities 127.0.0.1:57500 -gnmcli get 127.0.0.1:57500 -gnmcli set 127.0.0.1:57500 -delete_xpath Cisco-IOS-XR-shellutil-cfg:host-names/host-name -gnmcli subscribe 127.0.0.1:57500 -debug -auto_ssl_target_override -dump_file intfcounters.proto.txt +cisco-gnmi capabilities 127.0.0.1:57500 +cisco-gnmi get 127.0.0.1:57500 +cisco-gnmi set 127.0.0.1:57500 -delete_xpath Cisco-IOS-XR-shellutil-cfg:host-names/host-name +cisco-gnmi subscribe 127.0.0.1:57500 -debug -auto_ssl_target_override -dump_file intfcounters.proto.txt See --help for RPC options. @@ -219,12 +219,12 @@ optional arguments: #### Capabilities This command will output the `CapabilitiesResponse` to `stdout`. ``` -gnmcli capabilities 127.0.0.1:57500 -auto_ssl_target_override +cisco-gnmi capabilities 127.0.0.1:57500 -auto_ssl_target_override ``` ``` -gnmcli capabilities --help -usage: gnmcli [-h] [-os {None,IOS XR,NX-OS,IOS XE}] +cisco-gnmi capabilities --help +usage: cisco-gnmi [-h] [-os {None,IOS XR,NX-OS,IOS XE}] [-root_certificates ROOT_CERTIFICATES] [-private_key PRIVATE_KEY] [-certificate_chain CERTIFICATE_CHAIN] @@ -258,11 +258,11 @@ optional arguments: #### Get This command will output the `GetResponse` to `stdout`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. ``` -gnmcli get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +cisco-gnmi get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override ``` ``` -usage: gnmcli [-h] [-xpath XPATH] +usage: cisco-gnmi [-h] [-xpath XPATH] [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] [-data_type [{ALL,CONFIG,STATE,OPERATIONAL}]] [-dump_json] [-os {None,IOS XR,NX-OS,IOS XE}] @@ -305,7 +305,7 @@ optional arguments: #### Set This command has not been validated. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. ``` -usage: gnmcli [-h] [-update_json_config UPDATE_JSON_CONFIG] +usage: cisco-gnmi [-h] [-update_json_config UPDATE_JSON_CONFIG] [-replace_json_config REPLACE_JSON_CONFIG] [-delete_xpath DELETE_XPATH] [-no_ietf] [-dump_json] [-os {None,IOS XR,NX-OS,IOS XE}] @@ -350,12 +350,12 @@ optional arguments: #### Subscribe This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) ``` -gnmcli subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +cisco-gnmi subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override ``` ``` -gnmcli subscribe --help -usage: gnmcli [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] +cisco-gnmi subscribe --help +usage: cisco-gnmi [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] [-dump_json] [-sync_stop] [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] [-os {None,IOS XR,NX-OS,IOS XE}] diff --git a/setup.py b/setup.py index c33837b..5919f81 100644 --- a/setup.py +++ b/setup.py @@ -69,5 +69,5 @@ "coverage", ], }, - entry_points={"console_scripts": ["gnmcli = cisco_gnmi.cli:main"]}, + entry_points={"console_scripts": ["cisco-gnmi = cisco_gnmi.cli:main"]}, ) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index c0cabaf..963bc30 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -48,17 +48,17 @@ def main(): "set": gnmi_set, } parser = argparse.ArgumentParser( - description="gNMI CLI demonstrating library usage.", + description="gNMI CLI demonstrating cisco_gnmi library usage.", usage=""" -gnmcli [] +cisco-gnmi [] Supported RPCs: {supported_rpcs} -gnmcli capabilities 127.0.0.1:57500 -gnmcli get 127.0.0.1:57500 -xpath /interfaces/interface/state/counters -gnmcli set 127.0.0.1:57500 -update_json_config newconfig.json -gnmcli subscribe 127.0.0.1:57500 -xpath /interfaces/interface/state/counters -dump_file intfcounters.proto.txt +cisco-gnmi capabilities 127.0.0.1:57500 +cisco-gnmi get 127.0.0.1:57500 -xpath /interfaces/interface/state/counters +cisco-gnmi set 127.0.0.1:57500 -update_json_config newconfig.json +cisco-gnmi subscribe 127.0.0.1:57500 -xpath /interfaces/interface/state/counters -dump_file intfcounters.proto.txt See --help for RPC options. """.format( From 27e65e3cf26a50700aae38b9a2834c1a552ee2de Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Mon, 30 Mar 2020 15:04:12 -0700 Subject: [PATCH 14/17] Add copyright to setup.py --- setup.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/setup.py b/setup.py index 5919f81..31e3303 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,27 @@ +#!/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. +""" + """Derived from Flask https://github.com/pallets/flask/blob/master/setup.py """ From 8425605715ab2c7dc149ab09aac84080bfbddd4e Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Mon, 30 Mar 2020 15:21:50 -0700 Subject: [PATCH 15/17] Remove incorrect Get TODO --- src/cisco_gnmi/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 963bc30..7624f98 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -166,7 +166,6 @@ def gnmi_subscribe(): def gnmi_get(): """Provides Get RPC usage. Assumes JSON or JSON_IETF style configurations. - TODO: This is the least well understood/implemented. Need to validate if there is an OOO for update/replace/delete. """ parser = argparse.ArgumentParser( description="Performs Get RPC against network element." From 369c6680ed2d3ea3d3d90f662cd66a43d6b8c38e Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Mon, 30 Mar 2020 15:45:26 -0700 Subject: [PATCH 16/17] Add output examples and ToC --- README.md | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 88d4e50..6c54990 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,36 @@ This library wraps gNMI functionality to ease usage with Cisco implementations in Python programs. Derived from [openconfig/gnmi](https://github.com/openconfig/gnmi/tree/master/proto). +- [cisco-gnmi-python](#cisco-gnmi-python) + - [Usage](#usage) + - [cisco-gnmi CLI](#cisco-gnmi-cli) + - [ClientBuilder](#clientbuilder) + - [Initialization Examples](#initialization-examples) + - [Client](#client) + - [NXClient](#nxclient) + - [XEClient](#xeclient) + - [XRClient](#xrclient) + - [gNMI](#gnmi) + - [Development](#development) + - [Get Source](#get-source) + - [Code Hygiene](#code-hygiene) + - [Recompile Protobufs](#recompile-protobufs) + - [CLI Usage](#cli-usage) + - [Capabilities](#capabilities) + - [Usage](#usage-1) + - [Output](#output) + - [Get](#get) + - [Usage](#usage-2) + - [Output](#output-1) + - [Set](#set) + - [Usage](#usage-3) + - [Subscribe](#subscribe) + - [Usage](#usage-4) + - [Output](#output-2) + - [Licensing](#licensing) + - [Issues](#issues) + - [Related Projects](#related-projects) + ## Usage ```bash pip install cisco-gnmi @@ -185,7 +215,7 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ./update_protos.sh ``` -### CLI Usage +## CLI Usage The below details the current `cisco-gnmi` usage options. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. ``` @@ -216,12 +246,13 @@ optional arguments: -h, --help show this help message and exit ``` -#### Capabilities +### Capabilities This command will output the `CapabilitiesResponse` to `stdout`. ``` cisco-gnmi capabilities 127.0.0.1:57500 -auto_ssl_target_override ``` +#### Usage ``` cisco-gnmi capabilities --help usage: cisco-gnmi [-h] [-os {None,IOS XR,NX-OS,IOS XE}] @@ -255,12 +286,27 @@ optional arguments: -debug Print debug messages. ``` -#### Get +#### Output +``` +[cisco-gnmi-python] cisco-gnmi capabilities redacted:57500 -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:supported_models { + name: "Cisco-IOS-XR-qos-ma-oper" + organization: "Cisco Systems, Inc." + version: "2019-04-05" +} +... +``` + +### Get This command will output the `GetResponse` to `stdout`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. ``` cisco-gnmi get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override ``` +#### Usage ``` usage: cisco-gnmi [-h] [-xpath XPATH] [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] @@ -302,8 +348,37 @@ optional arguments: -debug Print debug messages. ``` -#### Set +#### Output +``` +[cisco-gnmi-python] cisco-gnmi get redacted:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:notification { + timestamp: 1585607100869287743 + update { + path { + elem { + name: "interfaces" + } + elem { + name: "interface" + } + elem { + name: "state" + } + elem { + name: "counters" + } + } + val { + json_ietf_val: "{\"in-unicast-pkts\":\"0\",\"in-octets\":\"0\"... +``` + +### Set This command has not been validated. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. + +#### Usage ``` usage: cisco-gnmi [-h] [-update_json_config UPDATE_JSON_CONFIG] [-replace_json_config REPLACE_JSON_CONFIG] @@ -347,12 +422,13 @@ optional arguments: -debug Print debug messages. ``` -#### Subscribe +### Subscribe This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) ``` cisco-gnmi subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override ``` +#### Usage ``` cisco-gnmi subscribe --help usage: cisco-gnmi [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] @@ -397,6 +473,49 @@ optional arguments: -debug Print debug messages. ``` +#### Output +``` +[cisco-gnmi-python] cisco-gnmi subscribe redacted:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:Dumping responses to stdout as textual proto ... +INFO:root:Subscribing to: +/interfaces/interface/state/counters +INFO:root:update { + timestamp: 1585607768601000000 + prefix { + origin: "openconfig" + elem { + name: "interfaces" + } + elem { + name: "interface" + key { + key: "name" + value: "Null0" + } + } + elem { + name: "state" + } + elem { + name: "counters" + } + } + update { + path { + elem { + name: "in-octets" + } + } + val { + uint_val: 0 + } + } +... +``` + ## Licensing `cisco-gnmi-python` is licensed as [Apache License, Version 2.0](LICENSE). From b4144e67db8554d76d077bebfbce7d720785e01f Mon Sep 17 00:00:00 2001 From: Remington Campbell Date: Tue, 31 Mar 2020 21:37:36 -0700 Subject: [PATCH 17/17] Add Set example --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++-- src/cisco_gnmi/cli.py | 2 +- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c54990..1be9d81 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,10 @@ This library wraps gNMI functionality to ease usage with Cisco implementations i - [Output](#output-1) - [Set](#set) - [Usage](#usage-3) + - [Output](#output-2) - [Subscribe](#subscribe) - [Usage](#usage-4) - - [Output](#output-2) + - [Output](#output-3) - [Licensing](#licensing) - [Issues](#issues) - [Related Projects](#related-projects) @@ -376,7 +377,7 @@ INFO:root:notification { ``` ### Set -This command has not been validated. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. +Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. Behavior is not fully validated. #### Usage ``` @@ -422,6 +423,51 @@ optional arguments: -debug Print debug messages. ``` +#### Output +Let's create a harmless loopback interface based from [`openconfig-interfaces.yang`](https://github.com/openconfig/public/blob/master/release/models/interfaces/openconfig-interfaces.yang). + +`config.json` +```json +{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "Loopback9339" + } + ] + } +} +``` + +``` +[cisco-gnmi-python] cisco-gnmi set redacted:57500 -os "IOS XR" -auto_ssl_target_override -update_json_config config.json +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:response { + path { + origin: "openconfig-interfaces" + elem { + name: "interfaces" + } + } + message { + } + op: UPDATE +} +message { +} +timestamp: 1585715036783451369 +``` + +And on IOS XR...a loopback interface! +``` +... +interface Loopback9339 +! +... +``` + ### Subscribe This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) ``` diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index 7624f98..f935b54 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -239,7 +239,7 @@ def load_json_file(filename): config = None with open(filename, "r") as config_fd: config = json.load(config_fd) - return config + return json.dumps(config) if args.update_json_config or args.replace_json_config: kwargs = {}