blob: 5d101af45bc13d27a3281f7c93a9b03513eb4607 [file] [log] [blame]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -05001#!/usr/bin/env python
2#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05003# Copyright 2017 the original author or authors.
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""protoc plugin to convert a protobuf schema to a yang schema
19
20 - basic support for message, fields. enumeration, service, method
21
22 - yang semantic rules needs to be implemented
23
24 - to run this plugin :
25
26 $ python -m grpc.tools.protoc -I.
27 --plugin=protoc-gen-custom=./proto2yang.py --custom_out=. <proto file>.proto
28
Khen Nursimulu0b9aed12017-02-06 15:33:46 -050029 - the above will produce a <proto file>.yang file formatted for yang
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050030
31 - two examples of proto that can be used in the same directory are
32 yang.proto and addressbook.proto
33
34"""
35
36import sys
37
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050038from google.protobuf.compiler import plugin_pb2 as plugin
Khen Nursimulub4e71472017-01-06 18:05:47 -050039from google.protobuf.descriptor_pb2 import DescriptorProto, \
40 FieldDescriptorProto
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050041from descriptor_parser import DescriptorParser
Khen Nursimulu7626ce12016-12-21 11:51:46 -050042import copy
Khen Nursimulu3676b7c2017-01-31 13:48:38 -050043from netconf.constants import Constants as C
Khen Nursimulu7626ce12016-12-21 11:51:46 -050044import yang_options_pb2
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050045
46from google.protobuf.descriptor import FieldDescriptor
47
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050048import jinja2
Khen Nursimulu3676b7c2017-01-31 13:48:38 -050049
50env = jinja2.Environment(extensions=["jinja2.ext.do", ], trim_blocks=True,
51 lstrip_blocks=True)
52
53template_yang_definition = env.from_string("""
54# Generated file; please do not edit
55
56from structlog import get_logger
57
58log = get_logger()
59
60message_definitions = {
61 {% for m in messages %}
62 '{{ m.name }}': {{ m.fields }},
63 {% if loop.last %}{% endif %}
64 {% endfor %}
65}
66
67def get_fields(package, type_name, **kw):
68 log.info('fields-request', type=type_name, package=package, **kw)
69 full_name = ''.join([package, '-', type_name])
70 if message_definitions.has_key(full_name):
71 return message_definitions[full_name]
72 else:
73 return None
74
75""")
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050076
77template_yang = env.from_string("""
Khen Nursimulu0b9aed12017-02-06 15:33:46 -050078module {{ module.name }} {
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050079
80 {% macro set_module_prefix(type) %}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050081 {% set found = [] %}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050082 {% for t in module.data_types %}
83 {% if t.type == type %}
84 {% if t.module != module.name %} {{ t.module }}:{{ type }};
85 {% else %} {{ type }};
86 {% endif %}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050087 {% do found.append(1) %}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050088 {% endif %}
89 {% if loop.last %}
90 {% if not found %} {{ type }}; {% endif %}
91 {% endif %}
92 {% endfor %}
93 {% endmacro %}
94
Khen Nursimulub4e71472017-01-06 18:05:47 -050095
96 {% macro process_oneofs(oneofs, ref_msgs) %}
97
98 {% for key, value in oneofs.iteritems() %}
99 choice {{ key }} {
100 {% for field in value %}
101 case {{ field.name }} {
102 {% if field.type_ref %}
103 {% for dict_item in ref_msgs %}
104 {% if dict_item.name == field.type %}
105 container {{ field.name }} {
106 uses {{ set_module_prefix(field.type) }}
107 description
108 "{{ field.description }}";
109 {% endif %}
110 {% endfor %}
111 }
112 {% else %}
113 leaf {{ field.name }} {
114 {% if field.type == "decimal64" %}
115 type {{ field.type }} {
116 fraction-digits 5;
117 }
118 {% else %}
119 type {{ set_module_prefix(field.type) }}
120 {% endif %}
121 description
122 "{{ field.description }}";
123 }
124 {% endif %}
125 }
126 {% endfor %}
127 }
128 {% endfor %}
129 {% endmacro %}
130
131
Khen Nursimulu0b9aed12017-02-06 15:33:46 -0500132 namespace "urn:opencord:params:xml:ns:voltha:{{ module.name }}";
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500133 prefix {{ module.name }};
134
135 {% for imp in module.imports %}
Khen Nursimulu0b9aed12017-02-06 15:33:46 -0500136 import {{ imp.name }} { prefix {{ imp.name }} ; }
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500137 {% endfor %}
138
139 organization "CORD";
140 contact
141 " Any name";
142
143 description
144 "{{ module.description }}";
145
146 revision "2016-11-15" {
147 description "Initial revision.";
148 reference "reference";
149 }
150
151 {% for enum in module.enums %}
152 typedef {{ enum.name }} {
153 type enumeration {
154 {% for v in enum.value %}
155 enum {{ v.name }} {
156 description "{{ v.description }}";
157 }
158 {% endfor %}
159 }
160 description
161 "{{ enum.description }}";
162 }
163 {% endfor %}
164
165 {% for message in module.messages recursive %}
166 {% if message.name in module.referred_messages %}
167 grouping {{ message.name }} {
168 {% else %}
169 container {{ message.name }} {
170 {% endif %}
171 description
172 "{{ message.description }}";
173 {% for field in message.fields %}
174 {% if field.type_ref %}
175 {% for dict_item in module.referred_messages_with_keys %}
176 {% if dict_item.name == field.type %}
177 {% if not field.repeated %}
178 container {{ field.name }} {
179 {% else %}
180 list {{ field.name }} {
181 key "{{ dict_item.key }}";
182 {% if not field.repeated %}
183 max-elements 1;
184 {% endif %}
185 {% endif %}
186 uses {{ set_module_prefix(field.type) }}
187 description
188 "{{ field.description }}";
189 }
190 {% endif %}
191 {% endfor %}
192 {% elif field.repeated %}
193 list {{ field.name }} {
194 key "{{ field.name }}";
195 leaf {{ field.name }} {
196 {% if field.type == "decimal64" %}
197 type {{ field.type }} {
198 fraction-digits 5;
199 }
200 {% else %}
201 type {{ set_module_prefix(field.type) }}
202 {% endif %}
203 description
204 "{{ field.description }}";
205 }
206 description
207 "{{ field.description }}";
208 }
209 {% else %}
210 leaf {{ field.name }} {
211 {% if field.type == "decimal64" %}
212 type {{ field.type }} {
213 fraction-digits 5;
214 }
215 {% else %}
216 type {{ set_module_prefix(field.type) }}
217 {% endif %}
218 description
219 "{{ field.description }}";
220 }
221 {% endif %}
222
223 {% endfor %}
Khen Nursimulub4e71472017-01-06 18:05:47 -0500224
225 {% if message.oneofs %}
226 {{ process_oneofs(message.oneofs, module.referred_messages_with_keys) }}
227 {% endif %}
228
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500229 {% for enum_type in message.enums %}
230 typedef {{ enum_type.name }} {
231 type enumeration {
232 {% for v in enum_type.value %}
233 enum {{ v.name }} {
234 description "{{ v.description }}";
235 }
236 {% endfor %}
237 }
238 description
239 "{{ enum_type.description }}";
240 }
241
242 {% endfor %}
243 {% if message.messages %}
244 {{ loop (message.messages)|indent(4, false) }}
245 {% endif %}
246 }
247
248 {% endfor %}
249 {% for service in module.services %}
250 {% if service.description %}
251 /* {{ service.description }}" */
252 {% endif %}
253 {% for method in service.methods %}
254 rpc {{ service.service }}-{{ method.method }} {
255 description
256 "{{ method.description }}";
257 {% if method.input %}
258 input {
259 {% if method.input_ref %}
260 uses {{ set_module_prefix(method.input) }}
261 {% else %}
262 leaf {{ method.input }} {
263 type {{ set_module_prefix(method.input) }}
264 }
265 {% endif %}
266 }
267 {% endif %}
268 {% if method.output %}
269 output {
270 {% if method.output_ref %}
271 uses {{ set_module_prefix(method.output) }}
272 {% else %}
273 leaf {{ method.output }} {
274 type {{ set_module_prefix(method.output) }}
275 }
276 {% endif %}
277 }
278 {% endif %}
279 }
280
281 {% endfor %}
282
283 {% endfor %}
284}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -0500285""")
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500286
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500287
288def traverse_field_options(fields, prefix):
289 field_options = []
290 for field in fields:
291 assert isinstance(field, FieldDescriptorProto)
292 full_name = prefix + '-' + field.name
293 option = None
294 if field.type == FieldDescriptor.TYPE_MESSAGE and field.label != \
295 FieldDescriptor.LABEL_REPEATED:
296 if field.options:
297 for fd, val in field.options.ListFields():
298 if fd.full_name == 'voltha.yang_inline_node':
299 field_options.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500300 {'name': full_name,
301 'option': fd.full_name,
302 'proto_name': val.id,
303 'proto_type': val.type
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500304 }
305 )
306 return field_options
307
308
309def traverse_message_options(message_types, prefix):
Khen Nursimulub4e71472017-01-06 18:05:47 -0500310 message_options = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500311 for message_type in message_types:
312 assert isinstance(message_type, DescriptorProto)
313 full_name = prefix + '-' + message_type.name
314 option_rules = []
315
316 options = message_type.options
317 if options:
318 for fd, val in options.ListFields():
319 if fd.full_name in ['voltha.yang_child_rule',
320 'voltha.yang_message_rule']:
321 option_rules.append({
Khen Nursimulub4e71472017-01-06 18:05:47 -0500322 'name': fd.full_name,
323 'value': val
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500324 })
325
326 # parse fields for options
327 field_options = traverse_field_options(message_type.field,
328 full_name)
329
330 # parse nested messages
331 nested_messages_options = []
332 nested = message_type.nested_type
333 if nested:
334 nested_messages_options = traverse_message_options(nested,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500335 full_name)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500336
337 if option_rules or nested_messages_options or field_options:
338 message_options.append(
339 {
340 'name': full_name,
341 'options': option_rules,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500342 'field_options': field_options,
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500343 'nested_options': nested_messages_options,
344 }
345 )
346 return message_options
347
348
349def get_message_options(name, options):
350 result = None
351 for opt in options:
352 if opt['name'] == name:
353 return opt['options']
354 if opt['nested_options']:
355 result = get_message_options(name, opt['nested_options'])
356 if result:
357 return result
358
Khen Nursimulub4e71472017-01-06 18:05:47 -0500359
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500360def get_field_options(name, options):
361 result = None
362 for opt in options:
363 if opt['field_options']:
364 for field_opt in opt['field_options']:
365 if field_opt['name'] == name:
366 result = field_opt
367 if opt['nested_options']:
368 result = get_field_options(name, opt['nested_options'])
369 if result:
370 return result
371
372
373def traverse_options(proto_file):
374 package = proto_file.name
375 prefix = package.replace('.proto', '')
376 if proto_file.message_type:
377 message_options = traverse_message_options(proto_file.message_type,
378 prefix)
379 return message_options
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500380
381
382def traverse_messages(message_types, prefix, referenced_messages):
383 messages = []
384 for message_type in message_types:
385 assert message_type['_type'] == 'google.protobuf.DescriptorProto'
386
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500387 full_name = prefix + '-' + message_type['name']
388 name = message_type['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500389
390 # parse the fields
Khen Nursimulub4e71472017-01-06 18:05:47 -0500391 oneofs, fields = traverse_fields(message_type.get('field', []),
392 full_name, referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500393
394 # parse the enums
395 enums = traverse_enums(message_type.get('enum_type', []), full_name)
396
397 # parse nested messages
398 nested = message_type.get('nested_type', [])
399 nested_messages = traverse_messages(nested, full_name,
400 referenced_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500401
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500402 messages.append(
403 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500404 'full_name': full_name,
405 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500406 'fields': fields,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500407 'oneofs': oneofs,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500408 'enums': enums,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500409 'messages': nested_messages,
410 'description': remove_unsupported_characters(
411 message_type.get('_description', '')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500412 }
413 )
414 return messages
415
416
417def traverse_fields(fields_desc, prefix, referenced_messages):
418 fields = []
Khen Nursimulub4e71472017-01-06 18:05:47 -0500419 oneofs = {}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500420 for field in fields_desc:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500421 # if field.get('oneof_index', None) >= 0:
422 # print '{},{}'.format(field.get('name', ''), field.get('number'))
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500423 assert field['_type'] == 'google.protobuf.FieldDescriptorProto'
424 yang_base_type = is_base_type(field['type'])
425 _type = get_yang_type(field)
426 if not yang_base_type:
427 referenced_messages.append(_type)
428 # add to referred messages also if it is an enumeration type
429 if is_enumeration(field['type']):
430 referenced_messages.append(_type)
431
Khen Nursimulub4e71472017-01-06 18:05:47 -0500432 if field.get('oneof_index', None) >= 0:
433 # Oneof fields
434 key = ''.join(['choice_', str(field['oneof_index'])])
435 if not oneofs.has_key(key):
436 oneofs[key] = []
437 oneofs[key].append(
438 {
439 'full_name': prefix + '-' + field.get('name', ''),
440 'oneof_index': field.get('oneof_index', None),
441 'name': field.get('name', ''),
442 'label': field.get('label', ''),
443 'repeated': field[
444 'label'] == FieldDescriptor.LABEL_REPEATED,
445 'number': field.get('number', ''),
446 'options': field.get('options', ''),
447 'type_name': field.get('type_name', ''),
448 'type': _type,
449 'type_ref': not yang_base_type,
450 'description': remove_unsupported_characters(field.get(
451 '_description', ''))
452 }
453 )
454 else:
455 fields.append(
456 {
457 'full_name': prefix + '-' + field.get('name', ''),
458 'name': field.get('name', ''),
459 'label': field.get('label', ''),
460 'repeated': field[
461 'label'] == FieldDescriptor.LABEL_REPEATED,
462 'number': field.get('number', ''),
463 'options': field.get('options', ''),
464 'type_name': field.get('type_name', ''),
465 'type': _type,
466 'type_ref': not yang_base_type,
467 'description': remove_unsupported_characters(field.get(
468 '_description', ''))
469 }
470 )
471 # print oneofs
472 return oneofs, fields
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500473
474
475def traverse_enums(enums_desc, prefix):
476 enums = []
477 for enum in enums_desc:
478 assert enum['_type'] == 'google.protobuf.EnumDescriptorProto'
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500479 full_name = prefix + '-' + enum.get('name', '')
480 name = enum.get('name', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500481 enums.append(
482 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500483 'full_name': full_name,
484 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500485 'value': enum.get('value', ''),
486 'description': remove_unsupported_characters(enum.get(
487 '_description', ''))
488 }
489 )
490 return enums
491
492
493def traverse_services(service_desc, referenced_messages):
494 services = []
495 for service in service_desc:
496 methods = []
497 for method in service.get('method', []):
498 assert method['_type'] == 'google.protobuf.MethodDescriptorProto'
499
500 input_name = method.get('input_type')
501 input_ref = False
502 if not is_base_type(input_name):
503 input_name = remove_first_character_if_match(input_name, '.')
504 # input_name = input_name.replace(".", "-")
505 input_name = input_name.split('.')[-1]
506 referenced_messages.append(input_name)
507 input_ref = True
508
509 output_name = method.get('output_type')
510 output_ref = False
511 if not is_base_type(output_name):
512 output_name = remove_first_character_if_match(output_name, '.')
513 # output_name = output_name.replace(".", "-")
514 output_name = output_name.split('.')[-1]
515 referenced_messages.append(output_name)
516 output_ref = True
517
518 methods.append(
519 {
520 'method': method.get('name', ''),
521 'input': input_name,
522 'input_ref': input_ref,
523 'output': output_name,
524 'output_ref': output_ref,
525 'description': remove_unsupported_characters(method.get(
526 '_description', '')),
527 'server_streaming': method.get('server_streaming',
528 False) == True
529 }
530 )
531 services.append(
532 {
533 'service': service.get('name', ''),
534 'methods': methods,
535 'description': remove_unsupported_characters(service.get(
536 '_description', '')),
537 }
538 )
539 return services
540
541
542def rchop(thestring, ending):
543 if thestring.endswith(ending):
544 return thestring[:-len(ending)]
545 return thestring
546
547
548def traverse_desc(descriptor):
549 referenced_messages = []
550 name = rchop(descriptor.get('name', ''), '.proto')
551 package = descriptor.get('package', '')
552 description = descriptor.get('_description', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500553 messages = traverse_messages(descriptor.get('message_type', []),
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500554 name, referenced_messages)
555 enums = traverse_enums(descriptor.get('enum_type', []), name)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500556 services = traverse_services(descriptor.get('service', []),
557 referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500558
559 # Get a list of type definitions (messages, enums) defined in this
560 # descriptor
561 defined_types = [m['name'].split('/')[-1] for m in messages] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500562 [e['name'].split('/')[-1] for e in enums]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500563
564 data = {
565 'name': name.split('/')[-1],
566 'package': package,
567 'description': description,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500568 'messages': messages,
569 'enums': enums,
570 'services': services,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500571 'defined_types': defined_types,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500572 'referenced_messages': list(set(referenced_messages)),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500573 }
574 return data
575
576
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500577# For now, annotations are added to first level messages only.
578# Therefore, at this time no need to tackle nested messages.
579def move_message_to_parent_level(message, messages, enums):
580 new_message = []
581 new_enum = copy.deepcopy(enums)
582 for msg in messages:
583 if msg['full_name'] == message['full_name']:
584 # Move all sub messages and enums to top level
585 if msg['messages']:
586 new_message = new_message + copy.deepcopy(msg['messages'])
587 if msg['enums']:
588 new_enum = new_enum + copy.deepcopy(msg['enums'])
589
590 # if the message has some fields then enclose them in a container
591 if msg['fields']:
592 new_message.append(
593 {
594 'full_name': msg['full_name'],
595 'name': msg['name'],
596 'fields': msg['fields'],
Khen Nursimulub4e71472017-01-06 18:05:47 -0500597 'oneofs': msg['oneofs'],
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500598 'description': msg['description'],
599 'messages': [],
600 'enums': []
601 }
602 )
603 else:
604 new_message.append(msg)
605
606 return new_message, new_enum
607
608
609def update_messages_per_annotations_rule(options, messages, enums):
610 new_messages = messages
611 new_enums = enums
612 # Used when a message needs to exist both as a type and a container
613 duplicate_messages = []
614 for message in messages:
615 opts = get_message_options(message['full_name'], options)
616 if opts:
617 for opt in opts:
618 if opt['name'] == 'voltha.yang_child_rule':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500619 new_messages, new_enums = move_message_to_parent_level(
620 message,
621 new_messages, new_enums)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500622 elif opt['name'] == 'voltha.yang_message_rule':
623 # create a duplicate message
Khen Nursimulub4e71472017-01-06 18:05:47 -0500624 # TODO: update references to point to the
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500625 duplicate_messages.append(message['name'])
626 clone = copy.deepcopy(message)
Khen Nursimulub4e71472017-01-06 18:05:47 -0500627 clone['full_name'] = ''.join(
628 [clone['full_name'], '_', 'grouping'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500629 clone['name'] = ''.join([clone['name'], '_', 'grouping'])
630 new_messages = new_messages + [clone]
631
632 return new_messages, new_enums, duplicate_messages
633
634
635def inline_field(message, field, option, messages):
636 new_message = copy.deepcopy(message)
637 new_message['fields'] = []
638 for f in message['fields']:
639 if f['full_name'] == field['full_name']:
640 # look for the message this field referred to.
641 # Addresses only top-level messages
642 for m in messages:
643 # 'proto_type' is the name of the message type this field
644 # refers to
645 if m['full_name'] == option['proto_type']:
646 # Copy all content of m into the field
647 new_message['fields'] = new_message['fields'] + \
648 copy.deepcopy(m['fields'])
Khen Nursimulub4e71472017-01-06 18:05:47 -0500649 new_message['oneofs'] = new_message['oneofs'].update(
650 copy.deepcopy(m['oneofs']))
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500651 new_message['enums'] = new_message['enums'] + \
652 copy.deepcopy(m['enums'])
653 new_message['messages'] = new_message['messages'] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500654 copy.deepcopy(m['messages'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500655 else:
656 new_message['fields'].append(f)
657
658 return new_message
659
Khen Nursimulub4e71472017-01-06 18:05:47 -0500660
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500661# Address only annotations on top-level messages, i.e. no nested messages
662def update_fields_per_annotations_rule(options, messages):
663 new_messages = []
664 for message in messages:
665 new_message = None
666 for field in message['fields']:
667 opt = get_field_options(field['full_name'], options)
668 if opt:
669 if opt['option'] == 'voltha.yang_inline_node':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500670 new_message = inline_field(message, field, opt, messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500671
672 if new_message:
673 new_messages.append(new_message)
674 else:
675 new_messages.append(message)
676
677 return new_messages
678
679
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500680def set_messages_keys(messages):
681 for message in messages:
682 message['key'] = _get_message_key(message, messages)
683 if message['messages']:
684 set_messages_keys(message['messages'])
685
Khen Nursimulub4e71472017-01-06 18:05:47 -0500686
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500687def _get_message_key(message, messages):
688 # assume key is first yang base type field
689 for field in message['fields']:
690 if not field['type_ref']:
691 return field['name']
692 else:
693 # if the field name is a message then loop for the key in that
694 # message
695 ref_message = _get_message(field['type'], messages)
696 if ref_message:
697 return _get_message_key(ref_message, messages)
698
699 # no key yet - search nested messaged
700 for m in message['messages']:
701 key = _get_message_key(m, messages)
702 if key is not None:
703 return key
704 else:
705 return None
706
Khen Nursimulub4e71472017-01-06 18:05:47 -0500707
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500708def _get_message(name, messages):
709 for m in messages:
710 if m['name'] == name:
711 return m
712 return None
713
Khen Nursimulub4e71472017-01-06 18:05:47 -0500714
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500715def get_message_key(message_name, messages):
716 for message in messages:
717 if message_name == message['name']:
718 return message['key']
719 if message['messages']:
720 return get_message_key(message_name, message['messages'])
721 return None
722
723
724def update_module_imports(module):
725 used_imports = set()
726 for ref_msg in module['referenced_messages']:
727 for type_dict in module['data_types']:
728 if ref_msg == type_dict['type']:
729 if module['name'] != type_dict['module']:
730 used_imports.add(type_dict['module'])
731 break
Khen Nursimulub4e71472017-01-06 18:05:47 -0500732 module['imports'] = [{'name': i} for i in used_imports]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500733
734
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500735def update_referred_messages(all_referred_messages, all_duplicate_messages):
736 new_referred_messages = []
737 for ref in all_referred_messages:
738 if ref in all_duplicate_messages:
739 new_referred_messages.append(''.join([ref, '_grouping']))
740 else:
741 new_referred_messages.append(ref)
742
743 return new_referred_messages
744
Khen Nursimulub4e71472017-01-06 18:05:47 -0500745
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500746def update_message_references_based_on_duplicates(duplicates, messages):
747 # Duplicates has a list of messages that exist both as a grouping and as
748 # a container. All reference to the container name by existing fields
749 # should be changed to the grouping name instead
750 for m in messages:
751 for f in m['fields']:
752 if f['type'] in duplicates:
753 f['type'] = ''.join([f['type'], '_grouping'])
754 if m['messages']:
755 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500756 m['messages'])
757
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500758
759def update_servic_references_based_on_duplicates(duplicates, services):
760 # Duplicates has a list of messages that exist both as a grouping and as
761 # a container. All reference to the container name by existing fields
762 # should be changed to the grouping name instead
763 for s in services:
764 for m in s['methods']:
765 if m['input_ref'] and m['input'] in duplicates:
766 m['input'] = ''.join([m['input'], '_grouping'])
767 if m['output_ref'] and m['output'] in duplicates:
768 m['output'] = ''.join([m['output'], '_grouping'])
769
770
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500771def get_module_name(type, data_types):
772 for t in data_types:
773 # Verify both the type and when it is a referred type as they will
774 # both be in the same module
775 if t['type'] in [type, ''.join([type, '_grouping'])]:
776 return t['module']
777
778 # return the default module name
779 return 'voltha'
780
781
782def get_message_defs(messages, data_types, msg_response):
783 for msg in messages:
784 fields = []
785
786 # First process the fields as they appear before the oneofs in the
787 # YANG module
788 for f in msg['fields']:
789 module_name = '.'
790 if f['type_ref']:
791 module_name = get_module_name(f['type'], data_types)
792 fields.append(
793 {
794 'oneof_key': None,
795 'repeated': f['repeated'],
796 'name': f['name'],
797 'full_name': f['full_name'],
798 'type': f['type'],
799 'type_ref': f['type_ref'],
800 'module': module_name
801 }
802 )
803
804 # Now process the oneofs
805 if msg['oneofs']:
806 for key, value in msg['oneofs'].iteritems():
807 # Value contains a list of fields
808 for v in value:
809 module_name = '.'
810 if v['type_ref']:
811 module_name = get_module_name(v['type'], data_types)
812 fields.append(
813 {
814 'oneof_key': key,
815 'repeated': v['repeated'],
816 'name': v['name'],
817 'full_name': v['full_name'],
818 'type': v['type'],
819 'type_ref': v['type_ref'],
820 'module': module_name
821 }
822 )
823
824 msg_response.append({
825 'name': msg['full_name'],
826 'fields': fields
827 })
828
829 if msg['messages']:
830 get_message_defs(msg['messages'], data_types, msg_response)
831
832
833def build_yang_definitions(all_proto_data):
834 msg_response = []
835 for proto_data in all_proto_data:
836 get_message_defs(proto_data['module']['messages'], proto_data[
837 'module']['data_types'], msg_response)
838
839 return msg_response
840
841
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500842def generate_code(request, response):
843 assert isinstance(request, plugin.CodeGeneratorRequest)
844
845 parser = DescriptorParser()
846
847 # First process the proto file with the imports
848 all_defined_types = []
849 all_proto_data = []
850 all_referred_messages = []
851 all_messages = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500852 all_duplicate_messages = []
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500853 for proto_file in request.proto_file:
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500854 options = traverse_options(proto_file)
855 # print options
856
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500857 native_data = parser.parse_file_descriptor(proto_file,
858 type_tag_name='_type',
859 fold_comments=True)
860
861 # Consolidate the defined types across imports
862 yang_data = traverse_desc(native_data)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500863
864 duplicates = []
865 if options:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500866 new_messages, new_enums, duplicates = \
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500867 update_messages_per_annotations_rule(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500868 options, yang_data['messages'], yang_data['enums'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500869
870 new_messages = update_fields_per_annotations_rule(options,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500871 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500872
873 # TODO: Need to do the change across all schema files. Not
874 # needed as annotations are single file based for now
875 if duplicates:
876 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500877 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500878 update_servic_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500879 yang_data[
880 'services'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500881
882 yang_data['messages'] = new_messages
883 yang_data['enums'] = new_enums
884
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500885 for type in yang_data['defined_types']:
886 all_defined_types.append(
887 {
Khen Nursimulub4e71472017-01-06 18:05:47 -0500888 'type': type,
889 'module': yang_data['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500890 }
891 )
892
893 all_proto_data.append(
894 {
Khen Nursimulu0b9aed12017-02-06 15:33:46 -0500895 'file_name': '{}'.format(proto_file.name.split(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500896 '/')[-1].replace('.proto', '.yang')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500897 'module': yang_data
898 }
899 )
900
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500901 # Consolidate all duplicate messages
902 all_duplicate_messages = all_duplicate_messages + duplicates
903
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500904 # Consolidate referred messages across imports
Khen Nursimulub4e71472017-01-06 18:05:47 -0500905 all_referred_messages = all_referred_messages + yang_data[
906 'referenced_messages']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500907
908 # consolidate all messages
909 all_messages = all_messages + yang_data['messages']
910
Khen Nursimulub4e71472017-01-06 18:05:47 -0500911 # # Update the referred_messages
912 all_referred_messages = update_referred_messages(all_referred_messages,
913 all_duplicate_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500914
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500915 # Set the message keys - required for List definitions (repeated label)
916 set_messages_keys(all_messages)
917 unique_referred_messages_with_keys = []
918 for m in all_messages:
919 unique_referred_messages_with_keys.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500920 {
921 'name': m['name'],
922 'key': m['key']
923 }
924 )
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500925
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500926 # print_referred_msg(unique_referred_messages_with_keys)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500927 # Create the files
928 for proto_data in all_proto_data:
929 f = response.file.add()
930 f.name = proto_data['file_name']
931 proto_data['module']['data_types'] = all_defined_types
932 proto_data['module']['referred_messages'] = all_referred_messages
Khen Nursimulub4e71472017-01-06 18:05:47 -0500933 proto_data['module'][
934 'referred_messages_with_keys'] = unique_referred_messages_with_keys
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500935 proto_data['module']['duplicates'] = all_duplicate_messages
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500936 update_module_imports(proto_data['module'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500937 # print_message(proto_data['module']['messages'])
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500938 f.content = template_yang.render(module=proto_data['module'])
939
Khen Nursimulu3676b7c2017-01-31 13:48:38 -0500940 # Create a summary of the YANG definitions with the order in which the
941 # attributes appear in each message. It would have been easier to sort
942 # the attributes in the YANG files and then sort the XML tags when a
943 # XML response is built. However, this strategy won't work with the oneof
944 # protobuf definition. The attributes in the oneof need to be kept
945 # together and as such will break the sort strategy.
946 msg_response = build_yang_definitions(all_proto_data)
947 yang_def = response.file.add()
948 yang_def.name = C.YANG_MESSAGE_DEFINITIONS_FILE
949 yang_def.content = template_yang_definition.render(messages=msg_response)
950
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500951
952def get_yang_type(field):
953 type = field['type']
954 if type in YANG_TYPE_MAP.keys():
955 _type, _ = YANG_TYPE_MAP[type]
956 if _type in ['enumeration', 'message', 'group']:
957 return field['type_name'].split('.')[-1]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500958 else:
959 return _type
960 else:
961 return type
962
Khen Nursimulub4e71472017-01-06 18:05:47 -0500963
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500964def is_enumeration(type):
965 if type in YANG_TYPE_MAP.keys():
966 _type, _ = YANG_TYPE_MAP[type]
967 return _type in ['enumeration']
968 return False
969
Khen Nursimulub4e71472017-01-06 18:05:47 -0500970
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500971def is_base_type(type):
972 # check numeric value of the type first
973 if type in YANG_TYPE_MAP.keys():
974 _type, _ = YANG_TYPE_MAP[type]
975 return _type not in ['message', 'group']
976 else:
977 # proto name of the type
978 result = [_format for (_, _format) in YANG_TYPE_MAP.values() if
979 _format == type and _format not in ['message',
980 'group']]
981 return len(result) > 0
982
983
984def remove_unsupported_characters(text):
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500985 unsupported_characters = ["{", "}", "[", "]", "\"", "\\", "*", "/", "<",
986 ">"]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500987 return ''.join([i if i not in unsupported_characters else ' ' for i in
988 text])
989
990
991def remove_first_character_if_match(str, char):
992 if str.startswith(char):
993 return str[1:]
994 return str
995
996
997YANG_TYPE_MAP = {
998 FieldDescriptor.TYPE_BOOL: ('boolean', 'boolean'),
999 FieldDescriptor.TYPE_BYTES: ('binary', 'byte'),
1000 FieldDescriptor.TYPE_DOUBLE: ('decimal64', 'double'),
1001 FieldDescriptor.TYPE_ENUM: ('enumeration', 'enum'),
1002 FieldDescriptor.TYPE_FIXED32: ('int32', 'int64'),
1003 FieldDescriptor.TYPE_FIXED64: ('int64', 'uint64'),
1004 FieldDescriptor.TYPE_FLOAT: ('decimal64', 'float'),
1005 FieldDescriptor.TYPE_INT32: ('int32', 'int32'),
1006 FieldDescriptor.TYPE_INT64: ('int64', 'int64'),
1007 FieldDescriptor.TYPE_SFIXED32: ('int32', 'int32'),
1008 FieldDescriptor.TYPE_SFIXED64: ('int64', 'int64'),
1009 FieldDescriptor.TYPE_STRING: ('string', 'string'),
1010 FieldDescriptor.TYPE_SINT32: ('int32', 'int32'),
1011 FieldDescriptor.TYPE_SINT64: ('int64', 'int64'),
1012 FieldDescriptor.TYPE_UINT32: ('uint32', 'int64'),
1013 FieldDescriptor.TYPE_UINT64: ('uint64', 'uint64'),
1014 FieldDescriptor.TYPE_MESSAGE: ('message', 'message'),
1015 FieldDescriptor.TYPE_GROUP: ('group', 'group')
1016}
1017
1018if __name__ == '__main__':
1019 # Read request message from stdin
1020 data = sys.stdin.read()
1021
1022 # Parse request
1023 request = plugin.CodeGeneratorRequest()
1024 request.ParseFromString(data)
1025
1026 # Create response
1027 response = plugin.CodeGeneratorResponse()
1028
1029 # Generate code
1030 generate_code(request, response)
1031
1032 # Serialise response message
1033 output = response.SerializeToString()
1034
1035 # Write to stdout
1036 sys.stdout.write(output)
1037 # print is_base_type(9)