Skip to content

Commit 562cb6a

Browse files
authored
Merge pull request #162 from CiscoTestAutomation/np_containers
Avoid create operation on NP containers
2 parents 2ad09f1 + d1c1f65 commit 562cb6a

File tree

5 files changed

+318
-11
lines changed

5 files changed

+318
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--------------------------------------------------------------------------------
2+
Fixes
3+
--------------------------------------------------------------------------------
4+
* yang.ncdiff
5+
* Modified ConfigDelta to avoid 'create' and 'delete' operations on non-presence containers.

ncdiff/src/yang/ncdiff/netconf.py

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,6 @@ def get_config_replace(self, node_self, node_other):
352352

353353
for child_other in in_o_not_in_s:
354354
child_self = etree.Element(child_other.tag,
355-
{operation_tag: self.preferred_delete},
356355
nsmap=child_other.nsmap)
357356
siblings = list(node_self.iterchildren(tag=child_other.tag))
358357
if siblings:
@@ -361,14 +360,24 @@ def get_config_replace(self, node_self, node_other):
361360
node_self.append(child_self)
362361
s_node = self.device.get_schema_node(child_other)
363362
if s_node.get('type') == 'leaf-list':
363+
child_self.set(operation_tag, self.preferred_delete)
364364
self._merge_text(child_other, child_self)
365365
elif s_node.get('type') == 'list':
366+
child_self.set(operation_tag, self.preferred_delete)
366367
keys = self._get_list_keys(s_node)
367368
for key in keys:
368369
key_node = child_other.find(key)
369370
e = etree.SubElement(
370371
child_self, key, nsmap=key_node.nsmap)
371372
e.text = key_node.text
373+
elif (
374+
s_node.get('type') == 'container' and
375+
s_node.get('presence') != 'true' and
376+
self.preferred_delete == 'delete'
377+
):
378+
self.set_delete_operation(child_self, child_other)
379+
else:
380+
child_self.set(operation_tag, self.preferred_delete)
372381

373382
for child_self, child_other in in_s_and_in_o:
374383
child_self.set(operation_tag, 'replace')
@@ -1066,7 +1075,6 @@ def node_sub(self, node_self, node_other, depth=0):
10661075
choice_nodes = {}
10671076
for child_self in in_s_not_in_o:
10681077
child_other = etree.Element(child_self.tag,
1069-
{operation_tag: self.preferred_delete},
10701078
nsmap=child_self.nsmap)
10711079
if self.diff_type == 'replace':
10721080
child_self.set(operation_tag, 'replace')
@@ -1084,8 +1092,10 @@ def node_sub(self, node_self, node_other, depth=0):
10841092
if s_node.get('ordered-by') == 'user' and \
10851093
s_node.tag not in ordered_by_user:
10861094
ordered_by_user[s_node.tag] = 'leaf-list'
1095+
child_other.set(operation_tag, self.preferred_delete)
10871096
self._merge_text(child_self, child_other)
10881097
elif s_node.get('type') == 'list':
1098+
child_other.set(operation_tag, self.preferred_delete)
10891099
keys = self._get_list_keys(s_node)
10901100
if s_node.get('ordered-by') == 'user' and \
10911101
s_node.tag not in ordered_by_user:
@@ -1095,12 +1105,19 @@ def node_sub(self, node_self, node_other, depth=0):
10951105
e = etree.SubElement(
10961106
child_other, key, nsmap=key_node.nsmap)
10971107
e.text = key_node.text
1108+
elif (
1109+
s_node.get('type') == 'container' and
1110+
s_node.get('presence') != 'true' and
1111+
self.preferred_delete == 'delete'
1112+
):
1113+
self.set_delete_operation(child_other, child_self)
1114+
else:
1115+
child_other.set(operation_tag, self.preferred_delete)
10981116
if s_node.getparent().get('type') == 'case':
10991117
# key: choice node, value: case node
11001118
choice_nodes[s_node.getparent().getparent()] = s_node.getparent()
11011119
for child_other in in_o_not_in_s:
11021120
child_self = etree.Element(child_other.tag,
1103-
{operation_tag: self.preferred_delete},
11041121
nsmap=child_other.nsmap)
11051122
if self.preferred_create == 'replace':
11061123
child_other.set(operation_tag, self.preferred_create)
@@ -1126,8 +1143,10 @@ def node_sub(self, node_self, node_other, depth=0):
11261143
if s_node.get('ordered-by') == 'user' and \
11271144
s_node.tag not in ordered_by_user:
11281145
ordered_by_user[s_node.tag] = 'leaf-list'
1146+
child_self.set(operation_tag, self.preferred_delete)
11291147
self._merge_text(child_other, child_self)
11301148
elif s_node.get('type') == 'list':
1149+
child_self.set(operation_tag, self.preferred_delete)
11311150
keys = self._get_list_keys(s_node)
11321151
if s_node.get('ordered-by') == 'user' and \
11331152
s_node.tag not in ordered_by_user:
@@ -1136,6 +1155,14 @@ def node_sub(self, node_self, node_other, depth=0):
11361155
key_node = child_other.find(key)
11371156
e = etree.SubElement(child_self, key, nsmap=key_node.nsmap)
11381157
e.text = key_node.text
1158+
elif (
1159+
s_node.get('type') == 'container' and
1160+
s_node.get('presence') != 'true' and
1161+
self.preferred_delete == 'delete'
1162+
):
1163+
self.set_delete_operation(child_self, child_other)
1164+
else:
1165+
child_self.set(operation_tag, self.preferred_delete)
11391166
for child_self, child_other in in_s_and_in_o:
11401167
s_node = self.device.get_schema_node(child_self)
11411168
if s_node.get('type') == 'leaf':
@@ -1246,16 +1273,74 @@ def set_create_operation(self, node):
12461273
'''
12471274

12481275
schema_node = self.device.get_schema_node(node)
1276+
1277+
# Create operation on non-presence containers is not allowed as per
1278+
# ConfD implementation although the expected behavior is ambiguous in
1279+
# RFC7950. More discussion can be found in the Tail-F ticket PS-47089.
12491280
if (
12501281
schema_node.get('type') == 'container' and
1251-
schema_node.get('presence') != 'true' and
1252-
len(self.device.default_in_use(schema_node)) > 0
1282+
schema_node.get('presence') != 'true'
12531283
):
1284+
default_xpaths = [self.device.get_xpath(n)
1285+
for n in self.device.default_in_use(schema_node)]
12541286
for child in node:
1255-
self.set_create_operation(child)
1287+
child_xpath = self.device.get_xpath(
1288+
self.device.get_schema_node(child))
1289+
if child_xpath not in default_xpaths:
1290+
self.set_create_operation(child)
12561291
else:
12571292
node.set(operation_tag, 'create')
12581293

1294+
def set_delete_operation(self, node, reference_node):
1295+
'''set_delete_operation
1296+
Low-level api: Set the `operation` attribute of a node to `delete` when
1297+
it is not already set. This method is used when the preferred_delete is
1298+
`delete`.
1299+
Parameters
1300+
----------
1301+
node : `Element`
1302+
A config node in a config tree. `delete` operation will be set on
1303+
descendants of this node.
1304+
reference_node : `Element`
1305+
A config node in a config tree as a reference when building the
1306+
input parameter `node`. This node has descendants that `node` does
1307+
not have.
1308+
Returns
1309+
-------
1310+
None
1311+
There is no return of this method.
1312+
'''
1313+
1314+
# Delete operation on non-presence containers is allowed even there is
1315+
# nothing inside the non-presence container as per ConfD implementation
1316+
# although the expected behavior is ambiguous in RFC7950. More
1317+
# discussion can be found in the Tail-F ticket PS-47089. In negative
1318+
# testing case, if we want the delete operation to be rejected when
1319+
# there is nothing inside the non-presence container, we have to put
1320+
# delete operations on the descendants of the non-presence container,
1321+
# which are not non-presence containers.
1322+
for reference_child in reference_node:
1323+
child = etree.SubElement(node, reference_child.tag,
1324+
nsmap=reference_child.nsmap)
1325+
schema_node = self.device.get_schema_node(reference_child)
1326+
if schema_node.get('type') == 'leaf-list':
1327+
child.set(operation_tag, 'delete')
1328+
self._merge_text(reference_child, child)
1329+
elif schema_node.get('type') == 'list':
1330+
child.set(operation_tag, 'delete')
1331+
keys = self._get_list_keys(schema_node)
1332+
for key in keys:
1333+
key_node = reference_child.find(key)
1334+
e = etree.SubElement(child, key, nsmap=key_node.nsmap)
1335+
e.text = key_node.text
1336+
elif (
1337+
schema_node.get('type') == 'container' and
1338+
schema_node.get('presence') != 'true'
1339+
):
1340+
self.set_delete_operation(child, reference_child)
1341+
else:
1342+
child.set(operation_tag, 'delete')
1343+
12591344
@staticmethod
12601345
def _url_to_prefix(node, id):
12611346
'''_url_to_prefix

ncdiff/src/yang/ncdiff/runningconfig.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
(re.compile(r'^ *ospfv3 neighbor '), 1),
106106
(re.compile(r'^ *ospfv3 \d+ ipv(\d) neighbor'), 1),
107107
(re.compile(r'^ *ospfv3 \d+ neighbor '), 1),
108+
(re.compile(r'^ *member vni '), 1),
108109
(re.compile(r'^ *ipv6 route '), 0),
109110
(re.compile(r'^ *ip route '), 0),
110111
]
@@ -181,6 +182,7 @@
181182
('exit-address-family', ' exit-address-family'),
182183
]
183184

185+
184186
class ListDiff(object):
185187
'''ListDiff
186188

0 commit comments

Comments
 (0)