Skip to content

Commit 04a656b

Browse files
authored
Merge pull request #67 from miott/dev
Added missing functions for nx.py class
2 parents 227baf7 + 576e6f2 commit 04a656b

File tree

4 files changed

+160
-13
lines changed

4 files changed

+160
-13
lines changed

src/cisco_gnmi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@
3030
from .xe import XEClient
3131
from .builder import ClientBuilder
3232

33-
__version__ = "1.0.10"
33+
__version__ = "1.0.11"

src/cisco_gnmi/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ def subscribe_xpaths(
277277
sample_interval=_NS_IN_S * 10,
278278
suppress_redundant=False,
279279
heartbeat_interval=None,
280+
prefix=None
280281
):
281282
"""A convenience wrapper of subscribe() which aids in building of SubscriptionRequest
282283
with request as subscribe SubscriptionList. This method accepts an iterable of simply xpath strings,
@@ -320,6 +321,9 @@ def subscribe_xpaths(
320321
Specifies the maximum allowable silent period in nanoseconds when
321322
suppress_redundant is in use. The target should send a value at least once
322323
in the period specified. Also applies in ON_CHANGE.
324+
prefix : proto.gnmi_pb2.Path, optional
325+
A common path prepended to all path elements in the message. This reduces message size by
326+
removing redundent path elements. Smaller message == improved thoughput.
323327
324328
Returns
325329
-------
@@ -335,6 +339,8 @@ def subscribe_xpaths(
335339
subscription_list.encoding = util.validate_proto_enum(
336340
"encoding", encoding, "Encoding", proto.gnmi_pb2.Encoding
337341
)
342+
if prefix:
343+
subscription_list.prefix.CopyFrom(prefix)
338344
if isinstance(
339345
xpath_subscriptions, (string_types, dict, proto.gnmi_pb2.Subscription)
340346
):

src/cisco_gnmi/nx.py

Lines changed: 148 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
"""Wrapper for NX-OS to simplify usage of gNMI implementation."""
2525

26-
26+
import json
2727
import logging
2828

2929
from six import string_types
@@ -56,12 +56,149 @@ class NXClient(Client):
5656
>>> capabilities = client.capabilities()
5757
>>> print(capabilities)
5858
"""
59+
def delete_xpaths(self, xpaths, prefix=None):
60+
"""A convenience wrapper for set() which constructs Paths from supplied xpaths
61+
to be passed to set() as the delete parameter.
62+
63+
Parameters
64+
----------
65+
xpaths : iterable of str
66+
XPaths to specify to be deleted.
67+
If prefix is specified these strings are assumed to be the suffixes.
68+
prefix : str
69+
The XPath prefix to apply to all XPaths for deletion.
70+
71+
Returns
72+
-------
73+
set()
74+
"""
75+
if isinstance(xpaths, string_types):
76+
xpaths = [xpaths]
77+
paths = []
78+
# prefix is not supported on NX yet
79+
prefix = None
80+
for xpath in xpaths:
81+
if prefix:
82+
if prefix.endswith("/") and xpath.startswith("/"):
83+
xpath = "{prefix}{xpath}".format(
84+
prefix=prefix[:-1], xpath=xpath[1:]
85+
)
86+
elif prefix.endswith("/") or xpath.startswith("/"):
87+
xpath = "{prefix}{xpath}".format(prefix=prefix, xpath=xpath)
88+
else:
89+
xpath = "{prefix}/{xpath}".format(prefix=prefix, xpath=xpath)
90+
paths.append(self.parse_xpath_to_gnmi_path(xpath))
91+
return self.set(deletes=paths)
92+
93+
def set_json(self, update_json_configs=None, replace_json_configs=None, ietf=False, prefix=None):
94+
"""A convenience wrapper for set() which assumes JSON payloads and constructs desired messages.
95+
All parameters are optional, but at least one must be present.
96+
97+
This method expects JSON in the same format as what you might send via the native gRPC interface
98+
with a fully modeled configuration which is then parsed to meet the gNMI implementation.
99+
100+
Parameters
101+
----------
102+
update_json_configs : iterable of JSON configurations, optional
103+
JSON configs to apply as updates.
104+
replace_json_configs : iterable of JSON configurations, optional
105+
JSON configs to apply as replacements.
106+
ietf : bool, optional
107+
Use JSON_IETF vs JSON.
108+
prefix : proto.gnmi_pb2.Path, optional
109+
A common path prepended to all path elements in the message. This reduces message size by
110+
removing redundent path elements. Smaller message == improved thoughput.
111+
112+
Returns
113+
-------
114+
set()
115+
"""
116+
# JSON_IETF and prefix are not supported on NX yet
117+
ietf = False
118+
prefix = None
119+
120+
if not any([update_json_configs, replace_json_configs]):
121+
raise Exception("Must supply at least one set of configurations to method!")
59122

60-
def get(self, *args, **kwargs):
61-
raise NotImplementedError("Get not yet supported on NX-OS!")
123+
def check_configs(name, configs):
124+
if isinstance(configs, string_types):
125+
logger.debug("Handling %s as JSON string.", name)
126+
try:
127+
configs = json.loads(configs)
128+
except:
129+
raise Exception("{name} is invalid JSON!".format(name=name))
130+
configs = [configs]
131+
elif isinstance(configs, dict):
132+
logger.debug("Handling %s as already serialized JSON object.", name)
133+
configs = [configs]
134+
elif not isinstance(configs, (list, set)):
135+
raise Exception(
136+
"{name} must be an iterable of configs!".format(name=name)
137+
)
138+
return configs
62139

63-
def set(self, *args, **kwargs):
64-
raise NotImplementedError("Set not yet supported on NX-OS!")
140+
def create_updates(name, configs):
141+
if not configs:
142+
return None
143+
configs = check_configs(name, configs)
144+
updates = []
145+
for config in configs:
146+
if not isinstance(config, dict):
147+
raise Exception("config must be a JSON object!")
148+
if len(config.keys()) > 1:
149+
raise Exception("config should only target one YANG module!")
150+
top_element = next(iter(config.keys()))
151+
update = proto.gnmi_pb2.Update()
152+
update.path.CopyFrom(self.parse_xpath_to_gnmi_path(top_element))
153+
config = config.pop(top_element)
154+
if ietf:
155+
update.val.json_ietf_val = json.dumps(config).encode("utf-8")
156+
else:
157+
update.val.json_val = json.dumps(config).encode("utf-8")
158+
updates.append(update)
159+
return updates
160+
161+
updates = create_updates("update_json_configs", update_json_configs)
162+
replaces = create_updates("replace_json_configs", replace_json_configs)
163+
return self.set(prefix=prefix, updates=updates, replaces=replaces)
164+
165+
def get_xpaths(self, xpaths, data_type="ALL", encoding="JSON"):
166+
"""A convenience wrapper for get() which forms proto.gnmi_pb2.Path from supplied xpaths.
167+
168+
Parameters
169+
----------
170+
xpaths : iterable of str or str
171+
An iterable of XPath strings to request data of
172+
If simply a str, wraps as a list for convenience
173+
data_type : proto.gnmi_pb2.GetRequest.DataType, optional
174+
A direct value or key from the GetRequest.DataType enum
175+
[ALL, CONFIG, STATE, OPERATIONAL]
176+
encoding : proto.gnmi_pb2.GetRequest.Encoding, optional
177+
A direct value or key from the Encoding enum
178+
[JSON] is only setting supported at this time
179+
180+
Returns
181+
-------
182+
get()
183+
"""
184+
supported_encodings = ["JSON"]
185+
encoding = util.validate_proto_enum(
186+
"encoding",
187+
encoding,
188+
"Encoding",
189+
proto.gnmi_pb2.Encoding,
190+
supported_encodings,
191+
)
192+
gnmi_path = None
193+
if isinstance(xpaths, (list, set)):
194+
gnmi_path = map(self.parse_xpath_to_gnmi_path, set(xpaths))
195+
elif isinstance(xpaths, string_types):
196+
gnmi_path = [self.parse_xpath_to_gnmi_path(xpaths)]
197+
else:
198+
raise Exception(
199+
"xpaths must be a single xpath string or iterable of xpath strings!"
200+
)
201+
return self.get(gnmi_path, data_type=data_type, encoding=encoding)
65202

66203
def subscribe_xpaths(
67204
self,
@@ -154,19 +291,18 @@ def subscribe_xpaths(
154291

155292
def parse_xpath_to_gnmi_path(self, xpath, origin=None):
156293
"""Attempts to determine whether origin should be YANG (device) or DME.
157-
Errors on OpenConfig until support is present.
158294
"""
159-
if xpath.startswith("openconfig"):
160-
raise NotImplementedError(
161-
"OpenConfig data models not yet supported on NX-OS!"
162-
)
163295
if origin is None:
164296
if any(
165-
map(xpath.startswith, ["Cisco-NX-OS-device", "/Cisco-NX-OS-device"])
297+
map(xpath.startswith, [
298+
"Cisco-NX-OS-device",
299+
"/Cisco-NX-OS-device",
300+
"cisco-nx-os-device",
301+
"/cisco-nx-os-device"])
166302
):
167303
origin = "device"
168304
# Remove the module
169305
xpath = xpath.split(":", 1)[1]
170306
else:
171-
origin = "DME"
307+
origin = "openconfig"
172308
return super(NXClient, self).parse_xpath_to_gnmi_path(xpath, origin)

src/cisco_gnmi/xe.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ def subscribe_xpaths(
230230
sample_interval=Client._NS_IN_S * 10,
231231
suppress_redundant=False,
232232
heartbeat_interval=None,
233+
prefix=None
233234
):
234235
"""A convenience wrapper of subscribe() which aids in building of SubscriptionRequest
235236
with request as subscribe SubscriptionList. This method accepts an iterable of simply xpath strings,
@@ -265,6 +266,9 @@ def subscribe_xpaths(
265266
Specifies the maximum allowable silent period in nanoseconds when
266267
suppress_redundant is in use. The target should send a value at least once
267268
in the period specified.
269+
prefix : proto.gnmi_pb2.Path, optional
270+
A common path prepended to all path elements in the message. This reduces message size by
271+
removing redundent path elements. Smaller message == improved thoughput.
268272
269273
Returns
270274
-------
@@ -305,6 +309,7 @@ def subscribe_xpaths(
305309
sample_interval,
306310
suppress_redundant,
307311
heartbeat_interval,
312+
prefix
308313
)
309314

310315
def parse_xpath_to_gnmi_path(self, xpath, origin=None):

0 commit comments

Comments
 (0)