This is the initial commit of the netconf server code.  It consists
of the following:
1) The server is built using Twisted Conch
2) It adapted an existing opensource netconf server (https://github.com/choppsv1/netconf)
   to handle some low-level protocols.  The adaptation is mostly around
   using Twisted Conch instead of Python Threads
3) A microservice to interface with Voltha on the SB and Netconf client on
   the NB
4) A set of credentials for the server and clients.  At this time these
   credentials are local and in files.  Additional work is required to
   secure these files
5) A rough-in to handle the rpc requests from Netconf clients
6) Code for initial handshaking is in place (hello)

Change-Id: I1ca0505d0ac35ff06066b107019ae87ae30e38f8
diff --git a/netconf/utils.py b/netconf/utils.py
new file mode 100644
index 0000000..455a395
--- /dev/null
+++ b/netconf/utils.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-#
+#
+# March 31 2015, Christian Hopps <chopps@gmail.com>
+#
+# Copyright (c) 2015, Deutsche Telekom AG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from __future__ import absolute_import, division, unicode_literals, \
+    print_function, nested_scopes
+from netconf import NSMAP
+import copy
+from lxml import etree
+
+
+# Tries to somewhat implement RFC6241 filtering
+
+
+def qname(tag):
+    try:
+        return etree.QName(tag)
+    except ValueError:
+        prefix, base = tag.split(":")
+        return etree.QName(NSMAP[prefix], base)
+
+
+def elm(tag, attrib=None, **extra):
+    if attrib is None:
+        attrib = dict()
+    return etree.Element(qname(tag), attrib, **extra)
+
+
+def leaf_elm(tag, value, attrib=None, **extra):
+    e = elm(tag, attrib, **extra)
+    e.text = str(value)
+    return e
+
+
+leaf = leaf_elm
+
+
+def subelm(pelm, tag, attrib=None, **extra):
+    if attrib is None:
+        attrib = dict()
+    return etree.SubElement(pelm, qname(tag), attrib, **extra)
+
+
+def is_selection_node(felm):
+    ftext = felm.text
+    return ftext is None or not ftext.strip()
+
+
+def filter_tag_match(filter_tag, elm_tag):
+    fqname = etree.QName(filter_tag)
+    eqname = qname(elm_tag)
+    if not fqname.namespace:
+        return fqname.localname == eqname.localname
+    return fqname == eqname
+
+
+def filter_node_match_no_value(filter_node, match_elm):
+    # First check to see if tag matches.
+    if not filter_tag_match(filter_node.tag, match_elm.tag):
+        return False
+
+    # Next check for attribute matches.
+    # XXX does this need to filter out namespace attributes?
+    if filter_node.attrib and filter_node.attrib != match_elm.attrib:
+        return False
+
+    return True
+
+
+def filter_node_match(filter_node, match_elm):
+    """Given a filter node element and a nodename and attribute dictionary
+    return true if the filter element matches the elmname, attributes and value
+    (if not None).
+
+    The filter element can use a wildcard namespace or a specific namespace
+    the attributes can be missing from the filter node but otherwise must match
+    and the value is only checked for a match if it is not None.
+    """
+    if not filter_node_match_no_value(filter_node, match_elm):
+        return False
+
+    # Finally check for matching value.
+    ftext = filter_node.text
+    if ftext is None:
+        return True
+
+    ftext = ftext.strip()
+    if not ftext:
+        return True
+
+    return ftext == match_elm.text
+
+
+def filter_leaf_values(fcontain_elm, dest_node, leaf_elms, append_to):
+    """Given a containment element (or None) verify that all leaf elements
+    in leaf_elms either match, have corresponding selection nodes (empty)
+    or are not present.
+
+    Additionally the correct leaf data will be added to dest_node, and dest_node
+    will be appended to append_to if append_to is not None.
+
+    The return value with be True, False, or a possibly empty set of selection/containment nodes
+    The only failing value is False, if True is returned then the caller should include all
+    containment sibling nodes, otherwise the caller should process the list of containment/selection
+    nodes.
+    """
+    children = fcontain_elm.getchildren() if fcontain_elm is not None else []
+    selected_elms = []
+    if not children:
+        selected_elms = leaf_elms
+
+    # Now look at all the leaf filter selector or match nodes
+    include_all_leaves = True
+    othernodes = []
+    for felm in children:
+        fchildren = felm.getchildren()
+        for lelm in leaf_elms:
+            if fchildren:
+                # Verify that this doesn't match a leaf node.
+                if filter_node_match_no_value(felm, lelm):
+                    # XXX this is an error we should raise some exception.
+                    return False
+                continue
+            elif filter_node_match(felm, lelm):
+                if not felm.text:
+                    # This was a selection node.
+                    include_all_leaves = False
+
+                selected_elms.append(lelm)
+                break
+        else:
+            if fchildren:
+                # This is OK we verified a containment filter didn't match leaf by getting here.
+                if felm.text:
+                    # XXX verify that there is no text on this node, report violation?
+                    return False
+
+                # Track selection/filter nodes
+                include_all_leaves = False
+                othernodes.append(felm)
+            elif not felm.text:
+                # This is OK as it means this is a selection node include it in othernodes
+                include_all_leaves = False
+                othernodes.append(felm)
+            else:
+                # We've exhausted all leaf elements to match this leaf filter so we failed.
+                return False
+
+    # Everything matched so add in the leaf data.
+    if append_to is not None:
+        append_to.append(dest_node)
+
+    if include_all_leaves:
+        dest_node.extend(leaf_elms)
+    else:
+        dest_node.extend(selected_elms)
+
+    if include_all_leaves:
+        return True
+    return othernodes
+
+
+def filter_containment_iter(fcontain_elm, dest_node, containment_nodes,
+                            leaf_elms, append_to):
+    """Given a containment filter node (or None) verify that all leaf elements
+    either match, have corresponding selection nodes (empty) or are not present.
+
+    If all leaf criteria are met then the iterator will return a triple of
+    (new_filter_node, new_dest_node, new_data). new_filter_node corresponds to the
+    matched containment node which is returned in new_dest_node, and new_data will be
+    an element corresponding to the passed in dest_node.
+
+    These should be processed by calling filter_containment_iter again.
+
+    Additionally the correct leaf data will be added to dest_node, and dest_node
+    will be appended to append_to if append_to is not None.
+
+    This implements RFC6241 section 6.2.5
+    """
+    # No containment node so add everything.
+    if fcontain_elm is None:
+        # Add in the leaf data
+        for e in leaf_elms:
+            dest_node.append(e)
+
+        # Append the match_node to the data
+        if append_to is not None:
+            append_to.append(dest_node)
+
+        for node in containment_nodes:
+            yield None, copy.copy(node), dest_node
+
+    else:
+        othernodes = filter_leaf_values(fcontain_elm, dest_node, leaf_elms,
+                                        append_to)
+        if othernodes is False:
+            # No match
+            pass
+        elif othernodes is True:
+            # All leaf values have matched and have been added and we should include all containers
+            for node in containment_nodes:
+                yield None, copy.copy(node), dest_node
+        else:
+            for felm in othernodes:
+                for node in containment_nodes:
+                    if filter_node_match_no_value(felm, node):
+                        yield felm, copy.copy(node), dest_node
+
+
+def filter_leaf_allows_add(filter_elm, tag, data, value):
+    if filter_leaf_allows(filter_elm, tag, value):
+        data.append(leaf_elm(tag, value))
+        return True
+    return False
+
+
+def filter_leaf_allows(filter_elm, xpath, value):
+    """Check the value at the xpath specified leaf matches the value.
+
+    If filter_elm is None then allow.
+    If there is no xpath element then allow if there are no other children.
+    XXX what about xpath that has embedded predicates!
+        perhaps what we want to call this is a normal path not an xpath.
+    """
+    if filter_elm is None:
+        return True
+
+    # If there are no children then allow everything.
+    if not filter_elm.getchildren():
+        return True
+
+    # No match or multiple matches not allowed for leaf.
+    flist = filter_elm.xpath(xpath, namespaces=NSMAP)
+    if not flist or len(flist) > 1:
+        return False
+    felm = flist[0]
+
+    # No children for leaf allowed (leaf)
+    if felm.getchildren():
+        return False
+
+    # Allowed if empty or if value matches.
+    if not felm.text or felm.text == str(value):
+        return True
+
+    return False
+
+
+def filter_list_iter(filter_list, key_xpath, keys):
+    """Return key, elm pairs that are allowed by keys using the values found using the given key_xpath"""
+    # If we have no filter elm then return all keys.
+    if filter_list is None:
+        for key in keys:
+            yield key, None
+
+    try:
+        # If this an element then make it a list of elements
+        filter_list.xpath  # pylint: disable=W0104
+        filter_list = [filter_list]
+    except AttributeError:
+        pass
+
+    for filter_elm in filter_list:
+        filter_elms = [x for x in
+                       filter_elm.xpath(key_xpath, namespaces=NSMAP)]
+        filter_keys = [x.text for x in filter_elms]
+        if not filter_keys:
+            for key in keys:
+                yield key, filter_elm
+        else:
+            # Now walk our keys returning any that are in the filter list.
+            for key in keys:
+                if key in filter_keys:
+                    yield key, filter_elm
+                    # try:
+                    #     idx = filter_keys.index(str(key))
+                    #     yield key, filter_elm
+                    # except ValueError:
+                    #     pass
+
+
+__author__ = 'Christian Hopps'
+__date__ = 'March 31 2015'
+__version__ = '1.0'
+__docformat__ = "restructuredtext en"