Skip to content

Commit a822c79

Browse files
committed
XE set working
1 parent 5b25216 commit a822c79

File tree

2 files changed

+121
-68
lines changed

2 files changed

+121
-68
lines changed

src/cisco_gnmi/nx.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def check_configs(self, configs):
186186
)
187187
return configs
188188

189-
def create_updates(self, configs, origin):
189+
def create_updates(self, configs, origin, json_ietf=False):
190190
if not configs:
191191
return None
192192
configs = self.check_configs(configs)
@@ -208,7 +208,10 @@ def create_updates(self, configs, origin):
208208
xpath, origin=origin
209209
)
210210
)
211-
update.val.json_val = payload
211+
if json_ietf:
212+
update.val.json_ietf_val = payload
213+
else:
214+
update.val.json_val = payload
212215
updates.append(update)
213216
return updates
214217
else:
@@ -217,15 +220,15 @@ def create_updates(self, configs, origin):
217220
update = proto.gnmi_pb2.Update()
218221
update.path.CopyFrom(self.parse_xpath_to_gnmi_path(top_element))
219222
config = config.pop(top_element)
220-
if ietf:
223+
if json_ietf:
221224
update.val.json_ietf_val = json.dumps(config).encode("utf-8")
222225
else:
223226
update.val.json_val = json.dumps(config).encode("utf-8")
224227
updates.append(update)
225228
return updates
226229

227230
def set_json(self, update_json_configs=None, replace_json_configs=None,
228-
origin='device'):
231+
origin='device', json_ietf=False):
229232
"""A convenience wrapper for set() which assumes JSON payloads and constructs desired messages.
230233
All parameters are optional, but at least one must be present.
231234
@@ -247,10 +250,19 @@ def set_json(self, update_json_configs=None, replace_json_configs=None,
247250
if not any([update_json_configs, replace_json_configs]):
248251
raise Exception("Must supply at least one set of configurations to method!")
249252

250-
updates = self.create_updates(update_json_configs, origin=origin)
251-
for update in updates:
253+
updates = self.create_updates(
254+
update_json_configs,
255+
origin=origin,
256+
json_ietf=json_ietf
257+
)
258+
replaces = self.create_updates(
259+
replace_json_configs,
260+
origin=origin,
261+
json_ietf=json_ietf
262+
)
263+
for update in updates + replaces:
252264
logger.info('\nGNMI set:\n{0}\n{1}'.format(9 * '=', str(update)))
253-
replaces = self.create_updates(replace_json_configs, origin=origin)
265+
254266
return self.set(updates=updates, replaces=replaces)
255267

256268
def get_xpaths(self, xpaths, data_type="ALL",

src/cisco_gnmi/xe.py

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import json
2727
import logging
28+
import os
2829

2930
from six import string_types
3031
from .client import Client, proto, util
@@ -181,7 +182,66 @@ def delete_xpaths(self, xpaths, prefix=None):
181182
paths.append(self.parse_xpath_to_gnmi_path(xpath))
182183
return self.set(deletes=paths)
183184

184-
def set_json(self, update_json_configs=None, replace_json_configs=None, ietf=True):
185+
def check_configs(self, configs):
186+
if isinstance(configs, string_types):
187+
logger.debug("Handling as JSON string.")
188+
try:
189+
configs = json.loads(configs)
190+
except:
191+
raise Exception("{0}\n is invalid JSON!".format(configs))
192+
configs = [configs]
193+
elif isinstance(configs, dict):
194+
logger.debug("Handling already serialized JSON object.")
195+
configs = [configs]
196+
elif not isinstance(configs, (list, set)):
197+
raise Exception(
198+
"{0} must be an iterable of configs!".format(str(configs))
199+
)
200+
return configs
201+
202+
def create_updates(self, configs, origin, json_ietf=True):
203+
if not configs:
204+
return None
205+
configs = self.check_configs(configs)
206+
207+
xpaths = []
208+
updates = []
209+
for config in configs:
210+
xpath = next(iter(config.keys()))
211+
xpaths.append(xpath)
212+
common_xpath = os.path.commonprefix(xpaths)
213+
214+
if common_xpath:
215+
update_configs = self.get_payload(configs)
216+
for update_cfg in update_configs:
217+
xpath, payload = update_cfg
218+
update = proto.gnmi_pb2.Update()
219+
update.path.CopyFrom(
220+
self.parse_xpath_to_gnmi_path(
221+
xpath, origin=origin
222+
)
223+
)
224+
if json_ietf:
225+
update.val.json_ietf_val = payload
226+
else:
227+
update.val.json_val = payload
228+
updates.append(update)
229+
return updates
230+
else:
231+
for config in configs:
232+
top_element = next(iter(config.keys()))
233+
update = proto.gnmi_pb2.Update()
234+
update.path.CopyFrom(self.parse_xpath_to_gnmi_path(top_element))
235+
config = config.pop(top_element)
236+
if json_ietf:
237+
update.val.json_ietf_val = json.dumps(config).encode("utf-8")
238+
else:
239+
update.val.json_val = json.dumps(config).encode("utf-8")
240+
updates.append(update)
241+
return updates
242+
243+
def set_json(self, update_json_configs=None, replace_json_configs=None,
244+
origin='device', json_ietf=True):
185245
"""A convenience wrapper for set() which assumes JSON payloads and constructs desired messages.
186246
All parameters are optional, but at least one must be present.
187247
@@ -194,8 +254,7 @@ def set_json(self, update_json_configs=None, replace_json_configs=None, ietf=Tru
194254
JSON configs to apply as updates.
195255
replace_json_configs : iterable of JSON configurations, optional
196256
JSON configs to apply as replacements.
197-
ietf : bool, optional
198-
Use JSON_IETF vs JSON.
257+
origin : openconfig, device, or DME
199258
200259
Returns
201260
-------
@@ -204,52 +263,19 @@ def set_json(self, update_json_configs=None, replace_json_configs=None, ietf=Tru
204263
if not any([update_json_configs, replace_json_configs]):
205264
raise Exception("Must supply at least one set of configurations to method!")
206265

207-
def check_configs(configs):
208-
if isinstance(configs, string_types):
209-
logger.debug("Handling as JSON string.")
210-
try:
211-
configs = json.loads(configs)
212-
except:
213-
raise Exception("{0}\n is invalid JSON!".format(configs))
214-
configs = [configs]
215-
elif isinstance(configs, dict):
216-
logger.debug("Handling already serialized JSON object.")
217-
configs = [configs]
218-
elif not isinstance(configs, (list, set)):
219-
raise Exception(
220-
"{0} must be an iterable of configs!".format(str(configs))
221-
)
222-
return configs
223-
224-
def create_updates(configs):
225-
if not configs:
226-
return None
227-
configs = check_configs(configs)
228-
updates = []
229-
for config in configs:
230-
if not isinstance(config, dict):
231-
raise Exception("config must be a JSON object!")
232-
if len(config.keys()) > 1:
233-
raise Exception("config should only target one YANG module!")
234-
top_element = next(iter(config.keys()))
235-
# start mike
236-
# path_obj = self.parse_xpath_to_gnmi_path(top_element)
237-
# config = config.pop(top_element)
238-
# value_obj = proto.gnmi_pb2.TypedValue(json_ietf_val=json.dumps(config).encode("utf-8"))
239-
# update = proto.gnmi_pb2.Update(path=path_obj, val=value_obj)
240-
# end mike
241-
update = proto.gnmi_pb2.Update()
242-
update.path.CopyFrom(self.parse_xpath_to_gnmi_path(top_element))
243-
config = config.pop(top_element)
244-
if ietf:
245-
update.val.json_ietf_val = json.dumps(config).encode("utf-8")
246-
else:
247-
update.val.json_val = json.dumps(config).encode("utf-8")
248-
updates.append(update)
249-
return updates
266+
updates = self.create_updates(
267+
update_json_configs,
268+
origin=origin,
269+
json_ietf=json_ietf
270+
)
271+
replaces = self.create_updates(
272+
replace_json_configs,
273+
origin=origin,
274+
json_ietf=json_ietf
275+
)
276+
for update in updates + replaces:
277+
logger.info('\nGNMI set:\n{0}\n{1}'.format(9 * '=', str(update)))
250278

251-
updates = create_updates(update_json_configs)
252-
replaces = create_updates(replace_json_configs)
253279
return self.set(updates=updates, replaces=replaces)
254280

255281
def get_xpaths(self, xpaths, data_type="ALL", encoding="JSON_IETF", origin=None):
@@ -296,9 +322,11 @@ def get_xpaths(self, xpaths, data_type="ALL", encoding="JSON_IETF", origin=None)
296322
def subscribe_xpaths(
297323
self,
298324
xpath_subscriptions,
299-
encoding="JSON_IETF",
325+
request_mode="STREAM",
326+
sub_mode="SAMPLE",
327+
encoding="PROTO",
300328
sample_interval=Client._NS_IN_S * 10,
301-
heartbeat_interval=None,
329+
origin='openconfig'
302330
):
303331
"""A convenience wrapper of subscribe() which aids in building of SubscriptionRequest
304332
with request as subscribe SubscriptionList. This method accepts an iterable of simply xpath strings,
@@ -315,26 +343,30 @@ def subscribe_xpaths(
315343
to SubscriptionRequest. Strings are parsed as XPaths and defaulted with the default arguments,
316344
dictionaries are treated as dicts of args to pass to the Subscribe init, and Subscription is
317345
treated as simply a pre-made Subscription.
346+
request_mode : proto.gnmi_pb2.SubscriptionList.Mode, optional
347+
Indicates whether STREAM to stream from target,
348+
ONCE to stream once (like a get),
349+
POLL to respond to POLL.
350+
[STREAM, ONCE, POLL]
351+
sub_mode : proto.gnmi_pb2.SubscriptionMode, optional
352+
The default SubscriptionMode on a per Subscription basis in the SubscriptionList.
353+
ON_CHANGE only streams updates when changes occur.
354+
SAMPLE will stream the subscription at a regular cadence/interval.
355+
[ON_CHANGE, SAMPLE]
318356
encoding : proto.gnmi_pb2.Encoding, optional
319357
A member of the proto.gnmi_pb2.Encoding enum specifying desired encoding of returned data
320-
[JSON, JSON_IETF]
358+
[JSON, PROTO]
321359
sample_interval : int, optional
322360
Default nanoseconds for sample to occur.
323361
Defaults to 10 seconds.
324-
heartbeat_interval : int, optional
325-
Specifies the maximum allowable silent period in nanoseconds when
326-
suppress_redundant is in use. The target should send a value at least once
327-
in the period specified.
328362
329363
Returns
330364
-------
331365
subscribe()
332366
"""
333-
supported_request_modes = ["STREAM"]
334-
request_mode = "STREAM"
335-
supported_sub_modes = ["SAMPLE"]
336-
sub_mode = "SAMPLE"
367+
supported_request_modes = ["STREAM", "ONCE", "POLL"]
337368
supported_encodings = ["JSON", "JSON_IETF"]
369+
supported_sub_modes = ["ON_CHANGE", "SAMPLE"]
338370
subscription_list = proto.gnmi_pb2.SubscriptionList()
339371
subscription_list.mode = util.validate_proto_enum(
340372
"mode",
@@ -358,7 +390,10 @@ def subscribe_xpaths(
358390
if isinstance(xpath_subscription, string_types):
359391
subscription = proto.gnmi_pb2.Subscription()
360392
subscription.path.CopyFrom(
361-
self.parse_xpath_to_gnmi_path(xpath_subscription)
393+
self.parse_xpath_to_gnmi_path(
394+
xpath_subscription,
395+
origin
396+
)
362397
)
363398
subscription.mode = util.validate_proto_enum(
364399
"sub_mode",
@@ -369,7 +404,10 @@ def subscribe_xpaths(
369404
)
370405
subscription.sample_interval = sample_interval
371406
elif isinstance(xpath_subscription, dict):
372-
path = self.parse_xpath_to_gnmi_path(xpath_subscription["path"])
407+
path = self.parse_xpath_to_gnmi_path(
408+
xpath_subscription["path"],
409+
origin
410+
)
373411
arg_dict = {
374412
"path": path,
375413
"mode": sub_mode,
@@ -391,6 +429,9 @@ def subscribe_xpaths(
391429
raise Exception("xpath in list must be xpath or dict/Path!")
392430
subscriptions.append(subscription)
393431
subscription_list.subscription.extend(subscriptions)
432+
logger.info('GNMI subscribe:\n{0}\n{1}'.format(
433+
15 * '=', str(subscription_list))
434+
)
394435
return self.subscribe([subscription_list])
395436

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

0 commit comments

Comments
 (0)