Schema serving grpc API for introspective clients
diff --git a/voltha/core/protos/Makefile b/voltha/core/protos/Makefile
index dd5542b..b602c4d 100644
--- a/voltha/core/protos/Makefile
+++ b/voltha/core/protos/Makefile
@@ -42,7 +42,15 @@
 
 %_pb2.py: %.proto Makefile
 	@echo "Building protocol buffer artifacts from $<"
-	env LD_LIBRARY_PATH=$(PROTOC_LIBDIR) python -m grpc.tools.protoc -I. -I./third_party --python_out=. --grpc_python_out=. --descriptor_set_out=$(basename $<).desc --include_source_info $<
+	env LD_LIBRARY_PATH=$(PROTOC_LIBDIR) python -m grpc.tools.protoc \
+	    -I. \
+	    -I./third_party \
+	    --python_out=. \
+	    --grpc_python_out=. \
+	    --descriptor_set_out=$(basename $<).desc \
+	    --include_imports \
+	    --include_source_info \
+	    $<
 
 clean:
 	rm -f $(PROTO_PB2_FILES) $(PROTO_DESC_FILES)
diff --git a/voltha/core/protos/third_party/google/api/annotations.desc b/voltha/core/protos/third_party/google/api/annotations.desc
index 67f970d..d159f06 100644
--- a/voltha/core/protos/third_party/google/api/annotations.desc
+++ b/voltha/core/protos/third_party/google/api/annotations.desc
Binary files differ
diff --git a/voltha/core/protos/voltha.desc b/voltha/core/protos/voltha.desc
index d22a73b..b1755ef 100644
--- a/voltha/core/protos/voltha.desc
+++ b/voltha/core/protos/voltha.desc
Binary files differ
diff --git a/voltha/core/protos/voltha.proto b/voltha/core/protos/voltha.proto
index 87b6dda..9ce135a 100644
--- a/voltha/core/protos/voltha.proto
+++ b/voltha/core/protos/voltha.proto
@@ -8,6 +8,17 @@
 option java_outer_classname = "VolthaProtos";
 option csharp_namespace = "Opencord.Voltha.Voltha";
 
+// Proto file and compiled descriptor for this interface
+message Schema {
+
+  // file name -> proto file content
+  map<string, string> protos = 1;
+
+  // file name -> gzip compressed protobuf of descriptor
+  map<string, bytes> descriptors = 2;
+
+}
+
 // Empty message
 message NullMessage {}
 
@@ -26,6 +37,17 @@
 
 }
 
+// Schema services
+service SchemaService {
+
+  // Return active grpc schemas
+  rpc GetSchema(NullMessage) returns (Schema) {
+    option (google.api.http) = {
+      get: "/schema"
+    };
+  }
+}
+
 // Health related services
 service HealthService {
 
diff --git a/voltha/core/protos/voltha_pb2.py b/voltha/core/protos/voltha_pb2.py
index 176d023..e82df67 100644
--- a/voltha/core/protos/voltha_pb2.py
+++ b/voltha/core/protos/voltha_pb2.py
@@ -20,7 +20,7 @@
   name='voltha.proto',
   package='voltha',
   syntax='proto3',
-  serialized_pb=_b('\n\x0cvoltha.proto\x12\x06voltha\x1a\x1cgoogle/api/annotations.proto\"\r\n\x0bNullMessage\"v\n\x0cHealthStatus\x12/\n\x05state\x18\x01 \x01(\x0e\x32 .voltha.HealthStatus.HealthState\"5\n\x0bHealthState\x12\x0b\n\x07HEALTHY\x10\x00\x12\x0e\n\nOVERLOADED\x10\x01\x12\t\n\x05\x44YING\x10\x02\"q\n\x07\x41\x64\x64ress\x12\n\n\x02id\x18\x07 \x01(\t\x12\x0e\n\x06street\x18\x01 \x01(\t\x12\x0f\n\x07street2\x18\x02 \x01(\t\x12\x0f\n\x07street3\x18\x03 \x01(\t\x12\x0c\n\x04\x63ity\x18\x04 \x01(\t\x12\r\n\x05state\x18\x05 \x01(\t\x12\x0b\n\x03zip\x18\x06 \x01(\r\"/\n\tAddresses\x12\"\n\taddresses\x18\x01 \x03(\x0b\x32\x0f.voltha.Address\"\x9f\x01\n\x0bMoreComplex\x12$\n\x06health\x18\x01 \x01(\x0b\x32\x14.voltha.HealthStatus\x12\x13\n\x0b\x66oo_counter\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12%\n\x08\x63hildren\x18\x04 \x03(\x0b\x32\x13.voltha.MoreComplex\x12 \n\x07\x61\x64\x64ress\x18\x05 \x01(\x0b\x32\x0f.voltha.Address\"\x10\n\x02ID\x12\n\n\x02id\x18\x01 \x01(\t2a\n\rHealthService\x12P\n\x0fGetHealthStatus\x12\x13.voltha.NullMessage\x1a\x14.voltha.HealthStatus\"\x12\x82\xd3\xe4\x93\x02\x0c\x12\x07/health:\x01*2\xb6\x02\n\x0e\x45xampleService\x12K\n\rListAddresses\x12\x13.voltha.NullMessage\x1a\x11.voltha.Addresses\"\x12\x82\xd3\xe4\x93\x02\x0c\x12\n/addresses\x12\x42\n\nGetAddress\x12\n.voltha.ID\x1a\x0f.voltha.Address\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/addresses/{id}\x12H\n\rCreateAddress\x12\x0f.voltha.Address\x1a\x0f.voltha.Address\"\x15\x82\xd3\xe4\x93\x02\x0f\"\n/addresses:\x01*\x12I\n\rDeleteAddress\x12\n.voltha.ID\x1a\x13.voltha.NullMessage\"\x17\x82\xd3\xe4\x93\x02\x11*\x0f/addresses/{id}B<\n\x13org.opencord.volthaB\x0cVolthaProtos\xaa\x02\x16Opencord.Voltha.Volthab\x06proto3')
+  serialized_pb=_b('\n\x0cvoltha.proto\x12\x06voltha\x1a\x1cgoogle/api/annotations.proto\"\xcd\x01\n\x06Schema\x12*\n\x06protos\x18\x01 \x03(\x0b\x32\x1a.voltha.Schema.ProtosEntry\x12\x34\n\x0b\x64\x65scriptors\x18\x02 \x03(\x0b\x32\x1f.voltha.Schema.DescriptorsEntry\x1a-\n\x0bProtosEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x32\n\x10\x44\x65scriptorsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\r\n\x0bNullMessage\"v\n\x0cHealthStatus\x12/\n\x05state\x18\x01 \x01(\x0e\x32 .voltha.HealthStatus.HealthState\"5\n\x0bHealthState\x12\x0b\n\x07HEALTHY\x10\x00\x12\x0e\n\nOVERLOADED\x10\x01\x12\t\n\x05\x44YING\x10\x02\"q\n\x07\x41\x64\x64ress\x12\n\n\x02id\x18\x07 \x01(\t\x12\x0e\n\x06street\x18\x01 \x01(\t\x12\x0f\n\x07street2\x18\x02 \x01(\t\x12\x0f\n\x07street3\x18\x03 \x01(\t\x12\x0c\n\x04\x63ity\x18\x04 \x01(\t\x12\r\n\x05state\x18\x05 \x01(\t\x12\x0b\n\x03zip\x18\x06 \x01(\r\"/\n\tAddresses\x12\"\n\taddresses\x18\x01 \x03(\x0b\x32\x0f.voltha.Address\"\x9f\x01\n\x0bMoreComplex\x12$\n\x06health\x18\x01 \x01(\x0b\x32\x14.voltha.HealthStatus\x12\x13\n\x0b\x66oo_counter\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12%\n\x08\x63hildren\x18\x04 \x03(\x0b\x32\x13.voltha.MoreComplex\x12 \n\x07\x61\x64\x64ress\x18\x05 \x01(\x0b\x32\x0f.voltha.Address\"\x10\n\x02ID\x12\n\n\x02id\x18\x01 \x01(\t2R\n\rSchemaService\x12\x41\n\tGetSchema\x12\x13.voltha.NullMessage\x1a\x0e.voltha.Schema\"\x0f\x82\xd3\xe4\x93\x02\t\x12\x07/schema2^\n\rHealthService\x12M\n\x0fGetHealthStatus\x12\x13.voltha.NullMessage\x1a\x14.voltha.HealthStatus\"\x0f\x82\xd3\xe4\x93\x02\t\x12\x07/health2\xb6\x02\n\x0e\x45xampleService\x12K\n\rListAddresses\x12\x13.voltha.NullMessage\x1a\x11.voltha.Addresses\"\x12\x82\xd3\xe4\x93\x02\x0c\x12\n/addresses\x12\x42\n\nGetAddress\x12\n.voltha.ID\x1a\x0f.voltha.Address\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/addresses/{id}\x12H\n\rCreateAddress\x12\x0f.voltha.Address\x1a\x0f.voltha.Address\"\x15\x82\xd3\xe4\x93\x02\x0f\"\n/addresses:\x01*\x12I\n\rDeleteAddress\x12\n.voltha.ID\x1a\x13.voltha.NullMessage\"\x17\x82\xd3\xe4\x93\x02\x11*\x0f/addresses/{id}B<\n\x13org.opencord.volthaB\x0cVolthaProtos\xaa\x02\x16Opencord.Voltha.Volthab\x06proto3')
   ,
   dependencies=[google_dot_api_dot_annotations__pb2.DESCRIPTOR,])
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -48,12 +48,124 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=134,
-  serialized_end=187,
+  serialized_start=342,
+  serialized_end=395,
 )
 _sym_db.RegisterEnumDescriptor(_HEALTHSTATUS_HEALTHSTATE)
 
 
+_SCHEMA_PROTOSENTRY = _descriptor.Descriptor(
+  name='ProtosEntry',
+  full_name='voltha.Schema.ProtosEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='voltha.Schema.ProtosEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='voltha.Schema.ProtosEntry.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=163,
+  serialized_end=208,
+)
+
+_SCHEMA_DESCRIPTORSENTRY = _descriptor.Descriptor(
+  name='DescriptorsEntry',
+  full_name='voltha.Schema.DescriptorsEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='voltha.Schema.DescriptorsEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='voltha.Schema.DescriptorsEntry.value', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=210,
+  serialized_end=260,
+)
+
+_SCHEMA = _descriptor.Descriptor(
+  name='Schema',
+  full_name='voltha.Schema',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='protos', full_name='voltha.Schema.protos', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='descriptors', full_name='voltha.Schema.descriptors', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[_SCHEMA_PROTOSENTRY, _SCHEMA_DESCRIPTORSENTRY, ],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=55,
+  serialized_end=260,
+)
+
+
 _NULLMESSAGE = _descriptor.Descriptor(
   name='NullMessage',
   full_name='voltha.NullMessage',
@@ -73,8 +185,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=54,
-  serialized_end=67,
+  serialized_start=262,
+  serialized_end=275,
 )
 
 
@@ -105,8 +217,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=69,
-  serialized_end=187,
+  serialized_start=277,
+  serialized_end=395,
 )
 
 
@@ -178,8 +290,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=189,
-  serialized_end=302,
+  serialized_start=397,
+  serialized_end=510,
 )
 
 
@@ -209,8 +321,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=304,
-  serialized_end=351,
+  serialized_start=512,
+  serialized_end=559,
 )
 
 
@@ -268,8 +380,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=354,
-  serialized_end=513,
+  serialized_start=562,
+  serialized_end=721,
 )
 
 
@@ -299,16 +411,21 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=515,
-  serialized_end=531,
+  serialized_start=723,
+  serialized_end=739,
 )
 
+_SCHEMA_PROTOSENTRY.containing_type = _SCHEMA
+_SCHEMA_DESCRIPTORSENTRY.containing_type = _SCHEMA
+_SCHEMA.fields_by_name['protos'].message_type = _SCHEMA_PROTOSENTRY
+_SCHEMA.fields_by_name['descriptors'].message_type = _SCHEMA_DESCRIPTORSENTRY
 _HEALTHSTATUS.fields_by_name['state'].enum_type = _HEALTHSTATUS_HEALTHSTATE
 _HEALTHSTATUS_HEALTHSTATE.containing_type = _HEALTHSTATUS
 _ADDRESSES.fields_by_name['addresses'].message_type = _ADDRESS
 _MORECOMPLEX.fields_by_name['health'].message_type = _HEALTHSTATUS
 _MORECOMPLEX.fields_by_name['children'].message_type = _MORECOMPLEX
 _MORECOMPLEX.fields_by_name['address'].message_type = _ADDRESS
+DESCRIPTOR.message_types_by_name['Schema'] = _SCHEMA
 DESCRIPTOR.message_types_by_name['NullMessage'] = _NULLMESSAGE
 DESCRIPTOR.message_types_by_name['HealthStatus'] = _HEALTHSTATUS
 DESCRIPTOR.message_types_by_name['Address'] = _ADDRESS
@@ -316,6 +433,29 @@
 DESCRIPTOR.message_types_by_name['MoreComplex'] = _MORECOMPLEX
 DESCRIPTOR.message_types_by_name['ID'] = _ID
 
+Schema = _reflection.GeneratedProtocolMessageType('Schema', (_message.Message,), dict(
+
+  ProtosEntry = _reflection.GeneratedProtocolMessageType('ProtosEntry', (_message.Message,), dict(
+    DESCRIPTOR = _SCHEMA_PROTOSENTRY,
+    __module__ = 'voltha_pb2'
+    # @@protoc_insertion_point(class_scope:voltha.Schema.ProtosEntry)
+    ))
+  ,
+
+  DescriptorsEntry = _reflection.GeneratedProtocolMessageType('DescriptorsEntry', (_message.Message,), dict(
+    DESCRIPTOR = _SCHEMA_DESCRIPTORSENTRY,
+    __module__ = 'voltha_pb2'
+    # @@protoc_insertion_point(class_scope:voltha.Schema.DescriptorsEntry)
+    ))
+  ,
+  DESCRIPTOR = _SCHEMA,
+  __module__ = 'voltha_pb2'
+  # @@protoc_insertion_point(class_scope:voltha.Schema)
+  ))
+_sym_db.RegisterMessage(Schema)
+_sym_db.RegisterMessage(Schema.ProtosEntry)
+_sym_db.RegisterMessage(Schema.DescriptorsEntry)
+
 NullMessage = _reflection.GeneratedProtocolMessageType('NullMessage', (_message.Message,), dict(
   DESCRIPTOR = _NULLMESSAGE,
   __module__ = 'voltha_pb2'
@@ -361,6 +501,10 @@
 
 DESCRIPTOR.has_options = True
 DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\023org.opencord.volthaB\014VolthaProtos\252\002\026Opencord.Voltha.Voltha'))
+_SCHEMA_PROTOSENTRY.has_options = True
+_SCHEMA_PROTOSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001'))
+_SCHEMA_DESCRIPTORSENTRY.has_options = True
+_SCHEMA_DESCRIPTORSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001'))
 import grpc
 from grpc.beta import implementations as beta_implementations
 from grpc.beta import interfaces as beta_interfaces
@@ -368,7 +512,98 @@
 from grpc.framework.interfaces.face import utilities as face_utilities
 
 
+class SchemaServiceStub(object):
+  """Schema services
+  """
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.GetSchema = channel.unary_unary(
+        '/voltha.SchemaService/GetSchema',
+        request_serializer=NullMessage.SerializeToString,
+        response_deserializer=Schema.FromString,
+        )
+
+
+class SchemaServiceServicer(object):
+  """Schema services
+  """
+
+  def GetSchema(self, request, context):
+    """Return active grpc schemas
+    """
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_SchemaServiceServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'GetSchema': grpc.unary_unary_rpc_method_handler(
+          servicer.GetSchema,
+          request_deserializer=NullMessage.FromString,
+          response_serializer=Schema.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'voltha.SchemaService', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
+
+
+class BetaSchemaServiceServicer(object):
+  """Schema services
+  """
+  def GetSchema(self, request, context):
+    """Return active grpc schemas
+    """
+    context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
+
+
+class BetaSchemaServiceStub(object):
+  """Schema services
+  """
+  def GetSchema(self, request, timeout, metadata=None, with_call=False, protocol_options=None):
+    """Return active grpc schemas
+    """
+    raise NotImplementedError()
+  GetSchema.future = None
+
+
+def beta_create_SchemaService_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None):
+  request_deserializers = {
+    ('voltha.SchemaService', 'GetSchema'): NullMessage.FromString,
+  }
+  response_serializers = {
+    ('voltha.SchemaService', 'GetSchema'): Schema.SerializeToString,
+  }
+  method_implementations = {
+    ('voltha.SchemaService', 'GetSchema'): face_utilities.unary_unary_inline(servicer.GetSchema),
+  }
+  server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout)
+  return beta_implementations.server(method_implementations, options=server_options)
+
+
+def beta_create_SchemaService_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None):
+  request_serializers = {
+    ('voltha.SchemaService', 'GetSchema'): NullMessage.SerializeToString,
+  }
+  response_deserializers = {
+    ('voltha.SchemaService', 'GetSchema'): Schema.FromString,
+  }
+  cardinalities = {
+    'GetSchema': cardinality.Cardinality.UNARY_UNARY,
+  }
+  stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size)
+  return beta_implementations.dynamic_stub(channel, 'voltha.SchemaService', cardinalities, options=stub_options)
+
+
 class HealthServiceStub(object):
+  """Health related services
+  """
 
   def __init__(self, channel):
     """Constructor.
@@ -384,6 +619,8 @@
 
 
 class HealthServiceServicer(object):
+  """Health related services
+  """
 
   def GetHealthStatus(self, request, context):
     """Return current health status of a Voltha instance
@@ -407,6 +644,8 @@
 
 
 class BetaHealthServiceServicer(object):
+  """Health related services
+  """
   def GetHealthStatus(self, request, context):
     """Return current health status of a Voltha instance
     """
@@ -414,6 +653,8 @@
 
 
 class BetaHealthServiceStub(object):
+  """Health related services
+  """
   def GetHealthStatus(self, request, timeout, metadata=None, with_call=False, protocol_options=None):
     """Return current health status of a Voltha instance
     """
diff --git a/voltha/northbound/grpc/grpc_client.py b/voltha/northbound/grpc/grpc_client.py
index fc2b316..60b0b6b 100644
--- a/voltha/northbound/grpc/grpc_client.py
+++ b/voltha/northbound/grpc/grpc_client.py
@@ -25,6 +25,17 @@
 
     channel = grpc.insecure_channel('localhost:50055')
 
+    # Test fetch the schema
+    stub = voltha_pb2.SchemaServiceStub(channel)
+    res = stub.GetSchema(voltha_pb2.NullMessage())
+    print '\nSchema:\n'
+    for key in res.protos:
+        print '%s %s file begins %s\n' % (30 * '~', key, (35 - len(key)) * '~')
+        print res.protos[key]
+        print '%s %s file ends %s' % (30 * '~', key, (37 - len(key)) * '~')
+    for key in res.descriptors:
+        print '%s -> descriptor of %d bytes' % (key, len(res.descriptors[key]))
+
     # Ping health state as an example
     stub = voltha_pb2.HealthServiceStub(channel)
     res = stub.GetHealthStatus(voltha_pb2.NullMessage())
diff --git a/voltha/northbound/grpc/grpc_server.py b/voltha/northbound/grpc/grpc_server.py
index 9e1e44e..5b1d4ca 100644
--- a/voltha/northbound/grpc/grpc_server.py
+++ b/voltha/northbound/grpc/grpc_server.py
@@ -16,16 +16,64 @@
 
 """gRPC server endpoint"""
 import uuid
-
+from os.path import abspath, basename, dirname, join, walk
 import grpc
 from concurrent import futures
 from structlog import get_logger
+import zlib
 
 from voltha.core.protos import voltha_pb2
 
 log = get_logger()
 
 
+class SchemaService(voltha_pb2.SchemaServiceServicer):
+
+    def __init__(self):
+        proto_map, descriptor_map = self._load_schema()
+        self.schema = voltha_pb2.Schema(
+            protos=proto_map,
+            descriptors=descriptor_map
+        )
+
+    def _load_schema(self):
+        """Pre-load schema file so that we can serve it up (file sizes
+           are small enough to do so
+        """
+        proto_dir = abspath(join(dirname(__file__), '../../core/protos'))
+
+        def find_files(dir, suffix):
+            proto_files = []
+            visitor = lambda _, d, fnames: proto_files.extend(
+                [join(d, fn) for fn in fnames if fn.endswith(suffix)])
+            walk(dir, visitor, None)
+            return proto_files
+
+        proto_map = {}
+        for fpath in find_files(proto_dir, '.proto'):
+            with open(fpath, 'r') as f:
+                content = f.read()
+            fname = basename(fpath)
+            # assure no two files have the same basename
+            assert fname not in proto_map
+            proto_map[fname] = content
+
+        descriptor_map = {}
+        for fpath in find_files(proto_dir, '.desc'):
+            with open(fpath, 'r') as f:
+                content = f.read()
+            fname = basename(fpath)
+            # assure no two files have the same basename
+            assert fname not in descriptor_map
+            descriptor_map[fname] = zlib.compress(content)
+
+        return proto_map, descriptor_map
+
+    def GetSchema(self, request, context):
+        """Return current schema files and descriptor"""
+        return self.schema
+
+
 class HealthService(voltha_pb2.HealthServiceServicer):
 
     def __init__(self, thread_pool):
@@ -91,6 +139,8 @@
             HealthService(self.thread_pool), self.server)
         voltha_pb2.add_ExampleServiceServicer_to_server(
             ExampleService(self.thread_pool), self.server)
+        voltha_pb2.add_SchemaServiceServicer_to_server(
+            SchemaService(), self.server)
 
         self.server.add_insecure_port('[::]:%s' % self.port)