SEBA-495 eliminate chameleon dependency

Change-Id: Ia359d751c3ac84bf8f7038f611d1c5f1a126d1df
diff --git a/lib/xos-api/xosapi/chameleon_client/__init__.py b/lib/xos-api/xosapi/chameleon_client/__init__.py
new file mode 100644
index 0000000..1a13cdf
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/__init__.py
@@ -0,0 +1,15 @@
+
+# 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.
+#
diff --git a/lib/xos-api/xosapi/chameleon_client/asleep.py b/lib/xos-api/xosapi/chameleon_client/asleep.py
new file mode 100644
index 0000000..295f675
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/asleep.py
@@ -0,0 +1,28 @@
+#
+# 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 twisted.internet import reactor
+from twisted.internet.defer import Deferred
+
+
+def asleep(dt):
+    """
+    Async (event driven) wait for given time period (in seconds)
+    :param dt: Delay in seconds
+    :return: Deferred to be fired with value None when time expires.
+    """
+    d = Deferred()
+    reactor.callLater(dt, lambda: d.callback(None))
+    return d
\ No newline at end of file
diff --git a/lib/xos-api/xosapi/chameleon_client/grpc_client.py b/lib/xos-api/xosapi/chameleon_client/grpc_client.py
new file mode 100644
index 0000000..a8ad69d
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/grpc_client.py
@@ -0,0 +1,327 @@
+#
+# 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.
+#
+
+"""
+gRPC client meant to connect to a gRPC server endpoint, and query the
+end-point's schema by calling SchemaService.Schema(Empty) and all of its
+semantics are derived from the recovered schema.
+"""
+
+import os
+import sys
+import time
+from random import randint
+from zlib import decompress
+
+import functools
+import grpc
+from consul import Consul
+from grpc._channel import _Rendezvous
+from structlog import get_logger
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue
+from werkzeug.exceptions import ServiceUnavailable
+
+from protos.schema_pb2_grpc import SchemaServiceStub
+from google.protobuf.empty_pb2 import Empty
+
+from asleep import asleep
+
+log = get_logger()
+
+
+class GrpcClient(object):
+    """
+    Connect to a gRPC server, fetch its schema, and process the downloaded
+    schema files to drive the customization of the north-bound interface(s)
+    of Chameleon.
+    """
+    RETRY_BACKOFF = [0.05, 0.1, 0.2, 0.5, 1, 2, 5]
+
+    def __init__(self, consul_endpoint, work_dir, endpoint='localhost:50055',
+                 reconnect_callback=None, credentials=None, restart_on_disconnect=False):
+        self.consul_endpoint = consul_endpoint
+        self.endpoint = endpoint
+        self.work_dir = work_dir
+        self.reconnect_callback = reconnect_callback
+        self.credentials = credentials
+        self.restart_on_disconnect = restart_on_disconnect
+
+        self.plugin_dir = os.path.abspath(os.path.join(
+            os.path.dirname(__file__), 'protoc_plugins'))
+
+        self.channel = None
+        self.schema = None
+        self.retries = 0
+        self.shutting_down = False
+        self.connected = False
+        self.was_connected = False
+
+    def start(self):
+        log.debug('starting')
+        if not self.connected:
+            reactor.callLater(0, self.connect)
+        log.info('started')
+        return self
+
+    def stop(self):
+        log.debug('stopping')
+        if self.shutting_down:
+            return
+        self.shutting_down = True
+        log.info('stopped')
+
+    def set_reconnect_callback(self, reconnect_callback):
+        self.reconnect_callback = reconnect_callback
+        return self
+
+    def connectivity_callback(self, client, connectivity):
+        if (self.was_connected) and (connectivity in [connectivity.TRANSIENT_FAILURE, connectivity.SHUTDOWN]):
+            log.info("connectivity lost -- restarting")
+            os.execv(sys.executable, ['python'] + sys.argv)
+
+        if (connectivity == connectivity.READY):
+            self.was_connected = True
+
+        # Sometimes gRPC transitions from READY to IDLE, skipping TRANSIENT_FAILURE even though a socket is
+        # disconnected. So on idle, force a connectivity check.
+        if (connectivity == connectivity.IDLE) and (self.was_connected):
+            connectivity = client.channel._channel.check_connectivity_state(True)
+            # The result will probably show IDLE, but passing in True has the side effect of reconnecting if the
+            # connection has been lost, which will trigger the TRANSIENT_FALURE we were looking for.
+
+    @inlineCallbacks
+    def connect(self):
+        """
+        (Re-)Connect to end-point
+        """
+
+        if self.shutting_down or self.connected:
+            return
+
+        try:
+            if self.endpoint.startswith('@'):
+                _endpoint = yield self._get_endpoint_from_consul(
+                    self.endpoint[1:])
+            else:
+                _endpoint = self.endpoint
+
+            if self.credentials:
+                log.info('securely connecting', endpoint=_endpoint)
+                self.channel = grpc.secure_channel(_endpoint, self.credentials)
+            else:
+                log.info('insecurely connecting', endpoint=_endpoint)
+                self.channel = grpc.insecure_channel(_endpoint)
+
+            if self.restart_on_disconnect:
+                connectivity_callback = functools.partial(self.connectivity_callback, self)
+                self.channel.subscribe(connectivity_callback)
+
+            # Delay between initiating connection and executing first gRPC. See CORD-3012.
+            time.sleep(0.5)
+
+            swagger_from = self._retrieve_schema()
+            self._compile_proto_files(swagger_from)
+            self._clear_backoff()
+
+            self.connected = True
+            if self.reconnect_callback is not None:
+                reactor.callLater(0, self.reconnect_callback)
+
+            return
+
+        except _Rendezvous, e:
+            if e.code() == grpc.StatusCode.UNAVAILABLE:
+                log.info('grpc-endpoint-not-available')
+            else:
+                log.exception('rendezvous error', e=e)
+            yield self._backoff('not-available')
+
+        except Exception, e:
+            if not self.shutting_down:
+                log.exception('cannot-connect', endpoint=_endpoint)
+            yield self._backoff('unknown-error')
+
+        reactor.callLater(0, self.connect)
+
+    def _backoff(self, msg):
+        wait_time = self.RETRY_BACKOFF[min(self.retries,
+                                           len(self.RETRY_BACKOFF) - 1)]
+        self.retries += 1
+        log.error(msg, retry_in=wait_time)
+        return asleep(wait_time)
+
+    def _clear_backoff(self):
+        if self.retries:
+            log.info('reconnected', after_retries=self.retries)
+            self.retries = 0
+
+    @inlineCallbacks
+    def _get_endpoint_from_consul(self, service_name):
+        """
+        Look up an appropriate grpc endpoint (host, port) from
+        consul, under the service name specified by service-name
+        """
+        host = self.consul_endpoint.split(':')[0].strip()
+        port = int(self.consul_endpoint.split(':')[1].strip())
+
+        while True:
+            log.debug('consul-lookup', host=host, port=port)
+            consul = Consul(host=host, port=port)
+            _, services = consul.catalog.service(service_name)
+            log.debug('consul-response', services=services)
+            if services:
+                break
+            log.warning('no-service', consul_host=host, consul_port=port,
+                        service_name=service_name)
+            yield asleep(1.0)
+
+        # pick local addresses when resolving a service via consul
+        # see CORD-815 (https://jira.opencord.org/browse/CORD-815)
+
+        service = services[randint(0, len(services) - 1)]
+        endpoint = '{}:{}'.format(service['ServiceAddress'],
+                                  service['ServicePort'])
+        returnValue(endpoint)
+
+    def _retrieve_schema(self):
+        """
+        Retrieve schema from gRPC end-point, and save all *.proto files in
+        the work directory.
+        """
+        assert isinstance(self.channel, grpc.Channel)
+        stub = SchemaServiceStub(self.channel)
+        # try:
+        schemas = stub.GetSchema(Empty(), timeout=120)
+        # except _Rendezvous, e:
+        #     if e.code == grpc.StatusCode.UNAVAILABLE:
+        #
+        #     else:
+        #         raise e
+
+        os.system('mkdir -p %s' % self.work_dir)
+        os.system('rm -fr /tmp/%s/*' %
+                  self.work_dir.replace('/tmp/', ''))  # safer
+
+        for proto_file in schemas.protos:
+            proto_fname = proto_file.file_name
+            proto_content = proto_file.proto
+            log.debug('saving-proto', fname=proto_fname, dir=self.work_dir,
+                      length=len(proto_content))
+            with open(os.path.join(self.work_dir, proto_fname), 'w') as f:
+                f.write(proto_content)
+
+            desc_content = decompress(proto_file.descriptor)
+            desc_fname = proto_fname.replace('.proto', '.desc')
+            log.debug('saving-descriptor', fname=desc_fname, dir=self.work_dir,
+                      length=len(desc_content))
+            with open(os.path.join(self.work_dir, desc_fname), 'wb') as f:
+                f.write(desc_content)
+        return schemas.swagger_from
+
+    def _compile_proto_files(self, swagger_from):
+        """
+        For each *.proto file in the work directory, compile the proto
+        file into the respective *_pb2.py file as well as generate the
+        web server gateway python file *_gw.py.
+        :return: None
+        """
+
+        chameleon_base_dir = os.path.abspath(os.path.join(
+            os.path.dirname(__file__), '.'
+        ))
+
+        for fname in [f for f in os.listdir(self.work_dir)
+                      if f.endswith('.proto')]:
+
+            need_swagger = fname == swagger_from
+            log.debug('compiling', file=fname, need_swagger=need_swagger)
+            cmd = (
+                'cd %s && '
+                'env PATH=%s PYTHONPATH=%s '
+                'python -m grpc.tools.protoc '
+                '-I. '
+                '--python_out=. '
+                '--grpc_python_out=. '
+                '--plugin=protoc-gen-gw=%s/gw_gen.py '
+                '--gw_out=. '
+                '%s' % (
+                    self.work_dir,
+                    ':'.join([os.environ['PATH'], self.plugin_dir]),
+                    chameleon_base_dir,
+                    self.plugin_dir,
+                    fname)
+            )
+            log.debug('executing', cmd=cmd, file=fname)
+            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)
+
+    @inlineCallbacks
+    def invoke(self, stub, method_name, request, metadata, retry=1):
+        """
+        Invoke a gRPC call to the remote server and return the response.
+        :param stub: Reference to the *_pb2 service stub
+        :param method_name: The method name inside the service stub
+        :param request: The request protobuf message
+        :param metadata: [(str, str), (str, str), ...]
+        :return: The response protobuf message and returned trailing metadata
+        """
+
+        if not self.connected:
+            raise ServiceUnavailable()
+
+        try:
+            method = getattr(stub(self.channel), method_name)
+            response, rendezvous = method.with_call(request, metadata=metadata)
+            returnValue((response, rendezvous.trailing_metadata()))
+
+        except grpc._channel._Rendezvous, e:
+            code = e.code()
+            if code == grpc.StatusCode.UNAVAILABLE:
+                e = ServiceUnavailable()
+
+                if self.connected:
+                    self.connected = False
+                    yield self.connect()
+                    if retry > 0:
+                        response = yield self.invoke(stub, method_name,
+                                                     request, metadata,
+                                                     retry=retry - 1)
+                        returnValue(response)
+
+            elif code in (
+                    grpc.StatusCode.NOT_FOUND,
+                    grpc.StatusCode.INVALID_ARGUMENT,
+                    grpc.StatusCode.ALREADY_EXISTS,
+                    grpc.StatusCode.UNAUTHENTICATED,
+                    grpc.StatusCode.PERMISSION_DENIED):
+
+                pass  # don't log error, these occur naturally
+
+            else:
+                log.exception(e)
+
+            raise e
diff --git a/lib/xos-api/xosapi/chameleon_client/protoc_plugins/gw_gen.py b/lib/xos-api/xosapi/chameleon_client/protoc_plugins/gw_gen.py
new file mode 100755
index 0000000..9cd2f6f
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protoc_plugins/gw_gen.py
@@ -0,0 +1,305 @@
+#!/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 xosapi.chameleon_client.protos import annotations_pb2, http_pb2
+
+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 }}
+try:
+    from {{ pypackage }} import {{ module }}_grpc
+except ImportError:
+    pass
+{% else %}
+import {{ module }}
+try:
+    import {{ module }}_grpc
+except ImportError:
+    pass
+{% endif %}
+{% endfor %}
+
+log = get_logger()
+
+def add_routes(app, grpc_client):
+
+    pass  # so that if no endpoints are defined, Python is still happy
+
+    {% for method in methods %}
+    {% set method_name = method['service'].rpartition('.')[2] + '_' + method['method'] %}
+    {% set path = method['path'].replace('{', '<string:').replace('}', '>') %}
+    @app.route('{{ path }}', methods=['{{ method['verb'].upper() }}'])
+    @inlineCallbacks
+    def {{ method_name }}(server, request, **kw):
+        log.debug('{{ method_name }}', request=request, server=server, **kw)
+        {% if method['body'] == '*' %}
+        data = load(request.content)
+        data.update(kw)
+        {% elif method['body'] == '' %}
+        data = kw
+        {% else %}
+        raise NotImplementedError('cannot handle specific body field list')
+        {% endif %}
+        try:
+            req = ParseDict(data, {{ type_map[method['input_type']] }}())
+        except Exception, e:
+            log.error('cannot-convert-to-protobuf', e=e, data=data)
+            raise
+        res, metadata = yield grpc_client.invoke(
+            {{ stub_map[method['service']] }}Stub,
+            '{{ method['method'] }}', req, request.getAllHeaders().items())
+        try:
+            out_data = MessageToDict(res, True, True)
+        except AttributeError, e:
+            filename = '/tmp/chameleon_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
+        for key, value in metadata:
+            request.setHeader(key, value)
+        request.setHeader('Content-Type', 'application/json')
+        log.debug('{{ method_name }}', **out_data)
+        returnValue(dumps(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:
+            options = method.options
+            assert isinstance(options, MethodOptions)
+            for fd, http in options.ListFields():
+                if fd.full_name == 'googleapi.http':
+                    assert fd.name == 'http'
+                    assert isinstance(http, http_pb2.HttpRule)
+
+                    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:]
+
+                    if http.delete:
+                        verb = 'delete'
+                        path = http.delete
+                    elif http.get:
+                        verb = 'get'
+                        path = http.get
+                    elif http.patch:
+                        verb = 'patch'
+                        path = http.patch
+                    elif http.post:
+                        verb = 'post'
+                        path = http.post
+                    elif http.put:
+                        verb = 'put'
+                        path = http.put
+                    else:
+                        raise AttributeError('No valid verb in method %s' %
+                                             method.name)
+
+                    body = http.body
+
+                    data = {
+                        'package': package,
+                        'filename': proto_file.name,
+                        'service': proto_file.package + '.' + service.name,
+                        'method': method.name,
+                        'input_type': input_type,
+                        'output_type': output_type,
+                        'path': path,
+                        'verb': verb,
+                        'body': body
+                    }
+
+                    yield data
+
+
+def generate_gw_code(file_name, methods, type_map, stub_map, includes):
+    return template.render(file_name=file_name, methods=methods,
+                           type_map=type_map, stub_map=stub_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.stub_map = {}
+        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
+        python_name = module_name + '_grpc.' + name + nested_name
+        self.stub_map[fullname] = python_name
+
+    def get_type_map(self):
+        return self.type_map
+
+    def get_stub_map(self):
+        return self.stub_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()
+        stub_map = include_manager.get_stub_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,  # TODO: is stub_map needed here?
+            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', '_gw.py')
+        f.content = generate_gw_code(proto_file.name,
+                                     methods, type_map, stub_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()
+        # with file('/tmp/buf', 'wb') as f:
+        #     f.write(data)
+
+    # 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/lib/xos-api/xosapi/chameleon_client/protos/Makefile b/lib/xos-api/xosapi/chameleon_client/protos/Makefile
new file mode 100644
index 0000000..b272a1a
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/Makefile
@@ -0,0 +1,40 @@
+#
+# 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.
+#
+
+# Makefile to build all protobuf and gRPC related artifacts
+
+default: build
+
+PROTO_FILES := $(wildcard *.proto)
+PROTO_PB2_FILES := $(foreach f,$(PROTO_FILES),$(subst .proto,_pb2.py,$(f)))
+PROTO_DESC_FILES := $(foreach f,$(PROTO_FILES),$(subst .proto,.desc,$(f)))
+
+PROTOC_PREFIX := /usr/local
+PROTOC_LIBDIR := $(PROTOC_PREFIX)/lib
+
+build: $(PROTO_PB2_FILES)
+
+%_pb2.py: %.proto Makefile
+	@echo "Building protocol buffer artifacts from $<"
+	env LD_LIBRARY_PATH=$(PROTOC_LIBDIR) python -m grpc.tools.protoc \
+	    -I. \
+	    --python_out=. \
+	    --grpc_python_out=. \
+	    $<
+
+clean:
+	rm -f $(PROTO_PB2_FILES) $(PROTO_DESC_FILES)
+
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/__init__.py b/lib/xos-api/xosapi/chameleon_client/protos/__init__.py
new file mode 100644
index 0000000..1a13cdf
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/__init__.py
@@ -0,0 +1,15 @@
+
+# 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.
+#
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/annotations.proto b/lib/xos-api/xosapi/chameleon_client/protos/annotations.proto
new file mode 100644
index 0000000..2ed81a6
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/annotations.proto
@@ -0,0 +1,34 @@
+// Copyright (c) 2015, Google Inc.
+// Modififications (C) 2018, Open Networking Foundation
+//
+// 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.
+
+// See NOTE in http.proto for the modifications made to these files
+
+syntax = "proto3";
+
+package googleapi;
+
+import "http.proto";
+import "google/protobuf/descriptor.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "AnnotationsProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+extend google.protobuf.MethodOptions {
+  // See `HttpRule`.
+  HttpRule http = 72295728;
+}
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2.py b/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2.py
new file mode 100644
index 0000000..5abc663
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2.py
@@ -0,0 +1,63 @@
+#!/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.
+#
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: annotations.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import http_pb2 as http__pb2
+from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='annotations.proto',
+  package='googleapi',
+  syntax='proto3',
+  serialized_pb=_b('\n\x11\x61nnotations.proto\x12\tgoogleapi\x1a\nhttp.proto\x1a google/protobuf/descriptor.proto:D\n\x04http\x12\x1e.google.protobuf.MethodOptions\x18\xb0\xca\xbc\" \x01(\x0b\x32\x13.googleapi.HttpRuleBn\n\x0e\x63om.google.apiB\x10\x41nnotationsProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3')
+  ,
+  dependencies=[http__pb2.DESCRIPTOR,google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,])
+
+
+HTTP_FIELD_NUMBER = 72295728
+http = _descriptor.FieldDescriptor(
+  name='http', full_name='googleapi.http', index=0,
+  number=72295728, type=11, cpp_type=10, label=1,
+  has_default_value=False, default_value=None,
+  message_type=None, enum_type=None, containing_type=None,
+  is_extension=True, extension_scope=None,
+  options=None, file=DESCRIPTOR)
+
+DESCRIPTOR.extensions_by_name['http'] = http
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+http.message_type = http__pb2._HTTPRULE
+google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(http)
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\016com.google.apiB\020AnnotationsProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI'))
+# @@protoc_insertion_point(module_scope)
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2_grpc.py b/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2_grpc.py
new file mode 100644
index 0000000..972edf5
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/annotations_pb2_grpc.py
@@ -0,0 +1,19 @@
+#!/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.
+#
+
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/http.proto b/lib/xos-api/xosapi/chameleon_client/protos/http.proto
new file mode 100644
index 0000000..99d0ce3
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/http.proto
@@ -0,0 +1,353 @@
+// Copyright 2018 Google LLC
+// Modififications (C) 2018, Open Networking Foundation
+//
+// 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.
+
+// NOTE: On the provenance of and modifications to http.proto and
+// annotations.proto
+//
+// TL;DR: The files http.proto and annotations.proto are originally from here:
+//  https://github.com/googleapis/googleapis
+// They have been modified slightly to avoid a namespace conflict.
+//
+// Long winded explanation:
+// These files are designed to interact with Google's first party API's, and
+// the recommended way to use them is to compiled them to code with protoc and
+// included in your codebase before being used.  Due to the fact that we're not
+// using them that way, and because of how Chameleon and XOS work (dynamically
+// defining our own API's), we have to ship these *.proto files as a part of
+// our artifacts.
+//
+// The problems start when you try to include these specific .proto files in
+// python. The protoc compiler includes the `google.protobuf` classes, which
+// python can look up in the standard python library path. Unfortunately these
+// files are namespaced with `google.api` in the path and aren't shipped with
+// protoc.  This leads to a path conflict - you can't have two library paths
+// start with the same path component (`google` in this case) without getting
+// an "ImportError: No module named ..." on one of the paths when you import
+// them.
+//
+// Historically, various confusing hacks were implemented to override and
+// special-case the python `include` directive to include a file at a different
+// path than was specified. These hacks also failed when updating the base OS,
+// and would likely continue to fail in other, stranger ways as we update the
+// codebase.  Specifically, Python 3 reimplemented these features in the
+// importlib section of the standard library, so there's little confidence our
+// hacks would continue to work.  As an aside, there are various protobuf
+// `options` statements to deal with this sort of issue in other languages (see
+// the `go_package` and `java_package` below ) but these don't currently exist
+// for python: https://github.com/google/protobuf/issues/973
+//
+// To avoid this entire psychotic namespace hellscape, it's much easier to
+// modify these files to remove the google.api path component, and have them
+// included directly at a path of our own choice.
+
+syntax = "proto3";
+
+package googleapi;
+
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "HttpProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+
+// Defines the HTTP configuration for an API service. It contains a list of
+// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
+// to one or more HTTP REST API methods.
+message Http {
+  // A list of HTTP configuration rules that apply to individual API methods.
+  //
+  // **NOTE:** All service configuration rules follow "last one wins" order.
+  repeated HttpRule rules = 1;
+
+  // When set to true, URL path parmeters will be fully URI-decoded except in
+  // cases of single segment matches in reserved expansion, where "%2F" will be
+  // left encoded.
+  //
+  // The default behavior is to not decode RFC 6570 reserved characters in multi
+  // segment matches.
+  bool fully_decode_reserved_expansion = 2;
+}
+
+// `HttpRule` defines the mapping of an RPC method to one or more HTTP
+// REST API methods. The mapping specifies how different portions of the RPC
+// request message are mapped to URL path, URL query parameters, and
+// HTTP request body. The mapping is typically specified as an
+// `google.api.http` annotation on the RPC method,
+// see "google/api/annotations.proto" for details.
+//
+// The mapping consists of a field specifying the path template and
+// method kind.  The path template can refer to fields in the request
+// message, as in the example below which describes a REST GET
+// operation on a resource collection of messages:
+//
+//
+//     service Messaging {
+//       rpc GetMessage(GetMessageRequest) returns (Message) {
+//         option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}";
+//       }
+//     }
+//     message GetMessageRequest {
+//       message SubMessage {
+//         string subfield = 1;
+//       }
+//       string message_id = 1; // mapped to the URL
+//       SubMessage sub = 2;    // `sub.subfield` is url-mapped
+//     }
+//     message Message {
+//       string text = 1; // content of the resource
+//     }
+//
+// The same http annotation can alternatively be expressed inside the
+// `GRPC API Configuration` YAML file.
+//
+//     http:
+//       rules:
+//         - selector: <proto_package_name>.Messaging.GetMessage
+//           get: /v1/messages/{message_id}/{sub.subfield}
+//
+// This definition enables an automatic, bidrectional mapping of HTTP
+// JSON to RPC. Example:
+//
+// HTTP | RPC
+// -----|-----
+// `GET /v1/messages/123456/foo`  | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))`
+//
+// In general, not only fields but also field paths can be referenced
+// from a path pattern. Fields mapped to the path pattern cannot be
+// repeated and must have a primitive (non-message) type.
+//
+// Any fields in the request message which are not bound by the path
+// pattern automatically become (optional) HTTP query
+// parameters. Assume the following definition of the request message:
+//
+//
+//     service Messaging {
+//       rpc GetMessage(GetMessageRequest) returns (Message) {
+//         option (google.api.http).get = "/v1/messages/{message_id}";
+//       }
+//     }
+//     message GetMessageRequest {
+//       message SubMessage {
+//         string subfield = 1;
+//       }
+//       string message_id = 1; // mapped to the URL
+//       int64 revision = 2;    // becomes a parameter
+//       SubMessage sub = 3;    // `sub.subfield` becomes a parameter
+//     }
+//
+//
+// This enables a HTTP JSON to RPC mapping as below:
+//
+// HTTP | RPC
+// -----|-----
+// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))`
+//
+// Note that fields which are mapped to HTTP parameters must have a
+// primitive type or a repeated primitive type. Message types are not
+// allowed. In the case of a repeated type, the parameter can be
+// repeated in the URL, as in `...?param=A&param=B`.
+//
+// For HTTP method kinds which allow a request body, the `body` field
+// specifies the mapping. Consider a REST update method on the
+// message resource collection:
+//
+//
+//     service Messaging {
+//       rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
+//         option (google.api.http) = {
+//           put: "/v1/messages/{message_id}"
+//           body: "message"
+//         };
+//       }
+//     }
+//     message UpdateMessageRequest {
+//       string message_id = 1; // mapped to the URL
+//       Message message = 2;   // mapped to the body
+//     }
+//
+//
+// The following HTTP JSON to RPC mapping is enabled, where the
+// representation of the JSON in the request body is determined by
+// protos JSON encoding:
+//
+// HTTP | RPC
+// -----|-----
+// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })`
+//
+// The special name `*` can be used in the body mapping to define that
+// every field not bound by the path template should be mapped to the
+// request body.  This enables the following alternative definition of
+// the update method:
+//
+//     service Messaging {
+//       rpc UpdateMessage(Message) returns (Message) {
+//         option (google.api.http) = {
+//           put: "/v1/messages/{message_id}"
+//           body: "*"
+//         };
+//       }
+//     }
+//     message Message {
+//       string message_id = 1;
+//       string text = 2;
+//     }
+//
+//
+// The following HTTP JSON to RPC mapping is enabled:
+//
+// HTTP | RPC
+// -----|-----
+// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")`
+//
+// Note that when using `*` in the body mapping, it is not possible to
+// have HTTP parameters, as all fields not bound by the path end in
+// the body. This makes this option more rarely used in practice of
+// defining REST APIs. The common usage of `*` is in custom methods
+// which don't use the URL at all for transferring data.
+//
+// It is possible to define multiple HTTP methods for one RPC by using
+// the `additional_bindings` option. Example:
+//
+//     service Messaging {
+//       rpc GetMessage(GetMessageRequest) returns (Message) {
+//         option (google.api.http) = {
+//           get: "/v1/messages/{message_id}"
+//           additional_bindings {
+//             get: "/v1/users/{user_id}/messages/{message_id}"
+//           }
+//         };
+//       }
+//     }
+//     message GetMessageRequest {
+//       string message_id = 1;
+//       string user_id = 2;
+//     }
+//
+//
+// This enables the following two alternative HTTP JSON to RPC
+// mappings:
+//
+// HTTP | RPC
+// -----|-----
+// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")`
+// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")`
+//
+// # Rules for HTTP mapping
+//
+// The rules for mapping HTTP path, query parameters, and body fields
+// to the request message are as follows:
+//
+// 1. The `body` field specifies either `*` or a field path, or is
+//    omitted. If omitted, it indicates there is no HTTP request body.
+// 2. Leaf fields (recursive expansion of nested messages in the
+//    request) can be classified into three types:
+//     (a) Matched in the URL template.
+//     (b) Covered by body (if body is `*`, everything except (a) fields;
+//         else everything under the body field)
+//     (c) All other fields.
+// 3. URL query parameters found in the HTTP request are mapped to (c) fields.
+// 4. Any body sent with an HTTP request can contain only (b) fields.
+//
+// The syntax of the path template is as follows:
+//
+//     Template = "/" Segments [ Verb ] ;
+//     Segments = Segment { "/" Segment } ;
+//     Segment  = "*" | "**" | LITERAL | Variable ;
+//     Variable = "{" FieldPath [ "=" Segments ] "}" ;
+//     FieldPath = IDENT { "." IDENT } ;
+//     Verb     = ":" LITERAL ;
+//
+// The syntax `*` matches a single path segment. The syntax `**` matches zero
+// or more path segments, which must be the last part of the path except the
+// `Verb`. The syntax `LITERAL` matches literal text in the path.
+//
+// The syntax `Variable` matches part of the URL path as specified by its
+// template. A variable template must not contain other variables. If a variable
+// matches a single path segment, its template may be omitted, e.g. `{var}`
+// is equivalent to `{var=*}`.
+//
+// If a variable contains exactly one path segment, such as `"{var}"` or
+// `"{var=*}"`, when such a variable is expanded into a URL path, all characters
+// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the
+// Discovery Document as `{var}`.
+//
+// If a variable contains one or more path segments, such as `"{var=foo/*}"`
+// or `"{var=**}"`, when such a variable is expanded into a URL path, all
+// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables
+// show up in the Discovery Document as `{+var}`.
+//
+// NOTE: While the single segment variable matches the semantics of
+// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2
+// Simple String Expansion, the multi segment variable **does not** match
+// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion
+// does not expand special characters like `?` and `#`, which would lead
+// to invalid URLs.
+//
+// NOTE: the field paths in variables and in the `body` must not refer to
+// repeated fields or map fields.
+message HttpRule {
+  // Selects methods to which this rule applies.
+  //
+  // Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
+  string selector = 1;
+
+  // Determines the URL pattern is matched by this rules. This pattern can be
+  // used with any of the {get|put|post|delete|patch} methods. A custom method
+  // can be defined using the 'custom' field.
+  oneof pattern {
+    // Used for listing and getting information about resources.
+    string get = 2;
+
+    // Used for updating a resource.
+    string put = 3;
+
+    // Used for creating a resource.
+    string post = 4;
+
+    // Used for deleting a resource.
+    string delete = 5;
+
+    // Used for updating a resource.
+    string patch = 6;
+
+    // The custom pattern is used for specifying an HTTP method that is not
+    // included in the `pattern` field, such as HEAD, or "*" to leave the
+    // HTTP method unspecified for this rule. The wild-card rule is useful
+    // for services that provide content to Web (HTML) clients.
+    CustomHttpPattern custom = 8;
+  }
+
+  // The name of the request field whose value is mapped to the HTTP body, or
+  // `*` for mapping all fields not captured by the path pattern to the HTTP
+  // body. NOTE: the referred field must not be a repeated field and must be
+  // present at the top-level of request message type.
+  string body = 7;
+
+  // Additional HTTP bindings for the selector. Nested bindings must
+  // not contain an `additional_bindings` field themselves (that is,
+  // the nesting may only be one level deep).
+  repeated HttpRule additional_bindings = 11;
+}
+
+// A custom pattern is used for defining custom HTTP verb.
+message CustomHttpPattern {
+  // The name of this custom HTTP verb.
+  string kind = 1;
+
+  // The path matched by this custom verb.
+  string path = 2;
+}
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/http_pb2.py b/lib/xos-api/xosapi/chameleon_client/protos/http_pb2.py
new file mode 100644
index 0000000..f420e61
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/http_pb2.py
@@ -0,0 +1,260 @@
+#!/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.
+#
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: http.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='http.proto',
+  package='googleapi',
+  syntax='proto3',
+  serialized_pb=_b('\n\nhttp.proto\x12\tgoogleapi\"S\n\x04Http\x12\"\n\x05rules\x18\x01 \x03(\x0b\x32\x13.googleapi.HttpRule\x12\'\n\x1f\x66ully_decode_reserved_expansion\x18\x02 \x01(\x08\"\xe8\x01\n\x08HttpRule\x12\x10\n\x08selector\x18\x01 \x01(\t\x12\r\n\x03get\x18\x02 \x01(\tH\x00\x12\r\n\x03put\x18\x03 \x01(\tH\x00\x12\x0e\n\x04post\x18\x04 \x01(\tH\x00\x12\x10\n\x06\x64\x65lete\x18\x05 \x01(\tH\x00\x12\x0f\n\x05patch\x18\x06 \x01(\tH\x00\x12.\n\x06\x63ustom\x18\x08 \x01(\x0b\x32\x1c.googleapi.CustomHttpPatternH\x00\x12\x0c\n\x04\x62ody\x18\x07 \x01(\t\x12\x30\n\x13\x61\x64\x64itional_bindings\x18\x0b \x03(\x0b\x32\x13.googleapi.HttpRuleB\t\n\x07pattern\"/\n\x11\x43ustomHttpPattern\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\tBj\n\x0e\x63om.google.apiB\tHttpProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xf8\x01\x01\xa2\x02\x04GAPIb\x06proto3')
+)
+
+
+
+
+_HTTP = _descriptor.Descriptor(
+  name='Http',
+  full_name='googleapi.Http',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='rules', full_name='googleapi.Http.rules', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fully_decode_reserved_expansion', full_name='googleapi.Http.fully_decode_reserved_expansion', index=1,
+      number=2, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=25,
+  serialized_end=108,
+)
+
+
+_HTTPRULE = _descriptor.Descriptor(
+  name='HttpRule',
+  full_name='googleapi.HttpRule',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='selector', full_name='googleapi.HttpRule.selector', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get', full_name='googleapi.HttpRule.get', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='put', full_name='googleapi.HttpRule.put', index=2,
+      number=3, 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='post', full_name='googleapi.HttpRule.post', index=3,
+      number=4, 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='delete', full_name='googleapi.HttpRule.delete', index=4,
+      number=5, 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='patch', full_name='googleapi.HttpRule.patch', index=5,
+      number=6, 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='custom', full_name='googleapi.HttpRule.custom', index=6,
+      number=8, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='body', full_name='googleapi.HttpRule.body', index=7,
+      number=7, 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='additional_bindings', full_name='googleapi.HttpRule.additional_bindings', index=8,
+      number=11, 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, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='pattern', full_name='googleapi.HttpRule.pattern',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=111,
+  serialized_end=343,
+)
+
+
+_CUSTOMHTTPPATTERN = _descriptor.Descriptor(
+  name='CustomHttpPattern',
+  full_name='googleapi.CustomHttpPattern',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='kind', full_name='googleapi.CustomHttpPattern.kind', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='path', full_name='googleapi.CustomHttpPattern.path', 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, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=345,
+  serialized_end=392,
+)
+
+_HTTP.fields_by_name['rules'].message_type = _HTTPRULE
+_HTTPRULE.fields_by_name['custom'].message_type = _CUSTOMHTTPPATTERN
+_HTTPRULE.fields_by_name['additional_bindings'].message_type = _HTTPRULE
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['get'])
+_HTTPRULE.fields_by_name['get'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['put'])
+_HTTPRULE.fields_by_name['put'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['post'])
+_HTTPRULE.fields_by_name['post'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['delete'])
+_HTTPRULE.fields_by_name['delete'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['patch'])
+_HTTPRULE.fields_by_name['patch'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+_HTTPRULE.oneofs_by_name['pattern'].fields.append(
+  _HTTPRULE.fields_by_name['custom'])
+_HTTPRULE.fields_by_name['custom'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
+DESCRIPTOR.message_types_by_name['Http'] = _HTTP
+DESCRIPTOR.message_types_by_name['HttpRule'] = _HTTPRULE
+DESCRIPTOR.message_types_by_name['CustomHttpPattern'] = _CUSTOMHTTPPATTERN
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Http = _reflection.GeneratedProtocolMessageType('Http', (_message.Message,), dict(
+  DESCRIPTOR = _HTTP,
+  __module__ = 'http_pb2'
+  # @@protoc_insertion_point(class_scope:googleapi.Http)
+  ))
+_sym_db.RegisterMessage(Http)
+
+HttpRule = _reflection.GeneratedProtocolMessageType('HttpRule', (_message.Message,), dict(
+  DESCRIPTOR = _HTTPRULE,
+  __module__ = 'http_pb2'
+  # @@protoc_insertion_point(class_scope:googleapi.HttpRule)
+  ))
+_sym_db.RegisterMessage(HttpRule)
+
+CustomHttpPattern = _reflection.GeneratedProtocolMessageType('CustomHttpPattern', (_message.Message,), dict(
+  DESCRIPTOR = _CUSTOMHTTPPATTERN,
+  __module__ = 'http_pb2'
+  # @@protoc_insertion_point(class_scope:googleapi.CustomHttpPattern)
+  ))
+_sym_db.RegisterMessage(CustomHttpPattern)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\016com.google.apiB\tHttpProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\370\001\001\242\002\004GAPI'))
+# @@protoc_insertion_point(module_scope)
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/http_pb2_grpc.py b/lib/xos-api/xosapi/chameleon_client/protos/http_pb2_grpc.py
new file mode 100644
index 0000000..972edf5
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/http_pb2_grpc.py
@@ -0,0 +1,19 @@
+#!/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.
+#
+
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/schema.proto b/lib/xos-api/xosapi/chameleon_client/protos/schema.proto
new file mode 100644
index 0000000..e10c5de
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/schema.proto
@@ -0,0 +1,45 @@
+// 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.
+
+syntax = "proto3";
+
+package schema;
+
+import "google/protobuf/empty.proto";
+
+// Contains the name and content of a *.proto file
+message ProtoFile {
+    string file_name = 1;  // name of proto file
+    string proto = 2;  // content of proto file
+    bytes descriptor = 3;  // compiled descriptor for proto (zlib compressed)
+}
+
+// Proto files and compiled descriptors for this interface
+message Schemas {
+
+    // Proto files
+    repeated ProtoFile protos = 1;
+
+    // Name of proto file to generae swagger.json from
+    string swagger_from = 2;
+
+}
+
+// Schema services
+service SchemaService {
+
+    // Return active grpc schemas
+    rpc GetSchema(google.protobuf.Empty) returns (Schemas) {}
+
+}
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2.py b/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2.py
new file mode 100644
index 0000000..06909b4
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2.py
@@ -0,0 +1,173 @@
+#!/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.
+#
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: schema.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='schema.proto',
+  package='schema',
+  syntax='proto3',
+  serialized_pb=_b('\n\x0cschema.proto\x12\x06schema\x1a\x1bgoogle/protobuf/empty.proto\"A\n\tProtoFile\x12\x11\n\tfile_name\x18\x01 \x01(\t\x12\r\n\x05proto\x18\x02 \x01(\t\x12\x12\n\ndescriptor\x18\x03 \x01(\x0c\"B\n\x07Schemas\x12!\n\x06protos\x18\x01 \x03(\x0b\x32\x11.schema.ProtoFile\x12\x14\n\x0cswagger_from\x18\x02 \x01(\t2G\n\rSchemaService\x12\x36\n\tGetSchema\x12\x16.google.protobuf.Empty\x1a\x0f.schema.Schemas\"\x00\x62\x06proto3')
+  ,
+  dependencies=[google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,])
+
+
+
+
+_PROTOFILE = _descriptor.Descriptor(
+  name='ProtoFile',
+  full_name='schema.ProtoFile',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='file_name', full_name='schema.ProtoFile.file_name', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='proto', full_name='schema.ProtoFile.proto', 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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='descriptor', full_name='schema.ProtoFile.descriptor', index=2,
+      number=3, 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, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=53,
+  serialized_end=118,
+)
+
+
+_SCHEMAS = _descriptor.Descriptor(
+  name='Schemas',
+  full_name='schema.Schemas',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='protos', full_name='schema.Schemas.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, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='swagger_from', full_name='schema.Schemas.swagger_from', 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, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=120,
+  serialized_end=186,
+)
+
+_SCHEMAS.fields_by_name['protos'].message_type = _PROTOFILE
+DESCRIPTOR.message_types_by_name['ProtoFile'] = _PROTOFILE
+DESCRIPTOR.message_types_by_name['Schemas'] = _SCHEMAS
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ProtoFile = _reflection.GeneratedProtocolMessageType('ProtoFile', (_message.Message,), dict(
+  DESCRIPTOR = _PROTOFILE,
+  __module__ = 'schema_pb2'
+  # @@protoc_insertion_point(class_scope:schema.ProtoFile)
+  ))
+_sym_db.RegisterMessage(ProtoFile)
+
+Schemas = _reflection.GeneratedProtocolMessageType('Schemas', (_message.Message,), dict(
+  DESCRIPTOR = _SCHEMAS,
+  __module__ = 'schema_pb2'
+  # @@protoc_insertion_point(class_scope:schema.Schemas)
+  ))
+_sym_db.RegisterMessage(Schemas)
+
+
+
+_SCHEMASERVICE = _descriptor.ServiceDescriptor(
+  name='SchemaService',
+  full_name='schema.SchemaService',
+  file=DESCRIPTOR,
+  index=0,
+  options=None,
+  serialized_start=188,
+  serialized_end=259,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GetSchema',
+    full_name='schema.SchemaService.GetSchema',
+    index=0,
+    containing_service=None,
+    input_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    output_type=_SCHEMAS,
+    options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_SCHEMASERVICE)
+
+DESCRIPTOR.services_by_name['SchemaService'] = _SCHEMASERVICE
+
+# @@protoc_insertion_point(module_scope)
diff --git a/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2_grpc.py b/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2_grpc.py
new file mode 100644
index 0000000..65c9afa
--- /dev/null
+++ b/lib/xos-api/xosapi/chameleon_client/protos/schema_pb2_grpc.py
@@ -0,0 +1,64 @@
+#!/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.
+#
+
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
+import schema_pb2 as schema__pb2
+
+
+class SchemaServiceStub(object):
+  """Schema services
+  """
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.GetSchema = channel.unary_unary(
+        '/schema.SchemaService/GetSchema',
+        request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
+        response_deserializer=schema__pb2.Schemas.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=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
+          response_serializer=schema__pb2.Schemas.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'schema.SchemaService', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/lib/xos-api/xosapi/xos_grpc_client.py b/lib/xos-api/xosapi/xos_grpc_client.py
index 13e969d..a80843f 100644
--- a/lib/xos-api/xosapi/xos_grpc_client.py
+++ b/lib/xos-api/xosapi/xos_grpc_client.py
@@ -36,7 +36,7 @@
 sys.path = [currentdir] + sys.path
 
 from xosconfig import Config
-import chameleon.grpc_client.grpc_client as chameleon_client
+import chameleon_client.grpc_client as chameleon_client
 
 from multistructlog import create_logger
 log = create_logger(Config().get("logging"))