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/capabilities.py b/netconf/capabilities.py
index 80fe63d..b033131 100755
--- a/netconf/capabilities.py
+++ b/netconf/capabilities.py
@@ -18,12 +18,11 @@
 import sys
 from constants import Constants as C
 
-# URN_PREFIX = "urn:ietf:params:netconf:capability:"
 URN_PREFIX = "urn:opencord:params:xml:ns:voltha:"
 log = structlog.get_logger()
 
-class Capabilities:
 
+class Capabilities:
     def __init__(self):
         self.server_caps = set()
         self.client_caps = set()
@@ -41,10 +40,14 @@
         self.server_caps.add(C.NETCONF_MONITORING)
         for schema in schemas:
             self.server_caps.add(''.join([URN_PREFIX, schema]))
+            self.server_caps.add(''.join([
+                                            URN_PREFIX,
+                                            schema,
+                                            ':writable-running'])
+                                )
             self.voltha_schemas.add(schema)
 
-
-    def set_schema_dir(self, schema_dir)        :
+    def set_schema_dir(self, schema_dir):
         self.schema_dir = schema_dir
 
     def get_yang_schemas_definitions(self):
@@ -53,18 +56,18 @@
             defs.append(
                 {
                     'id': schema,
-                    'version': '2016-11-15', #TODO: need to extract from voltha
+                    'version': '2016-11-15',
+                    # TODO: need to extract from voltha
                     'format': 'yang',
                     'location': 'NETCONF',
                     'namespace': ''.join([URN_PREFIX, schema])
-                 }
+                }
             )
         return defs
 
     def is_schema_supported(self, schema):
         return schema in self.voltha_schemas
 
-
     def get_schema_content(self, schema):
         if self.schema_dir not in sys.path:
             sys.path.insert(0, self.schema_dir)
@@ -77,4 +80,3 @@
         except Exception as e:
             log.error("error-opening-file", file=''.join([schema, '.yang']),
                       dir=self.schema_dir, exception=repr(e))
-
diff --git a/netconf/constants.py b/netconf/constants.py
index 332ec16..976ddff 100644
--- a/netconf/constants.py
+++ b/netconf/constants.py
@@ -16,96 +16,95 @@
 #
 
 class Constants:
+    SSH_SUBSYSTEM = "netconf"
 
-	SSH_SUBSYSTEM = "netconf"
+    # Send message max size
+    MAXSSHBUF = 1024 * 1024
 
-	# Send message max size
-	MAXSSHBUF = 1024 * 1024
+    # Secure credentials directories
+    # TODO:  In a production environment these locations require better
+    # protection.  For now the user_passwords file is just a plain text file.
+    KEYS_DIRECTORY = 'security/keys'
+    CERTS_DIRECTORY = 'security/certificates'
+    CLIENT_CRED_DIRECTORY = 'security/client_credentials'
 
-	# Secure credentials directories
-	# TODO:  In a production environment these locations require better
-	# protection.  For now the user_passwords file is just a plain text file.
-	KEYS_DIRECTORY = 'security/keys'
-	CERTS_DIRECTORY = 'security/certificates'
-	CLIENT_CRED_DIRECTORY = 'security/client_credentials'
+    # Datastores
+    RUNNING = "running"
+    CANDIDATE = "candidate"
+    STARTUP = "startup"
 
-	# Datastores
-	RUNNING = "running"
-	CANDIDATE = "candidate"
-	STARTUP = "startup"
+    # RPC - base netconf
+    GET = "get"
+    GET_CONFIG = "get-config"
+    COPY_CONFIG = "copy-config"
+    EDIT_CONFIG = "edit-config"
+    DELETE_CONFIG = "delete-config"
+    LOCK = "lock"
+    UNLOCK = "unlock"
+    CLOSE_SESSION = "close-session"
+    KILL_SESSION = "kill-session"
 
-	# RPC - base netconf
-	GET = "get"
-	GET_CONFIG = "get-config"
-	COPY_CONFIG = "copy-config"
-	EDIT_CONFIG = "edit-config"
-	DELETE_CONFIG = "delete-config"
-	LOCK = "lock"
-	UNLOCK = "unlock"
-	CLOSE_SESSION = "close-session"
-	KILL_SESSION = "kill-session"
-
-	# Operations
-	OPERATION = "operation"
-	DEFAULT_OPERATION = "default-operation"
-	MERGE = "merge"
-	REPLACE = "replace"
-	CREATE = "create"
-	DELETE = "delete"
-	NONE = "none"
+    # Operations
+    OPERATION = "operation"
+    DEFAULT_OPERATION = "default-operation"
+    MERGE = "merge"
+    REPLACE = "replace"
+    CREATE = "create"
+    DELETE = "delete"
+    NONE = "none"
 
     # 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"
+    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"
+    NETCONF_WRITABLE = "urn:ietf:params:netconf:capability:writable-running:1.0"
 
-	# XML
-	XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>"""
+    # XML
+    XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>"""
 
-	# Capability xpath
-	CAPABILITY_XPATH = "//nc:hello/nc:capabilities/nc:capability"
-	RPC_XPATH = "/nc:rpc"
+    # Capability xpath
+    CAPABILITY_XPATH = "//nc:hello/nc:capabilities/nc:capability"
+    RPC_XPATH = "/nc:rpc"
 
-	NC_SOURCE="nc:source"
-	SOURCE = "source"
-	TARGET = "target"
-	CONFIG = "config"
-	
+    NC_SOURCE = "nc:source"
+    SOURCE = "source"
+    TARGET = "target"
+    CONFIG = "config"
 
-	TEST_OPTION = "test-option"
-	TEST_THEN_SET = "test-then-set"
-	SET = "set"
+    TEST_OPTION = "test-option"
+    TEST_THEN_SET = "test-then-set"
+    SET = "set"
 
-	ERROR_OPTION = "error-option"
-	STOP_ON_ERROR = "stop-on-error"
-	CONTINUE_ON_ERROR = "continue-on-error"
-	ROLLBACK_ON_ERROR = "rollback-on-error"
+    ERROR_OPTION = "error-option"
+    STOP_ON_ERROR = "stop-on-error"
+    CONTINUE_ON_ERROR = "continue-on-error"
+    ROLLBACK_ON_ERROR = "rollback-on-error"
 
-	#tags
-	NC = "nc"
-	VOLTHA = 'voltha'
-	NCM = "ncm"
-	RPC = "rpc"
-	RPC_REPLY = "rpc-reply"
-	RPC_ERROR = "rpc-error"
-	CAPABILITY = "capability"
-	CAPABILITIES = "capabilities"
-	HELLO = "hello"
-	URL = "url"
-	NC_FILTER="nc:filter"
-	FILTER = "filter"
-	SUBTREE = "subtree"
-	XPATH = "xpath"
-	OK = "ok"
-	SESSION_ID = "session-id"
-	MESSAGE_ID = "message-id"
-	XMLNS = "xmlns"
-	DELIMITER = "]]>]]>"
+    # tags
+    NC = "nc"
+    VOLTHA = 'voltha'
+    HEALTH = 'health'
+    NCM = "ncm"
+    RPC = "rpc"
+    RPC_REPLY = "rpc-reply"
+    RPC_ERROR = "rpc-error"
+    CAPABILITY = "capability"
+    CAPABILITIES = "capabilities"
+    HELLO = "hello"
+    URL = "url"
+    NC_FILTER = "nc:filter"
+    FILTER = "filter"
+    SUBTREE = "subtree"
+    XPATH = "xpath"
+    OK = "ok"
+    SESSION_ID = "session-id"
+    MESSAGE_ID = "message-id"
+    XMLNS = "xmlns"
+    DELIMITER = "]]>]]>"
 
-	NS_MAP = {
-		'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0',
-		'voltha': 'urn:opencord:params:xml:ns:voltha:ietf-voltha',
-		'ncm': 'urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring'
-	}
-
-
+    NS_MAP = {
+        'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0',
+        'voltha': 'urn:opencord:params:xml:ns:voltha:ietf-voltha',
+        'ncm': 'urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring',
+        'health': 'urn:opencord:params:xml:ns:voltha:ietf-health'
+    }
diff --git a/netconf/grpc_client/grpc_client.py b/netconf/grpc_client/grpc_client.py
index 159abc2..9dd7f87 100644
--- a/netconf/grpc_client/grpc_client.py
+++ b/netconf/grpc_client/grpc_client.py
@@ -36,10 +36,11 @@
 from netconf.protos.schema_pb2 import SchemaServiceStub
 from google.protobuf.empty_pb2 import Empty
 from common.utils.consulhelpers import get_endpoint_from_consul
-from netconf.protos.voltha_pb2 import VolthaLocalServiceStub, \
-    VolthaGlobalServiceStub
-from google.protobuf import empty_pb2
-from google.protobuf.json_format import MessageToDict, ParseDict
+# from netconf.protos.voltha_pb2 import VolthaLocalServiceStub, \
+#     VolthaGlobalServiceStub
+# from google.protobuf import empty_pb2
+# from google.protobuf.json_format import MessageToDict, ParseDict
+from nc_rpc_mapper import get_nc_rpc_mapper_instance
 from google.protobuf import descriptor
 import base64
 import math
@@ -154,9 +155,6 @@
                 if self.reconnect_callback is not None:
                     reactor.callLater(0, self.reconnect_callback)
 
-                # self.local_stub = voltha_pb2.VolthaLocalServiceStub(self.channel)
-                # self.global_stub = voltha_pb2.VolthaGlobalServiceStub(self.channel)
-
                 return
 
         except _Rendezvous, e:
@@ -257,6 +255,8 @@
                 '-I%s '
                 '--python_out=. '
                 '--grpc_python_out=. '
+                '--plugin=protoc-gen-gw=%s/rpc_gw_gen.py '
+                '--gw_out=. '
                 '--plugin=protoc-gen-custom=%s/proto2yang.py '
                 '%s'
                 '%s' % (
@@ -265,6 +265,7 @@
                     ':'.join([google_api_dir, netconf_base_dir]),
                     google_api_dir,
                     self.plugin_dir,
+                    self.plugin_dir,
                     '--custom_out=. ' if need_yang else '',
                     fname)
             )
@@ -272,17 +273,9 @@
             os.system(cmd)
             log.info('compiled', file=fname)
 
-            # # test-load each _pb2 file to see all is right
-            # if self.work_dir not in sys.path:
-            #     sys.path.insert(0, self.work_dir)
-            #
-            # for fname in [f for f in os.listdir(self.work_dir)
-            #               if f.endswith('_pb2.py')]:
-            #     modname = fname[:-len('.py')]
-            #     log.debug('test-import', modname=modname)
-            #     _ = __import__(modname)
-
-            # TODO: find a different way to test the generated yang files
+        # Load the generated modules
+        mapper = get_nc_rpc_mapper_instance(self.work_dir, self)
+        mapper.load_modules()
 
     def _set_yang_schemas(self):
         if self.work_dir not in sys.path:
@@ -298,35 +291,27 @@
                 self.yang_schemas.add(fname[:-len('.yang')])
         log.info('yang-schemas', schemas=self.yang_schemas)
 
-    # TODO: should be generated code
-    # Focus for now is issuing a GET request for VolthaGlobalService or VolthaLocalService
     @inlineCallbacks
-    def invoke_voltha_api(self, key):
-        # TODO:  This should be part of a parameter request
-        depth = [('get-depth', '-1')]
+    def invoke_voltha_rpc(self, service, method, params, metadata=None):
         try:
-            data = {}
-            req = ParseDict(data, empty_pb2.Empty())
-            service_method = key.split('-')
-            service = service_method[0]
-            method = service_method[1]
-            if service == 'VolthaGlobalService':
-                stub = VolthaGlobalServiceStub
-            elif service == 'VolthaLocalService':
-                stub = VolthaLocalServiceStub
-            else:
-                raise  # Exception
+            mapper = get_nc_rpc_mapper_instance()
 
-            log.info('voltha-rpc', service=service, method=method, req=req,
-                     depth=depth)
+            # Get the mapping function using the service and method name
+            func = mapper.get_function(service, method)
+            if func is None:
+                log.info('unsupported-rpc', service=service, method=method)
+                return
 
-            res, metadata = yield self.invoke(stub, method, req, depth)
+            response = yield func(self, params, metadata)
 
-            # returnValue(MessageToDict(res, True, True))
-            returnValue(self.convertToDict(res))
+            log.info('rpc-result', service=service, method=method,
+                     response=response)
+
+            returnValue(response)
 
         except Exception, e:
-            log.error('failure', exception=repr(e))
+            log.exception('rpc-failure', service=service, method=method,
+                          params=params, e=e)
 
     @inlineCallbacks
     def invoke(self, stub, method_name, request, metadata, retry=1):
diff --git a/netconf/grpc_client/nc_rpc_mapper.py b/netconf/grpc_client/nc_rpc_mapper.py
new file mode 100644
index 0000000..dacfc2b
--- /dev/null
+++ b/netconf/grpc_client/nc_rpc_mapper.py
@@ -0,0 +1,84 @@
+#
+# 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 os
+import sys
+import inspect
+from structlog import get_logger
+
+log = get_logger()
+
+
+class NetconfRPCMapper:
+    # Keeps the mapping between a Netconf RPC request and a voltha GPRC
+    # request.  Singleton class.
+
+
+    instance = None
+
+    def __init__(self, work_dir, grpc_client):
+        self.work_dir = work_dir
+        self.grpc_client = grpc_client
+        self.rpc_map = {}
+
+    def _add_rpc_map(self, func_name, func_ref):
+        if not self.rpc_map.has_key(func_name):
+            log.debug('adding-function', name=func_name, ref=func_ref)
+            self.rpc_map[func_name] = func_ref
+
+    def _add_module_rpc(self, mod):
+        for name, ref in self.list_functions(mod):
+            self._add_rpc_map(name, ref)
+
+    def is_mod_function(self, mod, func):
+        return inspect.isfunction(func) and inspect.getmodule(func) == mod
+
+    def list_functions(self, mod):
+        return [(func.__name__, func) for func in mod.__dict__.itervalues()
+                if self.is_mod_function(mod, func)]
+
+    def load_modules(self):
+        if self.work_dir not in sys.path:
+            sys.path.insert(0, self.work_dir)
+
+        for fname in [f for f in os.listdir(self.work_dir)
+                      if f.endswith('_rpc_gw.py')]:
+            modname = fname[:-len('.py')]
+            log.debug('load-modules', modname=modname)
+            try:
+                m = __import__(modname)
+                self._add_module_rpc(m)
+            except Exception, e:
+                log.exception('loading-module-exception', modname=modname, e=e)
+
+    def get_function(self, service, method):
+        if service:
+            func_name = ''.join([service, '_', method])
+        else:
+            func_name = method
+
+        if self.rpc_map.has_key(func_name):
+            return self.rpc_map[func_name]
+        else:
+            return None
+
+    def is_rpc_exist(self, rpc_name):
+        return self.rpc_map.has_key(rpc_name)
+
+
+def get_nc_rpc_mapper_instance(work_dir=None, grpc_client=None):
+    if NetconfRPCMapper.instance == None:
+        NetconfRPCMapper.instance = NetconfRPCMapper(work_dir, grpc_client)
+    return NetconfRPCMapper.instance
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
diff --git a/netconf/protoc_plugins/rpc_gw_gen.py b/netconf/protoc_plugins/rpc_gw_gen.py
new file mode 100755
index 0000000..671bf28
--- /dev/null
+++ b/netconf/protoc_plugins/rpc_gw_gen.py
@@ -0,0 +1,242 @@
+#!/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 sys
+
+from google.protobuf.compiler import plugin_pb2 as plugin
+from google.protobuf.descriptor_pb2 import ServiceDescriptorProto, \
+    MethodOptions
+from jinja2 import Template
+from simplejson import dumps
+
+from netconf.protos.third_party.google.api import annotations_pb2, http_pb2
+
+_ = annotations_pb2, http_pb2  # to keep import line from being optimized out
+
+template = Template("""
+# Generated file; please do not edit
+
+from simplejson import dumps, load
+from structlog import get_logger
+from google.protobuf.json_format import MessageToDict, ParseDict
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+{% set package = file_name.replace('.proto', '') %}
+
+{% for pypackage, module in includes %}
+{% if pypackage %}
+from {{ pypackage }} import {{ module }}
+{% else %}
+import {{ module }}
+{% endif %}
+{% endfor %}
+
+log = get_logger()
+
+{% for method in methods %}
+{% set method_name = method['service'].rpartition('.')[2] + '_' + method['method'] %}
+@inlineCallbacks
+def {{ method_name }}(grpc_client, params, metadata, **kw):
+    log.info('{{ method_name }}', params=params, metadata=metadata, **kw)
+    data = params
+    data.update(kw)
+    try:
+        req = ParseDict(data, {{ type_map[method['input_type']] }}())
+    except Exception, e:
+        log.error('cannot-convert-to-protobuf', e=e, data=data)
+        raise
+    res, _ = yield grpc_client.invoke(
+        {{ type_map[method['service']] }}Stub,
+        '{{ method['method'] }}', req, metadata)
+    try:
+        out_data = grpc_client.convertToDict(res)
+    except AttributeError, e:
+        filename = '/tmp/netconf_failed_to_convert_data.pbd'
+        with file(filename, 'w') as f:
+            f.write(res.SerializeToString())
+        log.error('cannot-convert-from-protobuf', outdata_saved=filename)
+        raise
+    log.info('{{ method_name }}', **out_data)
+    returnValue(out_data)
+
+{% endfor %}
+
+""", trim_blocks=True, lstrip_blocks=True)
+
+
+def traverse_methods(proto_file):
+    package = proto_file.name
+    for service in proto_file.service:
+        assert isinstance(service, ServiceDescriptorProto)
+
+        for method in service.method:
+            input_type = method.input_type
+            if input_type.startswith('.'):
+                input_type = input_type[1:]
+
+            output_type = method.output_type
+            if output_type.startswith('.'):
+                output_type = output_type[1:]
+
+            data = {
+                'package': package,
+                'filename': proto_file.name,
+                'service': proto_file.package + '.' + service.name,
+                'method': method.name,
+                'input_type': input_type,
+                'output_type': output_type
+            }
+
+            yield data
+
+
+def generate_gw_code(file_name, methods, type_map, includes):
+    return template.render(file_name=file_name, methods=methods,
+                           type_map=type_map, includes=includes)
+
+
+class IncludeManager(object):
+    # need to keep track of what files define what message types and
+    # under what package name. Later, when we analyze the methods, we
+    # need to be able to derive the list of files we need to load and we
+    # also need to replce the <proto-package-name>.<artifact-name> in the
+    # templates with <python-package-name>.<artifact-name> so Python can
+    # resolve these.
+    def __init__(self):
+        self.package_to_localname = {}
+        self.fullname_to_filename = {}
+        self.prefix_table = []  # sorted table of top-level symbols in protos
+        self.type_map = {}  # full name as used in .proto -> python name
+        self.includes_needed = set()  # names of files needed to be included
+        self.filename_to_module = {}  # filename -> (package, module)
+
+    def extend_symbol_tables(self, proto_file):
+        # keep track of what file adds what top-level symbol to what abstract
+        # package name
+        package_name = proto_file.package
+        file_name = proto_file.name
+        self._add_filename(file_name)
+        all_defs = list(proto_file.message_type)
+        all_defs.extend(list(proto_file.enum_type))
+        all_defs.extend(list(proto_file.service))
+        for typedef in all_defs:
+            name = typedef.name
+            fullname = package_name + '.' + name
+            self.fullname_to_filename[fullname] = file_name
+            self.package_to_localname.setdefault(package_name, []).append(name)
+        self._update_prefix_table()
+
+    def _add_filename(self, filename):
+        if filename not in self.filename_to_module:
+            python_path = filename.replace('.proto', '_pb2').replace('/', '.')
+            package_name, _, module_name = python_path.rpartition('.')
+            self.filename_to_module[filename] = (package_name, module_name)
+
+    def _update_prefix_table(self):
+        # make a sorted list symbol prefixes needed to resolv for potential use
+        # of nested symbols
+        self.prefix_table = sorted(self.fullname_to_filename.iterkeys(),
+                                   reverse=True)
+
+    def _find_matching_prefix(self, fullname):
+        for prefix in self.prefix_table:
+            if fullname.startswith(prefix):
+                return prefix
+        # This should never happen
+        raise Exception('No match for type name "{}"'.format(fullname))
+
+    def add_needed_symbol(self, fullname):
+        if fullname in self.type_map:
+            return
+        top_level_symbol = self._find_matching_prefix(fullname)
+        name = top_level_symbol.rpartition('.')[2]
+        nested_name = fullname[len(top_level_symbol):]  # may be empty
+        file_name = self.fullname_to_filename[top_level_symbol]
+        self.includes_needed.add(file_name)
+        module_name = self.filename_to_module[file_name][1]
+        python_name = module_name + '.' + name + nested_name
+        self.type_map[fullname] = python_name
+
+    def get_type_map(self):
+        return self.type_map
+
+    def get_includes(self):
+        return sorted(
+            self.filename_to_module[fn] for fn in self.includes_needed)
+
+
+def generate_code(request, response):
+    assert isinstance(request, plugin.CodeGeneratorRequest)
+
+    include_manager = IncludeManager()
+    for proto_file in request.proto_file:
+
+        include_manager.extend_symbol_tables(proto_file)
+
+        methods = []
+
+        for data in traverse_methods(proto_file):
+            methods.append(data)
+            include_manager.add_needed_symbol(data['input_type'])
+            include_manager.add_needed_symbol(data['output_type'])
+            include_manager.add_needed_symbol(data['service'])
+
+        type_map = include_manager.get_type_map()
+        includes = include_manager.get_includes()
+
+        # as a nice side-effect, generate a json file capturing the essence
+        # of the RPC method entries
+        f = response.file.add()
+        f.name = proto_file.name + '.json'
+        f.content = dumps(dict(
+            type_rename_map=type_map,
+            includes=includes,
+            methods=methods), indent=4)
+
+        # generate the real Python code file
+        f = response.file.add()
+        assert proto_file.name.endswith('.proto')
+        f.name = proto_file.name.replace('.proto', '_rpc_gw.py')
+        f.content = generate_gw_code(proto_file.name,
+                                     methods, type_map, includes)
+
+
+if __name__ == '__main__':
+
+    if len(sys.argv) >= 2:
+        # read input from file, to allow troubleshooting
+        with open(sys.argv[1], 'r') as f:
+            data = f.read()
+    else:
+        # read input from stdin
+        data = sys.stdin.read()
+
+    # parse request
+    request = plugin.CodeGeneratorRequest()
+    request.ParseFromString(data)
+
+    # create response object
+    response = plugin.CodeGeneratorResponse()
+
+    # generate the output and the response
+    generate_code(request, response)
+
+    # serialize the response
+    output = response.SerializeToString()
+
+    # write response to stdout
+    sys.stdout.write(output)
diff --git a/netconf/protos/Makefile b/netconf/protos/Makefile
index 5a5da58..94bfaab 100644
--- a/netconf/protos/Makefile
+++ b/netconf/protos/Makefile
@@ -52,6 +52,7 @@
 	rm -f $(PROTO_PB2_FILES) $(PROTO_DESC_FILES)
 	rm $(TARGET_YANG_OPTION_DIR)/$(YANG_OPTION_FILE)
 	rm $(TARGET_PROTO_DIR)/*.py
+	rm $(TARGET_PROTO_DIR)/*.pyc
 
 
 
diff --git a/netconf/session/nc_protocol_handler.py b/netconf/session/nc_protocol_handler.py
index 441c29c..6b440e2 100644
--- a/netconf/session/nc_protocol_handler.py
+++ b/netconf/session/nc_protocol_handler.py
@@ -27,6 +27,7 @@
 
 log = structlog.get_logger()
 
+
 class NetconfProtocolError(Exception): pass
 
 
@@ -74,6 +75,17 @@
         log.info("RPC-Reply", reply=ucode)
         self.send_message(ucode)
 
+    def send_custom_rpc_reply(self, rpc_reply, origmsg):
+        reply = etree.Element(qmap(C.NC) + C.RPC_REPLY, attrib=origmsg.attrib,
+                              nsmap=rpc_reply.nsmap)
+        try:
+            reply.extend(rpc_reply.getchildren())
+        except AttributeError:
+            reply.extend(rpc_reply)
+        ucode = etree.tounicode(reply, pretty_print=True)
+        log.info("Custom-RPC-Reply", reply=ucode)
+        self.send_message(ucode)
+
     def set_framing_version(self):
         if C.NETCONF_BASE_11 in self.capabilities.client_caps:
             self.new_framing = True
@@ -106,7 +118,7 @@
             log.info('session-opened', session_id=self.session.session_id,
                      framing="1.1" if self.new_framing else "1.0")
         except Exception as e:
-            log.error('hello-failure', exception=repr(e))
+            log.exception('hello-failure', e=e)
             self.stop(repr(e))
             raise
 
@@ -178,10 +190,14 @@
                     log.info('handler',
                              rpc_handler=rpc_handler,
                              is_error=response.is_error,
+                             custom_rpc=response.custom_rpc,
                              response=response)
                     if not response.is_error:
-                        self.send_rpc_reply(response.node, rpc)
-                        # self.send_rpc_reply(self.get_mock_volthainstance(), rpc)
+                        if response.custom_rpc:
+                            self.send_custom_rpc_reply(response.node, rpc)
+                        else:
+                            self.send_rpc_reply(response.node, rpc)
+                            # self.send_rpc_reply(self.get_mock_volthainstance(), rpc)
                     else:
                         self.send_message(response.node.get_xml_reply())
 
@@ -205,14 +221,13 @@
                     log.error("Closing-1-0-session--malformed-message")
                     self.close()
             except (ncerror.NotImpl, ncerror.MissingElement) as e:
-                log.info('error', repr(e))
+                log.exception('error', e=e)
                 self.send_message(e.get_reply_msg())
-            except Exception as ex:
-                log.info('Exception', repr(ex))
-                error = ncerror.ServerException(rpc, ex)
+            except Exception as e:
+                log.exception('Exception', e=e)
+                error = ncerror.ServerException(rpc, e)
                 self.send_message(error.get_xml_reply())
 
-
     def stop(self, reason):
         if not self.exiting:
             log.debug('stopping')
@@ -236,50 +251,49 @@
             self.connected.callback(None)
             log.info('closing-client')
 
-
     # Example of a properly formatted Yang-XML message
     def get_mock_volthainstance(self):
         res = {'log_level': 'INFO',
                'device_types': [
-                    {'adapter': u'broadcom_onu',
+                   {'adapter': u'broadcom_onu',
                     'accepts_bulk_flow_update': True,
                     'id': u'broadcom_onu',
                     'accepts_add_remove_flow_updates': False
                     },
-                    {'adapter': u'maple_olt',
+                   {'adapter': u'maple_olt',
                     'accepts_bulk_flow_update': True,
                     'id': u'maple_olt',
-                     'accepts_add_remove_flow_updates': False
-                     },
-                    {'adapter': u'ponsim_olt',
-                     'accepts_bulk_flow_update': True,
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'ponsim_olt',
+                    'accepts_bulk_flow_update': True,
                     'id': u'ponsim_olt',
-                     'accepts_add_remove_flow_updates': False
-                     },
-                    {'adapter': u'ponsim_onu',
-                     'accepts_bulk_flow_update': True,
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'ponsim_onu',
+                    'accepts_bulk_flow_update': True,
                     'id': u'ponsim_onu',
-                     'accepts_add_remove_flow_updates': False
-                     },
-                    {'adapter': u'simulated_olt',
-                     'accepts_bulk_flow_update': True,
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'simulated_olt',
+                    'accepts_bulk_flow_update': True,
                     'id': u'simulated_olt',
-                     'accepts_add_remove_flow_updates': False
-                     },
-                    {'adapter': u'simulated_onu',
-                     'accepts_bulk_flow_update': True,
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'simulated_onu',
+                    'accepts_bulk_flow_update': True,
                     'id': u'simulated_onu',
-                     'accepts_add_remove_flow_updates': False
-                     },
-                     {'adapter': u'tibit_olt',
-                      'accepts_bulk_flow_update': True,
-                      'id': u'tibit_olt',
-                      'accepts_add_remove_flow_updates': False
-                      },
-                      {'adapter': u'tibit_onu',
-                       'accepts_bulk_flow_update': True,
-                       'id': u'tibit_onu',
-                       'accepts_add_remove_flow_updates': False}
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'tibit_olt',
+                    'accepts_bulk_flow_update': True,
+                    'id': u'tibit_olt',
+                    'accepts_add_remove_flow_updates': False
+                    },
+                   {'adapter': u'tibit_onu',
+                    'accepts_bulk_flow_update': True,
+                    'id': u'tibit_onu',
+                    'accepts_add_remove_flow_updates': False}
                ],
                'logical_devices': [],
                'devices': [],
@@ -336,7 +350,7 @@
                     'logical_device_ids': []
                     }
                ]
-        }
+               }
         devices_array = []
         flow_items = []
         for i in xrange(1, 10):
@@ -345,17 +359,17 @@
                     'id': str(i),
                     'table_id': 'table_id_' + str(i),
                     'flags': i,
-                    'instructions' : [
-                        {'type' : i, 'goto_table': 'table_id_' + str(i) },
+                    'instructions': [
+                        {'type': i, 'goto_table': 'table_id_' + str(i)},
                         {'type': i, 'meter': i},
                         {'type': i,
                          'actions': {'actions': [
-                                        {'type': 11,
-                                        'output': {
-                                            'port': i,
-                                            'max_len': i}
-                                         }
-                                    ]}
+                             {'type': 11,
+                              'output': {
+                                  'port': i,
+                                  'max_len': i}
+                              }
+                         ]}
                          }
                     ]
                 }
@@ -374,4 +388,4 @@
         # print etree.tounicode(root, pretty_print=True)
         request = {'class': 'VolthaInstance'}
         top = RpcResponse().build_yang_response(root, request)
-        return top
\ No newline at end of file
+        return top