Chameleon self boot-strapping API
diff --git a/.gitignore b/.gitignore
index 9805cec..988402a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,7 @@
# Editors
*.bak
+
+# Docker
+.docker-base-built
+
diff --git a/chameleon/grpc_client/grpc_client.py b/chameleon/grpc_client/grpc_client.py
index 2ce8d7c..5f373a7 100644
--- a/chameleon/grpc_client/grpc_client.py
+++ b/chameleon/grpc_client/grpc_client.py
@@ -21,25 +21,27 @@
semantics are derived from the recovered schema.
"""
import os
-
-import grpc
import sys
-from consul import Consul
from random import randint
-from structlog import get_logger
from zlib import decompress
-from chameleon.protos.schema_pb2 import NullMessage, Schema, SchemaServiceStub
+import grpc
+from consul import Consul
+from structlog import get_logger
+
+from chameleon.protos.schema_pb2 import NullMessage, SchemaServiceStub
log = get_logger()
class GrpcClient(object):
- def __init__(self, consul_endpoint, endpoint='localhost:50055'):
+ def __init__(self, consul_endpoint, work_dir, endpoint='localhost:50055'):
self.consul_endpoint = consul_endpoint
self.endpoint = endpoint
- self.work_dir = '/tmp/chameleon'
+ self.work_dir = work_dir
+ self.plugin_dir = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '../protoc_plugins'))
self.channel = None
self.schema = None
@@ -134,13 +136,22 @@
cmd = (
'cd %s && '
+ 'env PATH=%s '
'python -m grpc.tools.protoc '
'-I. '
'-I%s '
'--python_out=. '
'--grpc_python_out=. '
- '%s' % (self.work_dir, google_api_dir, fname)
+ '--plugin=protoc-gen-gw=%s/gw_gen.py '
+ '--gw_out=. '
+ '%s' % (
+ self.work_dir,
+ ':'.join([os.environ['PATH'], self.plugin_dir]),
+ google_api_dir,
+ self.plugin_dir,
+ fname)
)
+ log.debug('executing', cmd=cmd)
os.system(cmd)
# test-load each _pb2 file to see all is right
@@ -152,3 +163,7 @@
modname = fname[:-len('.py')]
log.debug('test-import', modname=modname)
_ = __import__(modname)
+
+ def invoke(self, stub, method_name, request):
+ response = getattr(stub(self.channel), method_name)(request)
+ return response
diff --git a/chameleon/main.py b/chameleon/main.py
index e75de77..470dc84 100755
--- a/chameleon/main.py
+++ b/chameleon/main.py
@@ -20,7 +20,7 @@
import argparse
import os
import sys
-import time
+
import yaml
from twisted.internet.defer import inlineCallbacks
@@ -32,6 +32,7 @@
from chameleon.nethelpers import get_my_primary_local_ipv4
from chameleon.dockerhelpers import get_my_containers_name
from chameleon.grpc_client.grpc_client import GrpcClient
+from chameleon.web_server.web_server import WebServer
defs = dict(
@@ -45,6 +46,7 @@
internal_host_address=os.environ.get('INTERNAL_HOST_ADDRESS',
get_my_primary_local_ipv4()),
rest_port=os.environ.get('REST_PORT', 8881),
+ work_dir=os.environ.get('WORK_DIR', '/tmp/chameleon')
)
@@ -140,6 +142,14 @@
action='count',
help=_help)
+ _help = ('work dir to compile and assemble generated files (default=%s)'
+ % defs['work_dir'])
+ parser.add_argument('-w', '--work-dir',
+ dest='work_dir',
+ action='store',
+ default=defs['work_dir'],
+ help=_help)
+
_help = ('use docker container name as Chameleon instance id'
' (overrides -i/--instance-id option)')
parser.add_argument('--instance-id-is-container-name',
@@ -207,14 +217,14 @@
def start(self):
self.start_reactor() # will not return except Keyboard interrupt
+ @inlineCallbacks
def startup_components(self):
self.log.info('starting-internal-components')
-
- self.grpc_client = \
- GrpcClient(self.args.consul, self.args.grpc_endpoint).run()
-
- # TODO init server
-
+ args = self.args
+ self.grpc_client = yield \
+ GrpcClient(args.consul, args.work_dir, args.grpc_endpoint).run()
+ self.web_server = yield \
+ WebServer(args.rest_port, args.work_dir, self.grpc_client).run()
self.log.info('started-internal-services')
@inlineCallbacks
diff --git a/chameleon/protoc_plugins/__init__.py b/chameleon/protoc_plugins/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chameleon/protoc_plugins/__init__.py
diff --git a/chameleon/protoc_plugins/gw_gen.py b/chameleon/protoc_plugins/gw_gen.py
new file mode 100755
index 0000000..aa2a3ce
--- /dev/null
+++ b/chameleon/protoc_plugins/gw_gen.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+import sys
+
+from google.protobuf.compiler import plugin_pb2 as plugin
+from google.protobuf.descriptor import FieldDescriptor
+from google.protobuf.descriptor_pb2 import ServiceDescriptorProto, MethodOptions
+from google.protobuf.message import Message
+from jinja2 import Template
+from simplejson import dumps
+
+# without this import, http method annotations would not be recognized:
+from google.api import annotations_pb2 as _, http_pb2
+
+
+template = Template("""
+# Generated file; please do not edit
+
+from simplejson import dumps, load
+from structlog import get_logger
+from protobuf_to_dict import protobuf_to_dict, dict_to_protobuf
+
+{% set package = file_name.replace('.proto', '') %}
+import {{ package + '_pb2' }} as {{ package }}
+
+log = get_logger()
+
+def add_routes(app, grpc_client):
+
+ {% for method in methods %}
+ {% set method_name = method['service'] + '_' + method['method'] %}
+ {% set path = method['path'].replace('{', '<string:').replace('}', '>') %}
+ @app.route('{{ path }}', methods=['{{ method['verb'].upper() }}'])
+ 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 %}
+ riase NotImplementedError('cannot handle specific body field list')
+ {% endif %}
+ req = dict_to_protobuf({{ method['input_type'] }}, data)
+ res = grpc_client.invoke(
+ {{ '.'.join([package, method['service']]) }}Stub,
+ '{{ method['method'] }}', req)
+ out_data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('{{ method_name }}', **out_data)
+ return 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 == 'google.api.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': 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):
+ return template.render(file_name=file_name, methods=methods)
+
+
+def generate_code(request, response):
+
+ assert isinstance(request, plugin.CodeGeneratorRequest)
+ for proto_file in request.proto_file:
+ output = []
+
+ for data in traverse_methods(proto_file):
+ output.append(data)
+
+ # 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(output, 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, output)
+
+
+if __name__ == '__main__':
+
+ if len(sys.argv) >= 2:
+ # read input from file, to allow troubleshooting
+ with open(sys.argv[1], 'r') as f:
+ data = f.read()
+ else:
+ # read input from stdin
+ data = sys.stdin.read()
+
+ # parse request
+ request = plugin.CodeGeneratorRequest()
+ request.ParseFromString(data)
+
+ # create response object
+ response = plugin.CodeGeneratorResponse()
+
+ # generate the output and the response
+ generate_code(request, response)
+
+ # serialize the response
+ output = response.SerializeToString()
+
+ # write response to stdout
+ sys.stdout.write(output)
diff --git a/chameleon/protos/schema.desc b/chameleon/protos/schema.desc
new file mode 100644
index 0000000..3cb025f
--- /dev/null
+++ b/chameleon/protos/schema.desc
Binary files differ
diff --git a/chameleon/protos/schema_pb2.py b/chameleon/protos/schema_pb2.py
new file mode 100644
index 0000000..eab43f5
--- /dev/null
+++ b/chameleon/protos/schema_pb2.py
@@ -0,0 +1,300 @@
+# 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()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='schema.proto',
+ package='schema',
+ syntax='proto3',
+ serialized_pb=_b('\n\x0cschema.proto\x12\x06schema\"\xcd\x01\n\x06Schema\x12*\n\x06protos\x18\x01 \x03(\x0b\x32\x1a.schema.Schema.ProtosEntry\x12\x34\n\x0b\x64\x65scriptors\x18\x02 \x03(\x0b\x32\x1f.schema.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\x0bNullMessage2C\n\rSchemaService\x12\x32\n\tGetSchema\x12\x13.schema.NullMessage\x1a\x0e.schema.Schema\"\x00\x62\x06proto3')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+
+_SCHEMA_PROTOSENTRY = _descriptor.Descriptor(
+ name='ProtosEntry',
+ full_name='schema.Schema.ProtosEntry',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='key', full_name='schema.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='schema.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=133,
+ serialized_end=178,
+)
+
+_SCHEMA_DESCRIPTORSENTRY = _descriptor.Descriptor(
+ name='DescriptorsEntry',
+ full_name='schema.Schema.DescriptorsEntry',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='key', full_name='schema.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='schema.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=180,
+ serialized_end=230,
+)
+
+_SCHEMA = _descriptor.Descriptor(
+ name='Schema',
+ full_name='schema.Schema',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='protos', full_name='schema.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='schema.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=25,
+ serialized_end=230,
+)
+
+
+_NULLMESSAGE = _descriptor.Descriptor(
+ name='NullMessage',
+ full_name='schema.NullMessage',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=232,
+ serialized_end=245,
+)
+
+_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
+DESCRIPTOR.message_types_by_name['Schema'] = _SCHEMA
+DESCRIPTOR.message_types_by_name['NullMessage'] = _NULLMESSAGE
+
+Schema = _reflection.GeneratedProtocolMessageType('Schema', (_message.Message,), dict(
+
+ ProtosEntry = _reflection.GeneratedProtocolMessageType('ProtosEntry', (_message.Message,), dict(
+ DESCRIPTOR = _SCHEMA_PROTOSENTRY,
+ __module__ = 'schema_pb2'
+ # @@protoc_insertion_point(class_scope:schema.Schema.ProtosEntry)
+ ))
+ ,
+
+ DescriptorsEntry = _reflection.GeneratedProtocolMessageType('DescriptorsEntry', (_message.Message,), dict(
+ DESCRIPTOR = _SCHEMA_DESCRIPTORSENTRY,
+ __module__ = 'schema_pb2'
+ # @@protoc_insertion_point(class_scope:schema.Schema.DescriptorsEntry)
+ ))
+ ,
+ DESCRIPTOR = _SCHEMA,
+ __module__ = 'schema_pb2'
+ # @@protoc_insertion_point(class_scope:schema.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__ = 'schema_pb2'
+ # @@protoc_insertion_point(class_scope:schema.NullMessage)
+ ))
+_sym_db.RegisterMessage(NullMessage)
+
+
+_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
+from grpc.framework.common import cardinality
+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(
+ '/schema.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(
+ 'schema.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 = {
+ ('schema.SchemaService', 'GetSchema'): NullMessage.FromString,
+ }
+ response_serializers = {
+ ('schema.SchemaService', 'GetSchema'): Schema.SerializeToString,
+ }
+ method_implementations = {
+ ('schema.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 = {
+ ('schema.SchemaService', 'GetSchema'): NullMessage.SerializeToString,
+ }
+ response_deserializers = {
+ ('schema.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, 'schema.SchemaService', cardinalities, options=stub_options)
+# @@protoc_insertion_point(module_scope)
diff --git a/chameleon/protos/third_party/google/api/annotations.desc b/chameleon/protos/third_party/google/api/annotations.desc
new file mode 100644
index 0000000..d159f06
--- /dev/null
+++ b/chameleon/protos/third_party/google/api/annotations.desc
Binary files differ
diff --git a/chameleon/protos/third_party/google/api/annotations.proto b/chameleon/protos/third_party/google/api/annotations.proto
new file mode 100644
index 0000000..cbd18b8
--- /dev/null
+++ b/chameleon/protos/third_party/google/api/annotations.proto
@@ -0,0 +1,29 @@
+// Copyright (c) 2015, Google Inc.
+//
+// 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 google.api;
+
+import "google/api/http.proto";
+import "google/protobuf/descriptor.proto";
+
+option java_multiple_files = true;
+option java_outer_classname = "AnnotationsProto";
+option java_package = "com.google.api";
+
+extend google.protobuf.MethodOptions {
+ // See `HttpRule`.
+ HttpRule http = 72295728;
+}
diff --git a/chameleon/protos/third_party/google/api/http.desc b/chameleon/protos/third_party/google/api/http.desc
new file mode 100644
index 0000000..7b2513a
--- /dev/null
+++ b/chameleon/protos/third_party/google/api/http.desc
Binary files differ
diff --git a/chameleon/protos/third_party/google/api/http.proto b/chameleon/protos/third_party/google/api/http.proto
new file mode 100644
index 0000000..ce07aa1
--- /dev/null
+++ b/chameleon/protos/third_party/google/api/http.proto
@@ -0,0 +1,127 @@
+// Copyright (c) 2015, Google Inc.
+//
+// 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 google.api;
+
+option java_multiple_files = true;
+option java_outer_classname = "HttpProto";
+option java_package = "com.google.api";
+
+
+// `HttpRule` defines the mapping of an RPC method to one or more HTTP REST API
+// methods. The mapping determines what portions of the request message are
+// populated from the path, query parameters, or body of the HTTP request. The
+// mapping is typically specified as an `google.api.http` annotation, see
+// "google/api/annotations.proto" for details.
+//
+// The mapping consists of a mandatory field specifying a path template and an
+// optional `body` field specifying what data is represented in the HTTP request
+// body. The field name for the path indicates the HTTP method. Example:
+//
+// ```
+// package google.storage.v2;
+//
+// import "google/api/annotations.proto";
+//
+// service Storage {
+// rpc CreateObject(CreateObjectRequest) returns (Object) {
+// option (google.api.http) {
+// post: "/v2/{bucket_name=buckets/*}/objects"
+// body: "object"
+// };
+// };
+// }
+// ```
+//
+// Here `bucket_name` and `object` bind to fields of the request message
+// `CreateObjectRequest`.
+//
+// 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 assumes there is no HTTP 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 ;
+//
+// `*` matches a single path component, `**` zero or more path components, and
+// `LITERAL` a constant. A `Variable` can match an entire path as specified
+// again by a template; this nested template must not contain further variables.
+// If no template is given with a variable, it matches a single path component.
+// The notation `{var}` is henceforth equivalent to `{var=*}`.
+//
+// Use CustomHttpPattern to specify any HTTP method that is not included in the
+// pattern field, such as HEAD, or "*" to leave the HTTP method unspecified for
+// a given URL path rule. The wild-card rule is useful for services that provide
+// content to Web (HTML) clients.
+message HttpRule {
+
+ // 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;
+
+ // Custom pattern is used for defining custom verbs.
+ 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.
+ string body = 7;
+
+ // Additional HTTP bindings for the selector. Nested bindings must not
+ // specify a selector and must not contain additional bindings.
+ 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/chameleon/swagger_ui/index.html b/chameleon/swagger_ui/index.html
index adcda63..fd2e909 100644
--- a/chameleon/swagger_ui/index.html
+++ b/chameleon/swagger_ui/index.html
@@ -37,7 +37,7 @@
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
} else {
- url = "http://petstore.swagger.io/v2/swagger.json";
+ url = "/v1/swagger.json";
}
hljs.configure({
diff --git a/chameleon/web_server/__init__.py b/chameleon/web_server/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chameleon/web_server/__init__.py
diff --git a/chameleon/web_server/web_server.py b/chameleon/web_server/web_server.py
new file mode 100644
index 0000000..4452ccd
--- /dev/null
+++ b/chameleon/web_server/web_server.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+
+from klein import Klein
+from simplejson import dumps, load
+from structlog import get_logger
+from twisted.internet import reactor, endpoints
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.tcp import Port
+from twisted.web.server import Site
+from twisted.web.static import File
+
+log = get_logger()
+
+'''
+## To be automated as part of the template
+from voltha.protos.voltha_pb2 import *
+from protobuf_to_dict import protobuf_to_dict, dict_to_protobuf
+
+def add_routes(app, grpc_client):
+
+ @app.route('/health', methods=['GET'])
+ def get_health(server, request):
+ log.debug('get-health-req', request=request, server=server)
+ req = NullMessage()
+ res = grpc_client.invoke(
+ HealthServiceStub, 'GetHealthStatus', req)
+ data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('get-health-res', **data)
+ return dumps(data)
+
+ @app.route('/addresses', methods=['GET'])
+ def list_addresses(server, request):
+ log.debug('list-addresses-req', request=request, server=server)
+ req = NullMessage()
+ res = grpc_client.invoke(
+ ExampleServiceStub, 'ListAddresses', req)
+ data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('list-addresses-res', **data)
+ return dumps(data)
+
+ @app.route('/addresses/<string:id>', methods=['GET'])
+ def get_address(server, request, id):
+ log.debug('get-address-req', request=request, server=server, id=id)
+ req = ID(id=id)
+ res = grpc_client.invoke(
+ ExampleServiceStub, 'GetAddress', req)
+ data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('get-address-res', **data)
+ return dumps(data)
+
+ @app.route('/addresses/<string:id>', methods=['DELETE'])
+ def delete_address(server, request, id):
+ log.debug('delete-address-req', request=request, server=server, id=id)
+ req = ID(id=id)
+ res = grpc_client.invoke(
+ ExampleServiceStub, 'DeleteAddress', req)
+ data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('delete-address-res', **data)
+ return dumps(data)
+
+ @app.route('/addresses', methods=['PATCH'])
+ def update_address(server, request):
+ log.debug('update-address-req', request=request, server=server)
+ data = load(request.content)
+ req = dict_to_protobuf(Address, data)
+ res = grpc_client.invoke(
+ ExampleServiceStub, 'UpdateAddress', req)
+ data = protobuf_to_dict(res, use_enum_labels=True)
+ request.setHeader('Content-Type', 'application/json')
+ log.debug('update-address-res', **data)
+ return dumps(data)
+
+## end
+'''
+
+class WebServer(object):
+
+ app = Klein()
+
+ def __init__(self, port, work_dir, grpc_client):
+ self.port = port
+ self.site = None
+ self.work_dir = work_dir
+ self.grpc_client = grpc_client
+
+ self.swagger_ui_root_dir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '../swagger_ui'))
+
+ self.tcp_port = None
+
+ @inlineCallbacks
+ def run(self):
+ yield self._open_endpoint()
+ yield self._load_generated_routes()
+ returnValue(self)
+
+ def _load_generated_routes(self):
+ for fname in os.listdir(self.work_dir):
+ if fname.endswith('_gw.py'):
+ module_name = fname.replace('.py', '')
+ print 'module_name', module_name
+ m = __import__(module_name)
+ print dir(m)
+ assert hasattr(m, 'add_routes')
+ m.add_routes(self.app, self.grpc_client)
+
+ @inlineCallbacks
+ def _open_endpoint(self):
+ endpoint = endpoints.TCP4ServerEndpoint(reactor, self.port)
+ self.site = Site(self.app.resource())
+ self.tcp_port = yield endpoint.listen(self.site)
+ log.info('web-server-started', port=self.port)
+ self.endpoint = endpoint
+
+ @inlineCallbacks
+ def shutdown(self):
+ if self.tcp_porte is not None:
+ assert isinstance(self.tcp_port, Port)
+ yield self.tcp_port.socket.close()
+
+ # static swagger_ui website as landing page (for now)
+
+ @app.route('/', branch=True)
+ def static(self, request):
+ try:
+ log.debug(request=request)
+ return File(self.swagger_ui_root_dir)
+ except Exception, e:
+ log.exception('file-not-found', request=request)
+
+ # static swagger.json file to serve the schema
+
+ @app.route('/v1/swagger.json')
+ def swagger_json(self, request):
+ try:
+ return File(os.path.join(self.work_dir, 'swagger.json'))
+ except Exception, e:
+ log.exception('file-not-found', request=request)
diff --git a/compose/docker-compose-system-test.yml b/compose/docker-compose-system-test.yml
index c6088d6..f205946 100644
--- a/compose/docker-compose-system-test.yml
+++ b/compose/docker-compose-system-test.yml
@@ -90,6 +90,7 @@
- 8881
depends_on:
- consul
+ - voltha
links:
- consul
- fluentd
diff --git a/env.sh b/env.sh
index 84f9359..4c73088 100644
--- a/env.sh
+++ b/env.sh
@@ -13,7 +13,7 @@
. $VENVDIR/bin/activate
# add top-level voltha dir to pythonpath
-export PYTHONPATH=$PYTHONPATH:$VOLTHA_BASE/voltha:$VOLTHA_BASE/voltha/protos/third_party
+export PYTHONPATH=$PYTHONPATH:$VOLTHA_BASE:$VOLTHA_BASE/voltha/protos/third_party
# assign DOCKER_HOST_IP to be the main ip address of this host
export DOCKER_HOST_IP=$(python voltha/nethelpers.py)
diff --git a/requirements.txt b/requirements.txt
index a0f7c50..99d4d70 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,6 +9,7 @@
grpcio-tools>=1.0.0
hash_ring>=1.3.1
hexdump>=3.3
+jinja2>=2.8
klein>=15.3.1
nose>=1.3.7
mock>=1.3.0
diff --git a/voltha/northbound/grpc/grpc_introspect.py b/voltha/northbound/grpc/grpc_introspect.py
index e2f24ca..366b725 100755
--- a/voltha/northbound/grpc/grpc_introspect.py
+++ b/voltha/northbound/grpc/grpc_introspect.py
@@ -22,14 +22,16 @@
import inspect
from collections import OrderedDict
+import sys
+
+from google.protobuf.compiler.plugin_pb2 import CodeGeneratorRequest
from google.protobuf.descriptor import FieldDescriptor, Descriptor
-from google.protobuf.message import Message
+from google.protobuf.descriptor_pb2 import FileDescriptorProto, MethodOptions
+from google.protobuf.message import Message, DecodeError
from simplejson import dumps
from google.protobuf import descriptor_pb2
-from google.api import http_pb2
-
class InvalidDescriptorError(Exception): pass
@@ -64,12 +66,18 @@
fold_comments=True,
type_tag_name='_type'):
- # decode desciription
- file_descriptor_set = descriptor_pb2.FileDescriptorSet()
- file_descriptor_set.ParseFromString(descriptor_blob)
+ # decode file descriptor set or if that is not possible,
+ # try plugin request
+ try:
+ message = descriptor_pb2.FileDescriptorSet()
+ message.ParseFromString(descriptor_blob)
+ except DecodeError:
+ message = CodeGeneratorRequest()
+ message.ParseFromString(descriptor_blob)
- d = self.parse(file_descriptor_set, type_tag_name=type_tag_name)
- for _file in d['file']:
+ d = self.parse(message, type_tag_name=type_tag_name)
+ print d.keys()
+ for _file in d.get('file', None) or d['proto_file']:
if fold_comments:
self.fold_comments_in(_file)
self.catalog[_file['package']] = _file
@@ -160,14 +168,22 @@
# try loading voltha descriptor and turn it into JSON data as a preparation
# for generating JSON Schema / swagger file (to be done later)
+ if len(sys.argv) >= 2:
+ desc_file = sys.argv[1]
+ else:
+ desc_dir = os.path.dirname(inspect.getfile(voltha_pb2))
+ desc_file = os.path.join(desc_dir, 'voltha.desc')
+
from voltha.protos import voltha_pb2
- desc_dir = os.path.dirname(inspect.getfile(voltha_pb2))
- desc_file = os.path.join(desc_dir, 'voltha.desc')
with open(desc_file, 'rb') as f:
descriptor_blob = f.read()
+
parser = DescriptorParser()
+ parser.save_file_desc = '/tmp/grpc_introspection.out'
+
parser.load_descriptor(descriptor_blob)
print dumps(parser.get_catalog(), indent=4)
+ sys.exit(0)
# try to see if we can decode binary data into JSON automatically
from random import seed, randint
@@ -180,7 +196,7 @@
health=voltha_pb2.HealthStatus(
state=voltha_pb2.HealthStatus.OVERLOADED
),
- address=voltha_pb2.MoreComplex.Address(
+ address=voltha_pb2.Address(
street='1383 N McDowell Blvd',
city='Petaluma',
zip=94954,
diff --git a/voltha/northbound/grpc/grpc_server.py b/voltha/northbound/grpc/grpc_server.py
index da824ea..fb2582f 100644
--- a/voltha/northbound/grpc/grpc_server.py
+++ b/voltha/northbound/grpc/grpc_server.py
@@ -20,6 +20,7 @@
from os.path import abspath, basename, dirname, join, walk
import grpc
from concurrent import futures
+from google.protobuf.message import Message
from structlog import get_logger
import zlib
@@ -127,6 +128,12 @@
del self.db[request.id]
return voltha_pb2.NullMessage()
+ def UpdateAddress(self, request, context):
+ log.info('update-address', request=request)
+ updated = self.db[request.id]
+ updated.MergeFrom(request)
+ return updated
+
class VolthaGrpcServer(object):
diff --git a/voltha/protos/voltha.desc b/voltha/protos/voltha.desc
index 2a42de8..564c61c 100644
--- a/voltha/protos/voltha.desc
+++ b/voltha/protos/voltha.desc
Binary files differ
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 87b6dda..5033e69 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -99,4 +99,12 @@
};
}
+ // Update an existing address record by ID
+ rpc UpdateAddress(Address) returns (Address) {
+ option (google.api.http) = {
+ patch: "/addresses/{id}"
+ body: "*"
+ };
+ }
+
}
diff --git a/voltha/protos/voltha_pb2.py b/voltha/protos/voltha_pb2.py
index 65ae82f..4a4ffce 100644
--- a/voltha/protos/voltha_pb2.py
+++ b/voltha/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(\t2^\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')
+ 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(\t2^\n\rHealthService\x12M\n\x0fGetHealthStatus\x12\x13.voltha.NullMessage\x1a\x14.voltha.HealthStatus\"\x0f\x82\xd3\xe4\x93\x02\t\x12\x07/health2\x85\x03\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}\x12M\n\rUpdateAddress\x12\x0f.voltha.Address\x1a\x0f.voltha.Address\"\x1a\x82\xd3\xe4\x93\x02\x14\x32\x0f/addresses/{id}:\x01*B<\n\x13org.opencord.volthaB\x0cVolthaProtos\xaa\x02\x16Opencord.Voltha.Volthab\x06proto3')
,
dependencies=[google_dot_api_dot_annotations__pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -487,6 +487,11 @@
request_serializer=ID.SerializeToString,
response_deserializer=NullMessage.FromString,
)
+ self.UpdateAddress = channel.unary_unary(
+ '/voltha.ExampleService/UpdateAddress',
+ request_serializer=Address.SerializeToString,
+ response_deserializer=Address.FromString,
+ )
class ExampleServiceServicer(object):
@@ -521,6 +526,13 @@
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
+ def UpdateAddress(self, request, context):
+ """Update an existing address record by ID
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
def add_ExampleServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
@@ -544,6 +556,11 @@
request_deserializer=ID.FromString,
response_serializer=NullMessage.SerializeToString,
),
+ 'UpdateAddress': grpc.unary_unary_rpc_method_handler(
+ servicer.UpdateAddress,
+ request_deserializer=Address.FromString,
+ response_serializer=Address.SerializeToString,
+ ),
}
generic_handler = grpc.method_handlers_generic_handler(
'voltha.ExampleService', rpc_method_handlers)
@@ -569,6 +586,10 @@
"""Delete an address record by ID
"""
context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
+ def UpdateAddress(self, request, context):
+ """Update an existing address record by ID
+ """
+ context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)
class BetaExampleServiceStub(object):
@@ -594,6 +615,11 @@
"""
raise NotImplementedError()
DeleteAddress.future = None
+ def UpdateAddress(self, request, timeout, metadata=None, with_call=False, protocol_options=None):
+ """Update an existing address record by ID
+ """
+ raise NotImplementedError()
+ UpdateAddress.future = None
def beta_create_ExampleService_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None):
@@ -602,18 +628,21 @@
('voltha.ExampleService', 'DeleteAddress'): ID.FromString,
('voltha.ExampleService', 'GetAddress'): ID.FromString,
('voltha.ExampleService', 'ListAddresses'): NullMessage.FromString,
+ ('voltha.ExampleService', 'UpdateAddress'): Address.FromString,
}
response_serializers = {
('voltha.ExampleService', 'CreateAddress'): Address.SerializeToString,
('voltha.ExampleService', 'DeleteAddress'): NullMessage.SerializeToString,
('voltha.ExampleService', 'GetAddress'): Address.SerializeToString,
('voltha.ExampleService', 'ListAddresses'): Addresses.SerializeToString,
+ ('voltha.ExampleService', 'UpdateAddress'): Address.SerializeToString,
}
method_implementations = {
('voltha.ExampleService', 'CreateAddress'): face_utilities.unary_unary_inline(servicer.CreateAddress),
('voltha.ExampleService', 'DeleteAddress'): face_utilities.unary_unary_inline(servicer.DeleteAddress),
('voltha.ExampleService', 'GetAddress'): face_utilities.unary_unary_inline(servicer.GetAddress),
('voltha.ExampleService', 'ListAddresses'): face_utilities.unary_unary_inline(servicer.ListAddresses),
+ ('voltha.ExampleService', 'UpdateAddress'): face_utilities.unary_unary_inline(servicer.UpdateAddress),
}
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)
@@ -625,18 +654,21 @@
('voltha.ExampleService', 'DeleteAddress'): ID.SerializeToString,
('voltha.ExampleService', 'GetAddress'): ID.SerializeToString,
('voltha.ExampleService', 'ListAddresses'): NullMessage.SerializeToString,
+ ('voltha.ExampleService', 'UpdateAddress'): Address.SerializeToString,
}
response_deserializers = {
('voltha.ExampleService', 'CreateAddress'): Address.FromString,
('voltha.ExampleService', 'DeleteAddress'): NullMessage.FromString,
('voltha.ExampleService', 'GetAddress'): Address.FromString,
('voltha.ExampleService', 'ListAddresses'): Addresses.FromString,
+ ('voltha.ExampleService', 'UpdateAddress'): Address.FromString,
}
cardinalities = {
'CreateAddress': cardinality.Cardinality.UNARY_UNARY,
'DeleteAddress': cardinality.Cardinality.UNARY_UNARY,
'GetAddress': cardinality.Cardinality.UNARY_UNARY,
'ListAddresses': cardinality.Cardinality.UNARY_UNARY,
+ 'UpdateAddress': 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.ExampleService', cardinalities, options=stub_options)