Repository: CiscoTestAutomation/yang (language: python)

Issue:
breakage seen with newly released pyats v19.5 packages
yang.connector is not handling the case where self.connectioninfo now contains the "credentials" member.  The credentials feature was released as part of pyATS v19.5.

$ pip list
Package              Version   
-------------------- ----------
asn1crypto           0.24.0    
asyncssh             1.16.1    
bcrypt               3.1.4     
certifi              2018.10.15
cffi                 1.11.5    
chardet              3.0.4     
cryptography         2.6.1     
defusedxml           0.5.0     
dill                 0.2.9     
distro               1.4.0     
httplib2             0.11.3    
idna                 2.7       
Jinja2               2.10      
jira                 2.0.0     
junit-xml            1.8       
lxml                 4.2.5     
MarkupSafe           1.1.0     
ncclient             0.5.3     
netconf-console      2.1.1     
oauthlib             3.0.1     
paramiko             2.4.2     
pathspec             0.5.9     
pbr                  5.1.3     
pexpect              4.7.0     
pip                  19.1.1    
psutil               5.4.8     
ptyprocess           0.6.0     
pyasn1               0.4.4     
pyats                19.5.0    
pyats.aereport       19.5.0    
pyats.aetest         19.5.0    
pyats.async          19.5.0    
pyats.connections    19.5.0    
pyats.datastructures 19.5.0    
pyats.easypy         19.5.0    
pyats.examples       19.5.0    
pyats.kleenex        19.5.0    
pyats.log            19.5.0    
pyats.results        19.5.0    
pyats.tcl            19.5.0    
pyats.templates      19.5.0    
pyats.topology       19.5.0    
pyats.utils          19.5.0    
pycparser            2.19      
PyJWT                1.7.1     
PyNaCl               1.3.0     
PyYAML               3.13      
requests             2.20.0    
requests-oauthlib    1.2.0     
requests-toolbelt    0.9.1     
setproctitle         1.1.10    
setuptools           39.0.1    
six                  1.11.0    
unicon               19.5.0    
urllib3              1.24.1    
xmltodict            0.11.0    
yamllint             1.15.0    
yang.connector       3.0.0     
(pyats) MBERETTA-M-K1L8:~ mberetta$ cat svo/temp/svo_100.yaml
devices:
  ncs:
    type: nso
    os: linux
    tacacs:
      username: admin
    passwords:
      tacacs: admin
      enable: admin
      line: admin
    connections:
      defaults:
        class: 'unicon.Unicon'
        via: netconf
      admin:
        protocol: https
        ip: 10.58.228.93
        port: 8443
        username: CISCO15
        password: otbu+2019
      host:
        protocol: ssh
        ip: 10.58.228.93
        port: 22
        username: root
        password: lablab
      netconf:
        class: yang.connector.Netconf
        ip : 10.58.228.100
        port: 830
        username: CISCO15
        password: otbu+2019
        hostkey_verify: False
        look_for_keys: False
        timeout: 120
    custom:
      host_type: Card
(pyats) MBERETTA-M-K1L8:~ mberetta$ 
(pyats) MBERETTA-M-K1L8:~ mberetta$ 
(pyats) MBERETTA-M-K1L8:~ mberetta$ python
Python 3.6.7 (v3.6.7:6ec5cf24b7, Oct 20 2018, 03:02:14) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ats.topology import loader
>>> testbed = loader.load('svo/temp/svo_100.yaml')
>>> svo = testbed.devices['ncs']
>>> svo.connect(via='netconf')
/Users/mberetta/pyats/lib/python3.6/site-packages/paramiko/ecdsakey.py:164: CryptographyDeprecationWarning: Support for unsafe construction of public numbers from encoded data will be removed in a future version. Please use EllipticCurvePublicKey.from_encoded_point
  self.ecdsa_curve.curve_class(), pointinfo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "src/pyats/connections/manager.py", line 305, in pyats.connections.manager.ConnectionManager.connect
  File "/Users/mberetta/pyats/lib/python3.6/site-packages/yang/connector/__init__.py", line 324, in connect
    self.session.connect(**defaults)
TypeError: connect() got an unexpected keyword argument 'credentials'
>>> 
 



Discussion / hints:
fixed in commit #5ad52c6  

Fix patch:
diff --git a/connector/src/yang/connector/netconf.py b/connector/src/yang/connector/netconf.py
index 9ec6d63..612bdb3 100644
--- a/connector/src/yang/connector/netconf.py
+++ b/connector/src/yang/connector/netconf.py
@@ -695,7 +695,8 @@ def request(self, msg, timeout=30, return_obj=False):
             exception.
         timeout : `int`, optional
             An optional keyed argument to set timeout value in seconds. Its
-            default value is 30 seconds.
+            default value is 30 seconds. If timeout is less than 30, 
+            timeout will use the default of 30 seconds.
         return_obj : `boolean`, optional
             Normally a string is returned as a reply. In other cases, we may
             want to return a RPCReply object, so we can access some attributes,
@@ -751,6 +752,9 @@ def request(self, msg, timeout=30, return_obj=False):
             >>>
         '''
 
+        if timeout <= self.timeout:
+            timeout = self.timeout
+
         rpc = RawRPC(session=self.session,
                      device_handler=self._device_handler,
                      timeout=timeout,
diff --git a/ncdiff/docs/changelog/undistributed/changelog_tahigash_ncdiff_sync_enh_20240208171500.rst b/ncdiff/docs/changelog/undistributed/changelog_tahigash_ncdiff_sync_enh_20240208171500.rst
new file mode 100644
index 0000000..4def46d
--- /dev/null
+++ b/ncdiff/docs/changelog/undistributed/changelog_tahigash_ncdiff_sync_enh_20240208171500.rst
@@ -0,0 +1,15 @@
+--------------------------------------------------------------------------------
+                                Fix
+--------------------------------------------------------------------------------
+
+* yang.connector
+    * updated `request` method
+* yang.ncdiff
+    * synced PR2, 3, 5, 8, 13, 14, 19, 21, 24, 25, 26 and 27 from ncdiff repository
+    * updated NetconfCalculator class
+        * added diff_type `minimum-replace`
+        * added `add_attribute_at_depath` method
+        * added `add_attribute_by_xpath` method
+        * added `find_by_tags` method
+    * updated ConfigDelta class
+        * added `replace_xpath` argument
\ No newline at end of file
diff --git a/ncdiff/docs/changelog/undistributed/template.rst b/ncdiff/docs/changelog/undistributed/template.rst
new file mode 100644
index 0000000..1761119
--- /dev/null
+++ b/ncdiff/docs/changelog/undistributed/template.rst
@@ -0,0 +1,34 @@
+Templates
+=========
+
+--------------------------------------------------------------------------------
+                                New
+--------------------------------------------------------------------------------
+* <Package>
+    * <Added|Removed|Modified> <Class>:
+        * <Description>
+
+--------------------------------------------------------------------------------
+                                Fix
+--------------------------------------------------------------------------------
+* <Package>
+    * <Added|Removed|Modified> <Class>:
+        * <Description>
+
+Examples
+========
+
+--------------------------------------------------------------------------------
+                                Fix
+--------------------------------------------------------------------------------
+* Log
+    * Modified Colours:
+        * Allowed for rainbow font options
+        * All output is now rainbow
+
+--------------------------------------------------------------------------------
+                                New
+--------------------------------------------------------------------------------
+* Log
+    * Added InvertedLogViewer:
+        * Enables inversion of LogViewer colours
diff --git a/ncdiff/src/yang/ncdiff/calculator.py b/ncdiff/src/yang/ncdiff/calculator.py
index b1de400..a84f4d2 100755
--- a/ncdiff/src/yang/ncdiff/calculator.py
+++ b/ncdiff/src/yang/ncdiff/calculator.py
@@ -1,6 +1,9 @@
 import logging
+from functools import lru_cache
+
 from ncclient import xml_
 
+from .ref import IdentityRef, InstanceIdentifier
 from .errors import ConfigError
 
 # create a logger for this module
@@ -42,6 +45,7 @@ def __init__(self, device, etree1, etree2):
         self.device = device
         self.etree1 = etree1
         self.etree2 = etree2
+        self.__attach_per_instance_cache()
 
     @staticmethod
     def _del_attrib(element):
@@ -107,6 +111,103 @@ def _get_sequence(scope, tag, parent):
         return [child for child in parent.iterchildren(tag=tag)
                 if child in new_scope]
 
+    def _pair_children(self, node_one, node_two):
+        """_pair_children
+         pair all children with their peers, resulting in a list of Tuples
+         Parameters
+        ----------
+        node_one : `Element`
+            An Element node in one Config instance.
+        node_two : `Element`
+            An Element node in the other Config instance.
+        Returns
+        -------
+        list
+            List of matching pairs, one of both items in the pair can be None
+        """
+        # Hash based approach, build two hashtables and match
+        # keys are (tag, self-key)
+        # self-key is
+        #    text for leaf-list
+        #    key tuple for list
+        #    none for others
+
+        def find_one_child(node, tag):
+            """ Find exactly one child with the given tag"""
+            s = list(node.iterchildren(tag=tag))
+            if len(s) < 1:
+                raise ConfigError("cannot find key '{}' in node {}" \
+                                  .format(tag,
+                                          self.device.get_xpath(node)))
+            if len(s) > 1:
+                raise ConfigError("not unique key '{}' in node {}" \
+                                  .format(tag,
+                                          self.device.get_xpath(node)))
+            return s[0]
+
+        def build_index_tuple(node, keys, s_node):
+            """
+            build a tuple containing the text of all fields listed in keys, taken from node
+            s_node is passed to prevent it being looked up twice.
+            """
+            out = [self._parse_text(find_one_child(node, k), s_node) for k in keys]
+            return tuple(out)
+
+        type_for_tag = {}
+        def get_type_for_tag(tag, child):
+            """
+            Given a child node with a given tag, find the type
+            caches based on tag
+            :return: a tuple, containing the s_node and node type
+            """
+            node_type = type_for_tag.get(tag, None)
+            if node_type is not None:
+                return node_type
+            s_node = self.device.get_schema_node(child)
+            node_type = s_node.get('type')
+
+            result = (s_node, node_type)
+            type_for_tag[tag] = result
+            return result
+
+        def build_unique_id(child):
+            """
+            Build the hash key for a node
+            """
+            tag = child.tag
+            key = None
+            s_node, node_type = get_type_for_tag(tag, child)
+            if node_type == 'leaf-list':
+                key = self._parse_text(child, s_node)
+            elif node_type == 'list':
+                keys = self._get_list_keys(s_node)
+                key = build_index_tuple(child, keys, s_node)
+            return (tag, key)
+
+        # build the hashmap for node_one
+        ones = {}
+        for child in node_one.getchildren():
+            key = build_unique_id(child)
+            if key in ones:
+                raise ConfigError('not unique peer of node {} {}' \
+                    .format(child, ones[key]))
+            ones[key] = child
+
+        # build the hashmap for node_two
+        twos = {}
+        for child in node_two.getchildren():
+            key = build_unique_id(child)
+            if key in twos:
+                raise ConfigError('not unique peer of node {} {}' \
+                                  .format(child, twos[key]))
+            twos[key] = child
+
+        # make pairs, in order
+        one_lookup = set(ones.keys())
+        keys_in_order = list(ones.keys())
+        keys_in_order.extend([two for two in twos.keys() if two not in one_lookup])
+        return [(ones.get(uid, None), twos.get(uid, None)) for uid in keys_in_order]
+
     def _group_kids(self, node_one, node_two):
         '''_group_kids
 
@@ -139,30 +240,15 @@ def _group_kids(self, node_one, node_two):
         in_1_not_in_2 = []
         in_2_not_in_1 = []
         in_1_and_in_2 = []
-        for child in node_one.getchildren():
-            peers = self._get_peers(child, node_two)
-            if len(peers) < 1:
-                # child in self but not in other
-                in_1_not_in_2.append(child)
-            elif len(peers) > 1:
-                # one child in self matches multiple children in other
-                raise ConfigError('not unique peer of node {}' \
-                                  .format(self.device.get_xpath(child)))
-            else:
-                # child matches one peer in other
-                in_1_and_in_2.append((child, peers[0]))
-        for child in node_two.getchildren():
-            peers = self._get_peers(child, node_one)
-            if len(peers) < 1:
-                # child in other but not in self
-                in_2_not_in_1.append(child)
-            elif len(peers) > 1:
-                # one child in other matches multiple children in self
-                raise ConfigError('not unique peer of node {}' \
-                                  .format(self.device.get_xpath(child)))
+
+        for one, two in self._pair_children(node_one, node_two):
+            if one is None:
+                in_2_not_in_1.append(two)
+            elif two is None:
+                in_1_not_in_2.append(one)
             else:
-                # child in self matches one peer in self
-                pass
+               in_1_and_in_2.append((one, two))
+
         return (in_1_not_in_2, in_2_not_in_1, in_1_and_in_2)
 
     @staticmethod
@@ -187,7 +273,7 @@ def _get_list_keys(schema_node):
 
         nodes = list(filter(lambda x: x.get('is_key'),
                             schema_node.getchildren()))
-        return [n.tag for n in nodes]
+        return sorted([n.tag for n in nodes])
 
     def _get_peers(self, child_self, parent_other):
         '''_get_peers
@@ -264,6 +350,125 @@ def _is_peer(self, keys, node_self, node_other):
                 return False
         return True
 
+    def __attach_per_instance_cache(self):
+        """
+        We want to cache _parse_text, as it is an expensive call
+        Python functools conveniently provides the @lru_cache annotation
+        (and no other way of having a pre-made lru_cache)
+        The lru_cache annotation however locks all arguments in memory, including self
+        This causes both trees for this calculator to be locked in memory
+        We want to use the @lru_cache annotation, but release the cache when this calculator is garbage collected
+        For this, we use the following construction:
+        https://stackoverflow.com/questions/14946264/python-lru-cache-decorator-per-instance
+        """
+
+        self._parse_text = lru_cache(maxsize=128)(self._parse_text)
+
+    # cache because this is an expensive call, often called multiple times on the same node in rapid succession
+    def _parse_text(self, node, schema_node=None):
+        '''_parse_text
+
+        Low-level api: Return text if a node. Pharsing is required if the node
+        data type is identityref or instance-identifier.
+
+        Parameters
+        ----------
+
+        node : `Element`
+            An Element node in data tree.
+
+        Returns
+        -------
+
+        None or str
+            None if the node does not have text, otherwise, text string of the
+            node.
+        '''
+
+        if node.text is None:
+            return None
+
+        if schema_node is None:
+            schema_node = self.device.get_schema_node(node)
+        if schema_node.get('datatype') is not None and \
+          (schema_node.get('datatype')[:11] == 'identityref'):
+            idref = IdentityRef(self.device, node)
+            return idref.default
+        elif schema_node.get('datatype') is not None and \
+             schema_node.get('datatype') == 'instance-identifier':
+            instanceid = InstanceIdentifier(self.device, node)
+            return instanceid.default
+        else:
+            if schema_node.get("type") == "container":
+                # prevent whitespace in container to cause problems
+                return None
+            return node.text
+
+
+    def _same_text(self, node1, node2):
+        '''_same_text
+
+        Low-level api: Compare text values of two nodes.
+
+        Parameters
+        ----------
+
+        node1 : `Element`
+            An Element node in data tree.
+
+        node2 : `Element`
+            An Element node in data tree.
+
+        Returns
+        -------
+
+        bool
+            True if text values of two nodes are same, otherwise, False.
+        '''
+
+        if node1.text is None and node2.text is None:
+            return True
+        return self._parse_text(node1) == self._parse_text(node2)
+
+    def _merge_text(self, from_node, to_node):
+        '''_merge_text
+
+        Low-level api: Set text value of to_node according to the text value of
+        from_node.
+
+        Parameters
+        ----------
+
+        from_node : `Element`
+            An Element node in data tree.
+
+        to_node : `Element`
+            An Element node in data tree.
+
+        Returns
+        -------
+
+        None
+            There is no return of this method.
+        '''
+
+        if from_node.text is None:
+            to_node.text = None
+            return
+        schema_node = self.device.get_schema_node(from_node)
+        if schema_node.get('datatype') is not None and \
+           schema_node.get('datatype')[:11] == 'identityref':
+            idref = IdentityRef(self.device,
+                                from_node, to_node=to_node)
+            to_node.text = idref.converted
+        elif schema_node.get('datatype') is not None and \
+             schema_node.get('datatype') == 'instance-identifier':
+            instanceid = InstanceIdentifier(self.device,
+                                            from_node, to_node=to_node)
+            to_node.text = instanceid.converted
+        else:
+            to_node.text = from_node.text
+
     @property
     def le(self):
         return self._node_le(self.etree1, self.etree2)
@@ -322,38 +527,40 @@ def _node_le(self, node_self, node_other):
             if a not in node_other.attrib or \
                node_self.attrib[a] != node_other.attrib[a]:
                 return False
-        for child in node_self.getchildren():
-            peers = self._get_peers(child, node_other)
-            if len(peers) < 1:
+        for child, child_other in self._pair_children(node_self, node_other):
+            if child is None:
+                # only in other, meaningless
+                continue
+            if child_other is None:
+                # only in other, false
                 return False
-            elif len(peers) > 1:
-                raise ConfigError('not unique peer of node {}' \
-                                  .format(self.device.get_xpath(child)))
-            else:
-                schma_node = self.device.get_schema_node(child)
-                if schma_node.get('ordered-by') == 'user' and \
-                   schma_node.get('type') == 'leaf-list' or \
-                   schma_node.get('ordered-by') == 'user' and \
-                   schma_node.get('type') == 'list':
-                    elder_siblings = list(child.itersiblings(tag=child.tag,
-                                                             preceding=True))
-                    if elder_siblings:
-                        immediate_elder_sibling = elder_siblings[0]
-                        peers_of_immediate_elder_sibling = \
-                            self._get_peers(immediate_elder_sibling,
-                                            node_other)
-                        if len(peers_of_immediate_elder_sibling) < 1:
-                            return False
-                        elif len(peers_of_immediate_elder_sibling) > 1:
-                            p = self.device.get_xpath(immediate_elder_sibling)
-                            raise ConfigError('not unique peer of node {}' \
-                                              .format(p))
-                        elder_siblings_of_peer = \
-                            list(peers[0].itersiblings(tag=child.tag,
-                                                       preceding=True))
-                        if peers_of_immediate_elder_sibling[0] not in \
-                           elder_siblings_of_peer:
-                            return False
-                if not self._node_le(child, peers[0]):
-                    return False
+            # both are present
+            schma_node = self.device.get_schema_node(child)
+            ordered_by = schma_node.get('ordered-by')
+            child_type = schma_node.get('type')
+            if ordered_by == 'user' and (
+                    child_type == 'leaf-list' or
+                    child_type == 'list'):
+                elder_siblings = list(child.itersiblings(tag=child.tag,
+                                                         preceding=True))
+                if elder_siblings:
+                    immediate_elder_sibling = elder_siblings[0]
+                    peers_of_immediate_elder_sibling = \
+                        self._get_peers(immediate_elder_sibling,
+                                        node_other)
+                    if len(peers_of_immediate_elder_sibling) < 1:
+                        return False
+                    elif len(peers_of_immediate_elder_sibling) > 1:
+                        p = self.device.get_xpath(immediate_elder_sibling)
+                        raise ConfigError('not unique peer of node {}' \
+                                          .format(p))
+                    elder_siblings_of_peer = \
+                        list(child_other.itersiblings(tag=child.tag,
+                                                   preceding=True))
+                    if peers_of_immediate_elder_sibling[0] not in \
+                            elder_siblings_of_peer:
+                        return False
+            if not self._node_le(child, child_other):
+                return False
+
         return True
diff --git a/ncdiff/src/yang/ncdiff/config.py b/ncdiff/src/yang/ncdiff/config.py
index 018937f..f75f430 100755
--- a/ncdiff/src/yang/ncdiff/config.py
+++ b/ncdiff/src/yang/ncdiff/config.py
@@ -62,7 +62,7 @@ class Config(object):
         `{url}tagname` notation, and values are corresponding model names.
     '''
 
-    def __init__(self, ncdevice, config=None):
+    def __init__(self, ncdevice, config=None, validate=True):
         '''
         __init__ instantiates a Config instance.
         '''
@@ -86,7 +86,8 @@ def __init__(self, ncdevice, config=None):
             raise TypeError("argument 'config' must be None, XML string, " \
                             "or Element, but not '{}'" \
                             .format(type(config)))
-        self.validate_config()
+        if validate:
+            self.validate_config()
 
     def __repr__(self):
         return '<{}.{} {} at {}>'.format(self.__class__.__module__,
@@ -100,7 +101,7 @@ def __str__(self):
                               pretty_print=True)
 
     def __bool__(self):
-        d = Config(self.device, None)
+        d = Config(self.device, None, False)
         if self == d:
             return False
         else:
@@ -111,21 +112,21 @@ def __add__(self, other):
             if ConfigCompatibility(self, other).is_compatible:
                 return Config(self.device,
                               NetconfCalculator(self.device,
-                                                self.ele, other.ele).add)
+                                                self.ele, other.ele).add, False)
         elif isinstance(other, ConfigDelta):
             if ConfigCompatibility(self, other).is_compatible:
                 return Config(self.device,
                               NetconfCalculator(self.device,
-                                                self.ele, other.nc).add)
+                                                self.ele, other.nc).add, False)
         elif etree.iselement(other):
             return Config(self.device,
-                          NetconfCalculator(self.device, self.ele, other).add)
+                          NetconfCalculator(self.device, self.ele, other).add, False)
         elif isinstance(other, Request):
             return Config(self.device,
-                          RestconfCalculator(self.device, self.ele, other).add)
+                          RestconfCalculator(self.device, self.ele, other).add, False)
         elif isinstance(other, SetRequest):
             return Config(self.device,
-                          gNMICalculator(self.device, self.ele, other).add)
+                          gNMICalculator(self.device, self.ele, other).add, False)
         else:
             return NotImplemented
 
@@ -454,26 +455,17 @@ class ConfigDelta(object):
         a transition.
 
     config_dst : `Config`
-        An instance of yang.ncdiff.Config, which is the destination config state
-        of a transition.
+        An instance of yang.ncdiff.Config, which is the destination config
+        state of a transition.
 
     nc : `Element`
         A lxml Element which contains the delta. This attribute can be used by
         ncclient edit_config() directly. It is the Netconf presentation of a
         ConfigDelta instance.
 
-    rc : `list`
-        A list of requests.models.Request instances. Each Request instance can
-        be used by prepare_request() in requests package. It is the Restconf
-        presentation of a ConfigDelta instance.
-
-    gnmi : `dict`
-        A gnmi_pb2.SetRequest instance. It is the gNMI presentation of a
-        ConfigDelta instance.
-
     ns : `dict`
-        A dictionary of namespaces used by the attribute 'nc'. Keys are prefixes
-        and values are URLs.
+        A dictionary of namespaces used by the attribute 'nc'. Keys are
+        prefixes and values are URLs.
 
     models : `list`
         A list of model names that self.roots belong to.
@@ -493,56 +485,76 @@ class ConfigDelta(object):
     preferred_delete : `str`
         Preferred operation of deleting an existing element. Choice of
         'delete' or 'remove'.
+
+    diff_type : `str`
+        Choice of 'minimum', 'minimum-replace' or 'replace'. This value has impact on attribute
+        nc. In general, there are two options to construct nc. The first
+        option is to find out minimal changes between config_src and
+        config_dst. Then attribute nc will reflect what needs to be modified.
+        The second option is to use 'replace' operation in Netconf. More
+        customers prefer 'replace' operation as it is more deterministic.
+
+    replace_depth : `int`
+        Specify the deepest level of replace operation when diff_type is
+        'replace'. Replace operation might be needed earlier before we reach
+        the specified level, depending on situations. Consider roots in a YANG
+        module are level 0, their children are level 1, and so on so forth.
+        The default value of replace_depth is 0.
+    
+    replace_xpath : `str` or `list`
+        Specify the xpath of the node to be replaced when diff_type is
+        'minimum-replace'. The default value of replace_xpath is None.
     '''
 
     def __init__(self, config_src, config_dst=None, delta=None,
                  preferred_create='merge',
                  preferred_replace='merge',
-                 preferred_delete='delete'):
+                 preferred_delete='delete',
+                 diff_type='minimum', replace_depth=0, replace_xpath=None):
         '''
         __init__ instantiates a ConfigDelta instance.
         '''
 
+        self.diff_type = diff_type
+        self.replace_depth = replace_depth
+        self.replace_xpath = replace_xpath
         if not isinstance(config_src, Config):
-            raise TypeError("argument 'config_src' must be " \
-                            "yang.ncdiff.Config, but not '{}'" \
+            raise TypeError("argument 'config_src' must be "
+                            "yang.ncdiff.Config, but not '{}'"
                             .format(type(config_src)))
         if preferred_create in ['merge', 'create', 'replace']:
             self.preferred_create = preferred_create
         else:
-            raise ValueError("only 'merge', 'create' or 'replace' are valid " \
+            raise ValueError("only 'merge', 'create' or 'replace' are valid "
                              "values of 'preferred_create'")
         if preferred_replace in ['merge', 'replace']:
             self.preferred_replace = preferred_replace
         else:
-            raise ValueError("only 'merge' or 'replace' are valid " \
+            raise ValueError("only 'merge' or 'replace' are valid "
                              "values of 'preferred_replace'")
         if preferred_delete in ['delete', 'remove']:
             self.preferred_delete = preferred_delete
         else:
-            raise ValueError("only 'delete' or 'remove' are valid " \
+            raise ValueError("only 'delete' or 'remove' are valid "
                              "values of 'preferred_delete'")
         self.config_src = config_src
         if delta is not None:
             if isinstance(delta, str) or etree.iselement(delta):
                 delta = NetconfParser(self.device, delta).ele
-            elif isinstance(delta, Request) or isinstance(delta, SetRequest):
-                delta = delta
             else:
-                raise TypeError("argument 'delta' must be XML string, " \
-                                "Element, requests.Request, or " \
-                                "gnmi_pb2.SetRequest, but not " \
-                                "'{}'".format(type(delta)))
+                raise TypeError("argument 'delta' must be XML string, "
+                                "Element, but not '{}'"
+                                .format(type(delta)))
         if not isinstance(config_dst, Config) and config_dst is not None:
-            raise TypeError("argument 'config_dst' must be " \
-                            "yang.ncdiff.Config or None, but not '{}'" \
+            raise TypeError("argument 'config_dst' must be "
+                            "yang.ncdiff.Config or None, but not '{}'"
                             .format(type(config_dst)))
         self.config_dst = config_dst
         if self.config_dst is None and delta is None:
-            raise ValueError("either 'config_dst' or 'delta' must present")
+            self.config_dst = self.config_src
         if delta is not None:
             if self.config_dst is not None:
-                logger.warning("argument 'config_dst' is ignored as 'delta' " \
+                logger.warning("argument 'config_dst' is ignored as 'delta' "
                                "is provided")
             self.config_dst = self.config_src + delta
         else:
@@ -554,21 +566,16 @@ def device(self):
 
     @property
     def nc(self):
-        return NetconfCalculator(self.device,
-                                 self.config_dst.ele, self.config_src.ele,
-                                 preferred_create=self.preferred_create,
-                                 preferred_replace=self.preferred_replace,
-                                 preferred_delete=self.preferred_delete).sub
-
-    @property
-    def rc(self):
-        return RestconfCalculator(self.device,
-                                  self.config_dst.ele, self.config_src.ele).sub
-
-    @property
-    def gnmi(self):
-        return gNMICalculator(self.device,
-                              self.config_dst.ele, self.config_src.ele).sub
+        return NetconfCalculator(
+            self.device,
+            self.config_dst.ele, self.config_src.ele,
+            preferred_create=self.preferred_create,
+            preferred_replace=self.preferred_replace,
+            preferred_delete=self.preferred_delete,
+            diff_type=self.diff_type,
+            replace_depth=self.replace_depth,
+            replace_xpath=self.replace_xpath,
+        ).sub
 
     @property
     def ns(self):
@@ -576,7 +583,7 @@ def ns(self):
 
     @property
     def models(self):
-        return sorted(list(set(self.config_src.models + \
+        return sorted(list(set(self.config_src.models +
                                self.config_dst.models)))
 
     @property
@@ -591,10 +598,7 @@ def __str__(self):
 
     def __neg__(self):
         return ConfigDelta(config_src=self.config_dst,
-                           config_dst=self.config_src,
-                           preferred_create=self.preferred_create,
-                           preferred_replace=self.preferred_replace,
-                           preferred_delete=self.preferred_delete)
+                           config_dst=self.config_src)
 
     def __pos__(self):
         return self
@@ -631,6 +635,7 @@ def __ne__(self, other):
         _cmperror(self, other)
 
 
+
 class ConfigCompatibility(object):
     '''ConfigCompatibility
 
@@ -700,6 +705,8 @@ def check_models(models):
 
     @property
     def namespaces_compatible(self):
+        if self.config1.device == self.config2.device:
+            return True
 
         def check_models(models):
             for device in [self.config1.device, self.config2.device]:
diff --git a/ncdiff/src/yang/ncdiff/device.py b/ncdiff/src/yang/ncdiff/device.py
index 7b4b313..6a030d0 100755
--- a/ncdiff/src/yang/ncdiff/device.py
+++ b/ncdiff/src/yang/ncdiff/device.py
@@ -1,5 +1,6 @@
 import os
 import re
+
 import logging
 from lxml import etree
 from ncclient import manager, operations, transport, xml_
@@ -7,7 +8,7 @@
 
 from .model import Model, ModelDownloader, ModelCompiler
 from .config import Config
-from .errors import ModelError, ModelMissing
+from .errors import ModelError, ModelMissing, ConfigError
 from .composer import Tag, Composer
 
 # create a logger for this module
@@ -65,12 +66,12 @@ def __init__(self, *args, **kwargs):
         '''
         __init__ instantiates a ModelDevice instance.
         '''
-
         Netconf.__init__(self, *args, **kwargs)
         self.models = {}
         self.nodes = {}
         self.compiler = None
         self._models_loadable = None
+        self.namespaces_cache = None
 
     def __repr__(self):
         return '<{}.{} object at {}>'.format(self.__class__.__module__,
@@ -78,7 +79,10 @@ def __repr__(self):
                                              hex(id(self)))
 
     @property
+    # extremely expensive call, cache
     def namespaces(self):
+        if self.namespaces_cache is not None:
+            return self.namespaces_cache
         if self.compiler is None:
             raise ValueError('please first call scan_models() to build '
                              'up supported namespaces of a device')
@@ -88,6 +92,7 @@ def namespaces(self):
                 device_namespaces.append((m.get('id'),
                                           m.get('prefix'),
                                           m.findtext('namespace')))
+            self.namespaces_cache = device_namespaces
             return device_namespaces
 
     @property
@@ -565,7 +570,7 @@ def get_schema_node(self, config_node):
         -------
 
         Element
-            A schema node, or None when nothing can be found.
+            A schema node.
 
         Raises
         ------
@@ -573,12 +578,15 @@ def get_schema_node(self, config_node):
         ModelError
             If identifier is not unique in a namespace.
 
+        ConfigError
+            when nothing can be found.
+
 
         Code Example::
 
-            >>> device.nc.load_model('openconfig-interfaces')
-            >>> reply = device.nc.get_config(models='openconfig-interfaces')
-            >>> config = device.nc.extract_config(reply)
+            >>> m.load_model('openconfig-interfaces')
+            >>> reply = m.get_config(models='openconfig-interfaces')
+            >>> config = m.extract_config(reply)
             >>> print(config)
             ...
             >>> config.ns
@@ -586,25 +594,25 @@ def get_schema_node(self, config_node):
             >>> config_nodes = config.xpath('/nc:config/oc-if:interfaces/oc-if:interface[oc-if:name="GigabitEthernet0/0"]')
             >>> config_node = config_nodes[0]
             >>>
-            >>> device.nc.get_schema_node(config_node)
+            >>> m.get_schema_node(config_node)
             <Element {http://openconfig.net/yang/interfaces}interface at 0xf11acfcc>
             >>>
         '''
 
         def get_child(parent, tag):
-            children = [i for i in parent.iter(tag=tag)
-                        if i.attrib['type'] != 'choice' and
-                        i.attrib['type'] != 'case' and
-                        is_parent(parent, i)]
+            children = [i for i in parent.iter(tag=tag) \
+                        if i.attrib['type'] != 'choice' and \
+                           i.attrib['type'] != 'case' and \
+                           is_parent(parent, i)]
             if len(children) == 1:
                 return children[0]
             elif len(children) > 1:
                 if parent.getparent() is None:
-                    raise ModelError("more than one root has tag '{}'"
+                    raise ModelError("more than one root has tag '{}'" \
                                      .format(tag))
                 else:
-                    raise ModelError("node {} has more than one child with "
-                                     "tag '{}'"
+                    raise ModelError("node {} has more than one child with " \
+                                     "tag '{}'" \
                                      .format(self.get_xpath(parent), tag))
             else:
                 return None
@@ -623,23 +631,28 @@ def is_parent(node1, node2):
             return True
 
         n = Composer(self, config_node)
-        config_path = n.path
-        if ' '.join(config_path) in self.nodes:
-            return self.nodes[' '.join(config_path)]
-        if len(config_path) > 1:
+        path = n.path
+        config_path_str = ' '.join(path)
+        if config_path_str in self.nodes:
+            return self.nodes[config_path_str]
+        if len(path) > 1:
             parent = self.get_schema_node(config_node.getparent())
-            if parent is None:
-                return None
-            else:
-                child = get_child(parent, config_node.tag)
-                if child is not None:
-                    self.nodes[' '.join(config_path)] = child
-                return child
+            child = get_child(parent, config_node.tag)
+            if child is None:
+                raise ConfigError("unable to locate a child '{}' of {} in " \
+                                  "schema tree" \
+                                  .format(config_node.tag,
+                                          self.get_xpath(parent)))
+            self.nodes[config_path_str] = child
+            return child
         else:
             tree = self.models[n.model_name].tree
             child = get_child(tree, config_node.tag)
-            if child is not None:
-                self.nodes[' '.join(config_path)] = child
+            if child is None:
+                raise ConfigError("unable to locate a root '{}' in {} schema " \
+                                  "tree" \
+                                  .format(config_node.tag, n.model_name))
+            self.nodes[config_path_str] = child
             return child
 
     def get_model_name(self, node):
@@ -826,6 +839,42 @@ def convert(ns):
         else:
             raise ValueError("unknown value '{}' in class Tag".format(dst[1]))
 
+    def convert_ns(self, ns, src=Tag.NAMESPACE, dst=Tag.NAME):
+        '''convert_ns
+
+        High-level api: Convert from one namespace format, model name, model
+        prefix or model URL, to another namespace format.
+
+        Parameters
+        ----------
+
+        ns : `str`
+            A namespace, which can be model name, model prefix or model URL.
+
+        src : `int`
+            An int constant defined in class Tag, specifying the namespace
+            format of ns.
+
+        dst : `int`
+            An int constant defined in class Tag, specifying the namespace
+            format of return value.
+
+        Returns
+        -------
+
+        str
+            Converted namespace in a format specified by dst.
+        '''
+
+        matches = [t for t in self.namespaces if t[src] == ns]
+        if len(matches) == 0:
+            raise ValueError("{} '{}' is not claimed by this device" \
+                             .format(Tag.STR[src], ns))
+        if len(matches) > 1:
+            raise ValueError("more than one {} '{}' are claimed by this " \
+                             "device".format(Tag.STR[src], ns))
+        return matches[0][dst]
+
     def _get_ns(self, reply):
         '''_get_ns
 
diff --git a/ncdiff/src/yang/ncdiff/model.py b/ncdiff/src/yang/ncdiff/model.py
index 49e139e..867198e 100755
--- a/ncdiff/src/yang/ncdiff/model.py
+++ b/ncdiff/src/yang/ncdiff/model.py
@@ -1,12 +1,22 @@
 import math
 import os
 import re
+import queue
 import logging
 from lxml import etree
 from copy import deepcopy
 from ncclient import operations
+from threading import Thread, current_thread
+from pyang import statements
 from subprocess import PIPE, Popen
-
+try:
+    from pyang.repository import FileRepository
+except ImportError:
+    from pyang import FileRepository
+try:
+    from pyang.context import Context
+except ImportError:
+    from pyang import Context
 from .errors import ModelError
 
 # create a logger for this module
@@ -431,6 +441,215 @@ def convert_tree(self, element1, element2=None):
             self.convert_tree(e1, e2)
         return element2
 
+class DownloadWorker(Thread):
+
+    def __init__(self, downloader):
+        Thread.__init__(self)
+        self.downloader = downloader
+
+    def run(self):
+        while not self.downloader.download_queue.empty():
+            try:
+                module = self.downloader.download_queue.get(timeout=0.01)
+            except queue.Empty:
+                pass
+            else:
+                self.downloader.download(module)
+                self.downloader.download_queue.task_done()
+        logger.debug('Thread {} exits'.format(current_thread().name))
+
+class ContextWorker(Thread):
+
+    def __init__(self, context):
+        Thread.__init__(self)
+        self.context = context
+
+    def run(self):
+        varnames = Context.add_module.__code__.co_varnames
+        while not self.context.modulefile_queue.empty():
+            try:
+                modulefile = self.context.modulefile_queue.get(timeout=0.01)
+            except queue.Empty:
+                pass
+            else:
+                with open(modulefile, 'r', encoding='utf-8') as f:
+                    text = f.read()
+                kwargs = {
+                    'ref': modulefile,
+                    'text': text,
+                }
+                if 'primary_module' in varnames:
+                    kwargs['primary_module'] = True
+                if 'format' in varnames:
+                    kwargs['format'] = 'yang'
+                if 'in_format' in varnames:
+                    kwargs['in_format'] = 'yang'
+                module_statement = self.context.add_module(**kwargs)
+                self.context.update_dependencies(module_statement)
+                self.context.modulefile_queue.task_done()
+        logger.debug('Thread {} exits'.format(current_thread().name))
+
+class CompilerContext(Context):
+
+    def __init__(self, repository):
+        Context.__init__(self, repository)
+        self.dependencies = None
+        self.modulefile_queue = None
+        if 'prune' in dir(statements.Statement):
+            self.num_threads = 2
+        else:
+            self.num_threads = 1
+
+    def _get_latest_revision(self, modulename):
+        latest = None
+        for module_name, module_revision in self.modules:
+            if module_name == modulename and (
+                latest is None or module_revision > latest
+            ):
+                latest = module_revision
+        return latest
+
+    def get_statement(self, modulename, xpath=None):
+        revision = self._get_latest_revision(modulename)
+        if revision is None:
+            return None
+        if xpath is None:
+            return self.modules[(modulename, revision)]
+
+        # in order to follow the Xpath, the module is required to be validated
+        node_statement = self.modules[(modulename, revision)]
+        if node_statement.i_is_validated is not True:
+            return None
+
+        # xpath is given, so find the node statement
+        xpath_list = xpath.split('/')
+
+        # only absolute Xpaths are supported
+        if len(xpath_list) < 2:
+            return None
+        if (
+            xpath_list[0] == '' and xpath_list[1] == '' or
+            xpath_list[0] != ''
+        ):
+            return None
+
+        # find the node statement
+        root_prefix = node_statement.i_prefix
+        for n in xpath_list[1:]:
+            node_statement = self.get_child(root_prefix, node_statement, n)
+            if node_statement is None:
+                return None
+        return node_statement
+
+    def get_child(self, root_prefix, parent, child_id):
+        child_id_list = child_id.split(':')
+        if len(child_id_list) > 1:
+            children = [
+                c for c in parent.i_children
+                if c.arg == child_id_list[1] and
+                c.i_module.i_prefix == child_id_list[0]
+            ]
+        elif len(child_id_list) == 1:
+            children = [
+                c for c in parent.i_children
+                if c.arg == child_id_list[0] and
+                c.i_module.i_prefix == root_prefix
+            ]
+        return children[0] if children else None
+
+    def update_dependencies(self, module_statement):
+        if self.dependencies is None:
+            self.dependencies = etree.Element('modules')
+        for m in [
+            m for m in self.dependencies
+            if m.attrib.get('id') == module_statement.arg
+        ]:
+            self.dependencies.remove(m)
+        module_node = etree.SubElement(self.dependencies, 'module')
+        module_node.set('id', module_statement.arg)
+        module_node.set('type', module_statement.keyword)
+        if module_statement.keyword == 'module':
+            statement = module_statement.search_one('prefix')
+            if statement is not None:
+                module_node.set('prefix', statement.arg)
+            statement = module_statement.search_one("namespace")
+            if statement is not None:
+                namespace = etree.SubElement(module_node, 'namespace')
+                namespace.text = statement.arg
+        if module_statement.keyword == 'submodule':
+            statement = module_statement.search_one("belongs-to")
+            if statement is not None:
+                belongs_to = etree.SubElement(module_node, 'belongs-to')
+                belongs_to.set('module', statement.arg)
+
+        dependencies = set()
+        for parent_node_name, child_node_name, attr_name in [
+            ('includes', 'include', 'module'),
+            ('imports', 'import', 'module'),
+            ('revisions', 'revision', 'date'),
+        ]:
+            parent = etree.SubElement(module_node, parent_node_name)
+            statements = module_statement.search(child_node_name)
+            if statements:
+                for statement in statements:
+                    child = etree.SubElement(parent, child_node_name)
+                    child.set(attr_name, statement.arg)
+                    if child_node_name in ['include', 'import']:
+                        dependencies.add(statement.arg)
+        return dependencies
+
+    def write_dependencies(self):
+        dependencies_file = os.path.join(
+            self.repository.dirs[0],
+            'dependencies.xml',
+        )
+        write_xml(dependencies_file, self.dependencies)
+
+    def read_dependencies(self):
+        dependencies_file = os.path.join(
+            self.repository.dirs[0],
+            'dependencies.xml',
+        )
+        self.dependencies = read_xml(dependencies_file)
+
+    def load_context(self):
+        self.modulefile_queue = queue.Queue()
+        for filename in os.listdir(self.repository.dirs[0]):
+            if filename.lower().endswith('.yang'):
+                filepath = os.path.join(self.repository.dirs[0], filename)
+                self.modulefile_queue.put(filepath)
+        for x in range(self.num_threads):
+            worker = ContextWorker(self)
+            worker.daemon = True
+            worker.name = 'context_worker_{}'.format(x)
+            worker.start()
+        self.modulefile_queue.join()
+        self.write_dependencies()
+
+    def validate_context(self):
+        revisions = {}
+        for mudule_name, module_revision in self.modules:
+            if mudule_name not in revisions or (
+                mudule_name in revisions and
+                revisions[mudule_name] < module_revision
+            ):
+                revisions[mudule_name] = module_revision
+        self.validate()
+        if 'prune' in dir(statements.Statement):
+            for mudule_name, module_revision in revisions.items():
+                self.modules[(mudule_name, module_revision)].prune()
+
+    def internal_reset(self):
+        self.modules = {}
+        self.revs = {}
+        self.errors = []
+        for mod, rev, handle in self.repository.get_modules_and_revisions(
+                self):
+            if mod not in self.revs:
+                self.revs[mod] = []
+            revs = self.revs[mod]
+            revs.append((rev, handle))
+
 
 class ModelDownloader(object):
     '''ModelDownloader
@@ -467,6 +686,10 @@ def __init__(self, nc_device, folder):
         if not os.path.isdir(self.dir_yang):
             os.makedirs(self.dir_yang)
         self.yang_capabilities = self.dir_yang + '/capabilities.txt'
+        repo = FileRepository(path=self.dir_yang)
+        self.context = CompilerContext(repository=repo)
+        self.download_queue = queue.Queue()
+        self.num_threads = 2
 
     @property
     def need_download(self):
@@ -601,6 +824,24 @@ def __init__(self, folder):
         self.dir_yang = os.path.abspath(folder)
         self.build_dependencies()
 
+    def _xml_from_cache(self, name):
+        try:
+            cached_name = os.path.join(self.dir_yang, f"{name}.xml")
+            if os.path.exists(cached_name):
+                with(open(cached_name, "r", encoding="utf-8")) as fh:
+                    parser = etree.XMLParser(remove_blank_text=True)
+                    tree = etree.XML(fh.read(), parser)
+                    return tree
+        except Exception:
+            # make the cache safe: any failure will just bypass the cache
+            logger.info(f"Unexpected failure during cache read of {name}, refreshing cache", exc_info=True)
+        return None
+
+    def _to_cache(self, name, value):
+        cached_name = os.path.join(self.dir_yang, f"{name}.xml")
+        with open(cached_name, "wb") as fh:
+            fh.write(value)
+
     def build_dependencies(self):
         '''build_dependencies
 
@@ -614,6 +855,11 @@ def build_dependencies(self):
             Nothing returns.
         '''
 
+        from_cache = self._xml_from_cache("$dependencies")
+        if from_cache is not None:
+            self.dependencies = from_cache
+            return
+
         cmd_list = ['pyang', '--plugindir', self.pyang_plugins]
         cmd_list += ['-p', self.dir_yang]
         cmd_list += ['-f', 'pyimport']
@@ -624,6 +870,9 @@ def build_dependencies(self):
         logger.info('pyang return code is {}'.format(p.returncode))
         logger.debug(stderr.decode())
         parser = etree.XMLParser(remove_blank_text=True)
+
+        self._to_cache("$dependencies",stdout)
+
         self.dependencies = etree.XML(stdout.decode(), parser)
 
     def get_dependencies(self, module):
@@ -676,6 +925,11 @@ def compile(self, module):
         Model
             A Model object.
         '''
+        cached_tree = self._xml_from_cache(module)
+
+        if cached_tree is not None:
+            m = Model(cached_tree)
+            return m
 
         imports, depends = self.get_dependencies(module)
         file_list = list(imports | depends) + [module]
@@ -692,7 +946,11 @@ def compile(self, module):
         else:
             logger.error(stderr.decode())
         parser = etree.XMLParser(remove_blank_text=True)
-        tree = etree.XML(stdout.decode(), parser)
+
+        self._to_cache(module, stdout)
+
+        out = stdout.decode()
+        tree = etree.XML(out, parser)
         return Model(tree)
 
 
diff --git a/ncdiff/src/yang/ncdiff/netconf.py b/ncdiff/src/yang/ncdiff/netconf.py
index 0ba664c..0cfd9ad 100755
--- a/ncdiff/src/yang/ncdiff/netconf.py
+++ b/ncdiff/src/yang/ncdiff/netconf.py
@@ -1,4 +1,5 @@
 import re
+import json
 import pprint
 import logging
 from lxml import etree
@@ -108,8 +109,8 @@ class NetconfCalculator(BaseCalculator):
     Attributes
     ----------
     sub : `Element`
-        Content of a Netconf edit-config which can achieve a transition from one
-        config, i.e., self.etree2, to another config, i.e., self.etree1.
+        Content of a Netconf edit-config which can achieve a transition from
+        one config, i.e., self.etree2, to another config, i.e., self.etree1.
 
     add : `Element`
         Content of a Config instance.
@@ -125,31 +126,55 @@ class NetconfCalculator(BaseCalculator):
     preferred_delete : `str`
         Preferred operation of deleting an existing element. Choice of
         'delete' or 'remove'.
+
+    diff_type : `str`
+        Choice of 'minimum' 'minimum-replace', or 'replace'. This value has impact on attribute
+        nc. In general, there are two options to construct nc. The first
+        option is to find out minimal changes between config_src and
+        config_dst. Then attribute nc will reflect what needs to be modified.
+        The second option is to use 'replace' operation in Netconf. More
+        customers prefer 'replace' operation as it is more deterministic.
+
+    replace_depth : `int`
+        Specify the deepest level of replace operation when diff_type is
+        'replace'. Replace operation might be needed earlier before we reach
+        the specified level, depending on situations. Consider roots in a YANG
+        module are level 0, their children are level 1, and so on so forth.
+        The default value of replace_depth is 0.
+    
+    replace_xpath: `str` or `list
+        Specify the xpath of the node to be replaced when diff_type is
+        'minimum-replace'. The default value of replace_xpath is None.
     '''
 
     def __init__(self, device, etree1, etree2,
                  preferred_create='merge',
                  preferred_replace='merge',
-                 preferred_delete='delete'):
+                 preferred_delete='delete',
+                 diff_type='minimum', replace_depth=0, replace_xpath=None):
         '''
         __init__ instantiates a NetconfCalculator instance.
         '''
 
         BaseCalculator.__init__(self, device, etree1, etree2)
+        self.device = device
+        self.diff_type = diff_type
+        self.replace_depth = replace_depth
+        self.replace_xpath = replace_xpath
         if preferred_create in ['merge', 'create', 'replace']:
             self.preferred_create = preferred_create
         else:
-            raise ValueError("only 'merge', 'create' or 'replace' are valid " \
+            raise ValueError("only 'merge', 'create' or 'replace' are valid "
                              "values of 'preferred_create'")
         if preferred_replace in ['merge', 'replace']:
             self.preferred_replace = preferred_replace
         else:
-            raise ValueError("only 'merge' or 'replace' are valid " \
+            raise ValueError("only 'merge' or 'replace' are valid "
                              "values of 'preferred_replace'")
         if preferred_delete in ['delete', 'remove']:
             self.preferred_delete = preferred_delete
         else:
-            raise ValueError("only 'delete' or 'remove' are valid " \
+            raise ValueError("only 'delete' or 'remove' are valid "
                              "values of 'preferred_delete'")
 
     @property
@@ -163,9 +188,255 @@ def add(self):
     def sub(self):
         ele1 = deepcopy(self.etree1)
         ele2 = deepcopy(self.etree2)
-        self.node_sub(ele1, ele2)
+        if self.diff_type == 'replace' and self.replace_depth == 0:
+            self.get_config_replace(ele1, ele2)
+        else:
+            self.node_sub(ele1, ele2, depth=0)
+        # add attribute at depth if diff_type is 'minimum-replace'
+        if self.diff_type == 'minimum-replace' and self.replace_xpath:
+            namespaces = self.device._get_ns(ele1)
+            logger.debug("Namespaces:\n{}".format(json.dumps(namespaces, indent=2)))
+            if isinstance(self.replace_xpath, list):
+                for xpath in self.replace_xpath:
+                    self.add_attribute_by_xpath(ele1, xpath, 'operation', 'replace', namespaces)
+            else:
+                self.add_attribute_by_xpath(ele1, self.replace_xpath, 'operation', 'replace', namespaces)
+        elif self.diff_type == 'minimum-replace':
+            self.add_attribute_at_depth(ele1, self.replace_depth+1, 'operation', 'replace')
         return ele1
 
+    def add_attribute_at_depth(self, root, depth, attribute, value):
+        '''add_attribute_at_depth
+
+        High-level api: Add an attribute to all nodes at a specified depth.
+
+        Parameters
+        ----------
+        root : `Element`
+            The root of a config tree.
+        depth : `int`
+            The depth of nodes to be added with an attribute.
+        attribute : `str`
+            The name of the attribute to be added.
+        value : `str`
+            The value of the attribute to be added.
+
+        Returns
+        -------
+        None
+
+        '''
+        # Initialize current_depth as -1
+        current_depth = -1
+
+        # Create a list with the root node and its depth (0)
+        nodes_to_visit = [(root, current_depth + 1)]
+
+        # While there are nodes to visit
+        while nodes_to_visit:
+            # Pop the first node and its depth from the list
+            node, current_depth = nodes_to_visit.pop(0)
+
+            # If the current node's depth matches the specified depth
+            if current_depth == depth:
+                # Add the attribute to the node
+                node.set(attribute, value)
+            # Add the attribute to the node
+            nodes_to_visit.extend((child, current_depth + 1) for child in node)
+
+    # Not used. Saved for further use-case
+    def find_by_tags(self, root, tags):
+        '''
+        Finds all nodes matching a list of tags.
+
+        Parameters
+        ----------
+        root : `Element`
+            The root of a config tree.
+        tags : `list` of `str`
+            The list of tags specifying the nodes to be found.
+
+        Returns
+        -------
+        `list` of `Element`
+            The list of matching nodes.
+        '''
+        # Start with the root element
+        nodes = [root]
+
+        # Traverse the tree for each tag
+        for tag in tags:
+            new_nodes = []
+            for node in nodes:
+                new_nodes.extend(node.findall('.//{}'.format(tag)))
+            nodes = new_nodes
+
+        return nodes
+
+    # Not used. Saved for further use-case
+    def add_attribute_by_tags(self, root, tags, attribute, value):
+        '''add_attribute_by_tags
+
+        '''
+        nodes = self.find_by_tags(self, root, tags)
+
+        for node in nodes:
+            node.set(attribute, value)
+
+    def add_attribute_by_xpath(self, root, xpath, attribute, value, namespaces=None):
+        '''
+        Adds an attribute to all nodes matching an XPath expression.
+
+        Parameters
+        ----------
+        root : `Element`
+            The root of a config tree.
+        xpath : `str`
+            The XPath expression specifying the nodes to be modified.
+        attribute : `str`
+            The name of the attribute to be added.
+        value : `str`
+            The value of the attribute to be added.
+        namespaces : `dict`, optional
+            The namespace prefix-URI mapping, by default None
+
+        Returns
+        -------
+        None
+        '''
+        # Find all nodes matching the XPath expression
+        nodes = root.xpath(xpath, namespaces=namespaces)
+
+        # Add the attribute to all matching nodes
+        for node in nodes:
+            node.set(attribute, value)
+
+    def get_config_replace(self, node_self, node_other):
+        '''get_config_replace
+
+        High-level api: Build an edit-config using operation='replace'. It
+        will be mostly from self.etree1.
+
+        Parameters
+        ----------
+
+        None
+
+        Returns
+        -------
+
+        Element
+            An element represnting an edit-config.
+        '''
+
+        in_s_not_in_o, in_o_not_in_s, in_s_and_in_o = \
+            self._group_kids(node_self, node_other)
+        ordered_by_user = {}
+
+        for child_self in in_s_not_in_o:
+            child_self.set(operation_tag, 'replace')
+            s_node = self.device.get_schema_node(child_self)
+            if s_node.get('type') == 'leaf-list':
+                if (
+                    s_node.get('ordered-by') == 'user' and
+                    s_node.tag not in ordered_by_user
+                ):
+                    ordered_by_user[s_node.tag] = 'leaf-list'
+            elif s_node.get('type') == 'list':
+                keys = self._get_list_keys(s_node)
+                if (
+                    s_node.get('ordered-by') == 'user' and
+                    s_node.tag not in ordered_by_user
+                ):
+                    ordered_by_user[s_node.tag] = keys
+
+        for child_other in in_o_not_in_s:
+            child_self = etree.Element(child_other.tag,
+                                       {operation_tag: self.preferred_delete},
+                                       nsmap=child_other.nsmap)
+            siblings = list(node_self.iterchildren(tag=child_other.tag))
+            if siblings:
+                siblings[-1].addnext(child_self)
+            else:
+                node_self.append(child_self)
+            s_node = self.device.get_schema_node(child_other)
+            if s_node.get('type') == 'leaf-list':
+                self._merge_text(child_other, child_self)
+            elif s_node.get('type') == 'list':
+                keys = self._get_list_keys(s_node)
+                for key in keys:
+                    key_node = child_other.find(key)
+                    e = etree.SubElement(
+                        child_self, key, nsmap=key_node.nsmap)
+                    e.text = key_node.text
+
+        for child_self, child_other in in_s_and_in_o:
+            child_self.set(operation_tag, 'replace')
+            child_other.set(operation_tag, 'replace')
+            s_node = self.device.get_schema_node(child_self)
+            if s_node.get('type') == 'leaf':
+                if self._same_text(child_self, child_other):
+                    if s_node.get('is_key'):
+                        child_self.attrib.pop(operation_tag)
+                    else:
+                        node_self.remove(child_self)
+            elif s_node.get('type') == 'leaf-list':
+                if s_node.get('ordered-by') == 'user':
+                    if s_node.tag not in ordered_by_user:
+                        ordered_by_user[s_node.tag] = 'leaf-list'
+                else:
+                    node_self.remove(child_self)
+            elif s_node.get('type') == 'container':
+                if (
+                    self._node_le(child_self, child_other) and
+                    self._node_le(child_other, child_self)
+                ):
+                    node_self.remove(child_self)
+            elif s_node.get('type') == 'list':
+                if (
+                    s_node.get('ordered-by') == 'user' and
+                    s_node.tag not in ordered_by_user
+                ):
+                    ordered_by_user[s_node.tag] = self._get_list_keys(s_node)
+                if (
+                    self._node_le(child_self, child_other) and
+                    self._node_le(child_other, child_self)
+                ):
+                    if s_node.get('ordered-by') != 'user':
+                        node_self.remove(child_self)
+            else:
+                path = self.device.get_xpath(s_node)
+                raise ModelError("unknown schema node type: type of node {}"
+                                 "is '{}'".format(path, s_node.get('type')))
+
+        for tag in ordered_by_user:
+            scope_o = in_s_not_in_o + in_s_and_in_o
+            sequence = self._get_sequence(scope_o, tag, node_self)
+            for i, item in enumerate(sequence):
+                # modifying the namespace mapping of a node is not possible
+                # in lxml. See https://bugs.launchpad.net/lxml/+bug/555602
+                # if 'yang' not in item.nsmap:
+                #     item.nsmap['yang'] = yang_url
+                if i == 0:
+                    item.set(insert_tag, 'first')
+                else:
+                    item.set(insert_tag, 'after')
+                    precursor = sequence[i - 1]
+                    if ordered_by_user[tag] == 'leaf-list':
+                        item.set(value_tag, precursor.text)
+                    else:
+                        keys = ordered_by_user[tag]
+                        key_nodes = {k: precursor.find(k) for k in keys}
+                        ids = {
+                            k: self._url_to_prefix(n, k)
+                            for k, n in key_nodes.items()
+                        }
+                        id_list = [
+                            "[{}='{}']".format(ids[k], key_nodes[k].text)
+                            for k in keys
+                        ]
+                        item.set(key_tag, ''.join(id_list))
+
     def node_add(self, node_sum, node_other):
         '''node_add
 
@@ -199,38 +470,70 @@ def node_add(self, node_sum, node_other):
             self._group_kids(node_sum, node_other)
         for child_self in in_s_not_in_o:
             pass
+
         for child_other in in_o_not_in_s:
+            this_operation = child_other.get(operation_tag, default='merge')
+
             # delete
-            if child_other.get(operation_tag) == 'delete':
-                raise ConfigDeltaError('data-missing: try to delete node {} ' \
-                                       'but it does not exist in config' \
-                                       .format(self.device \
-                                                   .get_xpath(child_other)))
+            if this_operation == 'delete':
+                raise ConfigDeltaError(
+                    'data-missing: try to delete node {} but it does not '
+                    'exist in config'.format(
+                        self.device.get_xpath(child_other)
+                    )
+                )
+
             # remove
-            elif child_other.get(operation_tag) == 'remove':
+            elif this_operation == 'remove':
                 pass
-            # merge, replace, create or none
-            else:
+
+            # merge, create, replace or none
+            elif (
+                this_operation == 'merge' or
+                this_operation == 'replace' or
+                this_operation == 'create'
+            ):
                 s_node = self.device.get_schema_node(child_other)
                 if s_node.get('type') in supported_node_type:
-                    getattr(self,
-                            '_node_add_without_peer_{}' \
-                            .format(s_node.get('type').replace('-', ''))) \
-                            (node_sum, child_other)
+                    getattr(
+                        self,
+                        '_node_add_without_peer_{}'.format(
+                            s_node.get('type').replace('-', '')
+                        )
+                    )(node_sum, child_other)
+
+            else:
+                raise ConfigDeltaError(
+                    "unknown operation: node {} contains operation "
+                    "'{}'".format(
+                        self.device.get_xpath(child_other),
+                        this_operation
+                    )
+                )
+
         for child_self, child_other in in_s_and_in_o:
             s_node = self.device.get_schema_node(child_self)
             if s_node.get('type') in supported_node_type:
-                getattr(self,
-                        '_node_add_with_peer_{}' \
-                        .format(s_node.get('type').replace('-', ''))) \
-                        (child_self, child_other)
+                getattr(
+                    self,
+                    '_node_add_with_peer_{}'.format(
+                        s_node.get('type').replace('-', '')
+                    )
+                )(child_self, child_other)
+
+            if not list(child_self):
+                if (
+                    s_node.get('type') == 'container' and
+                    s_node.get('presence') != 'true'
+                ):
+                    node_sum.remove(child_self)
 
     def _node_add_without_peer_leaf(self, node_sum, child_other):
         '''_node_add_without_peer_leaf
 
-        Low-level api: Apply delta child_other to node_sum when there is no peer
-        of child_other can be found under node_sum. child_other is a leaf node.
-        Element node_sum will be modified during the process.
+        Low-level api: Apply delta child_other to node_sum when there is no
+        peer of child_other can be found under node_sum. child_other is a leaf
+        node. Element node_sum will be modified during the process.
 
         Parameters
         ----------
@@ -255,9 +558,9 @@ def _node_add_without_peer_leaf(self, node_sum, child_other):
     def _node_add_without_peer_leaflist(self, node_sum, child_other):
         '''_node_add_without_peer_leaflist
 
-        Low-level api: Apply delta child_other to node_sum when there is no peer
-        of child_other can be found under node_sum. child_other is a leaf-list
-        node. Element node_sum will be modified during the process.
+        Low-level api: Apply delta child_other to node_sum when there is no
+        peer of child_other can be found under node_sum. child_other is a
+        leaf-list node. Element node_sum will be modified during the process.
 
         Parameters
         ----------
@@ -325,9 +628,9 @@ def _node_add_without_peer_leaflist(self, node_sum, child_other):
     def _node_add_without_peer_container(self, node_sum, child_other):
         '''_node_add_without_peer_container
 
-        Low-level api: Apply delta child_other to node_sum when there is no peer
-        of child_other can be found under node_sum. child_other is a container
-        node. Element node_sum will be modified during the process.
+        Low-level api: Apply delta child_other to node_sum when there is no
+        peer of child_other can be found under node_sum. child_other is a
+        container node. Element node_sum will be modified during the process.
 
         Parameters
         ----------
@@ -346,15 +649,23 @@ def _node_add_without_peer_container(self, node_sum, child_other):
             There is no return of this method.
         '''
 
-        e = deepcopy(child_other)
-        node_sum.append(self._del_attrib(e))
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation == 'merge':
+            e = etree.SubElement(node_sum, child_other.tag,
+                                 nsmap=child_other.nsmap)
+            self.node_add(e, child_other)
+        elif (
+            this_operation == 'replace' or
+            this_operation == 'create'
+        ):
+            node_sum.append(self._del_attrib(deepcopy(child_other)))
 
     def _node_add_without_peer_list(self, node_sum, child_other):
         '''_node_add_without_peer_list
 
-        Low-level api: Apply delta child_other to node_sum when there is no peer
-        of child_other can be found under node_sum. child_other is a list node.
-        Element node_sum will be modified during the process.
+        Low-level api: Apply delta child_other to node_sum when there is no
+        peer of child_other can be found under node_sum. child_other is a list
+        node. Element node_sum will be modified during the process.
 
         Parameters
         ----------
@@ -374,21 +685,32 @@ def _node_add_without_peer_list(self, node_sum, child_other):
         '''
 
         s_node = self.device.get_schema_node(child_other)
-        e = deepcopy(child_other)
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation == 'merge':
+            e = etree.Element(child_other.tag, nsmap=child_other.nsmap)
+            for list_key_tag in self._get_list_keys(s_node):
+                key_ele_other = child_other.find(list_key_tag)
+                key_ele_self = deepcopy(key_ele_other)
+                e.append(self._del_attrib(key_ele_self))
+        elif (
+            this_operation == 'replace' or
+            this_operation == 'create'
+        ):
+            e = self._del_attrib(deepcopy(child_other))
         scope = node_sum.getchildren()
         siblings = self._get_sequence(scope, child_other.tag, node_sum)
         if s_node.get('ordered-by') == 'user' and \
            child_other.get(insert_tag) is not None:
             if child_other.get(insert_tag) == 'first':
                 if siblings:
-                    siblings[0].addprevious(self._del_attrib(e))
+                    siblings[0].addprevious(e)
                 else:
-                    node_sum.append(self._del_attrib(e))
+                    node_sum.append(e)
             elif child_other.get(insert_tag) == 'last':
                 if siblings:
-                    siblings[-1].addnext(self._del_attrib(e))
+                    siblings[-1].addnext(e)
                 else:
-                    node_sum.append(self._del_attrib(e))
+                    node_sum.append(e)
             elif child_other.get(insert_tag) == 'before':
                 if child_other.get(key_tag) is None:
                     _inserterror('before', self.device.get_xpath(child_other),
@@ -400,7 +722,7 @@ def _node_add_without_peer_list(self, node_sum, child_other):
                     path = self.device.get_xpath(child_other)
                     key = child_other.get(key_tag)
                     _inserterror('before', path, 'key', key)
-                sibling.addprevious(self._del_attrib(e))
+                sibling.addprevious(e)
             elif child_other.get(insert_tag) == 'after':
                 if child_other.get(key_tag) is None:
                     _inserterror('after', self.device.get_xpath(child_other),
@@ -412,12 +734,14 @@ def _node_add_without_peer_list(self, node_sum, child_other):
                     path = self.device.get_xpath(child_other)
                     key = child_other.get(key_tag)
                     _inserterror('after', path, 'key', key)
-                sibling.addnext(self._del_attrib(e))
+                sibling.addnext(e)
         else:
             if siblings:
-                siblings[-1].addnext(self._del_attrib(e))
+                siblings[-1].addnext(e)
             else:
-                node_sum.append(self._del_attrib(e))
+                node_sum.append(e)
+        if this_operation == 'merge':
+            self.node_add(e, child_other)
 
     def _node_add_with_peer_leaf(self, child_self, child_other):
         '''_node_add_with_peer_leaf
@@ -444,33 +768,34 @@ def _node_add_with_peer_leaf(self, child_self, child_other):
             There is no return of this method.
         '''
 
-        if child_other.get(operation_tag) is None:
-            child_self.text = child_other.text
-        elif child_other.get(operation_tag) == 'merge':
-            child_self.text = child_other.text
-        elif child_other.get(operation_tag) == 'replace':
-            child_self.text = child_other.text
-        elif child_other.get(operation_tag) == 'create':
-            raise ConfigDeltaError('data-exists: try to create node {} but ' \
-                                   'it already exists' \
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation == 'merge':
+            self._merge_text(child_other, child_self)
+        elif this_operation == 'replace':
+            self._merge_text(child_other, child_self)
+        elif this_operation == 'create':
+            raise ConfigDeltaError('data-exists: try to create node {} but '
+                                   'it already exists'
                                    .format(self.device.get_xpath(child_other)))
-        elif child_other.get(operation_tag) == 'delete' or \
-             child_other.get(operation_tag) == 'remove':
+        elif (
+            this_operation == 'delete' or
+            this_operation == 'remove'
+        ):
             parent_self = child_self.getparent()
             parent_self.remove(child_self)
         else:
-            raise ConfigDeltaError("unknown operation: node {} contains " \
-                                   "operation '{}'" \
+            raise ConfigDeltaError("unknown operation: node {} contains "
+                                   "operation '{}'"
                                    .format(self.device.get_xpath(child_other),
-                                           child_other.get(operation_tag)))
+                                           this_operation))
 
     def _node_add_with_peer_leaflist(self, child_self, child_other):
         '''_node_add_with_peer_leaflist
 
         Low-level api: Apply delta child_other to child_self when child_self is
         the peer of child_other. Element child_self and child_other are
-        leaf-list nodes. Element child_self will be modified during the process.
-        RFC6020 section 7.7.7 is a reference of this method.
+        leaf-list nodes. Element child_self will be modified during the
+        process. RFC6020 section 7.7.7 is a reference of this method.
 
         Parameters
         ----------
@@ -491,9 +816,9 @@ def _node_add_with_peer_leaflist(self, child_self, child_other):
 
         parent_self = child_self.getparent()
         s_node = self.device.get_schema_node(child_self)
-        if child_other.get(operation_tag) is None or \
-           child_other.get(operation_tag) == 'merge' or \
-           child_other.get(operation_tag) == 'replace':
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation == 'merge' or \
+           this_operation == 'replace':
             if s_node.get('ordered-by') == 'user' and \
                child_other.get(insert_tag) is not None:
                 if child_other.get(insert_tag) == 'first':
@@ -536,26 +861,28 @@ def _node_add_with_peer_leaflist(self, child_self, child_other):
                         _inserterror('after', path, 'value', value)
                     if sibling[0] != child_self:
                         sibling[0].addnext(child_self)
-        elif child_other.get(operation_tag) == 'create':
-            raise ConfigDeltaError('data-exists: try to create node {} but ' \
-                                   'it already exists' \
+        elif this_operation == 'create':
+            raise ConfigDeltaError('data-exists: try to create node {} but '
+                                   'it already exists'
                                    .format(self.device.get_xpath(child_other)))
-        elif child_other.get(operation_tag) == 'delete' or \
-             child_other.get(operation_tag) == 'remove':
+        elif (
+            this_operation == 'delete' or
+            this_operation == 'remove'
+        ):
             parent_self.remove(child_self)
         else:
-            raise ConfigDeltaError("unknown operation: node {} contains " \
-                                   "operation '{}'" \
+            raise ConfigDeltaError("unknown operation: node {} contains "
+                                   "operation '{}'"
                                    .format(self.device.get_xpath(child_other),
-                                           child_other.get(operation_tag)))
+                                           this_operation))
 
     def _node_add_with_peer_container(self, child_self, child_other):
         '''_node_add_with_peer_container
 
         Low-level api: Apply delta child_other to child_self when child_self is
         the peer of child_other. Element child_self and child_other are
-        container nodes. Element child_self will be modified during the process.
-        RFC6020 section 7.5.8 is a reference of this method.
+        container nodes. Element child_self will be modified during the
+        process. RFC6020 section 7.5.8 is a reference of this method.
 
         Parameters
         ----------
@@ -575,24 +902,26 @@ def _node_add_with_peer_container(self, child_self, child_other):
         '''
 
         parent_self = child_self.getparent()
-        if child_other.get(operation_tag) is None or \
-           child_other.get(operation_tag) == 'merge':
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation == 'merge':
             self.node_add(child_self, child_other)
-        elif child_other.get(operation_tag) == 'replace':
-            e = deepcopy(child_other)
-            parent_self.replace(child_self, self._del_attrib(e))
-        elif child_other.get(operation_tag) == 'create':
-            raise ConfigDeltaError('data-exists: try to create node {} but ' \
-                                   'it already exists' \
+        elif this_operation == 'replace':
+            parent_self.replace(child_self,
+                                self._del_attrib(deepcopy(child_other)))
+        elif this_operation == 'create':
+            raise ConfigDeltaError('data-exists: try to create node {} but '
+                                   'it already exists'
                                    .format(self.device.get_xpath(child_other)))
-        elif child_other.get(operation_tag) == 'delete' or \
-             child_other.get(operation_tag) == 'remove':
+        elif (
+            this_operation == 'delete' or
+            this_operation == 'remove'
+        ):
             parent_self.remove(child_self)
         else:
-            raise ConfigDeltaError("unknown operation: node {} contains " \
-                                   "operation '{}'" \
+            raise ConfigDeltaError("unknown operation: node {} contains "
+                                   "operation '{}'"
                                    .format(self.device.get_xpath(child_other),
-                                           child_other.get(operation_tag)))
+                                           this_operation))
 
     def _node_add_with_peer_list(self, child_self, child_other):
         '''_node_add_with_peer_list
@@ -621,8 +950,9 @@ def _node_add_with_peer_list(self, child_self, child_other):
 
         parent_self = child_self.getparent()
         s_node = self.device.get_schema_node(child_self)
-        if child_other.get(operation_tag) != 'delete' and \
-           child_other.get(operation_tag) != 'remove' and \
+        this_operation = child_other.get(operation_tag, default='merge')
+        if this_operation != 'delete' and \
+           this_operation != 'remove' and \
            s_node.get('ordered-by') == 'user' and \
            child_other.get(insert_tag) is not None:
             if child_other.get(insert_tag) == 'first':
@@ -663,26 +993,27 @@ def _node_add_with_peer_list(self, child_self, child_other):
                     _inserterror('after', path, 'key', key)
                 if sibling != child_self:
                     sibling.addnext(child_self)
-        if child_other.get(operation_tag) is None or \
-           child_other.get(operation_tag) == 'merge':
+        if this_operation == 'merge':
             self.node_add(child_self, child_other)
-        elif child_other.get(operation_tag) == 'replace':
-            e = deepcopy(child_other)
-            parent_self.replace(child_self, self._del_attrib(e))
-        elif child_other.get(operation_tag) == 'create':
-            raise ConfigDeltaError('data-exists: try to create node {} but ' \
-                                   'it already exists' \
+        elif this_operation == 'replace':
+            parent_self.replace(child_self,
+                                self._del_attrib(deepcopy(child_other)))
+        elif this_operation == 'create':
+            raise ConfigDeltaError('data-exists: try to create node {} but '
+                                   'it already exists'
                                    .format(self.device.get_xpath(child_other)))
-        elif child_other.get(operation_tag) == 'delete' or \
-             child_other.get(operation_tag) == 'remove':
+        elif (
+            this_operation == 'delete' or
+            this_operation == 'remove'
+        ):
             parent_self.remove(child_self)
         else:
-            raise ConfigDeltaError("unknown operation: node {} contains " \
-                                   "operation '{}'" \
+            raise ConfigDeltaError("unknown operation: node {} contains "
+                                   "operation '{}'"
                                    .format(self.device.get_xpath(child_other),
-                                           child_other.get(operation_tag)))
+                                           this_operation))
 
-    def node_sub(self, node_self, node_other):
+    def node_sub(self, node_self, node_other, depth=0):
         '''node_sub
 
         Low-level api: Compute the delta of two configs. This method is
@@ -698,6 +1029,12 @@ def node_sub(self, node_self, node_other):
         node_other : `Element`
             A config node in another config tree that is being processed.
 
+        depth : `int`
+            Specify the current level of processing. In other words, how many
+            hops from node_self to roots. Consider roots in a YANG module are
+            level 0, their children are level 1, and so on so forth. The
+            default value of depth is 0.
+
         Returns
         -------
 
@@ -705,32 +1042,20 @@ def node_sub(self, node_self, node_other):
             There is no return of this method.
         '''
 
-        def same_leaf_list(tag):
-            list_self = [c for c in list(node_self) if c.tag == tag]
-            list_other = [c for c in list(node_other) if c.tag == tag]
-            s_node = self.device.get_schema_node((list_self + list_other)[0])
-            if s_node.get('ordered-by') == 'user':
-                if [i.text for i in list_self] == [i.text for i in list_other]:
-                    return True
-                else:
-                    return False
-            else:
-                if set([i.text for i in list_self]) == \
-                   set([i.text for i in list_other]):
-                    return True
-                else:
-                    return False
-
         if self.preferred_replace != 'merge':
-            t_self = [c.tag for c in list(node_self) \
-                      if self.device.get_schema_node(c).get('type') == \
-                         'leaf-list']
-            t_other = [c.tag for c in list(node_other) \
-                       if self.device.get_schema_node(c).get('type') == \
-                          'leaf-list']
+            t_self = [
+                c.tag for c in list(node_self)
+                if self.device.get_schema_node(c).get('type') == 'leaf-list'
+            ]
+            t_other = [
+                c.tag for c in list(node_other)
+                if self.device.get_schema_node(c).get('type') == 'leaf-list'
+            ]
             commonalities = set(t_self) & set(t_other)
             for commonality in commonalities:
-                if not same_leaf_list(commonality):
+                if not self._same_leaf_list(commonality,
+                                            node_self,
+                                            node_other):
                     node_self.set(operation_tag, 'replace')
                     node_other.set(operation_tag, 'replace')
                     return
@@ -745,6 +1070,8 @@ def same_leaf_list(tag):
                                         nsmap=child_self.nsmap)
             if self.preferred_create != 'merge':
                 child_self.set(operation_tag, self.preferred_create)
+            if self.diff_type == 'replace':
+                child_self.set(operation_tag, 'replace')
             siblings = list(node_other.iterchildren(tag=child_self.tag))
             if siblings:
                 siblings[-1].addnext(child_other)
@@ -755,7 +1082,7 @@ def same_leaf_list(tag):
                 if s_node.get('ordered-by') == 'user' and \
                    s_node.tag not in ordered_by_user:
                     ordered_by_user[s_node.tag] = 'leaf-list'
-                child_other.text = child_self.text
+                self._merge_text(child_self, child_other)
             elif s_node.get('type') == 'list':
                 keys = self._get_list_keys(s_node)
                 if s_node.get('ordered-by') == 'user' and \
@@ -763,7 +1090,8 @@ def same_leaf_list(tag):
                     ordered_by_user[s_node.tag] = keys
                 for key in keys:
                     key_node = child_self.find(key)
-                    e = etree.SubElement(child_other, key, nsmap=key_node.nsmap)
+                    e = etree.SubElement(
+                        child_other, key, nsmap=key_node.nsmap)
                     e.text = key_node.text
             if s_node.getparent().get('type') == 'case':
                 # key: choice node, value: case node
@@ -782,8 +1110,7 @@ def same_leaf_list(tag):
                 # Append node if:
                 # Node not in case
                 # Node in case but choice node not in self
-                # Node in case and choice node in self and the same case also
-                # in self
+                # Node in case and choice node in self and the same case also in self
                 if s_node.getparent().get('type') == 'case':
                     choice_node = s_node.getparent().getparent()
                     if choice_node not in choice_nodes or \
@@ -795,7 +1122,7 @@ def same_leaf_list(tag):
                 if s_node.get('ordered-by') == 'user' and \
                    s_node.tag not in ordered_by_user:
                     ordered_by_user[s_node.tag] = 'leaf-list'
-                child_self.text = child_other.text
+                self._merge_text(child_other, child_self)
             elif s_node.get('type') == 'list':
                 keys = self._get_list_keys(s_node)
                 if s_node.get('ordered-by') == 'user' and \
@@ -808,7 +1135,7 @@ def same_leaf_list(tag):
         for child_self, child_other in in_s_and_in_o:
             s_node = self.device.get_schema_node(child_self)
             if s_node.get('type') == 'leaf':
-                if child_self.text == child_other.text:
+                if self._same_text(child_self, child_other):
                     if not s_node.get('is_key'):
                         node_self.remove(child_self)
                         node_other.remove(child_other)
@@ -829,7 +1156,13 @@ def same_leaf_list(tag):
                     node_self.remove(child_self)
                     node_other.remove(child_other)
                 else:
-                    self.node_sub(child_self, child_other)
+                    if (
+                        self.diff_type == 'replace' and
+                        self.replace_depth == depth + 1
+                    ):
+                        self.get_config_replace(child_self, child_other)
+                    else:
+                        self.node_sub(child_self, child_other, depth=depth+1)
             elif s_node.get('type') == 'list':
                 if s_node.get('ordered-by') == 'user' and \
                    s_node.tag not in ordered_by_user:
@@ -849,16 +1182,24 @@ def same_leaf_list(tag):
                         node_self.remove(child_self)
                         node_other.remove(child_other)
                 else:
-                    self.node_sub(child_self, child_other)
+                    if (
+                        self.diff_type == 'replace' and
+                        self.replace_depth == depth + 1
+                    ):
+                        self.get_config_replace(child_self, child_other)
+                    else:
+                        self.node_sub(child_self, child_other, depth=depth+1)
             else:
                 path = self.device.get_xpath(s_node)
-                raise ModelError("unknown schema node type: type of node {}" \
+                raise ModelError("unknown schema node type: type of node {}"
                                  "is '{}'".format(path, s_node.get('type')))
         for tag in ordered_by_user:
             scope_s = in_s_not_in_o + in_s_and_in_o
             scope_o = in_o_not_in_s + in_s_and_in_o
-            for sequence in self._get_sequence(scope_s, tag, node_self), \
-                            self._get_sequence(scope_o, tag, node_other):
+            for sequence in (
+                self._get_sequence(scope_s, tag, node_self),
+                self._get_sequence(scope_o, tag, node_other),
+            ):
                 for item in sequence:
                     # modifying the namespace mapping of a node is not possible
                     # in lxml. See https://bugs.launchpad.net/lxml/+bug/555602
@@ -875,11 +1216,15 @@ def same_leaf_list(tag):
                         else:
                             keys = ordered_by_user[tag]
                             key_nodes = {k: precursor.find(k) for k in keys}
-                            ids = {k: self._url_to_prefix(n, k) \
-                                   for k, n in key_nodes.items()}
-                            l = ["[{}='{}']".format(ids[k], key_nodes[k].text) \
-                                 for k in keys]
-                            item.set(key_tag, ''.join(l))
+                            ids = {
+                                k: self._url_to_prefix(n, k)
+                                for k, n in key_nodes.items()
+                            }
+                            id_list = [
+                                "[{}='{}']".format(ids[k], key_nodes[k].text)
+                                for k in keys
+                            ]
+                            item.set(key_tag, ''.join(id_list))
 
     @staticmethod
     def _url_to_prefix(node, id):
@@ -893,7 +1238,7 @@ def _url_to_prefix(node, id):
         Parameters
         ----------
 
-        node : `str`
+        node : `Element`
             A config node. Its identifier will be converted.
 
         id : `str`
@@ -915,3 +1260,50 @@ def _url_to_prefix(node, id):
                 else:
                     return prefixes[ret.group(1)] + ':' + ret.group(2)
         return id
+
+    def _same_leaf_list(self, leaf_list_tag, parent_node_1, parent_node_2):
+        '''_same_leaf_list
+
+        Low-level api: Return True when two leaf-list's that are identified by
+        leaf_list_tag are same.
+
+        Parameters
+        ----------
+
+        leaf_list_tag : `str`
+            A config node. Its identifier will be converted.
+
+        parent_node_1 : `Element`
+            One parent node. One or multiple leaf-list nodes are its children.
+
+        parent_node_2 : `Element`
+            The other parent node. One or multiple leaf-list nodes are its
+            children.
+
+        Returns
+        -------
+
+        bool
+            True when the leaf-list nodes under two parent nodes are same.
+            Otherwise False.
+        '''
+
+        list_1 = [c for c in list(parent_node_1) if c.tag == leaf_list_tag]
+        list_2 = [c for c in list(parent_node_2) if c.tag == leaf_list_tag]
+        s_node = self.device.get_schema_node((list_1 + list_2)[0])
+        if s_node.get('ordered-by') == 'user':
+            if (
+                [self._parse_text(i, s_node) for i in list_1] ==
+                [self._parse_text(i, s_node) for i in list_2]
+            ):
+                return True
+            else:
+                return False
+        else:
+            if (
+                set([self._parse_text(i, s_node) for i in list_1]) ==
+                set([self._parse_text(i, s_node) for i in list_2])
+            ):
+                return True
+            else:
+                return False
\ No newline at end of file
diff --git a/ncdiff/src/yang/ncdiff/ref.py b/ncdiff/src/yang/ncdiff/ref.py
new file mode 100644
index 0000000..e7b03cf
--- /dev/null
+++ b/ncdiff/src/yang/ncdiff/ref.py
@@ -0,0 +1,398 @@
+import re
+
+from .errors import ConfigError
+from .composer import Tag
+
+
+class IdentityRef(object):
+    '''IdentityRef
+
+    A class to process YANG built-in type identityref.
+
+    Attributes
+    ----------
+    device : `object`
+        An instance of yang.ncdiff.ModelDevice, which represents a modeled
+        device.
+
+    node : `Element`
+        A data node of type identityref.
+
+    to_node : `Element`
+        Another data node of type identityref.
+
+    converted : `str`
+        Converted text value of node, based on to_node.nsmap.
+    '''
+
+    def __init__(self, device, node, to_node=None):
+        '''
+        __init__ instantiates a IdentityRef instance.
+        '''
+
+        self.device = device
+        self.node = node
+        self.to_node = to_node
+        self.node_url = self.device.convert_tag('', self.node.tag)[0]
+        self._default_ns = {}
+
+    @property
+    def converted(self):
+        if self.node.text is None:
+            raise ConfigError("the node with tag {} is an identityref but no " \
+                              "value" \
+                              .format(self.node.tag))
+        return self.convert(self.node.text, to_node=self.to_node)
+
+    @property
+    def default(self):
+        if self.node.text is None:
+            raise ConfigError("the node with tag {} is an identityref but no " \
+                              "value" \
+                              .format(self.node.tag))
+        return self.convert(self.node.text, to_node=None)
+
+    @property
+    def default_ns(self):
+        self._default_ns = {None: self.node_url}
+        self.default
+        return self._default_ns
+
+    def parse_prefixed_id(self, id, node):
+        match = re.search(Tag.COLON[0], id)
+        if match:
+            if match.group(1) in node.nsmap:
+                return node.nsmap[match.group(1)], match.group(2)
+            elif match.group(1) in [ns[0] for ns in self.device.namespaces]:
+                name_to_url = {ns[0]: ns[2] for ns in self.device.namespaces}
+                return name_to_url[match.group(1)], match.group(2)
+            else:
+                raise ConfigError("unknown prefix '{}' in the node with tag " \
+                                  "{}" \
+                                  .format(match.group(1), node.tag))
+        else:
+            # RFC7950 section 9.10.3 and RFC7951 section 6.8
+            if None in node.nsmap:
+                return node.nsmap[None], id
+            else:
+                raise ConfigError("fail to find default namespace of " \
+                                  "the node with tag {}" \
+                                  .format(node.tag))
+
+    def compose_prefixed_id(self, url, id, to_node=None):
+        def url_to_name(url, ns):
+            model_name = self.device.convert_ns(url,
+                                                src=Tag.NAMESPACE,
+                                                dst=Tag.NAME)
+            if url not in ns.values():
+                ns[model_name] = url
+            return model_name
+
+        if to_node is None:
+            # RFC7951 section 6.8
+            if url == self.node_url:
+                return id
+            else:
+                return '{}:{}'.format(url_to_name(url, self._default_ns), id)
+        else:
+            # RFC7950 section 9.10.3
+            url_to_prefix = {v: k for k, v in to_node.nsmap.items()}
+            if url in url_to_prefix:
+                if url_to_prefix[url] is None:
+                    return '{}'.format(id)
+                else:
+                    return '{}:{}'.format(url_to_prefix[url], id)
+            else:
+                raise ConfigError("URL '{}' is not found in to_node.nsmap " \
+                                  "{}, where the to_node has tag {}" \
+                                  .format(url, to_node.nsmap, to_node.tag))
+
+    def convert(self, tag, to_node=None):
+        url, id = self.parse_prefixed_id(tag, self.node)
+        return self.compose_prefixed_id(url, id, to_node=to_node)
+
+    def parse_instanceid(self, node, to_node=None):
+        if node.text is None:
+            raise ConfigError("the node with tag {} is an " \
+                              "instance-identifier but no value" \
+                              .format(node.tag))
+        tag = ''
+        new_instanceid = ''
+        expecting = '*./'
+        for char in node.text:
+            if char in '/.[]=*':
+                new_instanceid += char
+                if tag:
+                    url, id = self.parse_prefixed_id(tag, node)
+                    tag = ''
+                    new_instanceid += self.compose_prefixed_id(url, id,
+                                                               to_node=to_node)
+            else:
+                tag += char
+        if tag:
+            url, id = self.parse_prefixed_id(tag, node)
+            new_instanceid += self.compose_prefixed_id(url, id,
+                                                       to_node=to_node)
+        return new_instanceid
+
+
+class InstanceIdentifier(IdentityRef):
+    '''InstanceIdentifier
+
+    A class to process YANG built-in type instance-identifier.
+
+    Attributes
+    ----------
+    device : `object`
+        An instance of yang.ncdiff.ModelDevice, which represents a modeled
+        device.
+
+    node : `Element`
+        A data node of type instance-identifier.
+
+    to_node : `Element`
+        Another data node of type instance-identifier.
+
+    converted : `str`
+        Converted text value of node, based on to_node.nsmap.
+    '''
+
+    def __init__(self, device, node, to_node=None):
+        '''
+        __init__ instantiates a InstanceIdentifier instance.
+        '''
+
+        IdentityRef.__init__(self, device, node, to_node=to_node)
+        self.str_list = [(self.node.text, 0, self.node.text)]
+
+    @property
+    def converted(self):
+        self.str_list = [(self.node.text, 0, self.node.text)]
+        self.parse_quote()
+        self.parse_square_bracket(to_node=self.to_node)
+        self.parse_element(to_node=self.to_node)
+        self.convert_str_list(to_node=self.to_node)
+        return ''.join([p[2] for p in self.str_list])
+
+    @property
+    def default(self):
+        self.str_list = [(self.node.text, 0, self.node.text)]
+        self.parse_quote()
+        self.parse_square_bracket()
+        self.parse_element()
+        self.convert_str_list()
+        return ''.join([p[2] for p in self.str_list])
+
+    def string(self, phase_num):
+        return ''.join([p[0] for p in self.str_list
+                        if p[1] == 0 or p[1] >= phase_num])
+
+    def parse_prefixed_id(self, id, node):
+        match = re.search(Tag.COLON[0], id)
+        if match:
+            if match.group(1) in node.nsmap:
+                return node.nsmap[match.group(1)], match.group(2)
+            elif match.group(1) in [ns[0] for ns in self.device.namespaces]:
+                name_to_url = {ns[0]: ns[2] for ns in self.device.namespaces}
+                return name_to_url[match.group(1)], match.group(2)
+            else:
+                raise ConfigError("unknown prefix '{}' in the node with tag " \
+                                  "{}" \
+                                  .format(match.group(1), node.tag))
+        else:
+            # RFC7950 section 9.13.2 and RFC7951 section 6.11
+            return None, id
+
+    def compose_prefixed_id(self, url, id, to_node=None):
+        def url_to_name(url, ns):
+            model_name = self.device.convert_ns(url,
+                                                src=Tag.NAMESPACE,
+                                                dst=Tag.NAME)
+            if url not in ns.values():
+                ns[model_name] = url
+            return model_name
+
+        # RFC7950 section 9.13.2
+        if to_node is None:
+            return '{}:{}'.format(url_to_name(url, self._default_ns), id)
+        else:
+            url_to_prefix = {v: k for k, v in to_node.nsmap.items()
+                                  if k is not None}
+            if url in url_to_prefix:
+                return '{}:{}'.format(url_to_prefix[url], id)
+            else:
+                raise ConfigError("URL '{}' is not found in to_node.nsmap {} " \
+                                  "(default namespace cannot be used here), " \
+                                  "where the to_node has tag {}" \
+                                  .format(url, to_node.nsmap, to_node.tag))
+
+    def convert_str_list(self, to_node=None):
+        default_url = None
+        new_str_list = []
+        for piece in self.str_list:
+            if piece[1] <= 1:
+                new_str_list.append(piece)
+            elif piece[1] == 2:
+                if piece[0] == '[' or \
+                   piece[0] == ']' or \
+                   piece[0] == '=' or \
+                   piece[0] == '.':
+                    new_str_list.append((piece[0], piece[1], piece[0]))
+                else:
+                    url, id = self.parse_prefixed_id(piece[0], self.node)
+                    if url is None:
+                        url = default_url
+                    if url is None:
+                        raise ConfigError("in the instance-identifier node " \
+                                          "with tag {}, the leftmost data " \
+                                          "node name '{}' is not in " \
+                                          "namespace-qualified form" \
+                                          .format(self.node.tag, piece[0]))
+                    else:
+                        converted_id = self.compose_prefixed_id(url, id,
+                                                                to_node=to_node)
+                        new_str_list.append((piece[0], piece[1], converted_id))
+            elif piece[1] == 3:
+                url, id = self.parse_prefixed_id(piece[0], self.node)
+                if url is not None:
+                    default_url = url
+                if default_url is None:
+                    raise ConfigError("in the instance-identifier node " \
+                                      "with tag {}, the leftmost data " \
+                                      "node name '{}' is not in " \
+                                      "namespace-qualified form" \
+                                      .format(self.node.tag, piece[0]))
+                else:
+                    converted_id = self.compose_prefixed_id(default_url, id,
+                                                            to_node=to_node)
+                    new_str_list.append((piece[0], piece[1], converted_id))
+        self.str_list = new_str_list
+
+    def cut(self, start_idx, end_idx, converted_str, phase_num):
+        def this_piece(idx, start_idx, end_idx, converted_str, phase_num):
+            string = self.str_list[idx][0]
+            original_phase_num = self.str_list[idx][1]
+            ret = [(string[:start_idx], original_phase_num, string[:start_idx]),
+                   (string[start_idx:end_idx], phase_num, converted_str),
+                   (string[end_idx:], original_phase_num, string[end_idx:])]
+            return [piece for piece in ret if piece[0]]
+
+        def all_pieces(idx, start_idx, end_idx, converted_str, phase_num):
+            return self.str_list[:idx] + \
+                   this_piece(idx, start_idx, end_idx,
+                              converted_str, phase_num) + \
+                   self.str_list[idx+1:]
+
+        str_len = end_idx - start_idx
+        position = 0
+        for idx, piece in enumerate(self.str_list):
+            if piece[1] == 0 or piece[1] == phase_num:
+                end_position = position + len(piece[0])
+                if start_idx >= position and end_idx <= end_position:
+                    self.str_list = all_pieces(idx,
+                                               start_idx-position,
+                                               end_idx-position,
+                                               converted_str,
+                                               phase_num)
+                    return
+                position += len(piece[0])
+
+    @staticmethod
+    def convert_literal(literal):
+        converted_literal = literal[1:-1]
+        converted_literal = re.sub("'", "''", converted_literal)
+        return "'" + re.sub('""', '"', converted_literal) + "'"
+
+    def parse_quote(self):
+        string = self.string(1)
+        start_idx = None
+        start_escape = None
+        for idx, char in enumerate(string):
+            if start_idx is None and char == '=':
+                start_idx = idx + 1
+            elif start_idx is not None and idx == start_idx:
+                if char != "'" and char != '"':
+                    raise ConfigError("do not see a apostrophe or double " \
+                                      "quote after '=' in the node with tag " \
+                                      "{}" \
+                                      .format(self.node.tag))
+                else:
+                    opening_quote = char
+            elif start_idx is not None and idx > start_idx:
+                if char == opening_quote:
+                    if idx < len(string)-1 and string[idx+1] == opening_quote:
+                        start_escape = idx
+                        continue
+                    end_escape = idx + 1
+                    if start_escape is not None and \
+                       (end_escape-start_escape)%2 == 0:
+                        start_escape = None
+                        continue
+                    end_idx = idx + 1
+                    if opening_quote == '"':
+                        default_literal = \
+                            self.convert_literal(string[start_idx:end_idx])
+                    else:
+                        default_literal = string[start_idx:end_idx]
+                    self.cut(start_idx, end_idx, default_literal, 1)
+                    start_idx = None
+        if start_idx is not None:
+            raise ConfigError('found opening apostrophe or double quote, but ' \
+                              'not the closing one in the node with tag {}' \
+                              .format(self.node.tag))
+
+    def parse_square_bracket(self, to_node=None):
+        string = self.string(2)
+        start_idx = None
+        for idx, char in enumerate(string):
+            if start_idx is None and char == '[':
+                start_idx = idx
+            elif start_idx is not None and idx > start_idx:
+                if char == "]":
+                    end_idx = idx + 1
+                    substring = string[start_idx:end_idx]
+                    self.cut(start_idx, start_idx+1, '[', 2)
+                    self.cut(end_idx-1, end_idx, ']', 2)
+                    if substring[-2] == '=':
+                        tag = substring[1:-2]
+                        self.cut(end_idx-2, end_idx-1, '=', 2)
+                        if tag == '.':
+                            self.cut(start_idx+1, end_idx-2, '.', 2)
+                        else:
+                            self.cut(start_idx+1, end_idx-2, tag, 2)
+                        start_idx = None
+                    else:
+                        if re.search('^\[[1-9][0-9]*\]$', substring):
+                            numbers = substring[1:-1]
+                            self.cut(start_idx+1, end_idx-1, numbers, 2)
+                        else:
+                            tag = substring[1:-1]
+                            self.cut(start_idx+1, end_idx-1, tag, 2)
+                        start_idx = None
+        if start_idx is not None:
+            raise ConfigError('found opening square bracket, but not the ' \
+                              'closing bracket in the node with tag {}' \
+                              .format(self.node))
+
+    def parse_element(self, to_node=None):
+        SEPARATORS = "./"
+        string = self.string(3)
+        start_idx = None
+        for idx, char in enumerate(string):
+            if start_idx is None and char not in SEPARATORS:
+                start_idx = idx
+            elif start_idx is not None and char in SEPARATORS:
+                end_idx = idx
+                tag = string[start_idx:end_idx]
+                if tag == '*':
+                    self.cut(start_idx, end_idx, '*', 3)
+                else:
+                    self.cut(start_idx, end_idx, tag, 3)
+                start_idx = None
+        if start_idx is not None:
+            end_idx = idx + 1
+            tag = string[start_idx:end_idx]
+            if tag == '*':
+                self.cut(start_idx, end_idx, '*', 3)
+            else:
+                self.cut(start_idx, end_idx, tag, 3)
\ No newline at end of file
diff --git a/ncdiff/src/yang/ncdiff/runningconfig.py b/ncdiff/src/yang/ncdiff/runningconfig.py
index 227fc29..5178420 100755
--- a/ncdiff/src/yang/ncdiff/runningconfig.py
+++ b/ncdiff/src/yang/ncdiff/runningconfig.py
@@ -1,58 +1,210 @@
 import re
 import logging
-from copy import deepcopy
-from collections import OrderedDict
+
 
 # create a logger for this module
 logger = logging.getLogger(__name__)
 
+# Some no commands are not forgiving, e.g., "no license boot level
+# network-advantage addon dna-advantage" is rejected, but "no license boot
+# level" is acceptable. These cases are listed in SHORT_NO_COMMANDS.
+SHORT_NO_COMMANDS = [
+    'no license boot level ',
+    'no transport input ',
+    'no transport output ',
+    'no ip address ',
+    'no ipv6 address ',
+]
+
+# Some commands are orderless, e.g., "show running-config" output could be:
+# aaa authentication login admin-con group tacacs+ local
+# aaa authentication login admin-vty group tacacs+ local
+#
+# Or in other times it is displayed in a different order:
+# aaa authentication login admin-vty group tacacs+ local
+# aaa authentication login admin-con group tacacs+ local
+#
+# Since "aaa authentication login" is orderless at the the global config
+# level, regexp and depth are defined as "^ *aaa authentication login " and 0.
+#
+# In another example, "show running-config" output could be:
+# flow monitor meraki_monitor
+#   exporter meraki_exporter
+#   exporter customer_exporter
+#   record meraki_record
+#
+# Or in other times it is displayed in a different order:
+# flow monitor meraki_monitor
+#   exporter customer_exporter
+#   exporter meraki_exporter
+#   record meraki_record
+#
+# Here the config "exporter" at the second level is orderless. so regexp and
+# depth are defined as "^ *exporter " and 1.
+ORDERLESS_COMMANDS = [
+    (re.compile(r'^ *aaa authentication login '), 0),
+    (re.compile(r'^ *logging host '), 0),
+    (re.compile(r'^ *flow monitor '), 0),
+    (re.compile(r'^ *service-template '), 0),
+    (re.compile(r'^ *aaa group server radius '), 0),
+    (re.compile(r'^ *flow exporter '), 0),
+    (re.compile(r'^ *exporter '), 1),
+    (re.compile(r'^ *username '), 0),
+    (re.compile(r'^ *flow record '), 0),
+    (re.compile(r'^ *match ipv4 '), 1),
+    (re.compile(r'^ *match ipv6 '), 1),
+    (re.compile(r'^ *collect connection '), 1),
+]
+
+# Some commands can be overwritten without a no command. For example, changing
+# from:
+# username admin privilege 15 password 0 admin
+# to:
+# username admin privilege 15 password 7 15130F010D24
+# There is no need to send a no command before sending the second line.
+OVERWRITABLE_COMMANDS = [
+    re.compile(r'^ *username \S+ privilege [0-9]+ password '),
+    re.compile(r'^ *password '),
+    re.compile(r'^ *description '),
+    re.compile(r'^ *ip address( |$)'),
+    re.compile(r'^ *ipv6 address( |$)'),
+]
+
+# Some commands look like a parent-child relation but actually they are
+# siblings. One example is two lines of client config below:
+# aaa server radius proxy
+#  client 10.0.0.0 255.0.0.0
+#   !
+#   client 11.0.0.0 255.0.0.0
+#   !
+SIBLING_CAMMANDS = [
+    re.compile(r'^ *client '),
+]
+
+# As for the client command above, its children does not have indentation:
+# aaa server radius proxy
+#  client 10.0.0.0 255.0.0.0
+#   timer disconnect acct-stop 23
+#   !
+#   client 11.0.0.0 255.0.0.0
+#   accounting port 34
+#   timer disconnect acct-stop 22
+#   !
+#   client 12.0.0.0 255.0.0.0
+#   accounting port 56
+#   !
+# Logically, "accounting port 34" and "timer disconnect acct-stop 22" are
+# children of "client 11.0.0.0 255.0.0.0", but there is no indentation in the
+# "show running-config" output. The sub-section is indicated by the expression
+# mark.
+MISSING_INDENT_COMMANDS = [
+    r'^ *client ',
+]
 
-class DictDiff(object):
-    '''DictDiff
 
-    Abstraction of diff between two dictionaries. If all keys and their values
-    are same, two dictionaries are considered to be same. This class is intended
-    to be used by class RunningConfigDiff.
+class ListDiff(object):
+    '''ListDiff
+
+    Abstraction of diff between two lists. Each item in the list is a tuple.
+    If all tuples are same, two lists are considered to be same. This class is
+    intended to be used by class RunningConfigDiff.
 
     Attributes
     ----------
-    diff : `tuple`
-        A tuple with two elements. First one is a dictionary that contains
-        content in dict1 but not in dict2. Second one is a dictionary that
-        contains content in dict2 but not in dict1.
+    diff : `list`
+        A list whose items are tuples. The first item in the tuple is
+        the config line and the second item is its next level config. If a
+        config line does not have next level config, the second item value is
+        None. Otherwise, the value is a list of tuples. The third item in the
+        tuple has three possible values: '+' means the config is added; '-'
+        means it is deleted; '' means it remiains unchnaged but for some reason
+        it is needed (e.g., as a position reference).
     '''
 
-    def __init__(self, dict1, dict2):
-        self.dict1 = dict1
-        self.dict2 = dict2
+    def __init__(self, list1, list2):
+        self.list1 = list1
+        self.list2 = list2
 
     @property
     def diff(self):
-        diff1 = deepcopy(self.dict1)
-        diff2 = deepcopy(self.dict2)
-        self.simplify_single_dict(diff1, diff2)
-        return (diff1, diff2)
+        return self.compare(self.list1, self.list2)
 
     @staticmethod
-    def simplify_single_dict(dict1, dict2):
-        common_keys = set(dict1.keys()) & set(dict2.keys())
-        for key in common_keys:
-            if dict1[key] == dict2[key]:
-                del dict1[key]
-                del dict2[key]
+    def compare(list1, list2):
+        diff_1 = []
+        key_list_1 = [i[0] for i in list1]
+        key_list_2 = [i[0] for i in list2]
+        common_key_list = []
+        previous_index_1 = previous_index_2 = 0
+        for k2 in key_list_2:
+            if k2 in key_list_1[previous_index_1:]:
+                common_key_list.append(k2)
+                previous_index_1 += key_list_1[previous_index_1:].index(k2) + 1
+                previous_index_2 += key_list_2[previous_index_2:].index(k2) + 1
+        previous_index_1 = previous_index_2 = 0
+        for key in common_key_list:
+            current_index_1 = previous_index_1 + \
+                key_list_1[previous_index_1:].index(key)
+            current_index_2 = previous_index_2 + \
+                key_list_2[previous_index_2:].index(key)
+
+            # Stuff in list1 before a common key but not in list2
+            for k, v, i in list1[previous_index_1:current_index_1]:
+                diff_1.append((k, v, '-'))
+
+            # Stuff in list2 before a common key but not in list1
+            for k, v, i in list2[previous_index_2:current_index_2]:
+                diff_1.append((k, v, '+'))
+
+            # Stuff of the common key itself
+            if list1[current_index_1][1] == list2[current_index_2][1]:
+                if list1[current_index_1][1] is None:
+                    diff_1.append((key, None, '?'))
+                else:
+                    diff_1.append((key, [
+                        (k, v, '')
+                        for k, v, i in list1[current_index_1][1]], '?'))
             else:
-                child1_is_dict = isinstance(dict1[key], dict)
-                child2_is_dict = isinstance(dict2[key], dict)
-                if child1_is_dict and child2_is_dict:
-                    DictDiff.simplify_single_dict(dict1[key], dict2[key])
-                    if not dict1[key]:
-                        del dict1[key]
-                    if not dict2[key]:
-                        del dict2[key]
-                elif child1_is_dict and not child2_is_dict:
-                    del dict2[key]
-                elif child2_is_dict and not child1_is_dict:
-                    del dict1[key]
+                if (
+                    list1[current_index_1][1] is not None and
+                    list2[current_index_2][1] is not None
+                ):
+                    diff_1.append((key, ListDiff.compare(
+                        list1[current_index_1][1],
+                        list2[current_index_2][1],
+                    ), ''))
+                elif list1[current_index_1][1] is not None:
+                    diff_1.append((key, [
+                        (k, v, '-')
+                        for k, v, i in list1[current_index_1][1]], ''))
+                elif list2[current_index_2][1] is not None:
+                    diff_1.append((key, [
+                        (k, v, '+')
+                        for k, v, i in list2[current_index_2][1]], ''))
+
+            previous_index_1 = current_index_1 + 1
+            previous_index_2 = current_index_2 + 1
+
+        # Stuff after all common keys
+        for k, v, i in list1[previous_index_1:]:
+            diff_1.append((k, v, '-'))
+        for k, v, i in list2[previous_index_2:]:
+            diff_1.append((k, v, '+'))
+
+        # Cleanup
+        diff_2 = []
+        keys_before = set()
+        for index, item in enumerate(diff_1):
+            key, value, info = item
+            if info == '?':
+                keys_after = set([k for k, v, i in diff_1[index+1:]])
+                if keys_before & keys_after:
+                    diff_2.append((key, value, ''))
+            else:
+                diff_2.append((key, value, info))
+            keys_before.add(key)
+
+        return diff_2
 
 
 class RunningConfigDiff(object):
@@ -70,10 +222,19 @@ class RunningConfigDiff(object):
     running2 : `str`
         Second Cisco running-config.
 
-    diff : `tuple`
-        A tuple with two elements. First one is the running-config in running1
-        but not in running2. Second one is the running-config in running2 but
-        not in running1.
+    diff : `list`
+        A list from class ListDiff attribute diff, representing changes from
+        running1 to running2.
+
+    diff_reverse : `list`
+        A list from class ListDiff attribute diff, representing changes from
+        running2 to running1.
+
+    cli : `str`
+        CLIs that transits from running1 to running2.
+
+    cli_reverse : `str`
+        CLIs that transits from running2 to running1.
     '''
 
     def __init__(self, running1, running2):
@@ -83,18 +244,14 @@ def __init__(self, running1, running2):
 
         self.running1 = running1
         self.running2 = running2
+        self._diff_list = None
+        self._diff_list_reverse = None
 
     def __bool__(self):
-        diff1, diff2 = self.diff
-        if diff1 or diff2:
-            return True
-        else:
-            return False
+        return bool(self.diff)
 
     def __str__(self):
-        diff1, diff2 = self.diff
-        return '\n'.join(['-   ' + l for l in diff1.splitlines()] +
-                         ['+   ' + l for l in diff2.splitlines()])
+        return self.list2config(self.diff, diff_type='')
 
     def __eq__(self, other):
         if str(self) == str(other):
@@ -104,29 +261,59 @@ def __eq__(self, other):
 
     @property
     def diff(self):
-        dict1 = self.running2dict(self.running1)
-        dict2 = self.running2dict(self.running2)
-        diff_dict = DictDiff(dict1, dict2)
-        diff1, diff2 = diff_dict.diff
-        return (self.dict2running(diff1), self.dict2running(diff2))
-
-    def running2dict(self, str_in):
-        str_in = str_in.replace('exit-address-family', ' exit-address-family')
-        dict_ret = OrderedDict()
-        dict_ret['running-config'] = self.config2dict(str_in)
-        return dict_ret
-
-    def dict2running(self, dict_in):
-        if dict_in:
-            return self.dict2config(dict_in['running-config'])
+        return self.get_diff(reverse=False)
+
+    @property
+    def diff_reverse(self):
+        return self.get_diff(reverse=True)
+
+    @property
+    def cli(self):
+        return self.get_cli(reverse=False)
+
+    @property
+    def cli_reverse(self):
+        return self.get_cli(reverse=True)
+
+    def get_diff(self, reverse=False):
+        if self._diff_list is None:
+            list1, list2 = self.running2list(self.running1, self.running2)
+            self.handle_sibling_cammands(list1)
+            self.handle_sibling_cammands(list2)
+            self._diff_list = ListDiff(list1, list2).diff
+            self._diff_list_reverse = ListDiff(list2, list1).diff
+        diff_list = self._diff_list_reverse if reverse else self._diff_list
+        return diff_list if diff_list else None
+
+    def get_cli(self, reverse=False):
+        diff_list = self.get_diff(reverse=reverse)
+        if diff_list:
+            positive_str, negative_str = self.list2cli(diff_list)
+            if positive_str:
+                if negative_str:
+                    return negative_str + '\n!\n' + positive_str
+                else:
+                    return positive_str
+            else:
+                return negative_str
         else:
             return ''
 
-    def config2dict(self, str_in):
-        dict_ret = OrderedDict()
+    def running2list(self, str_in_1, str_in_2):
+        str_in_1 = str_in_1.replace('exit-address-family',
+                                    ' exit-address-family')
+        str_in_2 = str_in_2.replace('exit-address-family',
+                                    ' exit-address-family')
+        list_1 = self.config2list(str_in_1)
+        list_2 = self.config2list(str_in_2)
+        return self.handle_orderless(list_1, list_2, 0)
+
+    def config2list(self, str_in):
+        list_ret = []
         last_line = ''
         last_section = ''
         last_indentation = 0
+        missing_indent = False
         for line in str_in.splitlines():
             if len(line.strip()) > 22 and \
                line[:22] == 'Building configuration':
@@ -136,37 +323,317 @@ def config2dict(self, str_in):
                 continue
             if len(line.strip()) == 0:
                 continue
-            if re.search('^ *!', line):
+            if missing_indent and line.rstrip() == '!':
+                if last_line:
+                    if last_indentation > 0:
+                        list_ret.append((
+                            last_line, self.config2list(last_section), ''))
+                    else:
+                        list_ret.append((last_line, None, ''))
+                last_line = ''
+                last_section = ''
+                last_indentation = 0
+                missing_indent = False
+                continue
+            if re.search('^ *!', line) or re.search('^ *%', line):
                 continue
             if line[0] == ' ':
+                current_indentation = len(re.search('^ *', line).group(0))
                 if last_indentation == 0:
-                    last_indentation = len(re.search('^ *', line).group(0))
-                last_section += line[last_indentation:] + '\n'
+                    last_indentation = current_indentation
+
+                # There might be special cases. For example, the following
+                # running-config:
+                # ip dhcp class CLASS1
+                #    relay agent information
+                #  relay-information hex 01040101030402020102
+                # should be considered as:
+                # ip dhcp class CLASS1
+                #    relay agent information
+                #     relay-information hex 01040101030402020102
+                if current_indentation < last_indentation:
+                    last_section += ' ' * (last_indentation + 1) + \
+                        line[current_indentation:] + '\n'
+                else:
+                    last_section += line[last_indentation:] + '\n'
             else:
+                if any(
+                    [re.search(regx, line) for regx in MISSING_INDENT_COMMANDS]
+                ):
+                    missing_indent = True
+                elif missing_indent:
+                    current_indentation = 1
+                    if last_indentation == 0:
+                        last_indentation = current_indentation
+                    last_section += line + '\n'
+                    continue
                 if last_line:
                     if last_indentation > 0:
-                        dict_ret[last_line] = self.config2dict(last_section)
+                        list_ret.append((
+                            last_line, self.config2list(last_section), ''))
                     else:
-                        dict_ret[last_line] = ''
+                        list_ret.append((last_line, None, ''))
                 last_line = line
                 last_section = ''
                 last_indentation = 0
-        if last_indentation > 0:
-            dict_ret[last_line] = self.config2dict(last_section)
-        else:
-            dict_ret[last_line] = ''
-        return dict_ret
+        if last_line:
+            if last_indentation > 0:
+                list_ret.append((
+                    last_line, self.config2list(last_section), ''))
+            else:
+                list_ret.append((last_line, None, ''))
+        return list_ret
 
-    def dict2config(self, dict_in):
+    def list2config(self, list_in, diff_type=None):
         str_ret = ''
-        for k, v in dict_in.items():
-            str_ret += k + '\n'
-            if type(v) is OrderedDict:
-                str_ret += self.indent(self.dict2config(v))
+        if list_in is None:
+            return str_ret
+        for k, v, i in list_in:
+            if k == '':
+                continue
+            if diff_type == '':
+                local_diff_type = i
+            else:
+                local_diff_type = diff_type
+            if diff_type is None:
+                str_ret += '  ' + k + '\n'
+            else:
+                prefix = local_diff_type if local_diff_type != '' else ' '
+                str_ret += prefix + ' ' + k + '\n'
+            if v is not None:
+                str_ret += self.indent(
+                    self.list2config(v, diff_type=local_diff_type),
+                )
         return str_ret
 
+    def list2cli(self, list_in):
+        if list_in is None:
+            return ''
+        positive_list = []
+        negative_list = []
+        positive_keys = [k for k, v, i in list_in if i == '+' and v is None]
+        for k, v, i in list_in:
+            if k == '':
+                continue
+            if v is None:
+                if i == '+':
+                    self.append_command(k, negative_list, positive_list)
+                elif i == '-':
+                    # In a case that a CLI is updated, no command is not
+                    # needed:
+                    # - service timestamps debug datetime msec
+                    # + service timestamps debug datetime msec localtime
+                    #   show-timezone
+                    key_len = len(k)
+                    matching_positive_keys = [
+                        key for key in positive_keys if key[:key_len] == k]
+                    if not matching_positive_keys:
+                        self.append_command('no ' + k,
+                                            negative_list, positive_list)
+            else:
+                if i == '':
+                    positive_str, negative_str = self.list2cli(v)
+                    if positive_str:
+                        positive_list.append(
+                            k + '\n' + self.indent(positive_str).rstrip())
+                    if negative_str:
+                        negative_list.append(
+                            k + '\n' + self.indent(negative_str).rstrip())
+                if i == '+':
+                    positive_str = self.list2config(v, diff_type=None).rstrip()
+                    if positive_str:
+                        positive_list.append(k + '\n' + positive_str)
+                    else:
+                        positive_list.append(k)
+                elif i == '-':
+                    self.append_command('no ' + k,
+                                        negative_list, positive_list)
+
+        # Handle overwritable commands
+        idx_positive_dict = {}
+        idx_positive_list = []
+        for regx in OVERWRITABLE_COMMANDS:
+            for idx_positive, cmd in enumerate(positive_list):
+                m = re.search(regx, cmd)
+                if m:
+
+                    # Remove matching negative CLIs
+                    exact_cmd = 'no ' + m.group(0).lstrip()
+                    len_exact_cmd = len(exact_cmd)
+                    for idx_negative, line in enumerate(negative_list):
+                        cmd_line = line.lstrip()
+                        if (
+                            len(cmd_line) >= len_exact_cmd and
+                            cmd_line[:len_exact_cmd] == exact_cmd
+                        ):
+                            break
+                    else:
+                        idx_negative = None
+                    if idx_negative is not None:
+                        del negative_list[idx_negative]
+
+                    # Overwrite previous matching positive CLIs
+                    exact_cmd = m.group(0).strip()
+                    if exact_cmd in idx_positive_dict:
+                        idx_positive_list.append(idx_positive_dict[exact_cmd])
+                    idx_positive_dict[exact_cmd] = idx_positive
+
+        if idx_positive_list:
+            idx_positive_list.sort()
+            for idx in reversed(idx_positive_list):
+                del positive_list[idx]
+
+        # Handle duplicate commands
+        # Some commands their positive and negative lines are both appeared in
+        # the "show running-config" output, e.g., "platform punt-keepalive
+        # disable-kernel-core" and "no platform punt-keepalive
+        # disable-kernel-core" are both visible. This treatment is required to
+        # avoid two lines of the same CLI.
+        for cmd_list in [positive_list, negative_list]:
+            self.remove_duplicate_cammands(cmd_list)
+
+        return '\n'.join(positive_list), '\n'.join(reversed(negative_list))
+
+    @staticmethod
+    def append_command(cmd, negative_list, positive_list):
+        cmd, is_no_cmd = RunningConfigDiff.handle_command(cmd)
+        if is_no_cmd:
+            negative_list.append(cmd)
+        else:
+            positive_list.append(cmd)
+
+    @staticmethod
+    def handle_command(cmd):
+        if cmd[:6] == 'no no ':
+            return cmd[6:].strip(), False
+        for short_cmd in SHORT_NO_COMMANDS:
+            if cmd[:len(short_cmd)] == short_cmd:
+                return short_cmd.strip(), True
+        if cmd[:3] == 'no ':
+            return cmd.strip(), True
+        else:
+            return cmd.strip(), False
+
+    @staticmethod
+    def handle_orderless(list1, list2, depth):
+        # Find common lines that are orderless
+        lines1 = [i[0] for i in list1]
+        matches = {}
+        for idx, item in enumerate(list2):
+            result, match_type = RunningConfigDiff.match_orderless(item[0],
+                                                                   depth)
+            if result and item[0] in lines1:
+                matches[item[0]] = match_type, idx
+
+        # Romove common lines from list1 and save them in removed_items
+        type_start_idx = {}
+        to_be_removed = []
+        for idx, line in enumerate(lines1):
+            if line in matches:
+                if matches[line][0] not in type_start_idx:
+                    type_start_idx[matches[line][0]] = idx
+                to_be_removed.append(idx)
+        removed_items = {}
+        for idx in reversed(to_be_removed):
+            removed_items[list1[idx][0]] = list1[idx]
+            del list1[idx]
+
+        # Find common lines
+        lines1 = [i[0] for i in list1]
+        lines2 = [i[0] for i in list2]
+        common_lines = []
+        previous_idx_1 = previous_idx_2 = 0
+        for line2 in lines2:
+            if line2 in lines1[previous_idx_1:]:
+                previous_idx_1 += lines1[previous_idx_1:].index(line2) + 1
+                previous_idx_2 += lines2[previous_idx_2:].index(line2) + 1
+                common_lines.append((previous_idx_1 - 1, previous_idx_2 - 1))
+
+        # Insert common lines back to list1
+        offset_idx_1 = 0
+        previous_idx_2 = 0
+        for line, (line_type, list2_idx) in matches.items():
+            common_idx_1 = 0
+            if len(common_lines) > 0 and list2_idx > common_lines[0][1]:
+                for i, j in common_lines:
+                    # if previous_idx_2 <= j and j < list2_idx:
+                    if j < list2_idx:
+                        common_idx_1 = i + 1
+                        previous_idx_2 = j
+                    if j > list2_idx:
+                        break
+            start_idx = max(common_idx_1 + offset_idx_1,
+                            type_start_idx[line_type])
+            list1.insert(start_idx, removed_items[line])
+            offset_idx_1 = start_idx - common_idx_1 + 1
+
+        # Find common lines that have children
+        lines1 = {item[0]: idx for idx, item in enumerate(list1)
+                  if isinstance(item[1], list) and len(item[1]) > 0}
+        lines2 = {item[0]: idx for idx, item in enumerate(list2)
+                  if isinstance(item[1], list) and len(item[1]) > 0}
+        for line in set(lines1.keys()) & set(lines2.keys()):
+            RunningConfigDiff.handle_orderless(list1[lines1[line]][1],
+                                               list2[lines2[line]][1],
+                                               depth + 1)
+
+        return list1, list2
+
+    @staticmethod
+    def match_orderless(line, current_depth):
+        for idx, (regx, depth) in enumerate(ORDERLESS_COMMANDS):
+            if depth == current_depth and re.search(regx, line):
+                return True, idx
+        return False, None
+
+    @staticmethod
+    def remove_duplicate_cammands(config_list):
+        indexes = []
+        commands = set()
+        for idx, line in enumerate(config_list):
+            if line in commands:
+                indexes.append(idx)
+            else:
+                commands.add(line)
+        if indexes:
+            for idx in reversed(indexes):
+                del config_list[idx]
+
+    @staticmethod
+    def handle_sibling_cammands(config_list):
+        length = len(config_list)
+        lines_inserted = 0
+        for i in range(length):
+            idx = i + lines_inserted
+            tup = config_list[idx]
+            for regx in SIBLING_CAMMANDS:
+                if re.search(regx, tup[0]) and tup[1] is not None:
+                    siblings = []
+                    indexes = []
+                    for idx_c, tup_c in enumerate(tup[1]):
+                        if re.search(regx, tup_c[0]):
+                            indexes.append(idx_c)
+                    for idx_c in reversed(indexes):
+                        siblings.append(tup[1][idx_c])
+                        del tup[1][idx_c]
+                    if not tup[1]:
+                        tup = config_list[idx] = (tup[0], None, tup[2])
+                    j = 1
+                    for sibling in reversed(siblings):
+                        config_list.insert(i+j, sibling)
+                        j += 1
+                    break
+            if tup[1] is not None:
+                RunningConfigDiff.handle_sibling_cammands(tup[1])
+
     def indent(self, str_in):
         str_ret = ''
         for line in str_in.splitlines():
-            str_ret += ' ' + line + '\n'
-        return str_ret
+            if line:
+                if line[0] in '-+':
+                    diff_type = line[0]
+                    line = line[1:]
+                    str_ret += diff_type + '  ' + line + '\n'
+                else:
+                    str_ret += '  ' + line + '\n'
+        return str_ret
\ No newline at end of file


Test patch:
diff --git a/ncdiff/src/yang/ncdiff/tests/test_ncdiff.py b/ncdiff/src/yang/ncdiff/tests/test_ncdiff.py
index 0432435..03fdcad 100755
--- a/ncdiff/src/yang/ncdiff/tests/test_ncdiff.py
+++ b/ncdiff/src/yang/ncdiff/tests/test_ncdiff.py
@@ -3,18 +3,77 @@
 
 import unittest
 from lxml import etree
-from yang.ncdiff import *
-from yang.ncdiff.errors import *
+from yang.ncdiff.device import ModelDevice
+from yang.ncdiff.config import Config, ConfigDelta
+from yang.ncdiff.errors import ConfigDeltaError
+from yang.ncdiff.composer import Tag
 from yang.connector import Netconf
 from pyats.topology import loader
 
+from ncclient import operations, xml_
+from ncclient.manager import Manager
+from ncclient.devices.default import DefaultDeviceHandler
+
+nc_url = xml_.BASE_NS_1_0
+yang_url = 'urn:ietf:params:xml:ns:yang:1'
+operation_tag = '{' + nc_url + '}operation'
+insert_tag = '{' + yang_url + '}insert'
+value_tag = '{' + yang_url + '}value'
+key_tag = '{' + yang_url + '}key'
 
 def my_execute(*args, **kwargs):
+    if args[1] is operations.retrieve.GetSchema and len(args) > 2:
+        tests_dir = path.dirname(path.abspath(__file__))
+        filename = args[2] + ".yang"
+        with open(path.join(tests_dir, "yang", filename), "r") as f:
+            text = f.read()
+        reply_xml = """
+<rpc-reply message-id="103" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <data xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"></data>
+</rpc-reply>
+            """
+        reply_ele = xml_.to_ele(reply_xml)
+        data = reply_ele.find(
+            'mon:data',
+            namespaces={
+                'mon': 'urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring',
+            }
+        )
+        data.text = text
+        reply_xml = xml_.to_xml(reply_ele)
+        reply = operations.rpc.RPCReply(reply_xml)
+        reply.parse()
+        reply.data = text
+        return reply
+    elif (
+        args[1] is operations.retrieve.Get and (
+            'ietf-netconf-monitoring' in kwargs['filter'] or
+            'ietf-yang-library' in kwargs['filter']
+        )
+    ):
+        reply_xml = """
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <rpc-error>
+    <error-type>rpc</error-type>
+    <error-tag>missing-attribute</error-tag>
+    <error-severity>error</error-severity>
+    <error-info>
+      <bad-attribute>message-id</bad-attribute>
+      <bad-element>rpc</bad-element>
+    </error-info>
+  </rpc-error>
+</rpc-reply>
+        """
+        reply = operations.rpc.RPCReply(reply_xml)
+        reply.parse()
+        return reply
+
     reply_xml = """<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><data><network-instances xmlns="http://openconfig.net/yang/network-instance"><network-instance><name>Mgmt-intf</name><config><name>Mgmt-intf</name><type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:L3VRF</type><enabled-address-families xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</enabled-address-families><enabled-address-families xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</enabled-address-families></config><interfaces><interface><id>GigabitEthernet0</id><config><id>GigabitEthernet0</id><interface>GigabitEthernet0</interface></config></interface></interfaces><tables><table><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family><config><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family></config></table><table><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family><config><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family></config></table><table><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family><config><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family></config></table><table><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family><config><protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family></config></table></tables><protocols><protocol><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</identifier><name>100</name><config><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</identifier><name>100</name></config><bgp><global><graceful-restart><config><enabled>false</enabled></config></graceful-restart><route-selection-options><config><always-compare-med>false</always-compare-med></config></route-selection-options></global></bgp></protocol><protocol><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</identifier><name>DEFAULT</name><config><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</identifier><name>DEFAULT</name></config><static-routes><static><prefix>0.0.0.0/0</prefix><config><prefix>0.0.0.0/0</prefix></config><next-hops><next-hop><index>5.28.0.1</index><config><index>5.28.0.1</index><next-hop>5.28.0.1</next-hop></config></next-hop></next-hops></static></static-routes></protocol><protocol><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</identifier><name>DEFAULT</name><config><identifier xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</identifier><name>DEFAULT</name></config></protocol></protocols></network-instance></network-instances></data></rpc-reply>"""
     reply = operations.rpc.RPCReply(reply_xml)
     reply.parse()
     return reply
 
+
 Netconf.execute = my_execute
 
 
@@ -58,7 +117,6 @@ def add_listener(self, listener):
     def is_alive(self):
         return True
 
-
 server_capabilities = [
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-UNIFIED-FIREWALL-MIB?module=CISCO-UNIFIED-FIREWALL-MIB&revision=2005-09-22',
 'urn:ietf:params:xml:ns:yang:smiv2:IANA-RTPROTO-MIB?module=IANA-RTPROTO-MIB&revision=2000-09-26',
@@ -67,7 +125,6 @@ def is_alive(self):
 'urn:ietf:params:xml:ns:yang:smiv2:INET-ADDRESS-MIB?module=INET-ADDRESS-MIB&revision=2005-02-04',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-arp?module=Cisco-IOS-XE-arp&revision=2017-11-07',
 'urn:ietf:params:xml:ns:yang:smiv2:EtherLike-MIB?module=EtherLike-MIB&revision=2003-09-19',
-'urn:ietf:params:xml:ns:yang:smiv2:RFC-1212?module=RFC-1212',
 'urn:ietf:params:xml:ns:yang:ietf-diffserv-target?module=ietf-diffserv-target&revision=2015-04-07&features=target-inline-policy-config',
 'urn:ietf:params:xml:ns:yang:smiv2:RMON2-MIB?module=RMON2-MIB&revision=1996-05-27',
 'http://cisco.com/ns/yang/cisco-xe-bgp-policy-deviation?module=cisco-xe-openconfig-bgp-policy-deviation&revision=2017-07-24',
@@ -75,7 +132,6 @@ def is_alive(self):
 'http://cisco.com/ns/yang/Cisco-IOS-XE-spanning-tree?module=Cisco-IOS-XE-spanning-tree&revision=2017-11-27',
 'http://openconfig.net/yang/rib/bgp-types?module=openconfig-rib-bgp-types&revision=2016-04-11',
 'http://cisco.com/ns/yang/cisco-xe-openconfig-acl-ext?module=cisco-xe-openconfig-acl-ext&revision=2017-03-30',
-'urn:ietf:params:xml:ns:yang:smiv2:RFC1315-MIB?module=RFC1315-MIB',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-ospfv3?module=Cisco-IOS-XE-ospfv3&revision=2017-11-27',
 'http://cisco.com/ns/yang/cisco-xe-routing-policy-deviation?module=cisco-xe-openconfig-routing-policy-deviation&revision=2017-03-30',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-device-hardware-oper?module=Cisco-IOS-XE-device-hardware-oper&revision=2017-11-01',
@@ -87,10 +143,8 @@ def is_alive(self):
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-SIP-UA-MIB?module=CISCO-SIP-UA-MIB&revision=2004-02-19',
 'urn:ietf:params:xml:ns:netconf:base:1.0?module=ietf-netconf&revision=2011-06-01',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-isis?module=Cisco-IOS-XE-isis&revision=2017-11-27',
-'http://cisco.com/ns/yang/cisco-xe-openconfig-spanning-tree-deviation?module=cisco-xe-openconfig-spanning-tree-deviation&revision=2017-08-21',
 'urn:ietf:params:xml:ns:yang:smiv2:ENTITY-MIB?module=ENTITY-MIB&revision=2005-08-10',
 'urn:ietf:params:xml:ns:yang:smiv2:RMON-MIB?module=RMON-MIB&revision=2000-05-11',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-ptp?module=Cisco-IOS-XE-ptp&revision=2017-09-19',
 'urn:ietf:params:xml:ns:yang:smiv2:IPMROUTE-STD-MIB?module=IPMROUTE-STD-MIB&revision=2000-09-22', 'urn:ietf:params:xml:ns:yang:smiv2:HCNUM-TC?module=HCNUM-TC&revision=2000-06-08',
 'urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all-tagged',
 'urn:ietf:params:xml:ns:yang:smiv2:BRIDGE-MIB?module=BRIDGE-MIB&revision=2005-09-19',
@@ -102,7 +156,6 @@ def is_alive(self):
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RF-MIB?module=CISCO-RF-MIB&revision=2005-09-01',
 'urn:ietf:params:xml:ns:yang:smiv2:DIFFSERV-DSCP-TC?module=DIFFSERV-DSCP-TC&revision=2002-05-09',
 'urn:ietf:params:xml:ns:yang:ietf-ipv6-unicast-routing?module=ietf-ipv6-unicast-routing&revision=2015-05-25&deviations=cisco-xe-ietf-ipv6-unicast-routing-deviation',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-udld?module=Cisco-IOS-XE-udld&revision=2017-11-27',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSLA-ECHO-MIB?module=CISCO-IPSLA-ECHO-MIB&revision=2007-08-16',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-CBP-TC-MIB?module=CISCO-CBP-TC-MIB&revision=2008-06-24',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-environment-oper?module=Cisco-IOS-XE-environment-oper&revision=2017-10-23',
@@ -136,32 +189,26 @@ def is_alive(self):
 'http://cisco.com/ns/yang/Cisco-IOS-XE-nhrp?module=Cisco-IOS-XE-nhrp&revision=2017-02-07',
 'http://cisco.com/yang/cisco-odm?module=cisco-odm&revision=2017-04-25',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-AAA-SESSION-MIB?module=CISCO-AAA-SESSION-MIB&revision=2006-03-21',
-'http://openconfig.net/yang/platform?module=openconfig-platform&revision=2016-12-22&deviations=cisco-xe-openconfig-platform-deviation',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-TC?module=CISCO-TC&revision=2011-11-11',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-ntp-oper?module=Cisco-IOS-XE-ntp-oper&revision=2017-11-01',
 'http://cisco.com/yang/cisco-self-mgmt?module=cisco-self-mgmt&revision=2016-05-14',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-FLASH-MIB?module=CISCO-FLASH-MIB&revision=2013-08-06',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-switch?module=Cisco-IOS-XE-switch&revision=2017-11-27&deviations=Cisco-IOS-XE-switch-deviation',
-'http://cisco.com/ns/yang/cisco-xe-openconfig-platform-deviation?module=cisco-xe-openconfig-platform-deviation&revision=2017-09-01',
 'http://cisco.com/yang/cisco-dmi-aaa?module=cisco-dmi-aaa&revision=2017-05-17',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-VLAN-IFTABLE-RELATIONSHIP-MIB?module=CISCO-VLAN-IFTABLE-RELATIONSHIP-MIB&revision=2013-07-15',
 'urn:ietf:params:netconf:capability:yang-library:1.0?revision=2016-06-21&module-set-id=f47eea7ecf2253450bb07d0f78ca7104',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-eem?module=Cisco-IOS-XE-eem&revision=2017-11-20',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-cfm-oper?module=Cisco-IOS-XE-cfm-oper&revision=2017-06-06',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-avb?module=Cisco-IOS-XE-avb&revision=2017-09-19',
 'http://openconfig.net/yang/oc-mapping-acl?module=oc-mapping-acl&revision=2017-05-26',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-aaa-oper?module=Cisco-IOS-XE-aaa-oper&revision=2017-11-01',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-CONFIG-COPY-MIB?module=CISCO-CONFIG-COPY-MIB&revision=2005-04-06',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-CBP-TARGET-TC-MIB?module=CISCO-CBP-TARGET-TC-MIB&revision=2006-03-24',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-DOT3-OAM-MIB?module=CISCO-DOT3-OAM-MIB&revision=2006-05-31',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-switch-deviation?module=Cisco-IOS-XE-switch-deviation&revision=2016-12-01',
 'http://tail-f.com/yang/netconf-monitoring?module=tailf-netconf-monitoring&revision=2016-11-24',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-VTP-MIB?module=CISCO-VTP-MIB&revision=2013-10-14',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-BGP4-MIB?module=CISCO-BGP4-MIB&revision=2010-09-30',
 'http://openconfig.net/yang/network-instance-types?module=openconfig-network-instance-types&revision=2016-12-15',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-checkpoint-archive-oper?module=Cisco-IOS-XE-checkpoint-archive-oper&revision=2017-04-01', 'urn:ietf:params:netconf:capability:xpath:1.0',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-sanet?module=Cisco-IOS-XE-sanet&revision=2017-11-27',
-'urn:ietf:params:xml:ns:yang:ietf-interfaces-ext?module=ietf-interfaces-ext',
 'urn:ietf:params:xml:ns:yang:ietf-interfaces?module=ietf-interfaces&revision=2014-05-08&features=pre-provisioning,if-mib,arbitrary-names',
 'urn:ietf:params:netconf:capability:writable-running:1.0',
 'http://openconfig.net/yang/bgp-policy?module=openconfig-bgp-policy&revision=2016-06-21&deviations=cisco-xe-openconfig-bgp-policy-deviation',
@@ -180,7 +227,6 @@ def is_alive(self):
 'urn:ietf:params:xml:ns:yang:ietf-diffserv-policy?module=ietf-diffserv-policy&revision=2015-04-07&features=policy-template-support,hierarchial-policy-support',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-igmp?module=Cisco-IOS-XE-igmp&revision=2017-11-27',
 '\n        urn:ietf:params:netconf:capability:notification:1.1\n      ',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-vstack?module=Cisco-IOS-XE-vstack&revision=2017-02-07',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-mpls-ldp?module=Cisco-IOS-XE-mpls-ldp&revision=2017-02-07&features=mpls-ldp-nsr,mpls-ldp-iccp,mpls-ldp-extended,mpls-ldp-bgp',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IMAGE-MIB?module=CISCO-IMAGE-MIB&revision=1995-08-15',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-ppp?module=Cisco-IOS-XE-ppp&revision=2017-11-29',
@@ -204,14 +250,11 @@ def is_alive(self):
 'http://cisco.com/ns/yang/Cisco-IOS-XE-mdt-cfg?module=Cisco-IOS-XE-mdt-cfg&revision=2017-09-20',
 'urn:ietf:params:xml:ns:yang:ietf-event-notifications?module=ietf-event-notifications&revision=2016-10-27&features=json,configured-subscriptions&deviations=cisco-xe-ietf-event-notifications-deviation,cisco-xe-ietf-yang-push-deviation',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-vlan?module=Cisco-IOS-XE-vlan&revision=2017-10-02',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-device-sensor?module=Cisco-IOS-XE-device-sensor&revision=2017-02-07',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ST-TC?module=CISCO-ST-TC&revision=2012-08-08',
 'http://openconfig.net/yang/cisco-xe-openconfig-interfaces-deviation?module=cisco-xe-openconfig-interfaces-deviation&revision=2017-10-30',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-route-map?module=Cisco-IOS-XE-route-map&revision=2017-07-27',
-'http://cisco.com/ns/genet/genet-state?module=genet-state',
 'urn:ietf:params:xml:ns:yang:ietf-ipv4-unicast-routing?module=ietf-ipv4-unicast-routing&revision=2015-05-25&deviations=cisco-xe-ietf-ipv4-unicast-routing-deviation',
 'http://openconfig.net/yang/aaa?module=openconfig-aaa&revision=2017-09-18',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-coap?module=Cisco-IOS-XE-coap&revision=2017-02-07',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-acl?module=Cisco-IOS-XE-acl&revision=2017-08-01',
 'urn:ietf:params:xml:ns:yang:smiv2:SNMP-TARGET-MIB?module=SNMP-TARGET-MIB&revision=1998-08-04',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-mpls?module=Cisco-IOS-XE-mpls&revision=2017-11-27',
@@ -219,7 +262,6 @@ def is_alive(self):
 'http://openconfig.net/yang/lldp?module=openconfig-lldp&revision=2016-05-16',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-DATA-COLLECTION-MIB?module=CISCO-DATA-COLLECTION-MIB&revision=2002-10-30',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-flow?module=Cisco-IOS-XE-flow&revision=2017-11-27',
-'http://cisco.com/ns/yang/cisco-xe-openconfig-vlan-deviation?module=cisco-xe-openconfig-vlan-deviation&revision=2017-03-17',
 'urn:ietf:params:xml:ns:yang:smiv2:RFC1213-MIB?module=RFC1213-MIB',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-types?module=Cisco-IOS-XE-types&revision=2017-11-27',
 'urn:ietf:params:xml:ns:yang:ietf-yang-smiv2?module=ietf-yang-smiv2&revision=2012-06-22',
@@ -253,23 +295,20 @@ def is_alive(self):
 'http://cisco.com/ns/yang/Cisco-IOS-XE-bgp-common-oper?module=Cisco-IOS-XE-bgp-common-oper&revision=2017-02-07',
 'urn:ietf:params:xml:ns:yang:cisco-policy?module=cisco-policy&revision=2016-03-30',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-platform?module=Cisco-IOS-XE-platform&revision=2017-06-02',
-'http://cisco.com/ns/yang/cisco-xe-openconfig-vlan-ext?module=cisco-xe-openconfig-vlan-ext&revision=2017-06-14&deviations=cisco-xe-openconfig-vlan-deviation',
 'http://tail-f.com/yang/common-monitoring?module=tailf-common-monitoring&revision=2013-06-14',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-UBE-MIB?module=CISCO-UBE-MIB&revision=2010-11-29',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-power?module=Cisco-IOS-XE-power&revision=2017-11-27',
 'urn:ietf:params:xml:ns:yang:smiv2:SNMPv2-TC?module=SNMPv2-TC',
 'urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2013-07-15',
 'http://openconfig.net/yang/vlan-types?module=openconfig-vlan-types&revision=2016-05-26',
 'urn:ietf:params:xml:ns:yang:smiv2:DRAFT-MSDP-MIB?module=DRAFT-MSDP-MIB&revision=1999-12-16',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-HSRP-EXT-MIB?module=CISCO-HSRP-EXT-MIB&revision=2010-09-02',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RTTMON-TC-MIB?module=CISCO-RTTMON-TC-MIB&revision=2012-05-25',
-'urn:ietf:params:xml:ns:yang:smiv2:RFC1155-SMI?module=RFC1155-SMI',
 'http://tail-f.com/yang/confd-monitoring?module=tailf-confd-monitoring&revision=2013-06-14',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-NETSYNC-MIB?module=CISCO-NETSYNC-MIB&revision=2010-10-15',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-efp-oper?module=Cisco-IOS-XE-efp-oper&revision=2017-02-07',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-STP-EXTENSIONS-MIB?module=CISCO-STP-EXTENSIONS-MIB&revision=2013-03-07',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-features?module=Cisco-IOS-XE-features&revision=2017-02-07&features=vlan,table-map,switching-platform,qos-qsm,private-vlan,parameter-map,l2vpn,l2,dot1x,crypto',
-'http://openconfig.net/yang/vlan?module=openconfig-vlan&revision=2016-05-26&deviations=cisco-xe-openconfig-vlan-deviation',
+'http://openconfig.net/yang/vlan?module=openconfig-vlan&revision=2016-05-26',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IETF-BFD-MIB?module=CISCO-IETF-BFD-MIB&revision=2011-04-16',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-process-memory-oper?module=Cisco-IOS-XE-process-memory-oper&revision=2017-02-07',
 'urn:ietf:params:xml:ns:yang:smiv2:P-BRIDGE-MIB?module=P-BRIDGE-MIB&revision=2006-01-09',
@@ -300,16 +339,15 @@ def is_alive(self):
 'urn:ietf:params:xml:ns:yang:smiv2:TCP-MIB?module=TCP-MIB&revision=2005-02-18', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ENHANCED-MEMPOOL-MIB?module=CISCO-ENHANCED-MEMPOOL-MIB&revision=2008-12-05',
 'http://openconfig.net/yang/oc-mapping-network-instance?module=oc-mapping-network-instance&revision=2017-01-17',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-bgp-route-oper?module=Cisco-IOS-XE-bgp-route-oper&revision=2017-09-25',
-'http://cisco.com/ns/yang/Cisco-IOS-XE-vlan-oper?module=Cisco-IOS-XE-vlan-oper&revision=2017-05-05',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-nd?module=Cisco-IOS-XE-nd&revision=2017-11-27',
 'urn:ietf:params:xml:ns:yang:smiv2:IPV6-FLOW-LABEL-MIB?module=IPV6-FLOW-LABEL-MIB&revision=2003-08-28',
-'http://cisco.com/ns/yang/cisco-xe-openconfig-spanning-tree-ext?module=cisco-xe-openconfig-spanning-tree-ext&revision=2017-10-24',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-rip?module=Cisco-IOS-XE-rip&revision=2017-11-27',
 'urn:ietf:params:netconf:base:1.1',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-LICENSE-MGMT-MIB?module=CISCO-LICENSE-MGMT-MIB&revision=2012-04-19',
 'http://cisco.com/ns/yang/Cisco-IOS-XE-native?module=Cisco-IOS-XE-native&revision=2017-10-24&deviations=Cisco-IOS-XE-switch-deviation',
 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-DYNAMIC-TEMPLATE-MIB?module=CISCO-DYNAMIC-TEMPLATE-MIB&revision=2007-09-06',
-'http://cisco.com/ns/cisco-xe-ietf-routing-deviation?module=cisco-xe-ietf-routing-deviation&revision=2016-07-09', 'http://cisco.com/ns/yang/ios-xe/template?module=Cisco-IOS-XE-template&revision=2017-11-06', 'http://openconfig.net/yang/cisco-xe-openconfig-if-ethernet-deviation?module=cisco-xe-openconfig-if-ethernet-deviation&revision=2017-11-01', 'http://cisco.com/ns/yang/Cisco-IOS-XE-track?module=Cisco-IOS-XE-track&revision=2017-04-28', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-EMBEDDED-EVENT-MGR-MIB?module=CISCO-EMBEDDED-EVENT-MGR-MIB&revision=2006-11-07', 'http://cisco.com/ns/yang/Cisco-IOS-XE-common-types?module=Cisco-IOS-XE-common-types&revision=2017-09-25', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mvrp?module=Cisco-IOS-XE-mvrp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-interfaces-oper?module=Cisco-IOS-XE-interfaces-oper&revision=2017-10-10', 'urn:ietf:params:xml:ns:yang:smiv2:Q-BRIDGE-MIB?module=Q-BRIDGE-MIB&revision=2006-01-09', 'http://cisco.com/ns/yang/Cisco-IOS-XE-wccp?module=Cisco-IOS-XE-wccp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lldp-oper?module=Cisco-IOS-XE-lldp-oper&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:nvo?module=nvo&revision=2015-06-02&deviations=nvo-devs', 'http://openconfig.net/yang/interfaces/aggregate?module=openconfig-if-aggregate&revision=2016-12-22&deviations=cisco-xe-openconfig-interfaces-deviation', 'urn:ietf:params:xml:ns:yang:ietf-key-chain?module=ietf-key-chain&revision=2015-02-24&features=independent-send-accept-lifetime,hex-key-string,accept-tolerance', 'urn:ietf:params:xml:ns:yang:smiv2:ETHER-WIS?module=ETHER-WIS&revision=2003-09-19', 'urn:cisco:params:xml:ns:yang:pim?module=pim&revision=2014-06-27&features=bsr,auto-rp', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mld?module=Cisco-IOS-XE-mld&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:ENTITY-SENSOR-MIB?module=ENTITY-SENSOR-MIB&revision=2002-12-16', 'urn:ietf:params:netconf:capability:validate:1.1', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mmode?module=Cisco-IOS-XE-mmode&revision=2017-06-15', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ENVMON-MIB?module=CISCO-ENVMON-MIB&revision=2003-12-01', 'http://openconfig.net/yang/optical-amplfier?module=openconfig-optical-amplifier&revision=2016-03-31', 'urn:ietf:params:xml:ns:yang:smiv2:DISMAN-EXPRESSION-MIB?module=DISMAN-EXPRESSION-MIB&revision=2000-10-16', 'http://openconfig.net/yang/types/yang?module=openconfig-yang-types&revision=2017-07-30', 'urn:ietf:params:xml:ns:yang:cisco-ospf?module=cisco-ospf&revision=2016-03-30&features=graceful-shutdown,flood-reduction,database-filter', 'http://openconfig.net/yang/spanning-tree?module=openconfig-spanning-tree&revision=2017-07-14&deviations=cisco-xe-openconfig-spanning-tree-deviation', 'http://openconfig.net/yang/bgp?module=openconfig-bgp&revision=2016-06-21', 'urn:ietf:params:xml:ns:yang:smiv2:TOKEN-RING-RMON-MIB?module=TOKEN-RING-RMON-MIB', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ENTITY-ALARM-MIB?module=CISCO-ENTITY-ALARM-MIB&revision=1999-07-06', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet?module=Cisco-IOS-XE-ethernet&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-QOS-PIB-MIB?module=CISCO-QOS-PIB-MIB&revision=2007-08-29', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ospf?module=Cisco-IOS-XE-ospf&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:MPLS-VPN-MIB?module=MPLS-VPN-MIB&revision=2001-10-15', 'http://cisco.com/ns/yang/Cisco-IOS-XE-nat-oper?module=Cisco-IOS-XE-nat-oper&revision=2017-11-01', 'http://tail-f.com/ns/common/query?module=tailf-common-query&revision=2017-04-27', 'http://openconfig.net/yang/oc-mapping-stp?module=oc-mapping-stp&revision=2017-02-07', 'http://cisco.com/ns/yang/cisco-xe-openconfig-bgp-deviation?module=cisco-xe-openconfig-bgp-deviation&revision=2017-05-24', 'http://openconfig.net/yang/bgp-types?module=openconfig-bgp-types&revision=2016-06-21', 'http://openconfig.net/yang/policy-types?module=openconfig-policy-types&revision=2016-05-12', 'http://tail-f.com/ns/netconf/extensions', 'http://openconfig.net/yang/interfaces/ip-ext?module=openconfig-if-ip-ext&revision=2016-12-22', 'urn:ietf:params:xml:ns:yang:smiv2:LLDP-MIB?module=LLDP-MIB&revision=2005-05-06', 'http://cisco.com/ns/yang/cisco-xe-ietf-yang-push-deviation?module=cisco-xe-ietf-yang-push-deviation&revision=2017-08-22', 'urn:ietf:params:xml:ns:yang:smiv2:TUNNEL-MIB?module=TUNNEL-MIB&revision=2005-05-16', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ntp?module=Cisco-IOS-XE-ntp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mdt-common-defs?module=Cisco-IOS-XE-mdt-common-defs&revision=2017-07-01', 'urn:ietf:params:xml:ns:yang:ietf-yang-push?module=ietf-yang-push&revision=2016-10-28&features=on-change&deviations=cisco-xe-ietf-yang-push-deviation', 'http://openconfig.net/yang/transport-line-common?module=openconfig-transport-line-common&revision=2016-03-31', 'urn:ietf:params:xml:ns:yang:smiv2:OSPF-MIB?module=OSPF-MIB&revision=2006-11-10', 'http://cisco.com/ns/yang/cisco-xe-openconfig-rib-bgp-ext?module=cisco-xe-openconfig-rib-bgp-ext&revision=2016-11-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RTTMON-MIB?module=CISCO-RTTMON-MIB&revision=2012-08-16', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-HSRP-MIB?module=CISCO-HSRP-MIB&revision=2010-09-06', 'http://openconfig.net/yang/types/inet?module=openconfig-inet-types&revision=2017-08-24', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lldp?module=Cisco-IOS-XE-lldp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cts?module=Cisco-IOS-XE-cts&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-snmp?module=Cisco-IOS-XE-snmp&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults?module=ietf-netconf-with-defaults&revision=2011-06-01', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-PRODUCTS-MIB?module=CISCO-PRODUCTS-MIB&revision=2014-11-06', 'http://cisco.com/ns/cisco-xe-ietf-ospf-deviation?module=cisco-xe-ietf-ospf-deviation&revision=2015-09-11', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IMAGE-LICENSE-MGMT-MIB?module=CISCO-IMAGE-LICENSE-MGMT-MIB&revision=2007-10-16', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-FIREWALL-TC?module=CISCO-FIREWALL-TC&revision=2006-03-03', 'urn:ietf:params:xml:ns:yang:smiv2:IP-MIB?module=IP-MIB&revision=2006-02-02', 'http://cisco.com/ns/yang/Cisco-IOS-XE-virtual-service-oper?module=Cisco-IOS-XE-virtual-service-oper&revision=2017-09-25', 'urn:ietf:params:xml:ns:yang:cisco-policy-target?module=cisco-policy-target&revision=2016-03-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSLA-TC-MIB?module=CISCO-IPSLA-TC-MIB&revision=2007-03-23', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IF-EXTENSION-MIB?module=CISCO-IF-EXTENSION-MIB&revision=2013-03-13', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-FTP-CLIENT-MIB?module=CISCO-FTP-CLIENT-MIB&revision=2006-03-31', 'http://cisco.com/ns/yang/Cisco-IOS-XE-nbar?module=Cisco-IOS-XE-nbar&revision=2017-11-27', 'http://openconfig.net/yang/rib/bgp-ext?module=openconfig-rib-bgp-ext&revision=2016-04-11', 'urn:ietf:params:netconf:capability:interleave:1.0', 'http://cisco.com/ns/yang/Cisco-IOS-XE-process-cpu-oper?module=Cisco-IOS-XE-process-cpu-oper&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPMROUTE-MIB?module=CISCO-IPMROUTE-MIB&revision=2005-03-07', 'urn:ietf:params:netconf:base:1.0', 'urn:ietf:params:xml:ns:yang:c3pl-types?module=policy-types&revision=2013-10-07&features=protocol-name-support,match-wlan-user-priority-support,match-vpls-support,match-vlan-support,match-vlan-inner-support,match-src-mac-support,match-security-group-support,match-qos-group-support,match-prec-support,match-packet-length-support,match-mpls-exp-top-support,match-mpls-exp-imp-support,match-metadata-support,match-ipv6-acl-support,match-ipv6-acl-name-support,match-ipv4-acl-support,match-ipv4-acl-name-support,match-ip-rtp-support,match-input-interface-support,match-fr-dlci-support,match-fr-de-support,match-flow-record-support,match-flow-ip-support,match-dst-mac-support,match-discard-class-support,match-dei-support,match-dei-inner-support,match-cos-support,match-cos-inner-support,match-class-map-support,match-atm-vci-support,match-atm-clp-support,match-application-support', 'http://cisco.com/ns/yang/Cisco-IOS-XE-tcam-oper?module=Cisco-IOS-XE-tcam-oper&revision=2017-06-06', 'http://cisco.com/ns/yang/Cisco-IOS-XE-eigrp?module=Cisco-IOS-XE-eigrp&revision=2017-09-21', 'http://cisco.com/ns/cisco-xe-ietf-ipv4-unicast-routing-deviation?module=cisco-xe-ietf-ipv4-unicast-routing-deviation&revision=2015-09-11', 'http://cisco.com/ns/yang/Cisco-IOS-XE-platform-software-oper?module=Cisco-IOS-XE-platform-software-oper&revision=2017-10-10', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ezpm?module=Cisco-IOS-XE-ezpm&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-service-discovery?module=Cisco-IOS-XE-service-discovery&revision=2017-02-07', 'http://cisco.com/ns/yang/Cisco-IOS-XE-rsvp?module=Cisco-IOS-XE-rsvp&revision=2017-11-27', 'http://cisco.com/yang/cisco-ia?module=cisco-ia&revision=2017-03-02', 'urn:ietf:params:xml:ns:yang:ietf-netconf-notifications?module=ietf-netconf-notifications&revision=2012-02-06', 'http://cisco.com/ns/yang/Cisco-IOS-XE-stackwise-virtual?module=Cisco-IOS-XE-stackwise-virtual&revision=2017-06-05', 'http://openconfig.net/yang/interfaces/ethernet?module=openconfig-if-ethernet&revision=2016-12-22&deviations=cisco-xe-openconfig-if-ethernet-deviation,cisco-xe-openconfig-interfaces-deviation', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-CEF-MIB?module=CISCO-CEF-MIB&revision=2006-01-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ETHER-CFM-MIB?module=CISCO-ETHER-CFM-MIB&revision=2004-12-28', 'http://cisco.com/ns/mpls-static/devs?module=common-mpls-static-devs&revision=2015-09-11', 'http://cisco.com/ns/yang/Cisco-IOS-XE-object-group?module=Cisco-IOS-XE-object-group&revision=2017-07-31', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-OSPF-TRAP-MIB?module=CISCO-OSPF-TRAP-MIB&revision=2003-07-18', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSLA-AUTOMEASURE-MIB?module=CISCO-IPSLA-AUTOMEASURE-MIB&revision=2007-06-13', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ios-events-oper?module=Cisco-IOS-XE-ios-events-oper&revision=2017-10-10', 'urn:ietf:params:xml:ns:yang:iana-if-type?module=iana-if-type&revision=2014-05-08', 'urn:ietf:params:xml:ns:yang:smiv2:RFC-1215?module=RFC-1215', 'http://openconfig.net/yang/interfaces?module=openconfig-interfaces&revision=2016-12-22&deviations=cisco-xe-openconfig-interfaces-deviation', 'http://cisco.com/ns/yang/Cisco-IOS-XE-wsma?module=Cisco-IOS-XE-wsma&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:INTEGRATED-SERVICES-MIB?module=INTEGRATED-SERVICES-MIB&revision=1995-11-03', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RADIUS-EXT-MIB?module=CISCO-RADIUS-EXT-MIB&revision=2010-05-25', 'urn:ietf:params:netconf:capability:validate:1.0', 'http://tail-f.com/ns/netconf/actions/1.0', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-PROCESS-MIB?module=CISCO-PROCESS-MIB&revision=2011-06-23', 'http://cisco.com/ns/yang/Cisco-IOS-XE-spanning-tree-oper?module=Cisco-IOS-XE-spanning-tree-oper&revision=2017-08-10', 'http://cisco.com/ns/yang/Cisco-IOS-XE-l2vpn?module=Cisco-IOS-XE-l2vpn&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cdp?module=Cisco-IOS-XE-cdp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ip-sla-oper?module=Cisco-IOS-XE-ip-sla-oper&revision=2017-09-25', 'http://openconfig.net/yang/acl?module=openconfig-acl&revision=2017-04-26&deviations=cisco-xe-openconfig-acl-deviation', 'http://openconfig.net/yang/spanning-tree/types?module=openconfig-spanning-tree-types&revision=2017-07-14', 'http://cisco.com/ns/cisco-xe-ietf-ipv6-unicast-routing-deviation?module=cisco-xe-ietf-ipv6-unicast-routing-deviation&revision=2015-09-11', 'urn:ietf:params:xml:ns:yang:smiv2:ENTITY-STATE-TC-MIB?module=ENTITY-STATE-TC-MIB&revision=2005-11-22', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mdt-oper?module=Cisco-IOS-XE-mdt-oper&revision=2017-09-20', 'http://tail-f.com/yang/common?module=tailf-common&revision=2017-08-23', 'http://cisco.com/ns/yang/Cisco-IOS-XE-rpc?module=Cisco-IOS-XE-rpc&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSEC-POLICY-MAP-MIB?module=CISCO-IPSEC-POLICY-MAP-MIB&revision=2000-08-17', 'urn:ietf:params:xml:ns:yang:policy-attr?module=policy-attr&revision=2015-04-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-dhcp-oper?module=Cisco-IOS-XE-dhcp-oper&revision=2017-11-01', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cef?module=Cisco-IOS-XE-cef&revision=2017-05-19&features=asr1k-dpi', 'urn:ietf:params:xml:ns:yang:smiv2:IGMP-STD-MIB?module=IGMP-STD-MIB&revision=2000-09-28', 'urn:ietf:params:xml:ns:yang:ietf-ip?module=ietf-ip&revision=2014-06-16&features=ipv6-privacy-autoconf,ipv4-non-contiguous-netmasks&deviations=cisco-xe-ietf-ip-deviation', 'http://openconfig.net/yang/platform-types?module=openconfig-platform-types&revision=2017-08-16', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lisp?module=Cisco-IOS-XE-lisp&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-NTP-MIB?module=CISCO-NTP-MIB&revision=2006-07-31', 'urn:ietf:params:xml:ns:yang:smiv2:PIM-MIB?module=PIM-MIB&revision=2000-09-28', 'urn:ietf:params:xml:ns:yang:smiv2:IANAifType-MIB?module=IANAifType-MIB&revision=2006-03-31', 'http://cisco.com/ns/yang/Cisco-IOS-XE-flow-monitor-oper?module=Cisco-IOS-XE-flow-monitor-oper&revision=2017-11-30', 'urn:ietf:params:xml:ns:yang:ietf-netconf-acm?module=ietf-netconf-acm&revision=2012-02-22', 'urn:ietf:params:xml:ns:yang:smiv2:NHRP-MIB?module=NHRP-MIB&revision=1999-08-26', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-BGP-POLICY-ACCOUNTING-MIB?module=CISCO-BGP-POLICY-ACCOUNTING-MIB&revision=2002-07-26', 'urn:ietf:params:xml:ns:yang:smiv2:SNMP-PROXY-MIB?module=SNMP-PROXY-MIB&revision=2002-10-14', 'http://openconfig.net/yang/header-fields?module=openconfig-packet-match&revision=2017-04-26', 'urn:cisco:params:xml:ns:yang:cisco-xe-ietf-yang-push-ext?module=cisco-xe-ietf-yang-push-ext&revision=2017-08-14', 'http://cisco.com/ns/yang/Cisco-IOS-XE-platform-oper?module=Cisco-IOS-XE-platform-oper&revision=2017-10-11', 'urn:jon?module=jon&revision=2023-03-29']
+'http://cisco.com/ns/cisco-xe-ietf-routing-deviation?module=cisco-xe-ietf-routing-deviation&revision=2016-07-09', 'http://cisco.com/ns/yang/ios-xe/template?module=Cisco-IOS-XE-template&revision=2017-11-06', 'http://openconfig.net/yang/cisco-xe-openconfig-if-ethernet-deviation?module=cisco-xe-openconfig-if-ethernet-deviation&revision=2017-11-01', 'http://cisco.com/ns/yang/Cisco-IOS-XE-track?module=Cisco-IOS-XE-track&revision=2017-04-28', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-EMBEDDED-EVENT-MGR-MIB?module=CISCO-EMBEDDED-EVENT-MGR-MIB&revision=2006-11-07', 'http://cisco.com/ns/yang/Cisco-IOS-XE-common-types?module=Cisco-IOS-XE-common-types&revision=2017-09-25', 'http://cisco.com/ns/yang/Cisco-IOS-XE-interfaces-oper?module=Cisco-IOS-XE-interfaces-oper&revision=2017-10-10', 'urn:ietf:params:xml:ns:yang:smiv2:Q-BRIDGE-MIB?module=Q-BRIDGE-MIB&revision=2006-01-09', 'http://cisco.com/ns/yang/Cisco-IOS-XE-wccp?module=Cisco-IOS-XE-wccp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lldp-oper?module=Cisco-IOS-XE-lldp-oper&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:nvo?module=nvo&revision=2015-06-02&deviations=nvo-devs', 'http://openconfig.net/yang/interfaces/aggregate?module=openconfig-if-aggregate&revision=2016-12-22&deviations=cisco-xe-openconfig-interfaces-deviation', 'urn:ietf:params:xml:ns:yang:ietf-key-chain?module=ietf-key-chain&revision=2015-02-24&features=independent-send-accept-lifetime,hex-key-string,accept-tolerance', 'urn:ietf:params:xml:ns:yang:smiv2:ETHER-WIS?module=ETHER-WIS&revision=2003-09-19', 'urn:cisco:params:xml:ns:yang:pim?module=pim&revision=2014-06-27&features=bsr,auto-rp', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mld?module=Cisco-IOS-XE-mld&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:ENTITY-SENSOR-MIB?module=ENTITY-SENSOR-MIB&revision=2002-12-16', 'urn:ietf:params:xml:ns:yang:smiv2:DISMAN-EXPRESSION-MIB?module=DISMAN-EXPRESSION-MIB&revision=2000-10-16', 'http://openconfig.net/yang/types/yang?module=openconfig-yang-types&revision=2017-07-30', 'urn:ietf:params:xml:ns:yang:cisco-ospf?module=cisco-ospf&revision=2016-03-30&features=graceful-shutdown,flood-reduction,database-filter', 'http://openconfig.net/yang/bgp?module=openconfig-bgp&revision=2016-06-21', 'urn:ietf:params:xml:ns:yang:smiv2:TOKEN-RING-RMON-MIB?module=TOKEN-RING-RMON-MIB', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ENTITY-ALARM-MIB?module=CISCO-ENTITY-ALARM-MIB&revision=1999-07-06', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet?module=Cisco-IOS-XE-ethernet&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-QOS-PIB-MIB?module=CISCO-QOS-PIB-MIB&revision=2007-08-29', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ospf?module=Cisco-IOS-XE-ospf&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:MPLS-VPN-MIB?module=MPLS-VPN-MIB&revision=2001-10-15', 'http://cisco.com/ns/yang/Cisco-IOS-XE-nat-oper?module=Cisco-IOS-XE-nat-oper&revision=2017-11-01', 'http://tail-f.com/ns/common/query?module=tailf-common-query&revision=2017-04-27', 'http://cisco.com/ns/yang/cisco-xe-openconfig-bgp-deviation?module=cisco-xe-openconfig-bgp-deviation&revision=2017-05-24', 'http://openconfig.net/yang/bgp-types?module=openconfig-bgp-types&revision=2016-06-21', 'http://openconfig.net/yang/policy-types?module=openconfig-policy-types&revision=2016-05-12', 'http://tail-f.com/ns/netconf/extensions', 'http://openconfig.net/yang/interfaces/ip-ext?module=openconfig-if-ip-ext&revision=2016-12-22', 'urn:ietf:params:xml:ns:yang:smiv2:LLDP-MIB?module=LLDP-MIB&revision=2005-05-06', 'http://cisco.com/ns/yang/cisco-xe-ietf-yang-push-deviation?module=cisco-xe-ietf-yang-push-deviation&revision=2017-08-22', 'urn:ietf:params:xml:ns:yang:smiv2:TUNNEL-MIB?module=TUNNEL-MIB&revision=2005-05-16', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ntp?module=Cisco-IOS-XE-ntp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mdt-common-defs?module=Cisco-IOS-XE-mdt-common-defs&revision=2017-07-01', 'urn:ietf:params:xml:ns:yang:ietf-yang-push?module=ietf-yang-push&revision=2016-10-28&features=on-change&deviations=cisco-xe-ietf-yang-push-deviation', 'http://openconfig.net/yang/transport-line-common?module=openconfig-transport-line-common&revision=2016-03-31', 'urn:ietf:params:xml:ns:yang:smiv2:OSPF-MIB?module=OSPF-MIB&revision=2006-11-10', 'http://cisco.com/ns/yang/cisco-xe-openconfig-rib-bgp-ext?module=cisco-xe-openconfig-rib-bgp-ext&revision=2016-11-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RTTMON-MIB?module=CISCO-RTTMON-MIB&revision=2012-08-16', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-HSRP-MIB?module=CISCO-HSRP-MIB&revision=2010-09-06', 'http://openconfig.net/yang/types/inet?module=openconfig-inet-types&revision=2017-08-24', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lldp?module=Cisco-IOS-XE-lldp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cts?module=Cisco-IOS-XE-cts&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-snmp?module=Cisco-IOS-XE-snmp&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults?module=ietf-netconf-with-defaults&revision=2011-06-01', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-PRODUCTS-MIB?module=CISCO-PRODUCTS-MIB&revision=2014-11-06', 'http://cisco.com/ns/cisco-xe-ietf-ospf-deviation?module=cisco-xe-ietf-ospf-deviation&revision=2015-09-11', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IMAGE-LICENSE-MGMT-MIB?module=CISCO-IMAGE-LICENSE-MGMT-MIB&revision=2007-10-16', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-FIREWALL-TC?module=CISCO-FIREWALL-TC&revision=2006-03-03', 'urn:ietf:params:xml:ns:yang:smiv2:IP-MIB?module=IP-MIB&revision=2006-02-02', 'http://cisco.com/ns/yang/Cisco-IOS-XE-virtual-service-oper?module=Cisco-IOS-XE-virtual-service-oper&revision=2017-09-25', 'urn:ietf:params:xml:ns:yang:cisco-policy-target?module=cisco-policy-target&revision=2016-03-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSLA-TC-MIB?module=CISCO-IPSLA-TC-MIB&revision=2007-03-23', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IF-EXTENSION-MIB?module=CISCO-IF-EXTENSION-MIB&revision=2013-03-13', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-FTP-CLIENT-MIB?module=CISCO-FTP-CLIENT-MIB&revision=2006-03-31', 'http://cisco.com/ns/yang/Cisco-IOS-XE-nbar?module=Cisco-IOS-XE-nbar&revision=2017-11-27', 'http://openconfig.net/yang/rib/bgp-ext?module=openconfig-rib-bgp-ext&revision=2016-04-11', 'urn:ietf:params:netconf:capability:interleave:1.0', 'http://cisco.com/ns/yang/Cisco-IOS-XE-process-cpu-oper?module=Cisco-IOS-XE-process-cpu-oper&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPMROUTE-MIB?module=CISCO-IPMROUTE-MIB&revision=2005-03-07', 'urn:ietf:params:netconf:base:1.0', 'urn:ietf:params:xml:ns:yang:c3pl-types?module=policy-types&revision=2013-10-07&features=protocol-name-support,match-wlan-user-priority-support,match-vpls-support,match-vlan-support,match-vlan-inner-support,match-src-mac-support,match-security-group-support,match-qos-group-support,match-prec-support,match-packet-length-support,match-mpls-exp-top-support,match-mpls-exp-imp-support,match-metadata-support,match-ipv6-acl-support,match-ipv6-acl-name-support,match-ipv4-acl-support,match-ipv4-acl-name-support,match-ip-rtp-support,match-input-interface-support,match-fr-dlci-support,match-fr-de-support,match-flow-record-support,match-flow-ip-support,match-dst-mac-support,match-discard-class-support,match-dei-support,match-dei-inner-support,match-cos-support,match-cos-inner-support,match-class-map-support,match-atm-vci-support,match-atm-clp-support,match-application-support', 'http://cisco.com/ns/yang/Cisco-IOS-XE-eigrp?module=Cisco-IOS-XE-eigrp&revision=2017-09-21', 'http://cisco.com/ns/cisco-xe-ietf-ipv4-unicast-routing-deviation?module=cisco-xe-ietf-ipv4-unicast-routing-deviation&revision=2015-09-11', 'http://cisco.com/ns/yang/Cisco-IOS-XE-platform-software-oper?module=Cisco-IOS-XE-platform-software-oper&revision=2017-10-10', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ezpm?module=Cisco-IOS-XE-ezpm&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-service-discovery?module=Cisco-IOS-XE-service-discovery&revision=2017-02-07', 'http://cisco.com/ns/yang/Cisco-IOS-XE-rsvp?module=Cisco-IOS-XE-rsvp&revision=2017-11-27', 'http://cisco.com/yang/cisco-ia?module=cisco-ia&revision=2017-03-02', 'urn:ietf:params:xml:ns:yang:ietf-netconf-notifications?module=ietf-netconf-notifications&revision=2012-02-06', 'http://openconfig.net/yang/interfaces/ethernet?module=openconfig-if-ethernet&revision=2016-12-22&deviations=cisco-xe-openconfig-if-ethernet-deviation,cisco-xe-openconfig-interfaces-deviation', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-CEF-MIB?module=CISCO-CEF-MIB&revision=2006-01-30', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-ETHER-CFM-MIB?module=CISCO-ETHER-CFM-MIB&revision=2004-12-28', 'http://cisco.com/ns/mpls-static/devs?module=common-mpls-static-devs&revision=2015-09-11', 'http://cisco.com/ns/yang/Cisco-IOS-XE-object-group?module=Cisco-IOS-XE-object-group&revision=2017-07-31', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-OSPF-TRAP-MIB?module=CISCO-OSPF-TRAP-MIB&revision=2003-07-18', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSLA-AUTOMEASURE-MIB?module=CISCO-IPSLA-AUTOMEASURE-MIB&revision=2007-06-13', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ios-events-oper?module=Cisco-IOS-XE-ios-events-oper&revision=2017-10-10', 'urn:ietf:params:xml:ns:yang:iana-if-type?module=iana-if-type&revision=2014-05-08', 'http://openconfig.net/yang/interfaces?module=openconfig-interfaces&revision=2016-12-22&deviations=cisco-xe-openconfig-interfaces-deviation', 'http://cisco.com/ns/yang/Cisco-IOS-XE-wsma?module=Cisco-IOS-XE-wsma&revision=2017-02-07', 'urn:ietf:params:xml:ns:yang:smiv2:INTEGRATED-SERVICES-MIB?module=INTEGRATED-SERVICES-MIB&revision=1995-11-03', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-RADIUS-EXT-MIB?module=CISCO-RADIUS-EXT-MIB&revision=2010-05-25', 'urn:ietf:params:netconf:capability:validate:1.0', 'http://tail-f.com/ns/netconf/actions/1.0', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-PROCESS-MIB?module=CISCO-PROCESS-MIB&revision=2011-06-23', 'http://cisco.com/ns/yang/Cisco-IOS-XE-l2vpn?module=Cisco-IOS-XE-l2vpn&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cdp?module=Cisco-IOS-XE-cdp&revision=2017-11-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-ip-sla-oper?module=Cisco-IOS-XE-ip-sla-oper&revision=2017-09-25', 'http://openconfig.net/yang/acl?module=openconfig-acl&revision=2017-04-26&deviations=cisco-xe-openconfig-acl-deviation', 'http://cisco.com/ns/cisco-xe-ietf-ipv6-unicast-routing-deviation?module=cisco-xe-ietf-ipv6-unicast-routing-deviation&revision=2015-09-11', 'urn:ietf:params:xml:ns:yang:smiv2:ENTITY-STATE-TC-MIB?module=ENTITY-STATE-TC-MIB&revision=2005-11-22', 'http://cisco.com/ns/yang/Cisco-IOS-XE-mdt-oper?module=Cisco-IOS-XE-mdt-oper&revision=2017-09-20', 'http://tail-f.com/yang/common?module=tailf-common&revision=2017-08-23', 'http://cisco.com/ns/yang/Cisco-IOS-XE-rpc?module=Cisco-IOS-XE-rpc&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-IPSEC-POLICY-MAP-MIB?module=CISCO-IPSEC-POLICY-MAP-MIB&revision=2000-08-17', 'urn:ietf:params:xml:ns:yang:policy-attr?module=policy-attr&revision=2015-04-27', 'http://cisco.com/ns/yang/Cisco-IOS-XE-dhcp-oper?module=Cisco-IOS-XE-dhcp-oper&revision=2017-11-01', 'http://cisco.com/ns/yang/Cisco-IOS-XE-cef?module=Cisco-IOS-XE-cef&revision=2017-05-19&features=asr1k-dpi', 'urn:ietf:params:xml:ns:yang:smiv2:IGMP-STD-MIB?module=IGMP-STD-MIB&revision=2000-09-28', 'urn:ietf:params:xml:ns:yang:ietf-ip?module=ietf-ip&revision=2014-06-16&features=ipv6-privacy-autoconf,ipv4-non-contiguous-netmasks&deviations=cisco-xe-ietf-ip-deviation', 'http://openconfig.net/yang/platform-types?module=openconfig-platform-types&revision=2017-08-16', 'http://cisco.com/ns/yang/Cisco-IOS-XE-lisp?module=Cisco-IOS-XE-lisp&revision=2017-11-27', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-NTP-MIB?module=CISCO-NTP-MIB&revision=2006-07-31', 'urn:ietf:params:xml:ns:yang:smiv2:PIM-MIB?module=PIM-MIB&revision=2000-09-28', 'urn:ietf:params:xml:ns:yang:smiv2:IANAifType-MIB?module=IANAifType-MIB&revision=2006-03-31', 'http://cisco.com/ns/yang/Cisco-IOS-XE-flow-monitor-oper?module=Cisco-IOS-XE-flow-monitor-oper&revision=2017-11-30', 'urn:ietf:params:xml:ns:yang:ietf-netconf-acm?module=ietf-netconf-acm&revision=2012-02-22', 'urn:ietf:params:xml:ns:yang:smiv2:NHRP-MIB?module=NHRP-MIB&revision=1999-08-26', 'urn:ietf:params:xml:ns:yang:smiv2:CISCO-BGP-POLICY-ACCOUNTING-MIB?module=CISCO-BGP-POLICY-ACCOUNTING-MIB&revision=2002-07-26', 'urn:ietf:params:xml:ns:yang:smiv2:SNMP-PROXY-MIB?module=SNMP-PROXY-MIB&revision=2002-10-14', 'http://openconfig.net/yang/header-fields?module=openconfig-packet-match&revision=2017-04-26', 'urn:cisco:params:xml:ns:yang:cisco-xe-ietf-yang-push-ext?module=cisco-xe-ietf-yang-push-ext&revision=2017-08-14', 'http://cisco.com/ns/yang/Cisco-IOS-XE-platform-oper?module=Cisco-IOS-XE-platform-oper&revision=2017-10-11', 'urn:jon?module=jon&revision=2023-03-29']
+
 yaml = \
     'devices:\n' \
     '    dummy:\n' \
@@ -1326,6 +1364,1532 @@ def test_delta_10(self):
         self.assertEqual(str(delta1).strip(), expected_delta1.strip())
         self.assertEqual(str(delta2).strip(), expected_delta2.strip())
 
+    def test_delta_replace_1(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">abc</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>true</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Innovation</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Paul</last>
+                  <first>Bob</first>
+                  <street>Express</street>
+                  <city>Kanata</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Main</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+                <store xmlns="urn:jon">Quick</store>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">edf</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>false</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Carp</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Second</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+
+        # leaf foo
+        foo = delta.nc.xpath('//nc:config/jon:foo',
+                             namespaces=delta.ns)[0]
+        self.assertEqual(foo.get(operation_tag), 'replace')
+        self.assertEqual(foo.text, 'edf')
+
+        # container tracking
+        tracking = delta.nc.xpath('//nc:config/jon:tracking',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(tracking.get(operation_tag), 'replace')
+        enabled = delta.nc.xpath('//nc:config/jon:tracking/jon:enabled',
+                                 namespaces=delta.ns)[0]
+        self.assertEqual(enabled.text, 'false')
+
+        # list address
+        address = delta.nc.xpath('//nc:config/jon:address',
+                                 namespaces=delta.ns)
+        self.assertEqual(address[0].get(operation_tag), 'replace')
+        self.assertEqual(address[0].get(insert_tag), 'first')
+        street = address[0].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.text, 'Carp')
+        self.assertEqual(address[1].get(operation_tag), 'replace')
+        self.assertEqual(address[1].get(insert_tag), 'after')
+        self.assertEqual(address[1].get(key_tag),
+                         "[first='Bob'][last='Brown']")
+        street = address[1].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.text, 'Second')
+        self.assertEqual(address[2].get(operation_tag), 'delete')
+        first = address[2].xpath('jon:first',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(first.text, 'Bob')
+        last = address[2].xpath('jon:last',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(last.text, 'Paul')
+
+        # leaf-list store
+        store = delta.nc.xpath('//nc:config/jon:store', namespaces=delta.ns)
+        self.assertEqual(store[0].get(operation_tag), 'replace')
+        self.assertEqual(store[0].get(insert_tag), 'first')
+        self.assertEqual(store[0].text, 'Dollar')
+        self.assertEqual(store[1].get(operation_tag), 'replace')
+        self.assertEqual(store[1].get(insert_tag), 'after')
+        self.assertEqual(store[1].get(value_tag), 'Dollar')
+        self.assertEqual(store[1].text, 'Cheap')
+        self.assertEqual(store[2].get(operation_tag), 'delete')
+        self.assertEqual(store[2].text, 'Quick')
+
+        config3 = config1 + delta
+        self.assertEqual(config2, config3)
+
+    def test_delta_2(self):
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <router>
+                    <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+                      <id>10</id>
+                      <bgp>
+                        <router-id>10.8.55.30</router-id>
+                        <log-neighbor-changes/>
+                      </bgp>
+                    </bgp>
+                  </router>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <router>
+                    <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+                      <id>10</id>
+                      <bgp>
+                        <router-id>10.8.55.30</router-id>
+                        <log-neighbor-changes/>
+                        <listen>
+                          <limit>2100</limit>
+                          <range>
+                            <network-range>10.44.0.0/16</network-range>
+                            <peer-group>INET1-SPOKES</peer-group>
+                          </range>
+                        </listen>
+                      </bgp>
+                      <address-family>
+                        <no-vrf>
+                          <ipv4>
+                            <af-name>unicast</af-name>
+                          </ipv4>
+                        </no-vrf>
+                      </address-family>
+                    </bgp>
+                  </router>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        expected_delta1 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <router>
+      <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+        <id>10</id>
+        <bgp>
+          <listen>
+            <limit>2100</limit>
+            <range>
+              <network-range>10.44.0.0/16</network-range>
+              <peer-group>INET1-SPOKES</peer-group>
+            </range>
+          </listen>
+        </bgp>
+        <address-family>
+          <no-vrf>
+            <ipv4>
+              <af-name>unicast</af-name>
+            </ipv4>
+          </no-vrf>
+        </address-family>
+      </bgp>
+    </router>
+  </native>
+</nc:config>
+            """
+        expected_delta2 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <router>
+      <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+        <id>10</id>
+        <bgp>
+          <listen nc:operation="delete"/>
+        </bgp>
+        <address-family nc:operation="delete"/>
+      </bgp>
+    </router>
+  </native>
+</nc:config>
+            """
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        delta1 = config2 - config1
+        delta2 = -delta1
+        self.assertEqual(str(delta1).strip(), expected_delta1.strip())
+        self.assertEqual(str(delta2).strip(), expected_delta2.strip())
+
+    def test_delta_3(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <table-connections>
+                      <table-connection>
+                        <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                        <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                          <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                          <import-policy>ROUTEMAP1</import-policy>
+                          <import-policy>ROUTEMAP2</import-policy>
+                          <default-import-policy>REJECT_ROUTE</default-import-policy>
+                        </config>
+                      </table-connection>
+                    </table-connections>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <table-connections>
+                      <table-connection>
+                        <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                        <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                          <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                          <import-policy>ROUTEMAP1</import-policy>
+                          <import-policy>ROUTEMAP3</import-policy>
+                          <import-policy>ROUTEMAP0</import-policy>
+                          <import-policy>ROUTEMAP2</import-policy>
+                          <default-import-policy>REJECT_ROUTE</default-import-policy>
+                        </config>
+                      </table-connection>
+                    </table-connections>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        # modify schema node
+        nodes = config1.xpath('.//oc-netinst:network-instance'
+                              '/oc-netinst:table-connections'
+                              '/oc-netinst:table-connection'
+                              '/oc-netinst:config/oc-netinst:import-policy')
+        node = nodes[0]
+        schema_node = config1.get_schema_node(node)
+        schema_node.set('ordered-by', 'user')
+        delta1 = config2 - config1
+        config3 = config1 + delta1
+        self.assertEqual(config2, config3)
+        self.assertTrue(config2 <= config3)
+        self.assertTrue(config2 >= config3)
+        delta2 = config1 - config2
+        config4 = config2 + delta2
+        self.assertEqual(config1, config4)
+
+    def test_delta_4(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <tables>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                    </tables>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <tables>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                    </tables>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        # modify schema node
+        nodes = config1.xpath('.//oc-netinst:network-instance'
+                              '/oc-netinst:tables/oc-netinst:table')
+        node = nodes[0]
+        schema_node = config1.get_schema_node(node)
+        schema_node.set('ordered-by', 'user')
+        delta1 = config2 - config1
+        config3 = config1 + delta1
+        self.assertEqual(config2, config3)
+        self.assertTrue(config2 <= config3)
+        self.assertTrue(config2 >= config3)
+        delta2 = config1 - config2
+        config4 = config2 + delta2
+        self.assertEqual(config1, config4)
+
+    def test_delta_5(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <tables>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                    </tables>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <tables>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV44</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        </config>
+                      </table>
+                      <table>
+                        <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        <config>
+                          <protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:STATIC</protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV6</address-family>
+                        </config>
+                      </table>
+                    </tables>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        # modify schema node
+        nodes = config1.xpath('.//oc-netinst:network-instance'
+                              '/oc-netinst:tables/oc-netinst:table')
+        node = nodes[0]
+        schema_node = config1.get_schema_node(node)
+        schema_node.set('ordered-by', 'user')
+        delta1 = ConfigDelta(config_src=config1, config_dst=config2,
+                             preferred_create='create',
+                             preferred_replace='replace',
+                             preferred_delete='remove')
+        config3 = config1 + delta1
+        self.assertEqual(config2, config3)
+        self.assertTrue(config2 <= config3)
+        self.assertTrue(config2 >= config3)
+        delta2 = config1 - config2
+        config4 = config2 + delta2
+        self.assertEqual(config1, config4)
+
+    def test_delta_6(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <table-connections>
+                      <table-connection>
+                        <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                        <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                          <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                          <import-policy>ROUTEMAP1</import-policy>
+                          <import-policy>ROUTEMAP2</import-policy>
+                          <default-import-policy>REJECT_ROUTE</default-import-policy>
+                        </config>
+                      </table-connection>
+                    </table-connections>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <network-instances xmlns="http://openconfig.net/yang/network-instance">
+                  <network-instance>
+                    <name>default</name>
+                    <config>
+                      <name>default</name>
+                      <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:DEFAULT_INSTANCE</type>
+                      <description>default-vrf [read-only]</description>
+                    </config>
+                    <table-connections>
+                      <table-connection>
+                        <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                        <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                        <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                        <config>
+                          <src-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:DIRECTLY_CONNECTED</src-protocol>
+                          <address-family xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:IPV4</address-family>
+                          <dst-protocol xmlns:oc-pol-types="http://openconfig.net/yang/policy-types">oc-pol-types:BGP</dst-protocol>
+                          <import-policy>ROUTEMAP1</import-policy>
+                          <import-policy>ROUTEMAP3</import-policy>
+                          <import-policy>ROUTEMAP0</import-policy>
+                          <import-policy>ROUTEMAP2</import-policy>
+                          <default-import-policy>REJECT_ROUTE</default-import-policy>
+                        </config>
+                      </table-connection>
+                    </table-connections>
+                  </network-instance>
+                </network-instances>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+
+        # modify schema node
+        nodes = config1.xpath('.//oc-netinst:network-instance'
+                              '/oc-netinst:table-connections'
+                              '/oc-netinst:table-connection'
+                              '/oc-netinst:config/oc-netinst:import-policy')
+        node = nodes[0]
+        schema_node = config1.get_schema_node(node)
+        schema_node.set('ordered-by', 'user')
+
+        delta1 = ConfigDelta(config_src=config1, config_dst=config2,
+                             preferred_create='create',
+                             preferred_replace='replace',
+                             preferred_delete='remove')
+        config3 = config1 + delta1
+        self.assertEqual(config2, config3)
+        self.assertTrue(config2 <= config3)
+        self.assertTrue(config2 >= config3)
+        delta2 = config1 - config2
+        config4 = config2 + delta2
+        self.assertEqual(config1, config4)
+
+        # diff_type is 'replace'
+        delta3 = -delta2
+        delta3.diff_type = 'replace'
+        config5 = config1 + delta3
+        self.assertEqual(config2, config5)
+
+    def test_delta_7(self):
+        # choice
+        no_choice_xml = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <destination-config>
+                          <host>hostname</host>
+                        </destination-config>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <destination-config>
+                          <ipv4>1.1.1.1</ipv4>
+                        </destination-config>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml3 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <destination-config>
+                          <ipv4>2.2.2.2</ipv4>
+                        </destination-config>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        expected_delta1 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+          <destination-config>
+            <ipv4>1.1.1.1</ipv4>
+          </destination-config>
+        </tunnel>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        expected_delta2 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+          <destination-config>
+            <host>hostname</host>
+          </destination-config>
+        </tunnel>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        expected_delta3 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel" nc:operation="delete"/>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        expected_delta4 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+          <destination-config>
+            <ipv4>2.2.2.2</ipv4>
+          </destination-config>
+        </tunnel>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        no_choice = Config(self.d, no_choice_xml)
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        config3 = Config(self.d, xml3)
+        delta1 = config2 - config1
+        delta2 = config1 - config2
+        delta3 = no_choice - config1
+        delta4 = config1 - no_choice
+        delta5 = config3 - config2
+        delta6 = config2 - config3
+        self.assertEqual(str(delta1).strip(), expected_delta1.strip())
+        self.assertEqual(str(delta2).strip(), expected_delta2.strip())
+        self.assertEqual(str(delta3).strip(), expected_delta3.strip())
+        self.assertEqual(str(delta4).strip(), expected_delta2.strip())
+        self.assertEqual(str(delta5).strip(), expected_delta4.strip())
+        self.assertEqual(str(delta6).strip(), expected_delta1.strip())
+
+    def test_delta_8(self):
+        # multiple choices
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <mode>
+                          <ethernet-config>
+                            <gre>
+                              <ipv4/>
+                            </gre>
+                          </ethernet-config>
+                        </mode>
+                        <destination-config>
+                          <ipv4>1.1.1.1</ipv4>
+                        </destination-config>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <Tunnel>
+                      <name>5</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <mode>
+                          <ethernet-config>
+                            <l2tpv3>
+                              <manual/>
+                            </l2tpv3>
+                          </ethernet-config>
+                        </mode>
+                        <destination-config>
+                          <host>hostname</host>
+                        </destination-config>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        expected_delta1 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+          <mode>
+            <ethernet-config>
+              <l2tpv3>
+                <manual/>
+              </l2tpv3>
+            </ethernet-config>
+          </mode>
+          <destination-config>
+            <host>hostname</host>
+          </destination-config>
+        </tunnel>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        expected_delta2 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+    <interface>
+      <Tunnel>
+        <name>5</name>
+        <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+          <mode>
+            <ethernet-config>
+              <gre>
+                <ipv4/>
+              </gre>
+            </ethernet-config>
+          </mode>
+          <destination-config>
+            <ipv4>1.1.1.1</ipv4>
+          </destination-config>
+        </tunnel>
+      </Tunnel>
+    </interface>
+  </native>
+</nc:config>
+            """
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        delta1 = config2 - config1
+        delta2 = config1 - config2
+        self.assertEqual(str(delta1).strip(), expected_delta1.strip())
+        self.assertEqual(str(delta2).strip(), expected_delta2.strip())
+
+    def test_delta_9(self):
+        # choice with list
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <location xmlns="urn:jon">
+                  <ontario>
+                    <name>Ottawa</name>
+                  </ontario>
+                </location>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <location xmlns="urn:jon">
+                  <alberta xmlns="urn:jon">
+                    <name>Calgary</name>
+                  </alberta>
+                </location>
+              </data>
+            </rpc-reply>
+            """
+        expected_delta1 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <location xmlns="urn:jon">
+    <alberta>
+      <name>Calgary</name>
+    </alberta>
+  </location>
+</nc:config>
+            """
+        expected_delta2 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <location xmlns="urn:jon">
+    <ontario>
+      <name>Ottawa</name>
+    </ontario>
+  </location>
+</nc:config>
+            """
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        delta1 = config2 - config1
+        delta2 = config1 - config2
+        self.assertEqual(str(delta1).strip(), expected_delta1.strip())
+        self.assertEqual(str(delta2).strip(), expected_delta2.strip())
+
+    def test_delta_10(self):
+        # choice multiple nodes in one case
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <numbers xmlns="urn:jon">
+                  <first>one</first>
+                </numbers>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <numbers xmlns="urn:jon">
+                  <second>two</second>
+                  <third>three</third>
+                </numbers>
+              </data>
+            </rpc-reply>
+            """
+        expected_delta1 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <numbers xmlns="urn:jon">
+    <second>two</second>
+    <third>three</third>
+  </numbers>
+</nc:config>
+            """
+        expected_delta2 = """
+<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <numbers xmlns="urn:jon">
+    <first>one</first>
+  </numbers>
+</nc:config>
+            """
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        delta1 = config2 - config1
+        delta2 = config1 - config2
+        self.assertEqual(str(delta1).strip(), expected_delta1.strip())
+        self.assertEqual(str(delta2).strip(), expected_delta2.strip())
+
+    def test_delta_replace_1(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">abc</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>true</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Innovation</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Paul</last>
+                  <first>Bob</first>
+                  <street>Express</street>
+                  <city>Kanata</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Main</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+                <store xmlns="urn:jon">Quick</store>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">edf</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>false</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Carp</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Second</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+
+        # leaf foo
+        foo = delta.nc.xpath('//nc:config/jon:foo',
+                             namespaces=delta.ns)[0]
+        self.assertEqual(foo.get(operation_tag), 'replace')
+        self.assertEqual(foo.text, 'edf')
+
+        # container tracking
+        tracking = delta.nc.xpath('//nc:config/jon:tracking',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(tracking.get(operation_tag), 'replace')
+        enabled = delta.nc.xpath('//nc:config/jon:tracking/jon:enabled',
+                                 namespaces=delta.ns)[0]
+        self.assertEqual(enabled.text, 'false')
+
+        # list address
+        address = delta.nc.xpath('//nc:config/jon:address',
+                                 namespaces=delta.ns)
+        self.assertEqual(address[0].get(operation_tag), 'replace')
+        self.assertEqual(address[0].get(insert_tag), 'first')
+        street = address[0].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.text, 'Carp')
+        self.assertEqual(address[1].get(operation_tag), 'replace')
+        self.assertEqual(address[1].get(insert_tag), 'after')
+        self.assertEqual(address[1].get(key_tag),
+                         "[first='Bob'][last='Brown']")
+        street = address[1].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.text, 'Second')
+        self.assertEqual(address[2].get(operation_tag), 'delete')
+        first = address[2].xpath('jon:first',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(first.text, 'Bob')
+        last = address[2].xpath('jon:last',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(last.text, 'Paul')
+
+        # leaf-list store
+        store = delta.nc.xpath('//nc:config/jon:store', namespaces=delta.ns)
+        self.assertEqual(store[0].get(operation_tag), 'replace')
+        self.assertEqual(store[0].get(insert_tag), 'first')
+        self.assertEqual(store[0].text, 'Dollar')
+        self.assertEqual(store[1].get(operation_tag), 'replace')
+        self.assertEqual(store[1].get(insert_tag), 'after')
+        self.assertEqual(store[1].get(value_tag), 'Dollar')
+        self.assertEqual(store[1].text, 'Cheap')
+        self.assertEqual(store[2].get(operation_tag), 'delete')
+        self.assertEqual(store[2].text, 'Quick')
+
+        config3 = config1 + delta
+        self.assertEqual(config2, config3)
+
+    def test_delta_replace_2(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">abc</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>true</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Innovation</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Paul</last>
+                  <first>Bob</first>
+                  <street>Express</street>
+                  <city>Kanata</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Main</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+                <store xmlns="urn:jon">Quick</store>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <foo xmlns="urn:jon">edf</foo>
+                <tracking xmlns="urn:jon">
+                  <enabled>false</enabled>
+                </tracking>
+                <address xmlns="urn:jon">
+                  <last>Brown</last>
+                  <first>Bob</first>
+                  <street>Carp</street>
+                  <city>New York</city>
+                </address>
+                <address xmlns="urn:jon">
+                  <last>Wang</last>
+                  <first>Ken</first>
+                  <street>Second</street>
+                  <city>Boston</city>
+                </address>
+                <store xmlns="urn:jon">Dollar</store>
+                <store xmlns="urn:jon">Cheap</store>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+        delta.replace_depth = 1
+
+        # leaf foo
+        foo = delta.nc.xpath('//nc:config/jon:foo',
+                             namespaces=delta.ns)[0]
+        self.assertEqual(foo.get(operation_tag), None)
+        self.assertEqual(foo.text, 'edf')
+
+        # container tracking
+        tracking = delta.nc.xpath('//nc:config/jon:tracking',
+                                  namespaces=delta.ns)[0]
+        enabled = delta.nc.xpath('//nc:config/jon:tracking/jon:enabled',
+                                 namespaces=delta.ns)[0]
+        self.assertEqual(enabled.text, 'false')
+        self.assertEqual(enabled.get(operation_tag), 'replace')
+
+        # list address
+        address = delta.nc.xpath('//nc:config/jon:address',
+                                 namespaces=delta.ns)
+        self.assertEqual(address[0].get(operation_tag), None)
+        self.assertEqual(address[0].get(insert_tag), 'first')
+        street = address[0].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.get(operation_tag), 'replace')
+        self.assertEqual(street.text, 'Carp')
+        self.assertEqual(address[1].get(operation_tag), None)
+        self.assertEqual(address[1].get(insert_tag), 'after')
+        self.assertEqual(address[1].get(key_tag),
+                         "[first='Bob'][last='Brown']")
+        street = address[1].xpath('jon:street',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(street.get(operation_tag), 'replace')
+        self.assertEqual(street.text, 'Second')
+        self.assertEqual(address[2].get(operation_tag), 'delete')
+        first = address[2].xpath('jon:first',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(first.text, 'Bob')
+        last = address[2].xpath('jon:last',
+                                  namespaces=delta.ns)[0]
+        self.assertEqual(last.text, 'Paul')
+
+        # leaf-list store
+        store = delta.nc.xpath('//nc:config/jon:store', namespaces=delta.ns)
+        self.assertEqual(store[0].get(operation_tag), None)
+        self.assertEqual(store[0].get(insert_tag), 'first')
+        self.assertEqual(store[0].text, 'Dollar')
+        self.assertEqual(store[1].get(operation_tag), None)
+        self.assertEqual(store[1].get(insert_tag), 'after')
+        self.assertEqual(store[1].get(value_tag), 'Dollar')
+        self.assertEqual(store[1].text, 'Cheap')
+        self.assertEqual(store[2].get(operation_tag), 'delete')
+        self.assertEqual(store[2].text, 'Quick')
+
+        config3 = config1 + delta
+        self.assertEqual(config2, config3)
+
+    def test_delta_replace_3(self):
+        xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <router>
+                    <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+                      <id>10</id>
+                      <bgp>
+                        <router-id>10.8.55.30</router-id>
+                        <log-neighbor-changes/>
+                      </bgp>
+                    </bgp>
+                  </router>
+                  <vrf>
+                    <definition>
+                      <name>Mgmt-vrf</name>
+                      <address-family>
+                        <ipv4/>
+                        <ipv6/>
+                      </address-family>
+                    </definition>
+                  </vrf>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
+                       message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <router>
+                    <bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
+                      <id>10</id>
+                      <bgp>
+                        <router-id>10.8.55.30</router-id>
+                        <log-neighbor-changes/>
+                        <listen>
+                          <limit>2100</limit>
+                          <range>
+                            <network-range>10.44.0.0/16</network-range>
+                            <peer-group>INET1-SPOKES</peer-group>
+                          </range>
+                        </listen>
+                      </bgp>
+                      <address-family>
+                        <no-vrf>
+                          <ipv4>
+                            <af-name>unicast</af-name>
+                          </ipv4>
+                        </no-vrf>
+                      </address-family>
+                    </bgp>
+                  </router>
+                  <vrf>
+                    <definition>
+                      <name>Mgmt-vrf</name>
+                      <address-family>
+                        <ipv4/>
+                        <ipv6/>
+                      </address-family>
+                    </definition>
+                  </vrf>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, xml1)
+        config2 = Config(self.d, xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+        delta.replace_depth = 1
+
+        # container tracking
+        router = delta.nc.xpath('//nc:config/ios:native/ios:router',
+                                namespaces=delta.ns)[0]
+        self.assertEqual(router.get(operation_tag), 'replace')
+
+        # container vrf should not exist
+        vrf = delta.nc.xpath('//nc:config/ios:native/ios:vrf',
+                             namespaces=delta.ns)
+        self.assertEqual(vrf, [])
+
+    def test_delta_replace_4(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <GigabitEthernet>
+                      <name>1</name>
+                      <ip>
+                        <address>
+                          <primary>
+                            <address>10.77.130.126</address>
+                            <mask>255.255.255.0</mask>
+                          </primary>
+                        </address>
+                      </ip>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>2</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>3</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <Tunnel>
+                      <name>2</name>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <GigabitEthernet>
+                      <name>1</name>
+                      <ip>
+                        <address>
+                          <primary>
+                            <address>10.77.130.126</address>
+                            <mask>255.255.255.0</mask>
+                          </primary>
+                        </address>
+                      </ip>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>2</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>3</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <Tunnel>
+                      <name>2</name>
+                      <nat66 xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-nat">inside</nat66>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <source>1.1.1.1</source>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+        delta.replace_depth = 3
+
+        # leaf nat66
+        nat66 = delta.nc.xpath(
+            '//nc:config/ios:native/ios:interface/ios:Tunnel/ios-nat:nat66',
+            namespaces=delta.ns,
+        )[0]
+        self.assertEqual(nat66.get(operation_tag), 'replace')
+
+        # container tunnel
+        tunnel = delta.nc.xpath(
+            '//nc:config/ios:native/ios:interface/ios:Tunnel/ios-tun:tunnel',
+            namespaces=delta.ns
+        )[0]
+        self.assertEqual(tunnel.get(operation_tag), 'replace')
+
+        # self.assertEqual(str(delta), '')
+
+    def test_delta_replace_5(self):
+        config_xml1 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <GigabitEthernet>
+                      <name>1</name>
+                      <ip>
+                        <address>
+                          <primary>
+                            <address>10.77.130.126</address>
+                            <mask>255.255.255.0</mask>
+                          </primary>
+                        </address>
+                      </ip>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>2</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>3</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <Tunnel>
+                      <name>2</name>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <source>1.1.1.1</source>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+        config_xml2 = """
+            <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
+              <data>
+                <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+                  <interface>
+                    <GigabitEthernet>
+                      <name>1</name>
+                      <ip>
+                        <address>
+                          <primary>
+                            <address>10.77.130.126</address>
+                            <mask>255.255.255.0</mask>
+                          </primary>
+                        </address>
+                      </ip>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>2</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <GigabitEthernet>
+                      <name>3</name>
+                      <shutdown/>
+                      <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
+                        <auto>true</auto>
+                      </negotiation>
+                    </GigabitEthernet>
+                    <Tunnel>
+                      <name>2</name>
+                      <nat66 xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-nat">inside</nat66>
+                      <tunnel xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel">
+                        <destination>
+                          <ipaddress-or-host>2.2.2.2</ipaddress-or-host>
+                        </destination>
+                      </tunnel>
+                    </Tunnel>
+                  </interface>
+                </native>
+              </data>
+            </rpc-reply>
+            """
+
+        config1 = Config(self.d, config_xml1)
+        config2 = Config(self.d, config_xml2)
+        delta = config2 - config1
+        delta.diff_type = 'replace'
+        delta.replace_depth = 4
+
+        # leaf nat66 is at level 3, but it is a leaf, so the replace operation
+        # has to be at level 3.
+        nat66 = delta.nc.xpath(
+            '//nc:config/ios:native/ios:interface/ios:Tunnel/ios-nat:nat66',
+            namespaces=delta.ns,
+        )[0]
+        self.assertEqual(nat66.get(operation_tag), 'replace')
+
+        # container destination
+        destination = delta.nc.xpath(
+            '//nc:config/ios:native/ios:interface/ios:Tunnel/ios-tun:tunnel'
+            '/ios-tun:destination',
+            namespaces=delta.ns,
+        )[0]
+        self.assertEqual(destination.get(operation_tag), 'replace')
+
     def test_xpath_1(self):
         xml = """
             <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
diff --git a/ncdiff/src/yang/ncdiff/tests/test_running_config.py b/ncdiff/src/yang/ncdiff/tests/test_running_config.py
new file mode 100644
index 0000000..43ed033
--- /dev/null
+++ b/ncdiff/src/yang/ncdiff/tests/test_running_config.py
@@ -0,0 +1,1095 @@
+#!/bin/env python
+""" Unit tests for the ncdiff cisco-shared package. """
+
+import unittest
+from ncdiff import RunningConfigDiff
+
+
+class TestRunningConfig(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def test_diff_1(self):
+        config_1 = """
+ip dhcp pool ABC
+ network 10.0.20.0 255.255.255.0
+ class CLASS1
+  address range 10.0.20.1 10.0.20.100
+ class CLASS2
+  address range 10.0.20.101 10.0.20.200
+ class CLASS3
+  address range 10.0.20.201 10.0.20.254
+!
+ip dhcp pool dhcp_1
+ network 172.16.1.0 255.255.255.0
+ network 172.16.2.0 255.255.255.0 secondary
+ network 172.16.3.0 255.255.255.0 secondary
+ network 172.16.4.0 255.255.255.0 secondary
+!
+!
+ip dhcp class CLASS1
+   relay agent information
+!
+ip dhcp class CLASS2
+   relay agent information
+!
+ip dhcp class CLASS3
+   relay agent information
+!
+!
+login on-success log
+            """
+        config_2 = """
+ip dhcp pool dhcp_1
+ network 172.16.1.0 255.255.255.0
+ network 172.16.2.0 255.255.255.0 secondary
+ network 172.16.3.0 255.255.255.0 secondary
+ network 172.16.4.0 255.255.255.0 secondary
+!
+ip dhcp pool ABC
+ network 10.0.20.0 255.255.255.0
+ class CLASS1
+  address range 10.0.20.1 10.0.20.100
+ class CLASS2
+  address range 10.0.20.101 10.0.20.200
+ class CLASS3
+  address range 10.0.20.201 10.0.20.254
+!
+!
+ip dhcp class CLASS2
+   relay agent information
+ relay-information hex 01040101030402020102
+ relay-information hex 01040102030402020102
+!
+ip dhcp class CLASS1
+   relay agent information
+ relay-information hex 01030a0b0c02*
+ relay-information hex 01030a0b0c02050000000123
+!
+ip dhcp class CLASS3
+   relay agent information
+!
+!
+login on-success log
+!
+            """
+        expected_diff = """
+- ip dhcp pool ABC
+-   network 10.0.20.0 255.255.255.0
+-   class CLASS1
+-     address range 10.0.20.1 10.0.20.100
+-   class CLASS2
+-     address range 10.0.20.101 10.0.20.200
+-   class CLASS3
+-     address range 10.0.20.201 10.0.20.254
+  ip dhcp pool dhcp_1
+    network 172.16.1.0 255.255.255.0
+    network 172.16.2.0 255.255.255.0 secondary
+    network 172.16.3.0 255.255.255.0 secondary
+    network 172.16.4.0 255.255.255.0 secondary
+- ip dhcp class CLASS1
+-   relay agent information
++ ip dhcp pool ABC
++   network 10.0.20.0 255.255.255.0
++   class CLASS1
++     address range 10.0.20.1 10.0.20.100
++   class CLASS2
++     address range 10.0.20.101 10.0.20.200
++   class CLASS3
++     address range 10.0.20.201 10.0.20.254
+  ip dhcp class CLASS2
+    relay agent information
++     relay-information hex 01040101030402020102
++     relay-information hex 01040102030402020102
++ ip dhcp class CLASS1
++   relay agent information
++     relay-information hex 01030a0b0c02*
++     relay-information hex 01030a0b0c02050000000123
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line, expected_line)
+
+    def test_diff_2(self):
+        config_1 = """
+aa bb cc
+        """
+        config_2 = """
+aa bb cc
+  ee ff rr
+!
+        """
+        expected_diff = """
+  aa bb cc
++   ee ff rr
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line, expected_line)
+
+    def test_diff_3(self):
+        config_1 = """
+aa bb cc
+  ee ff rr
+!
+        """
+        config_2 = """
+aa bb cc
+        """
+        expected_diff = """
+  aa bb cc
+-   ee ff rr
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line, expected_line)
+
+    def test_diff_4(self):
+        config_1 = """
+vrf definition genericstring
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+        """
+        config_2 = """
+vrf definition genericstring
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+        """
+        expected_diff = ""
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line, expected_line)
+
+    def test_diff_5(self):
+        config_1 = """
+vrf definition genericstring
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+        """
+        config_2 = """
+vrf definition generic
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+        """
+        expected_diff = """
+- vrf definition genericstring
+-   address-family ipv4
+-     exit-address-family
+-   address-family ipv6
+-     exit-address-family
++ vrf definition generic
++   address-family ipv4
++     exit-address-family
++   address-family ipv6
++     exit-address-family
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line, expected_line)
+
+    def test_cli_short_no_commands(self):
+        config_1 = """
+vrf definition genericstring
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+license boot level network-advantage addon dna-advantage
+        """
+        config_2 = """
+vrf definition generic
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+        """
+        expected_cli = """
+no license boot level
+no vrf definition genericstring
+!
+vrf definition generic
+  address-family ipv4
+    exit-address-family
+  address-family ipv6
+    exit-address-family
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_orderless_commands(self):
+        config_1 = """
+license boot level network-advantage addon dna-advantage
+!
+aaa authentication login admin-con group tacacs+ local
+aaa authentication login admin-vty group tacacs+ local
+        """
+        config_2 = """
+aaa authentication login admin-vty group tacacs+ local
+aaa authentication login admin-con group tacacs+ local
+        """
+        expected_cli = """
+no license boot level
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_overwritable_commands(self):
+        config_1 = """
+line vty 0 4
+ exec-timeout 0 0
+ password 0 lab
+ transport input all
+!
+        """
+        config_2 = """
+line vty 0 4
+ exec-timeout 0 0
+ password 7 082D4D4C
+ transport input all
+!
+        """
+        expected_cli = """
+line vty 0 4
+ password 7 082D4D4C
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_doubleface_commands(self):
+        config_1 = """
+no platform punt-keepalive disable-kernel-core
+!
+        """
+        config_2 = """
+platform punt-keepalive disable-kernel-core
+        """
+        expected_cli = """
+platform punt-keepalive disable-kernel-core
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_sibling_commands(self):
+        config_1 = """
+aaa server radius proxy
+ client 10.0.0.0 255.0.0.0
+  timer disconnect acct-stop 23
+  !
+  client 11.0.0.0 255.0.0.0
+  accounting port 34
+  !
+  client 12.0.0.0 255.0.0.0
+  accounting port 56
+  !
+abc def
+  aaa server radius proxy
+    client 10.0.0.0 255.0.0.0
+      ert tyt
+      client 11.0.0.0 255.0.0.0
+        kdjfg kjkfdg
+      client 12.0.0.0 255.0.0.0
+        gdfh lijdf
+        """
+        config_2 = """
+aaa server radius proxy
+ client 11.0.0.0 255.0.0.0
+  accounting port 34
+  !
+  client 13.0.0.0 255.0.0.0
+  accounting port 77
+  !
+abc def
+  aaa server radius proxy
+    client 11.0.0.0 255.0.0.0
+      kdjfg ttttt
+      client 13.0.0.0 255.0.0.0
+        gdfh lijdf
+        """
+        expected_cli = """
+abc def
+  aaa server radius proxy
+    no client 12.0.0.0 255.0.0.0
+    client 11.0.0.0 255.0.0.0
+      no kdjfg kjkfdg
+    no client 10.0.0.0 255.0.0.0
+aaa server radius proxy
+  no client 12.0.0.0 255.0.0.0
+  no client 10.0.0.0 255.0.0.0
+!
+aaa server radius proxy
+  client 13.0.0.0 255.0.0.0
+    accounting port 77
+abc def
+  aaa server radius proxy
+    client 11.0.0.0 255.0.0.0
+      kdjfg ttttt
+    client 13.0.0.0 255.0.0.0
+      gdfh lijdf
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_ip_address(self):
+        config_1 = """
+interface GigabitEthernet8/0/1
+!
+interface Vlan69
+ ip address 192.168.100.1 255.255.255.0
+!
+interface Vlan70
+ ip address 192.168.101.1 255.255.255.0
+        """
+        config_2 = """
+ip dhcp pool Test
+ network 1.1.0.0 255.255.0.0
+!
+interface GigabitEthernet8/0/1
+ no switchport
+ ip address dhcp
+ shutdown
+!
+interface Vlan69
+ no ip address
+ ipv6 address 2001:FACE::1/64
+ ipv6 enable
+ ipv6 dhcp relay destination 2001:1234::2
+!
+interface Vlan70
+ no ip address
+ ipv6 address 2001:1234::1/64
+ ipv6 enable
+        """
+        expected_cli = """
+interface Vlan70
+  no ip address
+interface Vlan69
+  no ip address
+interface GigabitEthernet8/0/1
+  no switchport
+!
+ip dhcp pool Test
+  network 1.1.0.0 255.255.0.0
+interface GigabitEthernet8/0/1
+  ip address dhcp
+  shutdown
+interface Vlan69
+  ipv6 address 2001:FACE::1/64
+  ipv6 enable
+  ipv6 dhcp relay destination 2001:1234::2
+interface Vlan70
+  ipv6 address 2001:1234::1/64
+  ipv6 enable
+        """
+        expected_reverse_cli = """
+interface Vlan70
+  no ipv6 enable
+  no ipv6 address
+interface Vlan69
+  no ipv6 dhcp relay destination 2001:1234::2
+  no ipv6 enable
+  no ipv6 address
+interface GigabitEthernet8/0/1
+  no shutdown
+  no ip address
+no ip dhcp pool Test
+!
+interface GigabitEthernet8/0/1
+  switchport
+interface Vlan69
+  ip address 192.168.100.1 255.255.255.0
+interface Vlan70
+  ip address 192.168.101.1 255.255.255.0
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_cli = running_diff.cli.strip()
+        expected_cli = expected_cli.strip()
+        actual_lines = actual_cli.split('\n')
+        expected_lines = expected_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+        actual_reverse_cli = running_diff.cli_reverse.strip()
+        expected_reverse_cli = expected_reverse_cli.strip()
+        actual_lines = actual_reverse_cli.split('\n')
+        expected_lines = expected_reverse_cli.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_logging_host(self):
+        config_1 = """
+logging host 10.15.118.120
+logging host 10.200.159.168
+logging host 10.200.209.215
+        """
+        config_2 = """
+logging host 10.200.209.215
+logging host 10.15.118.120
+logging host 10.200.159.168
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_cli_flow_mon(self):
+        config_1 = """
+flow monitor eta-mon
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+        """
+        config_2 = """
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+flow monitor eta-mon
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_cli_service_template_1(self):
+        config_1 = """
+login on-success log
+!
+flow monitor eta-mon
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+!
+access-session mac-move deny
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template webauth-global-inactive
+  inactivity-timer 3600
+service-template eap-seen
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+        """
+        config_2 = """
+login on-success log
+!
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+flow monitor eta-mon
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+  aaa bbb ccc
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template eap-seen
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+service-template webauth-global-inactive
+  inactivity-timer 3600
+        """
+        expected_diff = """
+- access-session mac-move deny
+  service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
++   aaa bbb ccc
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_service_template_2(self):
+        config_1 = """
+login on-success log
+!
+flow monitor eta-mon
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template webauth-global-inactive
+  inactivity-timer 3600
+service-template eap-seen
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+        """
+        config_2 = """
+login on-success log
+!
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+flow monitor eta-mon
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template eap-seen
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+service-template webauth-global-inactive
+  inactivity-timer 3600
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_cli_service_template_3(self):
+        config_1 = """
+login on-success log
+!
+flow monitor eta-mon
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+!
+access-session mac-move deny
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template webauth-global-inactive
+  inactivity-timer 3600
+service-template eap-seen
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+        """
+        config_2 = """
+login on-success log
+!
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record
+flow monitor meraki_monitor_ipv6
+  exporter meraki_exporter
+  exporter customer_exporter
+  record meraki_record_ipv6
+flow monitor eta-mon
+!
+crypto pki trustpoint SLA-TrustPoint
+ enrollment pkcs12
+ revocation-check crl
+ hash sha256
+!
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE_NEW
+  voice vlan
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template eap-seen
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+service-template webauth-global-inactive
+  inactivity-timer 3600
+        """
+        expected_diff = """
+- access-session mac-move deny
++ service-template DEFAULT_CRITICAL_VOICE_TEMPLATE_NEW
++   voice vlan
+- service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+-   voice vlan
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertTrue(running_diff)
+        actual_diff = str(running_diff).strip()
+        expected_diff = expected_diff.strip()
+        actual_lines = actual_diff.split('\n')
+        expected_lines = expected_diff.split('\n')
+        self.assertEqual(len(actual_lines), len(expected_lines))
+        for actual_line, expected_line in zip(actual_lines, expected_lines):
+            self.assertEqual(actual_line.strip(), expected_line.strip())
+
+    def test_cli_aaa_group_server_radius(self):
+        config_1 = """
+aaa group server radius testing-10.5.0.110-1812
+  server name 10.5.0.110_1812_0
+aaa group server radius testing-6.0.0.2-1812
+  server name 6.0.0.2_1812_0
+aaa group server radius radius-group-0
+  server name 6.0.0.2_1812_0
+aaa group server radius radius-group-1
+  server name 10.5.0.110_1812_0
+aaa group server radius radius-group-2
+  server name 10.5.0.110_1812_0
+aaa group server radius radius-group-3
+  server name 10.5.0.110_1812_0
+aaa group server radius testing-10.5.0.110-0
+  server name 10.5.0.110_0_1813
+aaa group server radius radius-group-4
+  server name 10.5.0.110_1812_0
+        """
+        config_2 = """
+aaa group server radius radius-group-0
+  server name 6.0.0.2_1812_0
+aaa group server radius radius-group-1
+  server name 10.5.0.110_1812_0
+aaa group server radius radius-group-2
+  server name 10.5.0.110_1812_0
+aaa group server radius radius-group-3
+  server name 10.5.0.110_1812_0
+aaa group server radius radius-group-4
+  server name 10.5.0.110_1812_0
+aaa group server radius testing-10.5.0.110-0
+  server name 10.5.0.110_0_1813
+aaa group server radius testing-10.5.0.110-1812
+  server name 10.5.0.110_1812_0
+aaa group server radius testing-6.0.0.2-1812
+  server name 6.0.0.2_1812_0
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_service_template_2(self):
+        config_1 = """
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template webauth-global-inactive
+  inactivity-timer 3600
+service-template eap-seen
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+service-template failed-vlan-2
+  vlan 98
+service-template guest-vlan-0
+  vlan 900
+        """
+        config_2 = """
+service-template DEFAULT_CRITICAL_DATA_TEMPLATE
+service-template DEFAULT_CRITICAL_VOICE_TEMPLATE
+  voice vlan
+service-template DEFAULT_LINKSEC_POLICY_MUST_SECURE
+  linksec policy must-secure
+service-template DEFAULT_LINKSEC_POLICY_SHOULD_SECURE
+  linksec policy should-secure
+service-template aaa-unreachable-flag
+service-template bounce-port-flag
+service-template eap-seen
+service-template failed-vlan-2
+  vlan 98
+service-template guest-vlan-0
+  vlan 900
+service-template secure-connect-ap-aaa-down
+service-template secure-connect-break-flag
+service-template secure-connect-failed-flag
+service-template secure-connect-in-progress-flag
+service-template secure-connect-success-flag
+service-template webauth-global-inactive
+  inactivity-timer 3600
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_flow_exporter(self):
+        config_1 = """
+flow exporter meraki_exporter
+  destination 1.1.3.2
+  transport udp 9995
+flow exporter customer_exporter
+  destination 10.7.253.22
+  transport udp 2055
+  export-protocol ipfix
+  option interface-table timeout 3600
+  option exporter-stats timeout 300
+  option application-table timeout 3600
+        """
+        config_2 = """
+flow exporter customer_exporter
+  destination 10.7.253.22
+  transport udp 2055
+  export-protocol ipfix
+  option interface-table timeout 3600
+  option exporter-stats timeout 300
+  option application-table timeout 3600
+flow exporter meraki_exporter
+  destination 1.1.3.2
+  transport udp 9995
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_exporter(self):
+        config_1 = """
+flow monitor meraki_monitor
+  exporter meraki_exporter
+  exporter customer_exporter
+        """
+        config_2 = """
+flow monitor meraki_monitor
+  exporter customer_exporter
+  exporter meraki_exporter
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_username(self):
+        config_1 = """
+username guestshell privilege 0 password 7 03034E0E151B32444B0515
+username priv01 password 7 1209171E045B5D
+        """
+        config_2 = """
+username priv01 password 7 1209171E045B5D
+username guestshell privilege 0 password 7 03034E0E151B32444B0515
+        """
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
+
+    def test_flow_record(self):
+        config_1 = """
+flow record fr_1
+  match ipv6 protocol
+flow record fr_ipv4
+  match ipv4 protocol
+  match ipv4 version
+  collect connection initiator
+  collect connection new-connections
+  collect connection server counter bytes network long
+  collect connection server counter packets long
+flow record fr_ipv6
+  match ipv6 protocol
+  match ipv6 version
+  collect connection initiator
+  collect connection new-connections
+  collect connection server counter bytes network long
+  collect connection server counter packets long
+"""
+        config_2 = """
+flow record fr_ipv4
+  match ipv4 version
+  match ipv4 protocol
+  collect connection server counter bytes network long
+  collect connection server counter packets long
+  collect connection initiator
+  collect connection new-connections
+flow record fr_ipv6
+  match ipv6 version
+  match ipv6 protocol
+  collect connection server counter bytes network long
+  collect connection server counter packets long
+  collect connection initiator
+  collect connection new-connections
+flow record fr_1
+  match ipv6 protocol
+"""
+        running_diff = RunningConfigDiff(
+            running1=config_1,
+            running2=config_2,
+        )
+        self.assertFalse(running_diff)
+        self.assertEqual(running_diff.diff, None)
+        self.assertEqual(running_diff.diff_reverse, None)
+        self.assertEqual(running_diff.cli, '')
+        self.assertEqual(running_diff.cli_reverse, '')
\ No newline at end of file
diff --git a/ncdiff/src/yang/ncdiff/tests/yang/Cisco-IOS-XE-tunnel.yang b/ncdiff/src/yang/ncdiff/tests/yang/Cisco-IOS-XE-tunnel.yang
index d79c134..18a4a26 100644
--- a/ncdiff/src/yang/ncdiff/tests/yang/Cisco-IOS-XE-tunnel.yang
+++ b/ncdiff/src/yang/ncdiff/tests/yang/Cisco-IOS-XE-tunnel.yang
@@ -1,11 +1,22 @@
 module Cisco-IOS-XE-tunnel {
+  yang-version 1.1;
   namespace "http://cisco.com/ns/yang/Cisco-IOS-XE-tunnel";
   prefix ios-tun;
 
+  import cisco-semver {
+    prefix cisco-semver;
+  }
+  import cisco-semver-internal {
+    prefix cisco-semver-internal;
+  }
+ 
   import ietf-inet-types {
     prefix inet;
   }
 
+  import tailf-common {
+    prefix tailf;
+  }
 
   import Cisco-IOS-XE-native {
     prefix ios;
@@ -23,6 +34,14 @@ module Cisco-IOS-XE-tunnel {
     prefix ios-features;
   }
 
+  import Cisco-IOS-XE-interface-common {
+    prefix ios-ifc;
+  }
+
+  import Cisco-IOS-XE-policy {
+    prefix ios-policy;
+  }
+
   organization
     "Cisco Systems, Inc.";
 
@@ -39,18 +58,78 @@ module Cisco-IOS-XE-tunnel {
 
   description
     "Cisco XE Native Tunnel Interfaces Yang model.
-     Copyright (c) 2016-2017 by Cisco Systems, Inc.
+     Copyright (c) 2016-2019 by Cisco Systems, Inc.
      All rights reserved.";
 
   // =========================================================================
   // REVISION
   // =========================================================================
+  revision 2022-11-01 {
+    description
+      "- Update yang-version to 1.1"; 
+      cisco-semver-internal:os-version "17.10.1";
+  }
+  revision 2022-03-01 {
+    description
+      "- added dual-overlay support for tunnel mode";
+    cisco-semver-internal:os-version "17.8.1";
+  }
+
+  revision 2021-11-01 {
+    description
+      "- Added tunnel metadata command
+       - Ipsec v4 and v6 overlay support";
+    cisco-semver-internal:os-version "17.7.1";
+  }
+
+  revision 2020-11-01 {
+    description
+      "- Initial revision for 17.4.1";
+    cisco-semver-internal:os-version "17.4.1";
+  }
+
+  revision 2020-03-01 {
+    description
+      "- Added host option for tunnel destination";
+    cisco-semver-internal:os-version "17.2.1";
+  }
+  revision 2019-11-01 {
+    description 
+      "- Added route-via config";
+    cisco-semver:module-version "1.1.0";
+    cisco-semver-internal:os-version "17.1.1";
+  }
+  revision 2019-07-01 {
+    description 
+      "- Establish semantic version baseline
+       - Tunnel VRF model condition added
+       - Enabled default values for tunnel parameters
+       - Corrected various issues with the tunnel mode
+       - Added Tunnel submode l2tpv3 under ethernet mode";
+    cisco-semver:module-version "1.0.0";
+  }
+  revision 2018-11-21 {
+      description
+      "Cleanup spelling errors in description statements";
+  }
+  revision 2018-11-09 {
+    description
+      "added support for EoGRE P2P interfaces";
+  }
+  revision 2018-08-12 {
+    description
+      "added must constraint on tunnel source on interface Dialer";
+  }
+  revision 2018-03-06 {
+    description
+      "validate ip unnumbered and tunnel source are the same for tunnel mode sdwan";
+  }
   revision 2017-08-28 {
     description
       "Extend modeling for multicast";
   }
   revision 2017-07-11 {
-    description 
+    description
       "add tailf dependency extension to order tunnel mode before tunnel key";
   }
   revision 2017-04-28 {
@@ -67,6 +146,30 @@ module Cisco-IOS-XE-tunnel {
       "Initial revision";
   }
 
+  grouping tunnel-mode-ip-ipv6-opt-grouping {
+    choice tunmode-choice {
+      leaf ip {
+        description "over IP";
+        tailf:cli-full-command;
+        type empty;
+      }
+      leaf ipv6 {
+        description "over IPv6";
+        tailf:cli-full-command;
+        type empty;
+      }
+      container multipoint {
+        description "mode Multipoint";
+        presence "true";
+        tailf:cli-reset-container;
+        leaf "ipv6" {
+          description "over IPv6 (multipoint)";
+          tailf:cli-full-command;
+          type empty;
+        }
+      }
+    }
+  }
 
   grouping config-interface-tunnel-grouping {
     container nhrp {
@@ -80,6 +183,7 @@ module Cisco-IOS-XE-tunnel {
             "Number of seconds";
           type uint8 {
             range "1..22";
+            tailf:info "<1-22>;;Number of seconds";
           }
         }
       }
@@ -95,6 +199,8 @@ module Cisco-IOS-XE-tunnel {
           description
             "NHRP group mapping";
           list nhrp-name {
+            tailf:cli-drop-node-name;
+            tailf:cli-suppress-mode;
             key "nhrp-name";
             leaf nhrp-name {
               description
@@ -116,6 +222,9 @@ module Cisco-IOS-XE-tunnel {
       leaf route-watch {
         description
           "Enable NHRP route watch";
+        tailf:cli-boolean-no;
+        tailf:cli-trim-default;
+        tailf:cli-full-command;
         type boolean;
       }
     }
@@ -125,25 +234,40 @@ module Cisco-IOS-XE-tunnel {
         "protocol-over-protocol tunneling";
       // interface * / tunnel 6rd
       container tun-6rd {
+        when "../ios-tun:mode/ios-tun:ipv6ip-config/ios-tun:auto-6rd";
+        tailf:alt-name "6rd";
+        tailf:cli-diff-delete-before "../mode";
+        tailf:cli-diff-create-after "../mode";
+        tailf:cli-delete-when-empty;
         description
           "set tunnel 6rd parameters";
         leaf br {
           description
             "Border Relay parameters";
           type inet:ipv4-address;
+          tailf:cli-full-command;
         }
         container ipv4 {
           description
             "Common IPv4 transport source parameters";
+          tailf:cli-delete-when-empty;
+          tailf:cli-compact-syntax;
+          tailf:cli-full-no;
           leaf prefix-len {
             description
               "Common IPv4 transport source prefix length";
-            type uint8;
+            type uint8 {
+              range "0..31";
+              tailf:info "<0-31>;;Length in number of bits";
+            }
           }
           leaf suffix-len {
             description
               "Common IPv4 transport source suffix length";
-            type uint8;
+            type uint8 {
+              range "0..31";
+              tailf:info "<0-31>;;Length in number of bits";
+            }
           }
         }
         leaf prefix {
@@ -151,13 +275,37 @@ module Cisco-IOS-XE-tunnel {
             "Provider selected common IPv6 prefix";
           type ios-types:ipv6-prefix;
         }
+        leaf reverse-map-check-config {
+          description
+            "Enable source IP address and port verification";
+          tailf:alt-name "reverse-map-check";
+          tailf:cli-boolean-no;
+          tailf:cli-full-command;
+          type boolean;
+          default "true";
+        }
         leaf reverse-map-check {
           description
             "Enable source IP address and port verification";
+          status deprecated;
+          tailf:hidden deprecated;
+          tailf:cli-ignore-modified;
+          tailf:cli-full-command;
           type empty;
         }
       }
 
+
+      // interface * / tunnel src-mac-address
+      leaf src-mac-address {
+        description
+          "Mac address";
+        when "../ios-tun:mode/ios-tun:ethernet-config";
+        tailf:cli-full-command;
+        type ios-types:cisco-mac-address-type {
+          tailf:info "H.H.H;; Source MAC address";
+        }
+      }
       // interface * / tunnel bandwidth
       container bandwidth {
         description
@@ -165,12 +313,24 @@ module Cisco-IOS-XE-tunnel {
         leaf receive {
           description
             "Receive bandwidth";
-          type uint32;
+          tailf:cli-full-command;
+          type uint32 {
+            range "1..10000000";
+            tailf:info "<1-10000000>;;Bandwidth in kilobits";
+          }
+          default "8000";
+          tailf:cli-trim-default;
         }
         leaf transmit {
           description
             "Transmit bandwidth";
-          type uint32;
+          tailf:cli-full-command;
+          type uint32 {
+            range "1..10000000";
+            tailf:info "<1-10000000>;;Bandwidth in kilobits";
+          }
+          default "8000";
+          tailf:cli-trim-default;
         }
       }
 
@@ -178,35 +338,99 @@ module Cisco-IOS-XE-tunnel {
       leaf checksum {
         description
           "enable end to end checksumming of packets";
-        type empty;
+          tailf:cli-full-command;
+          type empty;
       }
 
       // interface * / tunnel source
       leaf source {
         description
           "source of tunnel packets";
+        must "starts-with(., 'Dialer') and "
+             + " (number(substring-after(., 'Dialer')) = /ios:native/ios:interface/ios:Dialer/ios:name)"
+             + " or not (starts-with(., 'Dialer'))" {
+             error-message "To use a Dialer interface, the Dialer interface has to be configured 1st, vice versa for delete";
+        }
         type string;
       }
 
       // interface * / tunnel destination
+      container destination-config {
+        description
+          "destination of tunnel";
+        tailf:alt-name "destination";
+        choice dest-choice {
+          leaf ipv4 {
+            description "ip address or host name";
+            tailf:cli-full-command;
+            tailf:cli-drop-node-name;
+            must "not (../../ios-tun:mode/ios-tun:gre-config/ios-tun:multipoint)
+                  and not (../../ios-tun:mode/ios-tun:ipv6ip-config/ios-tun:auto-6rd)
+                  and not (../../ios-tun:mode/ios-tun:ipv6ip-config/ios-tun:auto-6to4)
+                  and not (../../ios-tun:mode/ios-tun:ipv6ip-config/ios-tun:auto-isatap)" {
+              error-message "tunnel destination and this mode cannot be configured together.";
+            }
+            type inet:ipv4-address{
+              tailf:info "A.B.C.D;;Destination address";
+            }
+          }
+          leaf ipv6 {
+            description "IPv6 address";
+            tailf:cli-full-command;
+            tailf:cli-drop-node-name;
+            type inet:ipv6-address {
+              tailf:info "X:X:X:X::X;;IPv6 address";
+            }
+          }
+          leaf "dynamic" {
+            description "destination dynamic";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf host {
+            description "host name";
+            tailf:cli-drop-node-name;
+            type string;
+          }
+        }
+      }
       container destination {
         description
           "destination of tunnel";
+        status deprecated;
+        tailf:hidden deprecated;
+        tailf:cli-ignore-modified;
         leaf ipaddress-or-host {
+          status deprecated;
+          tailf:hidden deprecated;
+          tailf:cli-ignore-modified;
+          tailf:cli-drop-node-name;
           type inet:host;
         }
         container list {
           description
             "List of Tunnel destinations";
+          status deprecated;
+          tailf:hidden deprecated;
+          tailf:cli-ignore-modified;
           container mpls {
             description
               "MPLS destination list";
+            status deprecated;
+            tailf:hidden deprecated;
+            tailf:cli-ignore-modified;
               container traffic-eng {
                 description
                   "MPLS Traffic Engineering destination list";
+                status deprecated;
+                tailf:hidden deprecated;
+                tailf:cli-ignore-modified;
                 leaf name {
                   description
                     "Specify a destination list by name";
+                  status deprecated;
+                  tailf:hidden deprecated;
+                  tailf:cli-ignore-modified;
                   type string;
                 }
               }
@@ -214,14 +438,22 @@ module Cisco-IOS-XE-tunnel {
          }
       }
 
+
       // interface * / tunnel dst-port
       container dst-port {
         description
           "tunnel destination port";
+        when "../ios-tun:mode/ios-tun:udp-config";
+        tailf:cli-diff-delete-before "../ios-tun:mode/ios-tun:udp-config";
+        tailf:cli-diff-create-after "../ios-tun:mode/ios-tun:udp-config";
         choice dst-port-choice {
           case port-num-case {
             leaf port-num {
-              type uint16;
+              tailf:cli-drop-node-name;
+              type uint16 {
+                range "0..65535";
+                tailf:info "<0-65535>;;tunnel destination port number";
+              }
             }
           }
           case dynamic-case {
@@ -244,6 +476,9 @@ module Cisco-IOS-XE-tunnel {
           leaf output {
             description
               "apply QoS service policy in the output direction";
+            must "(/ios:native/ios:policy/ios-policy:policy-map[ios-policy:name = current()])" {
+              error-message "Warning: This control policy-map does not exist";
+            }
             type string;
           }
         }
@@ -253,6 +488,12 @@ module Cisco-IOS-XE-tunnel {
       leaf entropy {
         description
           "Compute hash (entropy) on payload and propagate to tunnel header";
+        tailf:cli-full-command;
+        must "((../ios-tun:mode/ios-tun:gre-config)
+              or (../ios-tun:mode/ios-tun:ethernet-config/ios-tun:gre/ios-tun:ipv4)
+              or (../ios-tun:mode/ios-tun:ethernet-config/ios-tun:gre/ios-tun:ipv6))" {
+          error-message "Tunnel entropy and this tunnel mode are not allowed.";
+        }
         type empty;
       }
 
@@ -282,30 +523,100 @@ module Cisco-IOS-XE-tunnel {
       leaf key {
         description
           "security or selector key";
+        tailf:cli-diff-delete-before "../mode";
+        tailf:cli-diff-create-after "../mode";
+        tailf:cli-full-command;
         type uint64 {
           range "0..4294967295";
+          tailf:info "<0-4294967295>;;key";
         }
       }
+
       // interface * / tunnel mode
       container mode {
         description
           "tunnel encapsulation method";
+        tailf:cli-full-no;
         choice mode-choice {
-          // interface * / tunnel mode ipsec
+          // interface * / tunnel mode aurp
+          leaf aurp {
+            description
+              "AURP TunnelTalk AppleTalk encapsulation";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf cayman {
+            description
+              "Cayman TunnelTalk AppleTalk encapsulation";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf dvmrp {
+            description
+              "DVMRP multicast tunnel";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf eon {
+            description
+              "EON compatible CLNS tunnel";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf iptalk {
+            description
+              "Apple IPTalk encapsulation";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf nos {
+            description
+              "IP over IP encapsulation (KA9Q/NOS compatible)";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf rbscp {
+            description
+              "RBSCP in IP tunnel";
+            tailf:cli-full-command;
+            type empty;
+          }
+          leaf tag-switching {
+            description
+              "IP over Tag Switching encapsulation";
+            tailf:cli-full-command;
+            type empty;
+          }
           container ipsec {
             description
               "IPSec tunnel encapsulation";
+            tailf:cli-reset-container;
             container ipv4 {
               description
                 "over IPv4";
+              tailf:cli-delete-when-empty;
               presence "true";
+              leaf v6-overlay {
+                type empty;
+              }
             }
             container ipv6 {
               description
                 "over IPv6";
+              tailf:cli-delete-when-empty;
               presence "true";
+              leaf v4-overlay {
+                type empty;
+              }
+            }
+            leaf dual-overlay {
+              description
+                "over dual-overlay";
+              tailf:cli-full-command;
+              type empty;
             }
           }
+
           // interface * / tunnel mode mpls
           container mpls {
             description
@@ -313,6 +624,7 @@ module Cisco-IOS-XE-tunnel {
             container traffic-eng {
               description
                 "Traffic engineering tunnels";
+              tailf:cli-delete-when-empty;
               presence "true";
               leaf multilsp {
                 description
@@ -327,40 +639,94 @@ module Cisco-IOS-XE-tunnel {
             }
           }
           // interface * / tunnel mode ethernet
-          container ethernet {
+          container ethernet-config {
             description
               "Ethernet over gre";
-            leaf gre {
-              description
-                "Ethernet over gre";
-              type enumeration {
-                enum "ipv4";
-                enum "ipv6";
+            tailf:alt-name "ethernet";
+            choice eth-mode-choice {
+              container gre {
+                description "Ethernet over GRE";
+                choice address-family-choice {
+                  container ipv4 {
+                    description "over ipv4";
+                    presence "true";
+                    tailf:cli-reset-container;
+                    leaf "p2p" {
+                      description "point-to-point over ipv4";
+                      tailf:cli-full-command;
+                      type empty;
+                    }
+                  }
+                  container ipv6 {
+                    description "over ipv6";
+                    presence "true";
+                    tailf:cli-reset-container;
+                    leaf "p2p" {
+                      description "point-to-point over ipv6";
+                      tailf:cli-full-command;
+                      type empty;
+                    }
+                  }
+                }
+              }
+              container l2tpv3 {
+                description "Use L2TPv3 encapsulation";
+                leaf "manual" {
+                  description "Manually configure L2TP parameters";
+                  tailf:cli-full-command;
+                  type empty;
+                }
               }
             }
           }
-          // interface * / tunnel mode gre
-          container gre {
+          container ethernet {
             description
-              "generic route encapsulation protocol";
-            leaf ip {
-              description
-                "over IP";
-              type empty;
-            }
-            leaf ipv6 {
-              description
-                "over IPv6";
-              type empty;
-            }
-            container multipoint {
+              "Ethernet over gre";
+            status deprecated;
+            tailf:hidden deprecated;
+            tailf:cli-ignore-modified;
+            container gre {
               description
-                "over IPv4 (multipoint)";
-              presence "true";
-              leaf ipv6 {
-                description
-                  "over IPv6";
-                type empty;
+                "Ethernet over gre";
+              status deprecated;
+              tailf:hidden deprecated;
+              tailf:cli-ignore-modified;
+              container ipv4 {
+                description "Ethernet over GRE ipv4";
+                status deprecated;
+                tailf:hidden deprecated;
+                tailf:cli-ignore-modified;
+                tailf:cli-delete-when-empty;
+                presence "true";
+                leaf "p2p" {
+                    must "count(/ios:native/ios:interface/ios:Tunnel/tunnel/mode/ethernet/gre/ipv4/p2p) + count (/ios:native/ios:interface/ios:Tunnel/tunnel/mode/ethernet/gre/ipv6/p2p) <= 10" {
+                        error-message "Too many EoGRE P2P interfaces";
+                    }
+                    description "point-to-point over ipv4";
+                    status deprecated;
+                    tailf:hidden deprecated;
+                    tailf:cli-ignore-modified;
+                    tailf:cli-full-command;
+                    type empty;
+                }
+              }
+              container ipv6 {
+                description "Ethernet over GRE ipv6";
+                presence "true";
+                status deprecated;
+                tailf:hidden deprecated;
+                tailf:cli-ignore-modified;
+                leaf "p2p" {
+                    must "count(/ios:native/ios:interface/ios:Tunnel/tunnel/mode/ethernet/gre/ipv4/p2p) + count (/ios:native/ios:interface/ios:Tunnel/tunnel/mode/ethernet/gre/ipv6/p2p) <= 10" {
+                        error-message "Too many EoGRE P2P interfaces";
+                    }
+                    description "point-to-point over ipv6";
+                    status deprecated;
+                    tailf:hidden deprecated;
+                    tailf:cli-ignore-modified;
+                    tailf:cli-full-command;
+                    type empty;
+                }
               }
             }
           }
@@ -369,9 +735,15 @@ module Cisco-IOS-XE-tunnel {
             description
               "IP over IP encapsulation";
             presence "true";
+            tailf:cli-reset-container;
+            tailf:cli-compact-syntax;
+            tailf:cli-sequence-commands {
+              tailf:cli-reset-all-siblings;
+            }
             leaf decapsulate-any {
               description
                 "Incoming traffic only";
+              tailf:cli-full-command;
               type empty;
             }
           }
@@ -379,84 +751,431 @@ module Cisco-IOS-XE-tunnel {
           leaf ipv6 {
             description
               "Generic packet tunneling in IPv6";
+            tailf:cli-full-command;
             type empty;
           }
           // interface * / tunnel mode ipv6ip
+          container ipv6ip-config {
+            description
+              "IPv6 over IP encapsulation";
+            tailf:alt-name "ipv6ip";
+            presence "true";
+            tailf:cli-reset-container;
+            choice ipv6ip-choice {
+              leaf auto-6rd {
+                tailf:alt-name "6rd";
+                description
+                  "IPv6 automatic tunnelling using 6rd";
+                tailf:cli-full-command;
+                type empty;
+              }
+              leaf auto-6to4 {
+                tailf:alt-name "6to4";
+                description
+                  "IPv6 automatic tunnelling using 6to4";
+                tailf:cli-full-command;
+                type empty;
+              }
+              leaf auto-isatap {
+                tailf:alt-name "isatap";
+                description
+                  "IPv6 automatic tunnelling using ISATAP";
+                tailf:cli-full-command;
+                type empty;
+              }
+            }
+          }
           container ipv6ip {
             description
               "IPv6 over IP encapsulation";
+            status deprecated;
+            tailf:hidden deprecated;
+            tailf:cli-ignore-modified;
             presence "true";
             leaf auto-6rd {
+              tailf:alt-name "6rd";
               description
                 "IPv6 automatic tunnelling using 6rd";
+              tailf:cli-full-command;
               type empty;
             }
             leaf auto-6to4 {
+              tailf:alt-name "6to4";
               description
                 "IPv6 automatic tunnelling using 6to4";
+              tailf:cli-full-command;
               type empty;
             }
             leaf isatap {
               description
                 "IPv6 automatic tunnelling using ISATAP";
+              tailf:cli-full-command;
+              type empty;
+            }
+          }
+          // interface * / tunnel mode gre
+          container gre-config {
+            description
+              "generic route encapsulation protocol";
+            tailf:alt-name "gre";
+            choice tunmode-choice {
+              leaf ip {
+                description "over IP";
+                tailf:cli-full-command;
+                tailf:cli-boolean-no;
+                type boolean;
+                default "true";
+                tailf:cli-trim-default;
+              }
+              leaf ipv6 {
+                description "over IPv6";
+                tailf:cli-full-command;
+                type empty;
+              }
+              container multipoint {
+                description "mode Multipoint";
+                presence "true";
+                tailf:cli-reset-container;
+                leaf "ipv6" {
+                  description "over IPv6 (multipoint)";
+                  tailf:cli-full-command;
+                  type empty;
+                }
+              }
+            }
+          }
+          container gre {
+            description
+              "generic route encapsulation protocol";
+            status deprecated;
+            tailf:hidden deprecated;
+            tailf:cli-ignore-modified;
+            leaf ip {
+              description
+                "over IP";
+              tailf:cli-full-command;
               type empty;
             }
+            leaf ipv6 {
+              description
+                "over IPv6";
+              tailf:cli-full-command;
+              type empty;
+            }
+            container multipoint {
+              description
+                "over IPv4 (multipoint)";
+              presence "true";
+              leaf ipv6 {
+                description
+                  "over IPv6";
+                tailf:cli-full-command;
+                type empty;
+              }
+            }
           }
           // interface * / tunnel mode udp
+          container udp-config {
+            description
+              "UDP encapsulation protocol";
+            tailf:alt-name "udp";
+            uses tunnel-mode-ip-ipv6-opt-grouping;
+          }
           leaf udp {
             description
               "UDP encapsulation protocol";
+            status deprecated;
+            tailf:hidden deprecated;
+            tailf:cli-ignore-modified;
             type enumeration {
               enum "ip";
               enum "multipoint";
             }
           }
+          // interface * / tunnel mode vxlan
+          container vxlan {
+            description
+              "VXLAN encapsulation";
+            choice tunmode-choice {
+              container ipv4 {
+                description "over ip";
+                choice tun-ip-vxlan-choice {
+                  leaf default-mac {
+                    description
+                      "default mac";
+                    tailf:cli-full-command;
+                    tailf:cli-delete-container-on-delete;
+                    type empty;
+                  }
+                  container src-mac {
+                    description
+                      "Source Mac address";
+                    tailf:cli-drop-node-name;
+                    tailf:cli-compact-syntax;
+                    tailf:cli-sequence-commands;
+                    leaf source-mac {
+                      description
+                        "Source Mac address";
+                      tailf:cli-drop-node-name;
+                      tailf:cli-incomplete-command;
+                      tailf:cli-delete-container-on-delete;
+                      type ios-types:cisco-mac-address-type {
+                        tailf:info "H.H.H;; Source MAC address";
+                      }
+                    }
+                    leaf dst-mac {
+                      description
+                        "Destination Mac address";
+                      tailf:cli-drop-node-name;
+                      tailf:cli-delete-container-on-delete;
+                      must "../ios-tun:source-mac" {
+                        error-message "Delete src-mac and dest-mac together";
+                      }
+                      type ios-types:cisco-mac-address-type {
+                        tailf:info "H.H.H;; Destination MAC address";
+                      }
+                    }
+                  }
+                }
+              }
+              container ipv6 {
+                description "over IPv6";
+                choice tun-ip-vxlan-choice {
+                  leaf default-mac {
+                    description
+                      "default mac";
+                    tailf:cli-full-command;
+                    tailf:cli-delete-container-on-delete;
+                    type empty;
+                  }
+                  container src-mac {
+                    description
+                      "Source Mac address";
+                    tailf:cli-drop-node-name;
+                    tailf:cli-compact-syntax;
+                    tailf:cli-sequence-commands;
+                    leaf source-mac {
+                      description
+                        "Source Mac address";
+                      tailf:cli-drop-node-name;
+                      tailf:cli-incomplete-command;
+                      tailf:cli-delete-container-on-delete;
+                      type ios-types:cisco-mac-address-type {
+                        tailf:info "H.H.H;; Source MAC address";
+                      }
+                    }
+                    leaf dst-mac {
+                      description
+                        "Destination Mac address";
+                      tailf:cli-drop-node-name;
+                      tailf:cli-delete-container-on-delete;
+                      type ios-types:cisco-mac-address-type {
+                        tailf:info "H.H.H;; Destination MAC address";
+                      }
+                    }
+                  }
+                }
+              }
+              container multipoint {
+                choice tunmode-mul-choice {
+                  leaf "ip" {
+                    description "over IPv4 (multipoint)";
+                    tailf:cli-full-command;
+                    tailf:cli-delete-container-on-delete;
+                    tailf:cli-drop-node-name;
+                    type empty;
+                  }
+                  leaf "ipv6" {
+                    description "over IPv6 (multipoint)";
+                    tailf:cli-full-command;
+                    tailf:cli-delete-container-on-delete;
+                    type empty;
+                  }
+                }
+              }
+            }
+          }
+          // interface * / tunnel mode vxlan-gpe
+          container vxlan-gpe {
+            description
+              "VXLAN gpe encapsulation";
+            choice tunmode-choice {
+              leaf ipv4 {
+                description "over ip";
+                tailf:cli-full-command;
+                tailf:cli-delete-container-on-delete;
+                type empty;
+              }
+              leaf ipv6 {
+                description "over IPv6";
+                tailf:cli-full-command;
+                tailf:cli-delete-container-on-delete;
+                type empty;
+              }
+              container multipoint {
+                choice tunmode-mul-choice {
+                  leaf "ip" {
+                    description "over IPv4 (multipoint)";
+                    tailf:cli-full-command;
+                    tailf:cli-drop-node-name;
+                    tailf:cli-delete-container-on-delete;
+                    type empty;
+                  }
+                  leaf "ipv6" {
+                    description "over IPv6 (multipoint)";
+                    tailf:cli-full-command;
+                    tailf:cli-delete-container-on-delete;
+                    type empty;
+                  }
+                }
+              }
+            }
+          }
+          // interface * / tunnel mode sdwan
+          leaf sdwan {
+          if-feature ios-features:sdwan;
+            description
+              "SDWAN";
+            tailf:cli-full-command;
+            must "../../source = /ios:native/ios:interface/ios:Tunnel/ios:ip/ios:unnumbered" {
+              error-message "ip unnumbered and tunnel source are not same for tunnel mode sdwan";
+            }
+            tailf:cli-diff-delete-before "../../source";
+            tailf:cli-diff-create-after "../../source";
+            type empty;
+          }
         }
       }
+
       // interface * / tunnel network-id
       container network-id {
         description
           "Set network-id";
+        tailf:cli-compact-syntax;
+        tailf:cli-delete-when-empty;
+        tailf:cli-reset-container;
+        tailf:cli-incomplete-command;
+        tailf:cli-sequence-commands {
+          tailf:cli-reset-all-siblings;
+        }
         leaf id {
-          type uint16;
+          description
+            "Set network-id";
+          tailf:cli-incomplete-command;
+          tailf:cli-delete-container-on-delete;
+          tailf:cli-drop-node-name;
+          must "../ios-tun:nexthop" {
+            error-message "Delete network-id and nexthop together";
+          }
+          type uint16 {
+            range "1..65535";
+            tailf:info "<1-65535>;;network id";
+          }
         }
         leaf nexthop {
           description
             "nexthop";
+          must "../ios-tun:id" {
+            error-message "Delete nexthop and network-id value together";
+          }
           type inet:ipv4-address;
         }
+        leaf weight {
+          description
+            "weight";
+          tailf:cli-optional-in-sequence;
+          must "../ios-tun:nexthop" {
+            error-message "Delete weight with the network-id deletion";
+          }
+          type uint16 {
+            range "1..65535";
+            tailf:info "<1-65535>;;weight";
+          }
+        }
         leaf qos {
           description
             "QoS profile";
+          must "../ios-tun:nexthop" {
+            error-message "Delete QoS profile first with the network-id deletion";
+          }
           type string;
         }
-        leaf weight {
-          description
-            "weight";
-          type uint16;
-        }
       }
+
       // interface * / tunnel path-mtu-discovery
       container path-mtu-discovery {
         description
           "Enable Path MTU Discovery on tunnel";
         presence "true";
+        tailf:cli-delete-when-empty;
+        tailf:cli-display-separated;
+        leaf age-timer {
+          description
+            "Set PMTUD aging timer";
+          tailf:cli-full-command;
+          type union {
+            type uint8 {
+              range "10..30";
+              tailf:info "<10-30>;;Aging time";
+            }
+            type enumeration {
+              enum "infinite";
+            }
+          }
+        }
+        leaf min-mtu {
+          description
+            "Min pmtud mtu allowed";
+          tailf:cli-full-command;
+          type uint16 {
+            range "92..65535";
+            tailf:info "<92-65535>;;Bytes";
+          }
+        }
       }
+
       // interface * / tunnel protection
       container protection {
         description
           "Enable tunnel protection";
         // @mount Cisco-IOS-XE-crypto
       }
+
+      // interface * / tunnel tsp-hop
+      leaf tsp-hop {
+        description
+          "Define a TSP hop";
+        tailf:cli-full-command;
+        type empty;
+      }
       // interface * / tunnel mpls
       container mpls {
         uses ios-mpls:config-tunnel-mpls-grouping;
       }
 
       // interface * / tunnel raw-packet-interface
+      container raw-packet-interface-config {
+        description
+          "physical interface for all packets entering into be tunneled and for all packets entering the tunnel to exit";
+        tailf:alt-name "raw-packet-interface";
+        tailf:cli-compact-syntax;
+        tailf:cli-sequence-commands;
+        uses ios-ifc:interface-grouping;
+      }
+
       leaf raw-packet-interface {
         description
-          "physical interface for all packets entering itto be tunneled and for all packets entering the tunnel to exit";
+          "physical interface for all packets entering into be tunneled and for all packets entering the tunnel to exit";
+        status deprecated;
+        tailf:hidden deprecated;
+        tailf:cli-ignore-modified;
+        type string;
+      }
+
+      // interface * / tunnel rrr
+      leaf rrr {
+        description
+          "RRR configuration";
         type string;
       }
 
@@ -467,58 +1186,148 @@ module Cisco-IOS-XE-tunnel {
         leaf ack-split {
           description
             "ACK splitting";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type uint8 {
             range "1..32";
+            tailf:info "<1-32>;;Split number";
           }
         }
         leaf delay {
           description
             "enable delay";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type empty;
         }
+        leaf drop-scale {
+          description
+            "Drop scale";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
+          type uint8 {
+            range "1..255";
+            tailf:info "<1-255>;;Drop scale";
+          }
+        }
+        leaf fuzz {
+          description
+            "Fuzz factor";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
+          type uint8 {
+            range "1..255";
+            tailf:info "<1-255>;;Fuzz factor";
+          }
+        }
+        leaf init-tsn {
+          description
+            "Initial TSN";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
+          type uint32 {
+            range "1..16777215";
+            tailf:info "<1-16777215>;;Initial TSN";
+          }
+        }
         leaf input-drop {
           description
             "max tunnel queue size (number of bw*delay)";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type uint8 {
             range "1..10";
+            tailf:info "<1-10>;;Number of bw*delay products";
           }
         }
         leaf long-drop {
           description
             "Drop non-transmitted packets w/excessive delay";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
+          type empty;
+        }
+        leaf order {
+          description
+            "release packets in order";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type empty;
         }
         leaf report {
           description
             "enable SCTP report chunk";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
+          type empty;
+        }
+        leaf scaling {
+          description
+            "ACK split scaling";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type empty;
         }
         leaf window-stuff {
           description
             "Window stuffing";
+          tailf:cli-full-command;
+          must "../../ios-tun:mode/ios-tun:rbscp" {
+            error-message "Only valid in tunnel rbscp mode";
+          }
           type uint8 {
             range "1..20";
+            tailf:info "<1-20>;;Window step up size";
           }
         }
       }
 
-      // interface * / tunnel snooping
+       // interface * / tunnel snooping
       leaf snooping {
         description
           "Snooping on tunnel";
+        tailf:cli-full-command;
+        when "../ios-tun:mode/ios-tun:ethernet-config";
         type enumeration {
           enum "enable";
         }
       }
 
-      // interface * / tunnel src-port
+     // interface * / tunnel src-port
       container src-port {
         description
           "tunnel source port";
+        when "../ios-tun:mode/ios-tun:udp-config";
+        tailf:cli-diff-delete-before "../ios-tun:mode/ios-tun:udp-config";
+        tailf:cli-diff-create-after "../ios-tun:mode/ios-tun:udp-config";
         choice src-port-choice {
           case port-num-case {
             leaf port-num {
-              type uint16;
+              tailf:cli-drop-node-name;
+              type uint16 {
+                range "0..65535";
+                tailf:info "<0-65535>;;Tunnel Source port number";
+              }
             }
           }
           case dynamic-case {
@@ -534,21 +1343,71 @@ module Cisco-IOS-XE-tunnel {
       // interface * / tunnel tos
       leaf tos {
         description
-          "set type of sevice byte";
-        type uint8;
+          "set type of service byte";
+        tailf:cli-full-command;
+        type uint8 {
+          range "0..255";
+          tailf:info "<0-255>;;tos";
+        }
+      }
+
+      // interface * / tunnel metadata
+      container metadata {
+        description
+          "metadata sharing options";
+        leaf src_vpn {
+          description
+            "enable metadata sharing src-vpn";
+          type empty;
+        }
       }
 
       // interface * / tunnel ttl
       leaf ttl {
         description
           "set time to live";
-        type uint8;
+        tailf:cli-full-command;
+        type uint8 {
+          range "1..255";
+          tailf:info "<1-255>;;ttl";
+        }
+        default "255";
+        tailf:cli-trim-default;
       }
 
       // interface * / tunnel udlr
+      container udlr-config {
+        description
+          "associate tunnel with unidirectional interface";
+        tailf:alt-name "udlr";
+        leaf address-resolution {
+          description
+            "Enable ARP and NHRP over a UDLR Tunnel";
+          tailf:cli-full-command;
+          must "(../ios-tun:send-only)" {
+            error-message "UDL Tunnel ARP is for transmit-only tunnel interfaces";
+          }
+          type empty;
+        }
+        choice udlr-choice {
+          container receive-only {
+            description
+              "Tunnel is receive-only capable";
+            uses ios-ifc:interface-grouping;
+          }
+          container send-only {
+            description
+              "Tunnel is send-only capable";
+            uses ios-ifc:interface-grouping;
+          }
+        }
+      }
       container udlr {
         description
           "associate tunnel with unidirectional interface";
+         status deprecated;
+        tailf:hidden deprecated;
+        tailf:cli-ignore-modified;
         leaf address-resolution {
           description
             "Enable ARP and NHRP over a UDLR Tunnel";
@@ -566,27 +1425,227 @@ module Cisco-IOS-XE-tunnel {
         }
       }
 
-      // interface * / tunnel vlan
+      // interface * / tunnel vxlan
+      container vxlan {
+        description "Tunnel vxlan network";
+        tailf:cli-compact-syntax;
+        when "../ios-tun:mode/ios-tun:vxlan";
+        leaf vni {
+          description
+            "Tunnel vxlan network identifier";
+          tailf:cli-full-command;
+          type uint32 {
+            range "1..16777215";
+            tailf:info "<1-16777215>;;Tunnel vxlan network identifier number";
+          }
+        }
+        container src-port-range {
+          description
+            "Tunnel vxlan source port range";
+          tailf:cli-drop-node-name;
+          tailf:cli-compact-syntax;
+          tailf:cli-sequence-commands {
+            tailf:cli-reset-all-siblings;
+          }
+          leaf source-port-range {
+            description
+              "Tunnel vxlan source port number start";
+            tailf:cli-incomplete-command;
+            type uint16 {
+              range "1..65535";
+              tailf:info "<1-65535>;;Tunnel vxlan source port number start";
+            }
+          }
+          leaf source-port-end {
+            description
+              "Tunnel vxlan source port number end";
+            tailf:cli-drop-node-name;
+            tailf:cli-full-command;
+            type uint16 {
+              range "1..65535";
+              tailf:info "<1-65535>;;Tunnel vxlan source port number end";
+            }
+          }
+        }
+      }
+
+     // interface * / tunnel vlan
       container vlan {
         description
           "Set vlan-id for ethernet over gre mode";
+        when "../ios-tun:mode/ios-tun:ethernet-config";
+        tailf:cli-compact-syntax;
+        tailf:cli-sequence-commands;
         leaf id1 {
-          type uint16;
+          tailf:cli-drop-node-name;
+          type uint16 {
+            range "1..4094";
+            tailf:info "<1-4094>;;vlan-id";
+          }
         }
         leaf hyphen {
+          tailf:alt-name "-";
+          tailf:cli-break-sequence-commands;
           type empty;
         }
         leaf id2 {
-          type uint16;
+          tailf:cli-drop-node-name;
+          type uint16 {
+            range "1..4094";
+            tailf:info "<1-4094>;;vlan-id";
+          }
         }
       }
-
       // interface * / tunnel vrf
+      container vrf-config {
+        description "set tunnel vrf membership";
+        tailf:alt-name "vrf";
+        tailf:cli-compact-syntax;
+        leaf multiplexing {
+          description
+            "multiple vrf's multiplexing";
+          tailf:cli-full-command;
+          type empty;
+          must "not (../vrf-common/vrf)" {
+            error-message "common vrf configured already. Remove it and then try again";
+          }
+          must "not (../vrf-egress/vrf) and not (../vrf-ingress/vrf)" {
+            error-message "egress-only ingress-only vrf configured already. Remove it and then try again";
+          }
+        }
+        container vrf-common {
+          description "vrf common config";
+          tailf:cli-drop-node-name;
+          leaf vrf {
+            tailf:cli-full-command;
+            tailf:cli-drop-node-name;
+            type string;
+            tailf:cli-diff-delete-before "/ios:native/ios:interface/ios:Tunnel/ios-tun:tunnel/ios-tun:vrf-config/ios-tun:multiplexing";
+            tailf:cli-diff-delete-before "/ios:native/ios:vrf/ios:definition/ios:name" {
+              tailf:cli-when-target-delete;
+            }
+            tailf:cli-diff-delete-before "/ios:native/ios:ip/ios:vrf/ios:name" {
+              tailf:cli-when-target-delete;
+            }
+            tailf:cli-diff-create-after "/ios:native/ios:vrf/ios:definition/ios:name" {
+              tailf:cli-when-target-set;
+            }
+            tailf:cli-diff-create-after "/ios:native/ios:ip/ios:vrf/ios:name" {
+              tailf:cli-when-target-set;
+            }
+            must "/ios:native/ios:vrf/ios:definition[ios:name=current()]
+                  or /ios:native/ios:ip/ios:vrf[ios:name=current()]" {
+              error-message "VRF is not configured";
+            }
+            must "not (../../vrf-egress/vrf) and not (../../vrf-ingress/vrf)" {
+              error-message "egress-only ingress-only vrf configured already. Remove it and then try again";
+            }
+            must "not (../../multiplexing)" {
+              error-message "vrf multiplexing configured already. Remove it and then try again";
+            }
+          }
+        }
+        container vrf-egress {
+          description "egress-only";
+          tailf:cli-drop-node-name;
+          tailf:cli-sequence-commands;
+          tailf:cli-compact-syntax;
+          leaf vrf {
+            tailf:cli-drop-node-name;
+            type string;
+            must "not (../../vrf-common/vrf) and not (../../vrf-ingress/vrf=current())" {
+              error-message "ingress-only and egress-only vrf should not be same";
+            }
+            must "not (../../multiplexing)" {
+              error-message "vrf multiplexing configured already. Remove it and then try again";
+            }
+            must "/ios:native/ios:vrf/ios:definition[ios:name=current()]
+                or /ios:native/ios:ip/ios:vrf[ios:name=current()]" {
+              error-message "VRF is not configured";
+            }
+          }
+          leaf egress-only {
+            type empty;
+          }
+        }
+        container vrf-ingress {
+          description "ingress-only";
+          tailf:cli-drop-node-name;
+          tailf:cli-sequence-commands;
+          tailf:cli-compact-syntax;
+          leaf vrf {
+            tailf:cli-drop-node-name;
+            type string;
+            must "not (../../vrf-common/vrf) and not (../../vrf-egress/vrf=current())" {
+              error-message "ingress-only and egress-only vrf should not be same";
+            }
+            must "not (../../multiplexing)" {
+              error-message "vrf multiplexing configured already. Remove it and then try again";
+            }
+            must "/ios:native/ios:vrf/ios:definition[ios:name=current()]
+                or /ios:native/ios:ip/ios:vrf[ios:name=current()]" {
+              error-message "VRF is not configured";
+            }
+          }
+          leaf ingress-only {
+            type empty;
+          }
+        }
+      }
+      // interface * / route-via <interface> preferred | mandatory
+      container route-via {
+        description
+          "Select subset of routes for tunnel transport";
+        tailf:cli-sequence-commands;
+        tailf:cli-compact-syntax;
+        leaf interface {
+          tailf:cli-drop-node-name;
+          tailf:cli-incomplete-command;
+          description
+            "Routing interface for tunnel packets";
+          must "starts-with(., 'Dialer') and "
+               + " (number(substring-after(., 'Dialer')) = /ios:native/ios:interface/ios:Dialer/ios:name)"
+               + " or not (starts-with(., 'Dialer'))" {
+               error-message "To use a Dialer interface, the Dialer interface has to be configured 1st, vice versa for delete";
+          }
+          type string;
+        }
+        choice route-via-choice {
+          case preferred-case {
+            leaf preferred {
+              description
+                "Preferred route, if not available, use any route";
+              type empty;
+            }
+          }
+          case mandatory-case {
+            leaf mandatory {
+              description
+                "Mandatory route, if not available, drop traffic";
+              type empty;
+            }
+          }
+        }
+      }
       leaf vrf {
         description
           "set tunnel vrf membership";
+        status deprecated;
+        tailf:hidden deprecated;
+        tailf:cli-ignore-modified;
         type string;
       }
+      leaf mpls-ip-only {
+        description
+          "Copy DF bit from MPLS header to outer GRE";
+          must "../ios-tun:path-mtu-discovery" {
+            error-message "tunnel path-mtu-discovery needs to be configured before configuring mpls-ip-only, vice-versa for delete";	
+	  }
+        tailf:cli-diff-create-after "../ios-tun:path-mtu-discovery";
+        tailf:cli-diff-delete-before "../ios-tun:path-mtu-discovery";  
+        tailf:cli-full-command;
+        type empty;
+      }
     }
   }
 
diff --git a/ncdiff/src/yang/ncdiff/tests/yang/jon.yang b/ncdiff/src/yang/ncdiff/tests/yang/jon.yang
index 4544704..00c61df 100644
--- a/ncdiff/src/yang/ncdiff/tests/yang/jon.yang
+++ b/ncdiff/src/yang/ncdiff/tests/yang/jon.yang
@@ -81,4 +81,4 @@ module jon {
       }
     }
   }
-}
\ No newline at end of file
+}