Skip to content

Commit

Permalink
Merge pull request #46 from cisco-ie/actual-doc-examples
Browse files Browse the repository at this point in the history
Add several examples
  • Loading branch information
remingtonc authored Apr 17, 2020
2 parents de478bf + 43e08d7 commit bb9dc4b
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gnmi_sub.json
140 changes: 140 additions & 0 deletions examples/custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/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.
"""

"""Custom usage, no wrapper.
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
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 <RPC>Requests (SubscribeRequest) etc.
To directly use the gNMI RPCs access via client.service.<RPC>().
So - either:
* Pass args to the client.<RPC>() methods.
* Pass full <RPC>Request protos to client.service.<RPC>()
This code passes args to the client.<RPC>() methods.
"""
target = input("Host/Port: ")
username = input("Username: ")
password = getpass()
client = (
ClientBuilder(target)
.set_secure_from_target()
.set_ssl_target_override()
.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!
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.
"""
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")
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.extend([sampled_subscription])
for subscribe_response in client.subscribe([subscription_list]):
print(subscribe_response)
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")
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.extend([onchange_subscription])
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)
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.
"""
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(
"""
{
"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.
88 changes: 88 additions & 0 deletions examples/load_subscribe_dump.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions examples/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cisco_gnmi
123 changes: 123 additions & 0 deletions examples/subscribe_dump.py
Original file line number Diff line number Diff line change
@@ -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="<host>:<port>", 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()
Loading

0 comments on commit bb9dc4b

Please sign in to comment.