This commit consists of:
1) Parsing protobuf responses from Voltha into a dict which will also
   include the yang proto annotations
2) Converting a protobuf response into a yang-compatible XML format
3) Support for GET (no request params) for Voltha, VolthaInstance and VolthaInstances
4) Minor bug fixes
5) Testing done using the MG-Soft Netconf client

Change-Id: Ibb7f62a391e19b0240cc739919fccc689a316005
diff --git a/netconf/constants.py b/netconf/constants.py
index 7a503bc..e59a73e 100644
--- a/netconf/constants.py
+++ b/netconf/constants.py
@@ -54,6 +54,7 @@
     # Netconf namespaces
 	NETCONF_BASE_10 = "urn:ietf:params:netconf:base:1.0"
 	NETCONF_BASE_11 = "urn:ietf:params:netconf:base:1.1"
+	NETCONF_MONITORING = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
 
 	# XML
 	XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>"""
diff --git a/netconf/grpc_client/grpc_client.py b/netconf/grpc_client/grpc_client.py
index d65410e..bce6de3 100644
--- a/netconf/grpc_client/grpc_client.py
+++ b/netconf/grpc_client/grpc_client.py
@@ -40,6 +40,17 @@
     VolthaGlobalServiceStub
 from google.protobuf import empty_pb2
 from google.protobuf.json_format import MessageToDict, ParseDict
+from google.protobuf import descriptor
+import base64
+import math
+
+_INT64_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_INT64,
+                          descriptor.FieldDescriptor.CPPTYPE_UINT64])
+_FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT,
+                          descriptor.FieldDescriptor.CPPTYPE_DOUBLE])
+_INFINITY = 'Infinity'
+_NEG_INFINITY = '-Infinity'
+_NAN = 'NaN'
 
 log = get_logger()
 
@@ -281,17 +292,21 @@
             service_method = key.split('-')
             service = service_method[0]
             method = service_method[1]
-            stub = None
-            # if service == 'VolthaGlobalService':
-            #     stub = VolthaGlobalServiceStub
-            # elif service == 'VolthaLocalService':
-            #     stub = VolthaLocalServiceStub
-            # else:
-            #     raise  # Exception
+            if service == 'VolthaGlobalService':
+                stub = VolthaGlobalServiceStub
+            elif service == 'VolthaLocalService':
+                stub = VolthaLocalServiceStub
+            else:
+                raise  # Exception
+
+            log.info('voltha-rpc', service=service, method=method, req=req,
+                     depth=depth)
 
             res, metadata = yield self.invoke(stub, method, req, depth)
 
-            returnValue(MessageToDict(res, True, True))
+            # returnValue(MessageToDict(res, True, True))
+            returnValue(self.convertToDict(res))
+
         except Exception, e:
             log.error('failure', exception=repr(e))
 
@@ -339,3 +354,135 @@
                 log.exception(e)
 
             raise e
+
+    # Below is an adaptation of Google's MessageToDict() which includes
+    # protobuf options extensions
+
+    class Error(Exception):
+        """Top-level module error for json_format."""
+
+    class SerializeToJsonError(Error):
+        """Thrown if serialization to JSON fails."""
+
+    def _IsMapEntry(self, field):
+        return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and
+                field.message_type.has_options and
+                field.message_type.GetOptions().map_entry)
+
+    def convertToDict(self, message):
+        """Converts message to an object according to Proto3 JSON Specification."""
+
+        js = {}
+        return self._RegularMessageToJsonObject(message, js)
+
+    def get_yang_option(self, field):
+        opt = field.GetOptions()
+        yang_opt = {}
+        for fd, val in opt.ListFields():
+            if fd.full_name == 'voltha.yang_inline_node':
+                yang_opt['id'] = val.id
+                yang_opt['type'] = val.type
+                # Fow now, a max of 1 yang option is set per field
+                return yang_opt
+
+    def _RegularMessageToJsonObject(self, message, js):
+        """Converts normal message according to Proto3 JSON Specification."""
+        fields = message.ListFields()
+
+        try:
+            for field, value in fields:
+                # Check for options
+                yang_opt = self.get_yang_option(field)
+
+                name = field.name
+                if self._IsMapEntry(field):
+                    # Convert a map field.
+                    v_field = field.message_type.fields_by_name['value']
+                    js_map = {}
+                    for key in value:
+                        if isinstance(key, bool):
+                            if key:
+                                recorded_key = 'true'
+                            else:
+                                recorded_key = 'false'
+                        else:
+                            recorded_key = key
+                        js_map[recorded_key] = self._FieldToJsonObject(
+                            v_field, value[key])
+                    js[name] = js_map
+                elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
+                    # Convert a repeated field.
+                    js[name] = [self._FieldToJsonObject(field, k)
+                                for k in value]
+                else:
+                    # This specific yang option applies only to non-repeated
+                    # fields
+                    if yang_opt:  # Create a map
+                        js_map = {}
+                        js_map['yang_field_option'] = True
+                        js_map['yang_field_option_id'] = yang_opt['id']
+                        js_map['yang_field_option_type'] = yang_opt['type']
+                        js_map['name'] = name
+                        js_map[name] = self._FieldToJsonObject(field, value)
+                        js[name] = js_map
+                    else:
+                        js[name] = self._FieldToJsonObject(field, value)
+
+            # Serialize default value if including_default_value_fields is True.
+            message_descriptor = message.DESCRIPTOR
+            for field in message_descriptor.fields:
+                # Singular message fields and oneof fields will not be affected.
+                if ((
+                                    field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
+                                    field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
+                        field.containing_oneof):
+                    continue
+                name = field.name
+                if name in js:
+                    # Skip the field which has been serailized already.
+                    continue
+                if self._IsMapEntry(field):
+                    js[name] = {}
+                elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
+                    js[name] = []
+                else:
+                    js[name] = self._FieldToJsonObject(field,
+                                                       field.default_value)
+
+        except ValueError as e:
+            raise self.SerializeToJsonError(
+                'Failed to serialize {0} field: {1}.'.format(field.name, e))
+
+        return js
+
+    def _FieldToJsonObject(self, field, value):
+        """Converts field value according to Proto3 JSON Specification."""
+        if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
+            return self.convertToDict(value)
+        elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
+            enum_value = field.enum_type.values_by_number.get(value, None)
+            if enum_value is not None:
+                return enum_value.name
+            else:
+                raise self.SerializeToJsonError('Enum field contains an '
+                                                'integer value '
+                                                'which can not mapped to an enum value.')
+        elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
+            if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
+                # Use base64 Data encoding for bytes
+                return base64.b64encode(value).decode('utf-8')
+            else:
+                return value
+        elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
+            return bool(value)
+        elif field.cpp_type in _INT64_TYPES:
+            return str(value)
+        elif field.cpp_type in _FLOAT_TYPES:
+            if math.isinf(value):
+                if value < 0.0:
+                    return _NEG_INFINITY
+                else:
+                    return _INFINITY
+            if math.isnan(value):
+                return _NAN
+        return value
diff --git a/netconf/nc_rpc/base/close_session.py b/netconf/nc_rpc/base/close_session.py
index ce187cd..7e76b45 100644
--- a/netconf/nc_rpc/base/close_session.py
+++ b/netconf/nc_rpc/base/close_session.py
@@ -24,9 +24,8 @@
 
 class CloseSession(Rpc):
 
-    def __init__(self, rpc_request, rpc_method, grpc_client, session):
-        super(CloseSession, self).__init__(rpc_request, rpc_method,
-                                           grpc_client, session)
+    def __init__(self, request, grpc_client, session):
+        super(CloseSession, self).__init__(request, grpc_client, session)
         self._validate_parameters()
 
     def execute(self):
diff --git a/netconf/nc_rpc/base/commit.py b/netconf/nc_rpc/base/commit.py
index 8933dc3..06c1746 100644
--- a/netconf/nc_rpc/base/commit.py
+++ b/netconf/nc_rpc/base/commit.py
@@ -24,9 +24,8 @@
 
 class Commit(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(Commit, self).__init__(rpc_request, rpc_method, grpc_client,
-									 session)
+	def __init__(self, request, grpc_client, session):
+		super(Commit, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/copy_config.py b/netconf/nc_rpc/base/copy_config.py
index e287770..a59081d 100644
--- a/netconf/nc_rpc/base/copy_config.py
+++ b/netconf/nc_rpc/base/copy_config.py
@@ -23,9 +23,8 @@
 
 class CopyConfig(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(CopyConfig, self).__init__(rpc_request, rpc_method,
-										 grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(CopyConfig, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/delete_config.py b/netconf/nc_rpc/base/delete_config.py
index e267807..8ad5ea4 100644
--- a/netconf/nc_rpc/base/delete_config.py
+++ b/netconf/nc_rpc/base/delete_config.py
@@ -23,9 +23,8 @@
 
 class DeleteConfig(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(DeleteConfig, self).__init__(rpc_request, rpc_method,
-										   grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(DeleteConfig, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/discard_changes.py b/netconf/nc_rpc/base/discard_changes.py
index 57bdfed..961eea0 100644
--- a/netconf/nc_rpc/base/discard_changes.py
+++ b/netconf/nc_rpc/base/discard_changes.py
@@ -23,9 +23,8 @@
 
 class DiscardChanges(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(DiscardChanges, self).__init__(rpc_request, rpc_method,
-											 grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(DiscardChanges, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/edit_config.py b/netconf/nc_rpc/base/edit_config.py
index 469e957..92fa085 100644
--- a/netconf/nc_rpc/base/edit_config.py
+++ b/netconf/nc_rpc/base/edit_config.py
@@ -23,9 +23,8 @@
 
 class EditConfig(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(EditConfig, self).__init__(rpc_request, rpc_method,
-										 grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(EditConfig, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/get.py b/netconf/nc_rpc/base/get.py
index 7334dba..71449ad 100644
--- a/netconf/nc_rpc/base/get.py
+++ b/netconf/nc_rpc/base/get.py
@@ -28,55 +28,113 @@
 
 
 class Get(Rpc):
-    def __init__(self, rpc_request, rpc_method, voltha_method_ref, grpc_client,
-                 session):
-        super(Get, self).__init__(rpc_request, rpc_method, voltha_method_ref,
-                                        grpc_client, session)
+    def __init__(self, request, grpc_client, session):
+        super(Get, self).__init__(request, grpc_client, session)
         self._validate_parameters()
 
     @inlineCallbacks
     def execute(self):
-        log.info('get-request', session=self.session.session_id,
-                 method=self.rpc_method)
         if self.rpc_response.is_error:
             returnValue(self.rpc_response)
 
-        # TODO: for debugging only, assume we are doing a voltha-getinstance
-        self.voltha_method_ref='VolthaLocalService-GetVolthaInstance'
+        log.info('get-request', session=self.session.session_id,
+                 request=self.request)
+
+        rpc = self.get_voltha_rpc(self.request)
+        if not rpc:
+            log.info('unsupported-request', request=self.request)
+            self.rpc_response.is_error = True
+            self.rpc_response.node = ncerror.BadMsg(self.request)
+            return
+
         # Invoke voltha via the grpc client
-        res_dict = yield self.grpc_client.invoke_voltha_api(self.voltha_method_ref)
+        res_dict = yield self.grpc_client.invoke_voltha_api(rpc)
 
         # convert dict to xml
-        xml = dicttoxml.dicttoxml(res_dict, attr_type=False)
+        xml = dicttoxml.dicttoxml(res_dict, attr_type=True)
         log.info('voltha-info', res=res_dict, xml=xml)
 
         root_elem = self.get_root_element(xml)
-        root_elem.tag = 'data'
 
-        log.info('rpc-method', etree.tounicode(self.rpc_method,
-                                               pretty_print=True))
-
-        self.rpc_method.append(root_elem)
-        self.rpc_response.node = self.rpc_method
+        # Build the yang response
+        self.rpc_response.node = self.rpc_response.build_yang_response(
+            root_elem, self.request)
         self.rpc_response.is_error = False
 
         returnValue(self.rpc_response)
 
-
     def _validate_parameters(self):
         log.info('validate-parameters', session=self.session.session_id)
-        self.params = self.rpc_method.getchildren()
-        if len(self.params) > 1:
-            self.rpc_response.is_error = True
-            self.rpc_response.node = ncerror.BadMsg(self.rpc_request)
-            return
+        # Validate the GET command
+        if self.request:
+            try:
+                if self.request['command'] != 'get':
+                    self.rpc_response.is_error = True
+                    self.rpc_response.node = ncerror.BadMsg('No GET in get '
+                                                            'request')
 
-        if self.params and not filter_tag_match(self.params[0], C.NC_FILTER):
-            self.rpc_response.is_error = True
-            self.rpc_response.node = ncerror.UnknownElement(
-                self.rpc_request, self.params[0])
-            return
+                if self.request.has_key('filter'):
+                    if not self.request.has_key('class'):
+                        self.rpc_response.is_error = True
+                        self.rpc_response.node = ncerror.BadMsg(
+                            'Missing filter sub-element')
 
-        if not self.params:
-            self.params = [None]
+            except Exception as e:
+                self.rpc_response.is_error = True
+                self.rpc_response.node = ncerror.BadMsg(self.request)
+                return
 
+    def get_voltha_rpc(self, request):
+        if request.has_key('class'):
+            rpcs = self.rpc_request_mapping.get(request['class'])
+            if rpcs is None:
+                return None
+            for rpc in rpcs:
+                if request.has_key('subclass'):
+                    # search first for subclass
+                    if rpc['subclass'] and request['subclass'] == rpc[
+                        'subclass']:
+                        return rpc['rpc']
+
+            # If we are here then no subclass exists.  Just return the rpc
+            # associated with theNone subclass
+            for rpc in rpcs:
+                if rpc['subclass'] is None:
+                    return rpc['rpc']
+
+        return None
+
+    # Supported Get Methods
+    rpc_request_mapping = {
+        'Voltha': [
+            {'subclass': None,
+             'rpc': 'VolthaGlobalService-GetVoltha'
+             }],
+        'VolthaInstance': [
+            {'subclass': None,
+             'rpc': 'VolthaLocalService-GetVolthaInstance'
+             },
+            {'subclass': 'health',
+             'rpc': 'VolthaLocalService-GetHealth'
+             },
+            {'subclass': 'adapters',
+             'rpc': 'VolthaLocalService-ListAdapters'
+             },
+            {'subclass': 'logical_devices',
+             'rpc': 'VolthaLocalService-ListLogicalDevices'
+             },
+            {'subclass': 'devices',
+             'rpc': 'VolthaLocalService-ListDevices'
+             },
+            {'subclass': 'device_types',
+             'rpc': 'VolthaLocalService-ListDeviceTypes'
+             },
+            {'subclass': 'device_groups',
+             'rpc': 'VolthaLocalService-ListDeviceGroups'
+             },
+        ],
+        'VolthaInstances': [
+            {'subclass': None,
+             'rpc': 'VolthaGlobalService-ListVolthaInstances'
+             }],
+    }
diff --git a/netconf/nc_rpc/base/get_config.py b/netconf/nc_rpc/base/get_config.py
index 09f90b4..2fb8ad0 100644
--- a/netconf/nc_rpc/base/get_config.py
+++ b/netconf/nc_rpc/base/get_config.py
@@ -26,9 +26,8 @@
 
 class GetConfig(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(GetConfig, self).__init__(rpc_request, rpc_method,
-										grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(GetConfig, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/kill_session.py b/netconf/nc_rpc/base/kill_session.py
index c9a3352..16461cd 100644
--- a/netconf/nc_rpc/base/kill_session.py
+++ b/netconf/nc_rpc/base/kill_session.py
@@ -25,9 +25,8 @@
 
 class KillSession(Rpc):
 
-    def __init__(self, rpc_request, rpc_method, grpc_client, session):
-        super(KillSession, self).__init__(rpc_request, rpc_method,
-                                          grpc_client, session)
+    def __init__(self, request, grpc_client, session):
+        super(KillSession, self).__init__(request, grpc_client, session)
         self._validate_parameters()
 
     def execute(self):
diff --git a/netconf/nc_rpc/base/lock.py b/netconf/nc_rpc/base/lock.py
index 2f0130d..2f41e24 100644
--- a/netconf/nc_rpc/base/lock.py
+++ b/netconf/nc_rpc/base/lock.py
@@ -23,9 +23,8 @@
 
 class Lock(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(Lock, self).__init__(rpc_request, rpc_method, grpc_client,
-								   session)
+	def __init__(self, request, grpc_client, session):
+		super(Lock, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/validate.py b/netconf/nc_rpc/base/validate.py
index 93faf60..8671b12 100644
--- a/netconf/nc_rpc/base/validate.py
+++ b/netconf/nc_rpc/base/validate.py
@@ -23,9 +23,8 @@
 
 class Validate(Rpc):
 
-	def __init__(self, rpc_request, rpc_method, grpc_client, session):
-		super(Validate, self).__init__(rpc_request, rpc_method,
-									   grpc_client, session)
+	def __init__(self, request, grpc_client, session):
+		super(Validate, self).__init__(request, grpc_client, session)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/ext/get_voltha.py b/netconf/nc_rpc/ext/get_voltha.py
index a083401..0c51e9a 100644
--- a/netconf/nc_rpc/ext/get_voltha.py
+++ b/netconf/nc_rpc/ext/get_voltha.py
@@ -28,10 +28,8 @@
 
 
 class GetVoltha(Rpc):
-    def __init__(self, rpc_request, rpc_method, voltha_method_ref, grpc_client,
-                 session):
-        super(GetVoltha, self).__init__(rpc_request, rpc_method, voltha_method_ref,
-                                        grpc_client, session)
+    def __init__(self, request, grpc_client, session):
+        super(GetVoltha, self).__init__(request, grpc_client, session)
         self._validate_parameters()
 
     @inlineCallbacks
diff --git a/netconf/nc_rpc/rpc.py b/netconf/nc_rpc/rpc.py
index 5b579c9..bafc58e 100644
--- a/netconf/nc_rpc/rpc.py
+++ b/netconf/nc_rpc/rpc.py
@@ -21,14 +21,13 @@
 from lxml import etree
 import io
 
+
+
 class Rpc(object):
-    def __init__(self,rpc_request, rpc_method, voltha_method_ref,
-                 grpc_client, session):
-        self.rpc_request = rpc_request
-        self.rpc_method = rpc_method
+    def __init__(self, request, grpc_client, session):
+        self.request = request
         self.rpc_response = RpcResponse()
         self.grpc_client =  grpc_client
-        self.voltha_method_ref = voltha_method_ref
         self.session = session
 
 
diff --git a/netconf/nc_rpc/rpc_factory.py b/netconf/nc_rpc/rpc_factory.py
index aa0ced1..230c57a 100644
--- a/netconf/nc_rpc/rpc_factory.py
+++ b/netconf/nc_rpc/rpc_factory.py
@@ -35,6 +35,11 @@
 log = structlog.get_logger()
 from lxml import etree
 
+ns_map = {
+    'base': '{urn:ietf:params:xml:ns:netconf:base:1.0}',
+    'voltha': '{urn:opencord:params:xml:ns:voltha:ietf-voltha}'
+}
+
 
 class RpcFactory:
     instance = None
@@ -51,6 +56,39 @@
     def _get_key(self, namespace, service, name):
         return ''.join([namespace, service, name])
 
+    def get_attribute_value(self, name, attributes):
+        for tup in attributes.items():
+            if tup[0] == name:
+                return tup[1]
+
+    # Parse a request (node is an ElementTree) and return a dictionary
+    # TODO:  This parser is specific to a GET request.  Need to be it more
+    # generic
+    def parse_xml_request(self, node):
+        request = {}
+        if not len(node):
+            return request
+        for elem in node.iter():
+            if elem.tag.find(ns_map['base']) != -1:  # found
+                elem_name = elem.tag.replace(ns_map['base'], "")
+                if elem_name == 'rpc':
+                    request['type'] = 'rpc'
+                    request['message_id'] = self.get_attribute_value(
+                        'message-id', elem.attrib)
+                elif elem_name == 'filter':
+                    request['filter'] = self.get_attribute_value('type',
+                                                                 elem.attrib)
+                else:
+                    request[
+                        'command'] = elem_name  # attribute is empty for now
+            elif elem.tag.find(ns_map['voltha']) != -1:  # found
+                if request.has_key('class'):
+                    request['subclass'] = elem.tag.replace(ns_map['voltha'],
+                                                           "")
+                else:
+                    request['class'] = elem.tag.replace(ns_map['voltha'], "")
+        return request
+
     def register_rpc(self, namespace, service, name, klass):
         key = self._get_key(namespace, service, name)
         if key not in self.rpc_map.keys():
@@ -63,33 +101,32 @@
 
     def get_rpc_handler(self, rpc_node, msg, grpc_channel, session):
         try:
-            msg_id = rpc_node.get('message-id')
-            log.info("Received-rpc-message-id", msg_id=msg_id)
+            # Parse the request into a dictionary
+            log.info("rpc-node",
+                     node=etree.tostring(rpc_node, pretty_print=True))
 
-        except (TypeError, ValueError):
-            raise ncerror.SessionError(msg,
-                                       "No valid message-id attribute found")
+            request = self.parse_xml_request(rpc_node)
+            if not request:
+                log.error("request-bad-format")
+                raise ncerror.BadMsg(rpc_node)
 
-        log.info("rpc-node", node=etree.tostring(rpc_node, pretty_print=True))
+            if not request.has_key('message_id') or \
+                    not request.has_key('command'):
+                log.error("request-no-message-id")
+                raise ncerror.BadMsg(rpc_node)
 
-        # Get the first child of rpc as the method name
-        rpc_method = rpc_node.getchildren()
-        if len(rpc_method) != 1:
-            log.error("badly-formatted-rpc-method", msg_id=msg_id)
+            log.info("parsed-request", request=request)
+
+            class_handler = self.rpc_class_handlers.get(request['command'],
+                                                        None)
+            if class_handler is not None:
+                return class_handler(request, grpc_channel, session)
+
+            log.error("rpc-not-implemented", rpc=request['command'])
+
+        except Exception as e:
             raise ncerror.BadMsg(rpc_node)
 
-        rpc_method = rpc_method[0]
-
-        rpc_name = rpc_method.tag.replace(qmap('nc'), "")
-
-        log.info("rpc-request", rpc=rpc_name)
-        class_handler = self.rpc_class_handlers.get(rpc_name, None)
-        if class_handler is not None:
-            return class_handler(rpc_node, rpc_method, None, grpc_channel,
-                                 session)
-
-        log.error("rpc-not-implemented", rpc=rpc_name)
-
     rpc_class_handlers = {
         'getvoltha': GetVoltha,
         'get-config': GetConfig,
@@ -109,13 +146,3 @@
     if RpcFactory.instance == None:
         RpcFactory.instance = RpcFactory()
     return RpcFactory.instance
-
-
-if __name__ == '__main__':
-    fac = get_rpc_factory_instance()
-    fac.register_rpc('urn:opencord:params:xml:ns:voltha:ietf-voltha',
-                     'VolthaGlobalService', 'GetVoltha', GetVoltha)
-    rpc = fac.get_handler('urn:opencord:params:xml:ns:voltha:ietf-voltha',
-                          'VolthaGlobalService', 'GetVoltha')
-    # rpc = fac.rpc_class_handlers.get('getvoltha', None)
-    print rpc(None, None, None, None)
diff --git a/netconf/nc_rpc/rpc_response.py b/netconf/nc_rpc/rpc_response.py
index cdbe167..584e35b 100644
--- a/netconf/nc_rpc/rpc_response.py
+++ b/netconf/nc_rpc/rpc_response.py
@@ -16,6 +16,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+import structlog
+from lxml import etree
+import netconf.nc_common.error as ncerror
+
+log = structlog.get_logger()
+
 
 class RpcResponse():
     def __init__(self):
@@ -23,4 +29,144 @@
         # if there is an error then the reply_node will contains an Error
         # object
         self.reply_node = None
-        self.close_session = False
\ No newline at end of file
+        self.close_session = False
+
+    def build_xml_response(self, request, voltha_response):
+        if request is None:
+            return
+        voltha_xml_string = etree.tostring(voltha_response)
+
+        # Remove the leading and trailing <root> tags
+        if voltha_xml_string.startswith('<yang>'):
+            voltha_xml_string = voltha_xml_string[len('<yang>'):]
+            if voltha_xml_string.endswith('</yang>'):
+                voltha_xml_string = voltha_xml_string[:-len('</yang>')]
+
+        # Create the xml body as
+        if request.has_key('subclass'):
+            body = ''.join([
+                '<data>',
+                '<',
+                request['class'],
+                ' xmlns="urn:opencord:params:xml:ns:voltha:ietf-voltha">',
+                '<',
+                request['subclass'],
+                '>',
+                voltha_xml_string,
+                '</',
+                request['subclass'],
+                '>',
+                '</',
+                request['class'],
+                '>',
+                '</data>'
+            ])
+        else:
+            body = ''.join([
+                '<data>',
+                '<',
+                request['class'],
+                ' xmlns="urn:opencord:params:xml:ns:voltha:ietf-voltha">',
+                voltha_xml_string,
+                '</',
+                request['class'],
+                '>',
+                '</data>'
+            ])
+
+        return etree.fromstring(body)
+
+    def add_node(self, new_node, tree):
+        if new_node.tag == 'ignore':
+            # We want only sub-elements
+            for elem in list(new_node):
+                tree.append(elem)
+        else:
+            tree.append(new_node)
+
+    def copy_basic_element(self, elm):
+        new_elem = etree.Element(elm.tag)
+        new_elem.text = elm.text
+        return new_elem
+
+    def process_inline_option(self, elem):
+        inline_option = False
+        inline_node_name = None
+        for elm in list(elem):
+            if elm.tag == 'yang_field_option':
+                inline_option = True
+            if elm.tag == 'name':
+                inline_node_name = elm.text
+        if not inline_option:
+            new_elem = etree.Element(elem.tag)
+            return new_elem, elem
+
+        # look for the node with the inline_node_name
+        for elm in list(elem):
+            if elm.tag == inline_node_name:
+                new_elem = etree.Element('ignore')
+                return new_elem, elm
+
+    def process_element(self, elem):
+        attrib = elem.get('type')
+        if (attrib == 'list'):
+            if list(elem) is None:
+                return self.copy_basic_element(elem)
+            new_elem = etree.Element('ignore')
+            for elm in list(elem):
+                elm.tag = elem.tag
+                if elm.get('type') in ['list', 'dict']:
+                    self.add_node(self.process_element(elm), new_elem)
+                else:
+                    new_elem.append(self.copy_basic_element(elm))
+            return new_elem
+        elif (attrib == 'dict'):
+            # Empty case
+            if list(elem) is None:
+                return self.copy_basic_element(elem)
+
+            # Process field option.
+            new_elem, elem = self.process_inline_option(elem)
+
+            for elm in list(elem):
+                if elm.get('type') in ['list', 'dict']:
+                    self.add_node(self.process_element(elm), new_elem)
+                else:
+                    new_elem.append(self.copy_basic_element(elm))
+            return new_elem
+        else:
+            return self.copy_basic_element(elem)
+
+    def to_yang_xml(self, from_xml):
+        # Parse from_xml as follows:
+        # 1.  Any element having a list attribute shoud have each item move 1 level
+        #     up and retag using the parent tag
+        # 2.  Any element having a dict attribute and has a <yang_field_option>
+        #     sub-element should have all it's items move to teh parent level
+        top = etree.Element('yang')
+        elms = list(from_xml)
+
+        # special case the xml contain a list type
+        if len(elms) == 1:
+            item = elms[0]
+            if item.get('type') == 'list':
+                item.tag = 'ignore'
+                self.add_node(self.process_element(item), top)
+                return top
+
+        # Process normally for all other cases
+        for elm in elms:
+            self.add_node(self.process_element(elm), top)
+
+        return top
+
+    def build_yang_response(self, root, request):
+        try:
+            yang_xml = self.to_yang_xml(root)
+            log.info('yang-xml', yang_xml=etree.tounicode(yang_xml,
+                                                          pretty_print=True))
+            return self.build_xml_response(request, yang_xml)
+        except Exception as e:
+            self.rpc_response.is_error = True
+            self.rpc_response.node = ncerror.BadMsg(request)
+            return
diff --git a/netconf/session/nc_protocol_handler.py b/netconf/session/nc_protocol_handler.py
index c64c2c1..64005c4 100644
--- a/netconf/session/nc_protocol_handler.py
+++ b/netconf/session/nc_protocol_handler.py
@@ -180,8 +180,8 @@
                              rpc_handler=rpc_handler,
                              is_error=response.is_error,
                              response=response)
-                    # self.send_rpc_reply(response.node, rpc)
-                    self.send_rpc_reply(self.get_instance(), rpc)
+                    self.send_rpc_reply(response.node, rpc)
+                    # self.send_rpc_reply(self.get_instance(), rpc)
 
                     if response.close_session:
                         log.info('response-closing-session', response=response)
diff --git a/voltha/protos/device.proto b/voltha/protos/device.proto
index 20eec6d..285c5ae 100644
--- a/voltha/protos/device.proto
+++ b/voltha/protos/device.proto
@@ -6,6 +6,7 @@
 import "google/protobuf/any.proto";
 import "common.proto";
 import "openflow_13.proto";
+import "yang_options.proto";
 
 // A Device Type
 message DeviceType {
@@ -29,6 +30,7 @@
 }
 
 message Port {
+    option (voltha.yang_child_rule) = MOVE_TO_PARENT_LEVEL;
 
     enum PortType {
         UNKNOWN = 0;
@@ -64,6 +66,7 @@
 
 // A Physical Device instance
 message Device {
+    option (voltha.yang_child_rule) = MOVE_TO_PARENT_LEVEL;
 
     // Voltha's device identifier
     string id = 1 [(access) = READ_ONLY];