Skip to content

Commit 1fd25ab

Browse files
committed
Complex set working.
1 parent 294a48f commit 1fd25ab

File tree

2 files changed

+256
-190
lines changed

2 files changed

+256
-190
lines changed

src/cisco_gnmi/client.py

Lines changed: 168 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import logging
2727
from collections import OrderedDict
2828
from xml.etree.ElementPath import xpath_tokenizer_re
29+
import re
30+
import json
31+
import os
2932
from six import string_types
3033

3134
from . import proto
@@ -338,77 +341,171 @@ def parse_xpath_to_gnmi_path(self, xpath, origin=None):
338341
path.elem.extend(path_elems)
339342
return path
340343

341-
def xpath_iterator(self, xpath):
342-
for token in xpath[1:].split('/'):
343-
#elem = proto.gnmi_pb2.PathElem()
344-
if '[' in token:
345-
keys = OrderedDict()
346-
subtoken = token.replace('[', ',').replace(']', '').split(',')
347-
for k in subtoken:
348-
if '=' not in k:
349-
elem = {'elem': OrderedDict({'name': k})}
350-
#elem.name = k
344+
def combine_configs(self, payload, last_xpath, xpath, config):
345+
last_set = set(last_xpath.split('/'))
346+
curr_diff = set(xpath.split('/')) - last_set
347+
if len(curr_diff) > 1:
348+
print('combine_configs() error1')
349+
return payload
350+
index = curr_diff.pop()
351+
curr_xpath = xpath[xpath.find(index):]
352+
curr_xpath = curr_xpath.split('/')
353+
curr_xpath.reverse()
354+
for seg in curr_xpath:
355+
config = {seg: config}
356+
357+
last_diff = last_set - set(xpath.split('/'))
358+
if len(last_diff) > 1:
359+
print('combine_configs() error2')
360+
return payload
361+
last_xpath = last_xpath[last_xpath.find(last_diff.pop()):]
362+
last_xpath = last_xpath.split('/')
363+
last_xpath.reverse()
364+
for seg in last_xpath:
365+
if seg not in payload:
366+
payload = {seg: payload}
367+
payload.update(config)
368+
return payload
369+
370+
def xpath_to_json(self, configs, last_xpath='', payload={}):
371+
for i, cfg in enumerate(configs, 1):
372+
xpath, config, is_key = cfg
373+
if last_xpath and xpath not in last_xpath:
374+
# Branched config here
375+
# |---last xpath config
376+
# --|
377+
# |---this xpath config
378+
payload = self.combine_configs(payload, last_xpath, xpath, config)
379+
return self.xpath_to_json(configs[i:], xpath, payload)
380+
xpath_segs = xpath.split('/')
381+
xpath_segs.reverse()
382+
for seg in xpath_segs:
383+
if not seg:
384+
continue
385+
if payload:
386+
if is_key:
387+
if seg in payload:
388+
if isinstance(payload[seg], list):
389+
payload[seg].append(config)
390+
elif isinstance(payload[seg], dict):
391+
payload[seg].update(config)
392+
else:
393+
payload.update(config)
394+
payload = {seg: [payload]}
351395
else:
352-
k, val = tuple(k.replace('"', '').split('='))
353-
keys['name'] = k
354-
keys['value'] = val
355-
elem['elem'].update({'key': keys})
356-
#elem.key.update(keys)
357-
else:
358-
elem = {'elem': {'name': token}}
359-
#elem.name = token
360-
yield elem
361-
362-
def combine_segments(self, segments):
363-
xpaths = [seg[0] for seg in segments]
364-
prev_path = ''
365-
extentions = []
366-
for path in xpaths:
367-
if not prev_path:
368-
prev_path = path
369-
continue
370-
if len(path) > len(prev_path):
371-
short_path = prev_path
372-
long_path = path
373-
else:
374-
short_path = path
375-
long_path = prev_path
376-
if short_path in long_path:
377-
end = long_path[:len(short_path)].split()
378-
extentions.append((short_path, end))
379-
removes = [seg for seg in segments if seg[0] == short_path]
380-
for seg in removes:
381-
segments.remove(seg)
382-
import pdb; pdb.set_trace()
383-
print(segments)
384-
385-
386-
387-
def resolve_segments(self, segments, required_segments=[]):
388-
duplicates = []
389-
if not segments:
390-
return required_segments
391-
xpath, elems, value = segments.pop(0)
392-
for seg in segments:
393-
if seg == (xpath, elems, value):
394-
# Duplicate so move on
395-
duplicates.append(seg)
396-
continue
397-
next_xpath, next_elems, next_value = seg
398-
399-
if xpath in next_xpath:
400-
# Check if segment is a key
401-
for seg in segments:
402-
#for elem in seg[1]:
403-
if xpath != seg['elem'].get('keybase', ''):
404-
continue
396+
config.update(payload)
397+
payload = {seg: config}
398+
return self.xpath_to_json(configs[i:], xpath, payload)
399+
else:
400+
if is_key:
401+
payload = {seg: [config]}
405402
else:
406-
break
403+
payload = {seg: config}
404+
return self.xpath_to_json(configs[i:], xpath, payload)
405+
return payload
406+
407+
# Pattern to detect keys in an xpath
408+
RE_FIND_KEYS = re.compile(r'\[.*?\]')
409+
410+
def get_payload(self, configs):
411+
# Number of updates are limited so try to consolidate into lists.
412+
xpaths_cfg = []
413+
first_key = set()
414+
# Find first common keys for all xpaths_cfg of collection.
415+
for config in configs:
416+
xpath = next(iter(config.keys()))
417+
418+
# Change configs to tuples (xpath, config) for easier management
419+
xpaths_cfg.append((xpath, config[xpath]))
420+
421+
xpath_split = xpath.split('/')
422+
for seg in xpath_split:
423+
if '[' in seg:
424+
first_key.add(seg)
425+
break
426+
427+
# Common first key/configs represents one GNMI update
428+
updates = []
429+
for key in first_key:
430+
update = []
431+
remove_cfg = []
432+
for config in xpaths_cfg:
433+
xpath, cfg = config
434+
if key in xpath:
435+
update.append(config)
407436
else:
408-
# This is a key
409-
return self.resolve_segments(
410-
segments,
411-
required_segments
412-
)
413-
required_segments.append((xpath, elems, value))
414-
return self.resolve_segments(segments, required_segments)
437+
for k, v in cfg.items():
438+
if '[{0}="{1}"]'.format(k, v) not in key:
439+
break
440+
else:
441+
# This cfg sets the first key so we don't need it
442+
remove_cfg.append((xpath, cfg))
443+
if update:
444+
for upd in update:
445+
# Remove this config out of main list
446+
xpaths_cfg.remove(upd)
447+
for rem_cfg in remove_cfg:
448+
# Sets a key in update path so remove it
449+
xpaths_cfg.remove(rem_cfg)
450+
updates.append(update)
451+
break
452+
453+
# Add remaining configs to updates
454+
if xpaths_cfg:
455+
updates.append(xpaths_cfg)
456+
457+
# Combine all xpath configs of each update if possible
458+
xpaths = []
459+
compressed_updates = []
460+
for update in updates:
461+
xpath_consolidated = {}
462+
config_compressed = []
463+
for seg in update:
464+
xpath, config = seg
465+
if xpath in xpath_consolidated:
466+
xpath_consolidated[xpath].update(config)
467+
else:
468+
xpath_consolidated[xpath] = config
469+
config_compressed.append((xpath, xpath_consolidated[xpath]))
470+
xpaths.append(xpath)
471+
472+
# Now get the update path for this batch of configs
473+
common_xpath = os.path.commonprefix(xpaths)
474+
cfg_compressed = []
475+
keys = []
476+
477+
# Need to reverse the configs to build the dict correctly
478+
config_compressed.reverse()
479+
for seg in config_compressed:
480+
is_key = False
481+
prepend_path = ''
482+
xpath, config = seg
483+
end_path = xpath[len(common_xpath):]
484+
if end_path.startswith('['):
485+
# Don't start payload with a list
486+
tmp = common_xpath.split('/')
487+
prepend_path = '/' + tmp.pop()
488+
common_xpath = '/'.join(tmp)
489+
end_path = prepend_path + end_path
490+
491+
# Building json, need to identify configs that set keys
492+
for key in keys:
493+
if [k for k in config.keys() if k in key]:
494+
is_key = True
495+
keys += re.findall(self.RE_FIND_KEYS, end_path)
496+
cfg_compressed.append((end_path, config, is_key))
497+
498+
update = (common_xpath, cfg_compressed)
499+
compressed_updates.append(update)
500+
501+
updates = []
502+
for update in compressed_updates:
503+
common_xpath, cfgs = update
504+
payload = self.xpath_to_json(cfgs)
505+
updates.append(
506+
(
507+
common_xpath,
508+
json.dumps(payload).encode('utf-8')
509+
)
510+
)
511+
return updates

0 commit comments

Comments
 (0)