The commit consists of:

1) Support metadata (e.g. get-depth) for rpc request.
2) Support parameters in rpc requests
3) Generate the code for netconf rpc to Voltha grpc mapping
4) Initial Support custom rpc requests (those defined in the voltha YANG schemas).

Change-Id: I24dc7fd75b5f71d0d8591637579672b25fda57ec
diff --git a/netconf/nc_rpc/base/close_session.py b/netconf/nc_rpc/base/close_session.py
index 45a6cc2..05b56b4 100644
--- a/netconf/nc_rpc/base/close_session.py
+++ b/netconf/nc_rpc/base/close_session.py
@@ -27,7 +27,7 @@
     def __init__(self, request, request_xml, grpc_client, session,
                  capabilities):
         super(CloseSession, self).__init__(request, request_xml, grpc_client,
-                                           session)
+                                           session, capabilities)
         self._validate_parameters()
 
     def execute(self):
diff --git a/netconf/nc_rpc/base/commit.py b/netconf/nc_rpc/base/commit.py
index 22a0e6a..35ea6e6 100644
--- a/netconf/nc_rpc/base/commit.py
+++ b/netconf/nc_rpc/base/commit.py
@@ -25,7 +25,8 @@
 class Commit(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(Commit, self).__init__(request, request_xml, grpc_client, session)
+		super(Commit, self).__init__(request, request_xml, grpc_client,
+									 session, capabilities)
 		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 1d96b76..74519d3 100644
--- a/netconf/nc_rpc/base/copy_config.py
+++ b/netconf/nc_rpc/base/copy_config.py
@@ -24,7 +24,8 @@
 class CopyConfig(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(CopyConfig, self).__init__(request, request_xml, grpc_client, session)
+		super(CopyConfig, self).__init__(request, request_xml, grpc_client,
+										 session, capabilities)
 		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 e21d2d4..d0135fe 100644
--- a/netconf/nc_rpc/base/delete_config.py
+++ b/netconf/nc_rpc/base/delete_config.py
@@ -23,8 +23,10 @@
 
 class DeleteConfig(Rpc):
 
-	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(DeleteConfig, self).__init__(request, request_xml, grpc_client, session)
+	def __init__(self, request, request_xml, grpc_client, session,
+				 capabilities):
+		super(DeleteConfig, self).__init__(request, request_xml,
+										   grpc_client, session, capabilities)
 		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 4b4b219..2855a6d 100644
--- a/netconf/nc_rpc/base/discard_changes.py
+++ b/netconf/nc_rpc/base/discard_changes.py
@@ -23,8 +23,10 @@
 
 class DiscardChanges(Rpc):
 
-	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(DiscardChanges, self).__init__(request, request_xml, grpc_client, session)
+	def __init__(self, request, request_xml, grpc_client, session,
+				 capabilities):
+		super(DiscardChanges, self).__init__(request, request_xml,
+											 grpc_client, session, capabilities)
 		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 0991c67..0fa6e5d 100644
--- a/netconf/nc_rpc/base/edit_config.py
+++ b/netconf/nc_rpc/base/edit_config.py
@@ -24,7 +24,8 @@
 class EditConfig(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(EditConfig, self).__init__(request, request_xml, grpc_client, session)
+		super(EditConfig, self).__init__(request, request_xml, grpc_client,
+										 session, capabilities)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/get.py b/netconf/nc_rpc/base/get.py
index b2bb9fe..6337b5f 100644
--- a/netconf/nc_rpc/base/get.py
+++ b/netconf/nc_rpc/base/get.py
@@ -24,8 +24,13 @@
 
 
 class Get(Rpc):
-    def __init__(self, request, request_xml, grpc_client, session, capabilities):
-        super(Get, self).__init__(request, request_xml, grpc_client, session)
+    def __init__(self, request, request_xml, grpc_client, session,
+                 capabilities):
+        super(Get, self).__init__(request, request_xml, grpc_client, session,
+                                  capabilities)
+        self.service = None
+        self.method = None
+        self.metadata = None
         self._validate_parameters()
 
     @inlineCallbacks
@@ -43,8 +48,26 @@
             self.rpc_response.node = ncerror.NotImpl(self.request_xml)
             returnValue(self.rpc_response)
 
-        # Invoke voltha via the grpc client
-        res_dict = yield self.grpc_client.invoke_voltha_api(rpc)
+        # Extract the service and method name from the rpc
+        command = rpc.split('-')
+        if len(command) != 2:
+            log.debug('unsupported-rpc', rpc=rpc)
+            self.rpc_response.is_error = True
+            self.rpc_response.node = ncerror.NotImpl(self.request_xml)
+            returnValue(self.rpc_response)
+
+        self.service = command[0]
+        self.method = command[1]
+        self.params = {}
+        if self.request.has_key('metadata'):
+            self.metadata = self.request['metadata']
+
+        # Execute the request
+        res_dict = yield self.grpc_client.invoke_voltha_rpc(
+            service=self.service,
+            method=self.method,
+            params=self.params,
+            metadata=self.metadata)
 
         # convert dict to xml
         xml = dicttoxml.dicttoxml(res_dict, attr_type=True)
@@ -72,12 +95,14 @@
                 if self.request.has_key('filter'):
                     if not self.request.has_key('class'):
                         self.rpc_response.is_error = True
-                        self.rpc_response.node = ncerror.NotImpl(self.request_xml)
+                        self.rpc_response.node = ncerror.NotImpl(
+                            self.request_xml)
                     return
 
             except Exception as e:
                 self.rpc_response.is_error = True
-                self.rpc_response.node = ncerror.ServerException(self.request_xml)
+                self.rpc_response.node = ncerror.ServerException(
+                    self.request_xml)
                 return
 
     def get_voltha_rpc(self, request):
diff --git a/netconf/nc_rpc/base/get_config.py b/netconf/nc_rpc/base/get_config.py
index e7ade72..ae8ca02 100644
--- a/netconf/nc_rpc/base/get_config.py
+++ b/netconf/nc_rpc/base/get_config.py
@@ -24,7 +24,8 @@
 class GetConfig(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(GetConfig, self).__init__(request, request_xml, grpc_client, session)
+		super(GetConfig, self).__init__(request, request_xml, grpc_client,
+										session, capabilities)
 		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 e10f3a5..b04b444 100644
--- a/netconf/nc_rpc/base/kill_session.py
+++ b/netconf/nc_rpc/base/kill_session.py
@@ -26,7 +26,8 @@
 class KillSession(Rpc):
 
     def __init__(self, request, request_xml, grpc_client, session, capabilities):
-        super(KillSession, self).__init__(request, request_xml, grpc_client, session)
+        super(KillSession, self).__init__(request, request_xml, grpc_client,
+                                          session, capabilities)
         self._validate_parameters()
 
     def execute(self):
diff --git a/netconf/nc_rpc/base/lock.py b/netconf/nc_rpc/base/lock.py
index 5a59376..39de197 100644
--- a/netconf/nc_rpc/base/lock.py
+++ b/netconf/nc_rpc/base/lock.py
@@ -24,7 +24,8 @@
 class Lock(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(Lock, self).__init__(request, request_xml, grpc_client, session)
+		super(Lock, self).__init__(request, request_xml, grpc_client,
+								   session, capabilities)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/unlock.py b/netconf/nc_rpc/base/unlock.py
index b5db7c1..e2bc8e8 100644
--- a/netconf/nc_rpc/base/unlock.py
+++ b/netconf/nc_rpc/base/unlock.py
@@ -24,7 +24,8 @@
 class UnLock(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(UnLock, self).__init__(request, request_xml, grpc_client, session)
+		super(UnLock, self).__init__(request, request_xml, grpc_client,
+									 session, capabilities)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/base/validate.py b/netconf/nc_rpc/base/validate.py
index 61e2f80..309c00e 100644
--- a/netconf/nc_rpc/base/validate.py
+++ b/netconf/nc_rpc/base/validate.py
@@ -24,7 +24,7 @@
 class Validate(Rpc):
 
 	def __init__(self, request, request_xml, grpc_client, session, capabilities):
-		super(Validate, self).__init__(request, grpc_client, session)
+		super(Validate, self).__init__(request, grpc_client, session, capabilities)
 		self._validate_parameters()
 
 	def execute(self):
diff --git a/netconf/nc_rpc/ext/get_schema.py b/netconf/nc_rpc/ext/get_schema.py
index 569a3b0..a90906e 100644
--- a/netconf/nc_rpc/ext/get_schema.py
+++ b/netconf/nc_rpc/ext/get_schema.py
@@ -26,8 +26,8 @@
 
 class GetSchema(Rpc):
     def __init__(self, request, request_xml, grpc_client, session, capabilities):
-        super(GetSchema, self).__init__(request, request_xml, grpc_client, session)
-        self.capabilities = capabilities
+        super(GetSchema, self).__init__(request, request_xml, grpc_client,
+                                        session, capabilities)
         # specific schema parsing required
         self.parse_schema_request(request_xml)
         self._validate_parameters()
diff --git a/netconf/nc_rpc/ext/get_schemas.py b/netconf/nc_rpc/ext/get_schemas.py
index cc09ebb..094c8df 100644
--- a/netconf/nc_rpc/ext/get_schemas.py
+++ b/netconf/nc_rpc/ext/get_schemas.py
@@ -26,9 +26,9 @@
 
 class GetSchemas(Rpc):
     def __init__(self, request, request_xml, grpc_client, session, capabilities):
-        super(GetSchemas, self).__init__(request, request_xml, grpc_client, session)
+        super(GetSchemas, self).__init__(request, request_xml, grpc_client,
+                                         session, capabilities)
         self._validate_parameters()
-        self.capabilities = capabilities
 
     @inlineCallbacks
     def execute(self):
diff --git a/netconf/nc_rpc/ext/get_voltha.py b/netconf/nc_rpc/ext/get_voltha.py
deleted file mode 100644
index 5b004e7..0000000
--- a/netconf/nc_rpc/ext/get_voltha.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2017 the original author or authors.
-#
-# 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 lxml import etree
-import structlog
-from netconf.nc_rpc.rpc import Rpc
-import netconf.nc_common.error as ncerror
-from twisted.internet.defer import inlineCallbacks, returnValue
-import dicttoxml
-
-log = structlog.get_logger()
-
-
-class GetVoltha(Rpc):
-    def __init__(self, request, request_xml, grpc_client, session, capabilities):
-        super(GetVoltha, self).__init__(request, request_xml, grpc_client, session)
-        self._validate_parameters()
-
-
-    @inlineCallbacks
-    def execute(self):
-        log.info('get-voltha-request', session=self.session.session_id,
-                 method=self.rpc_method)
-        if self.rpc_response.is_error:
-            returnValue(self.rpc_response)
-
-        # Invoke voltha via the grpc client
-        res_dict = yield self.grpc_client.invoke_voltha_api(self.voltha_method_ref)
-
-        # convert dict to xml
-        xml = dicttoxml.dicttoxml(res_dict, attr_type=False)
-        log.info('voltha-info', res=res_dict, xml=xml)
-
-        root_elem = self.get_root_element(xml)
-        root_elem.tag = 'data'
-
-        self.rpc_method.append(root_elem)
-        self.rpc_response.node = self.rpc_method
-        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
-
-        if not self.params:
-            self.params = [None]
diff --git a/netconf/nc_rpc/ext/voltha_rpc.py b/netconf/nc_rpc/ext/voltha_rpc.py
new file mode 100644
index 0000000..37ba305
--- /dev/null
+++ b/netconf/nc_rpc/ext/voltha_rpc.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 the original author or authors.
+#
+# 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.
+#
+import structlog
+from netconf.nc_rpc.rpc import Rpc
+import netconf.nc_common.error as ncerror
+from twisted.internet.defer import inlineCallbacks, returnValue
+import dicttoxml
+from netconf.nc_common.utils import qmap, ns
+from netconf.constants import Constants as C
+from netconf.grpc_client.nc_rpc_mapper import \
+    get_nc_rpc_mapper_instance
+
+log = structlog.get_logger()
+
+
+class VolthaRpc(Rpc):
+    def __init__(self, request, request_xml, grpc_client, session,
+                 capabilities):
+        super(VolthaRpc, self).__init__(request, request_xml, grpc_client,
+                                        session, capabilities)
+        self.service = None
+        self.method = None
+        self.metadata = None
+        self._extract_parameters()
+        if not self.rpc_response.is_error:
+            self._validate_parameters()
+
+    @inlineCallbacks
+    def execute(self):
+        if self.rpc_response.is_error:
+            returnValue(self.rpc_response)
+
+        log.info('voltha-rpc-request', session=self.session.session_id,
+                 request=self.request)
+
+        # Execute the request
+        res_dict = yield self.grpc_client.invoke_voltha_rpc(
+            service=self.service,
+            method=self.method,
+            params=self.request['params'],
+            metadata=self.metadata)
+
+        # convert dict to xml
+        xml = dicttoxml.dicttoxml(res_dict, attr_type=True)
+        log.info('voltha-info', res=res_dict, xml=xml)
+
+        root_elem = self.get_root_element(xml)
+
+        # Build the yang response
+        self.rpc_response.node = self.rpc_response.build_yang_response(
+            root_elem, self.request, custom_rpc=True)
+        self.rpc_response.is_error = False
+
+        returnValue(self.rpc_response)
+
+    def _validate_parameters(self):
+        log.info('validate-parameters', session=self.session.session_id)
+        # For now just validate that the command is presenf
+        if self.request:
+            try:
+                if self.request['command'] is None:
+                    self.rpc_response.is_error = True
+                    self.rpc_response.node = ncerror.BadMsg(self.request_xml)
+                    return
+
+            except Exception as e:
+                self.rpc_response.is_error = True
+                self.rpc_response.node = ncerror.ServerException(
+                    self.request_xml)
+                return
+
+    def _extract_parameters(self):
+        try:
+            rpc_node = self.request_xml.find(''.join(
+                [qmap(C.VOLTHA),
+                 self.request['command']])
+            )
+            self.request['params'] = {}
+            if rpc_node is not None:
+                for r in rpc_node:
+                    self.request['params'][
+                        r.tag.replace(qmap(C.VOLTHA), "")] = r.text
+
+            # Remove the subclass element in the request if it is present as
+            # it is not required for rpc calls
+            if self.request.has_key('subclass'):
+                self.request.pop('subclass', None)
+
+            # Extract the service and method from the rpc command
+            command = self.request['command'].split('-')
+            if len(command) != 2:
+                log.debug('invalid-format', command=self.request['command'])
+                raise
+
+            self.service = command[0]
+            self.method = command[1]
+            if self.request.has_key('metadata'):
+                self.metadata = self.request['metadata']
+
+        except Exception as e:
+            self.rpc_response.is_error = True
+            self.rpc_response.node = ncerror.BadMsg(self.request_xml)
+            log.exception('params-parsing-error', xml=self.request_xml, e=e)
diff --git a/netconf/nc_rpc/rpc.py b/netconf/nc_rpc/rpc.py
index 3f1eff4..9f48b4c 100644
--- a/netconf/nc_rpc/rpc.py
+++ b/netconf/nc_rpc/rpc.py
@@ -22,15 +22,15 @@
 import io
 
 
-
 class Rpc(object):
-    def __init__(self, request_dict, request_xml, grpc_client, session):
+    def __init__(self, request_dict, request_xml, grpc_client, session,
+                 capabilities):
         self.request = request_dict
         self.request_xml = request_xml
-        self.rpc_response = RpcResponse()
-        self.grpc_client =  grpc_client
+        self.rpc_response = RpcResponse(capabilities)
+        self.grpc_client = grpc_client
         self.session = session
-
+        self.capabilities = capabilities
 
     def execute(self):
         """ run the command - returns a OperationResponse """
@@ -46,4 +46,3 @@
     def get_root_element(self, xml_msg):
         tree = etree.parse(io.BytesIO(xml_msg))
         return tree.getroot()
-
diff --git a/netconf/nc_rpc/rpc_factory.py b/netconf/nc_rpc/rpc_factory.py
index d51942c..47c1eaa 100644
--- a/netconf/nc_rpc/rpc_factory.py
+++ b/netconf/nc_rpc/rpc_factory.py
@@ -30,14 +30,15 @@
 from base.kill_session import KillSession
 from ext.get_schemas import GetSchemas
 from ext.get_schema import GetSchema
-from ext.get_voltha import GetVoltha
+from ext.voltha_rpc import VolthaRpc
 import netconf.nc_common.error as ncerror
 from netconf.nc_common.utils import qmap, ns
+from netconf.grpc_client.nc_rpc_mapper import get_nc_rpc_mapper_instance
 from lxml import etree
 
-
 log = structlog.get_logger()
 
+
 class RpcFactory:
     instance = None
 
@@ -52,10 +53,14 @@
             if tup[0] == name:
                 return tup[1]
 
+    def get_filtered_attributes(self, names_to_filter_out, attributes):
+        result = []
+        for tup in attributes.items():
+            if tup[0] not in names_to_filter_out:
+                result.append((tup[0], tup[1]))
+        return result
 
     # Parse a request (node is an ElementTree) and return a dictionary
-    # TODO:  This parser is specific for a GET/GET SCHEMAS request.  Need to be
-    #  it more generic
     def parse_xml_request(self, node):
         request = {}
         if not len(node):
@@ -70,17 +75,37 @@
                 elif elem_name == 'filter':
                     request['filter'] = self.get_attribute_value('type',
                                                                  elem.attrib)
+                    # Get the metadata
+                    request['metadata'] = self.get_filtered_attributes(
+                        ['type'],
+                        elem.attrib)
                 else:
-                    request['command'] = elem_name  # attribute is empty for now
+                    request[
+                        'command'] = elem_name  # attribute is empty for now
             elif elem.tag.find(qmap(C.VOLTHA)) != -1:  # found
                 request['namespace'] = ns(C.VOLTHA)
                 if request.has_key('class'):
-                    request['subclass'] = elem.tag.replace(qmap(C.VOLTHA),"")
+                    request['subclass'] = elem.tag.replace(qmap(C.VOLTHA), "")
                 else:
                     elem_name = elem.tag.replace(qmap(C.VOLTHA), "")
                     request['class'] = elem_name
                     if not request.has_key('command'):
                         request['command'] = elem_name
+                        request['metadata'] = self.get_filtered_attributes(
+                            ['xmlns'],
+                            elem.attrib)
+            elif elem.tag.find(qmap(C.HEALTH)) != -1:  # found
+                request['namespace'] = ns(C.HEALTH)
+                if request.has_key('class'):
+                    request['subclass'] = elem.tag.replace(qmap(C.HEALTH), "")
+                else:
+                    elem_name = elem.tag.replace(qmap(C.HEALTH), "")
+                    request['class'] = elem_name
+                    if not request.has_key('command'):
+                        request['command'] = elem_name
+                        request['metadata'] = self.get_filtered_attributes(
+                            ['xmlns'],
+                            elem.attrib)
             elif elem.tag.find(qmap(C.NCM)) != -1:  # found
                 request['namespace'] = ns(C.NCM)
                 elem_name = elem.tag.replace(qmap(C.NCM), "")
@@ -95,7 +120,6 @@
 
         return request
 
-
     def get_rpc_handler(self, rpc_node, msg, grpc_channel, session,
                         capabilities):
         try:
@@ -114,8 +138,8 @@
                 log.error("request-no-message-id")
                 raise ncerror.BadMsg(rpc_node)
 
-            class_handler = self.rpc_class_handlers.get(request['command'],
-                                                        None)
+            class_handler = self._get_rpc_handler(request['command'])
+
             if class_handler is not None:
                 return class_handler(request, rpc_node, grpc_channel, session,
                                      capabilities)
@@ -128,10 +152,19 @@
             raise ncerror.BadMsg(rpc_node)
 
         except Exception as e:
+            log.exception('exception', e=e)
             raise ncerror.ServerException(rpc_node, exception=e)
 
+    def _get_rpc_handler(self, command):
+        # If there is a generic mapping of that command then use it
+        rpc_mapper = get_nc_rpc_mapper_instance()
+        rpc = command.replace('-', '_')
+        if rpc_mapper.is_rpc_exist(rpc):
+            return VolthaRpc
+        else:
+            return self.rpc_class_handlers.get(command, None)
+
     rpc_class_handlers = {
-        'getvoltha': GetVoltha,
         'get-config': GetConfig,
         'get': Get,
         'get-schemas': GetSchemas,
diff --git a/netconf/nc_rpc/rpc_response.py b/netconf/nc_rpc/rpc_response.py
index 4d921dd..2d60d77 100644
--- a/netconf/nc_rpc/rpc_response.py
+++ b/netconf/nc_rpc/rpc_response.py
@@ -24,14 +24,16 @@
 
 
 class RpcResponse():
-    def __init__(self):
+    def __init__(self, capabilities):
         self.is_error = False
         # if there is an error then the reply_node will contains an Error
         # object
         self.reply_node = None
         self.close_session = False
+        self.capabilities = capabilities
+        self.custom_rpc = False
 
-    def build_xml_response(self, request, voltha_response):
+    def build_xml_response(self, request, voltha_response, custom_rpc=False):
         if request is None:
             return
         voltha_xml_string = etree.tostring(voltha_response)
@@ -45,38 +47,46 @@
         elif voltha_xml_string.startswith('<yang/>'):
             voltha_xml_string = ''
 
-        # Create the xml body as
-        if request.has_key('subclass'):
+        if not custom_rpc:
+            # Create the xml body as
+            if request.has_key('subclass'):
+                body = ''.join([
+                    '<data>',
+                    '<',
+                    request['class'],
+                    ' xmlns="',
+                    request['namespace'],
+                    '">',
+                    '<',
+                    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>'
+                ])
+        else:  # custom_rpc
             body = ''.join([
-                '<data>',
-                '<',
-                request['class'],
-                ' xmlns="',
-                request['namespace'],
-                '">',
-                '<',
-                request['subclass'],
-                '>',
-                voltha_xml_string,
-                '</',
-                request['subclass'],
-                '>',
-                '</',
-                request['class'],
-                '>',
-                '</data>'
-            ])
-        else:
-            body = ''.join([
-                '<data>',
-                '<',
-                request['class'],
+                '<rpc-reply',
                 ' xmlns="urn:opencord:params:xml:ns:voltha:ietf-voltha">',
                 voltha_xml_string,
-                '</',
-                request['class'],
-                '>',
-                '</data>'
+                '</rpc-reply>',
             ])
 
         return etree.fromstring(body)
@@ -145,7 +155,7 @@
         else:
             return self.copy_basic_element(elem)
 
-    def to_yang_xml(self, from_xml, request):
+    def to_yang_xml(self, from_xml, request, custom_rpc=False):
         # 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
@@ -155,7 +165,7 @@
         elms = list(from_xml)
 
         # special case the xml contain a list type
-        if len(elms) == 1:
+        if len(elms) == 1 and not custom_rpc:
             item = elms[0]
             # TODO: Address name 'items' clash when a list name is actually
             # 'items'.
@@ -166,7 +176,6 @@
                     del request['subclass']
                 else:
                     item.tag = 'ignore'
-                # item.tag = 'ignore'
                 self.add_node(self.process_element(item), top)
                 return top
 
@@ -176,13 +185,18 @@
 
         return top
 
-    def build_yang_response(self, root, request):
+    # custom_rpc refers to custom RPCs different from Netconf default RPCs
+    # like get, get-config, edit-config, etc
+    def build_yang_response(self, root, request, custom_rpc=False):
         try:
-            yang_xml = self.to_yang_xml(root, request)
+            self.custom_rpc = custom_rpc
+            yang_xml = self.to_yang_xml(root, request, custom_rpc)
             log.info('yang-xml', yang_xml=etree.tounicode(yang_xml,
                                                           pretty_print=True))
-            return self.build_xml_response(request, yang_xml)
+            return self.build_xml_response(request, yang_xml, custom_rpc)
         except Exception as e:
+            log.exception('error-building-yang-response', request=request,
+                          xml=etree.tostring(root))
             self.rpc_response.is_error = True
             self.rpc_response.node = ncerror.BadMsg(request)
             return