blob: aca2468dc691840d03191640c7d1f288fea48ebc [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Sapan Bhatia3cfdf632017-06-08 05:14:03 +020017import plyxproto.model as m
Sapan Bhatiadb183c22017-06-23 02:47:42 -070018from plyxproto.helpers import Visitor
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070019import argparse
Sapan Bhatia3cfdf632017-06-08 05:14:03 +020020import plyxproto.parser as plyxproto
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070021import traceback
22import sys
23import jinja2
24import os
Sapan Bhatia1e397df2017-05-24 12:17:28 +020025import copy
Sapan Bhatiad3fcb662017-07-25 21:13:48 -040026import pdb
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070027
Sapan Bhatiad3fcb662017-07-25 21:13:48 -040028class MissingPolicyException(Exception):
29 pass
30
31def find_missing_policy_calls(name, policies, policy):
32 if isinstance(policy, dict):
33 (k, lst), = policy.items()
34 if k=='policy':
35 policy_name = lst[0]
36 if policy_name not in policies: raise MissingPolicyException("Policy %s invoked missing policy %s"%(name, policy_name))
37 else:
38 for p in lst:
39 find_missing_policy_calls(name, policies, p)
40 elif isinstance(policy, list):
41 for p in lst:
42 find_missing_policy_calls(name, policies, p)
Matteo Scandolo67654fa2017-06-09 09:33:17 -070043
Sapan Bhatia3cfdf632017-06-08 05:14:03 +020044def dotname_to_fqn(dotname):
45 b_names = [part.pval for part in dotname]
46 package = '.'.join(b_names[:-1])
47 name = b_names[-1]
48 if package:
49 fqn = package + '.' + name
50 else:
51 fqn = name
Matteo Scandolo67654fa2017-06-09 09:33:17 -070052 return {'name': name, 'fqn': fqn, 'package': package}
53
Sapan Bhatia3cfdf632017-06-08 05:14:03 +020054
55def dotname_to_name(dotname):
56 b_names = [part.pval for part in dotname]
57 return '.'.join(b_names)
58
59
Sapan Bhatia504cc972017-04-27 01:56:28 +020060def count_messages(body):
61 count = 0
62 for e in body:
Matteo Scandolo67654fa2017-06-09 09:33:17 -070063 if (type(e) == m.MessageDefinition):
64 count += 1
Sapan Bhatia504cc972017-04-27 01:56:28 +020065 return count
66
Matteo Scandolo67654fa2017-06-09 09:33:17 -070067
Sapan Bhatia504cc972017-04-27 01:56:28 +020068def count_fields(body):
69 count = 0
70 for e in body:
Matteo Scandolo67654fa2017-06-09 09:33:17 -070071 if (type(e) in [m.LinkDefinition, m.FieldDefinition, m.LinkSpec]):
72 count += 1
Sapan Bhatia504cc972017-04-27 01:56:28 +020073 return count
74
Matteo Scandolo67654fa2017-06-09 09:33:17 -070075
Sapan Bhatia3cfdf632017-06-08 05:14:03 +020076def name_to_value(obj):
77 try:
78 value = obj.value.value.pval
79 except AttributeError:
80 try:
81 value = obj.value.value
82 except AttributeError:
83 value = obj.value.pval
84
85 return value
86
Matteo Scandolo67654fa2017-06-09 09:33:17 -070087
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070088class Stack(list):
Matteo Scandolo67654fa2017-06-09 09:33:17 -070089 def push(self, x):
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070090 self.append(x)
91
Matteo Scandolo67654fa2017-06-09 09:33:17 -070092
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070093''' XOS2Jinja overrides the underlying visitor pattern to transform the tree
94 in addition to traversing it '''
Matteo Scandolo67654fa2017-06-09 09:33:17 -070095
Sapan Bhatiadb183c22017-06-23 02:47:42 -070096class XOS2Jinja(Visitor):
Scott Bakerc237f882018-09-28 14:12:47 -070097 def __init__(self, args):
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070098 super(XOS2Jinja, self).__init__()
99
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700100 self.stack = Stack()
101 self.models = {}
102 self.options = {}
103 self.package = None
104 self.message_options = {}
105 self.count_stack = Stack()
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700106 self.policies = {}
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700107 self.content = ""
108 self.offset = 0
109 self.current_message_name = None
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700110 self.verbose = 0
111 self.first_field = True
112 self.first_method = True
Scott Bakerc237f882018-09-28 14:12:47 -0700113 self.args = args
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700114
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700115 def visit_PolicyDefinition(self, obj):
116 if self.package:
117 pname = '.'.join([self.package, obj.name.value.pval])
118 else:
119 pname = obj.name.value.pval
120
121 self.policies[pname] = obj.body
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400122 find_missing_policy_calls(pname, self.policies, obj.body)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400123
124 return True
125
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700126 def visit_PackageStatement(self, obj):
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200127 dotlist = obj.name.value
128 dotlist2 = [f.pval for f in dotlist]
129 dotstr = '.'.join(dotlist2)
130 self.package = dotstr
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700131 return True
132
133 def visit_ImportStatement(self, obj):
134 '''Ignore'''
135 return True
136
137 def visit_OptionStatement(self, obj):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700138 if not hasattr(obj, 'mark_for_deletion'):
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200139 if (self.current_message_name):
140 self.message_options[obj.name.value.pval] = obj.value.value.pval
141 else:
142 self.options[obj.name.value.pval] = obj.value.value.pval
Sapan Bhatia504cc972017-04-27 01:56:28 +0200143
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700144 return True
145
146 def visit_LU(self, obj):
147 return True
148
149 def visit_default(self, obj):
150 return True
151
152 def visit_FieldDirective(self, obj):
153 return True
154
155 def visit_FieldDirective_post(self, obj):
156
157 try:
158 name = obj.name.value.pval
159 except AttributeError:
160 name = obj.name.value
161
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700162 if type(obj.value) == list:
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200163 value = dotname_to_name(obj.value)
164 else:
165 value = name_to_value(obj)
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700166
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700167 self.stack.push([name, value])
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700168 return True
169
170 def visit_FieldType(self, obj):
171 '''Field type, if type is name, then it may need refactoring consistent with refactoring rules according to the table'''
172 return True
173
174 def visit_LinkDefinition(self, obj):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700175 s = {}
Sapan Bhatia8a57c662017-04-11 10:39:47 -0700176
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200177 try:
178 s['link_type'] = obj.link_type.pval
179 except AttributeError:
180 s['link_type'] = obj.link_type
181
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700182 s['src_port'] = obj.src_port.value.pval
183 s['name'] = obj.src_port.value.pval
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700184 try:
185 s['policy'] = obj.policy.pval
186 except AttributeError:
187 s['policy'] = None
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200188
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700189 try:
190 s['dst_port'] = obj.dst_port.value.pval
191 except AttributeError:
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200192 s['dst_port'] = obj.dst_port
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200193
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700194 if type(obj.through) == list:
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200195 s['through'] = dotname_to_fqn(obj.through)
196 else:
197 try:
198 s['through'] = obj.through.pval
199 except AttributeError:
200 s['through'] = obj.through
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200201
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700202 if type(obj.name) == list:
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200203 s['peer'] = dotname_to_fqn(obj.name)
204 else:
205 try:
206 s['peer'] = obj.name.pval
207 except AttributeError:
208 s['peer'] = obj.name
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200209
Scott Bakerc237f882018-09-28 14:12:47 -0700210 try:
211 s['reverse_id'] = obj.reverse_id.pval
212 except AttributeError:
213 s['reverse_id'] = obj.reverse_id
214
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700215 s['_type'] = 'link'
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700216 s['options'] = {'modifier': 'optional'}
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700217
218 self.stack.push(s)
219 return True
220
221 def visit_FieldDefinition(self, obj):
222 self.count_stack.push(len(obj.fieldDirective))
223 return True
224
225 def visit_FieldDefinition_post(self, obj):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700226 s = {}
227
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700228 if isinstance(obj.ftype, m.Name):
229 s['type'] = obj.ftype.value
230 else:
231 s['type'] = obj.ftype.name.pval
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200232
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700233 s['name'] = obj.name.value.pval
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700234
235 try:
236 s['policy'] = obj.policy.pval
237 except AttributeError:
238 s['policy'] = None
239
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700240 s['modifier'] = obj.field_modifier.pval
241 s['id'] = obj.fieldId.pval
242
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700243 opts = {'modifier': s['modifier']}
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700244 n = self.count_stack.pop()
245 for i in range(0, n):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700246 k, v = self.stack.pop()
Sapan Bhatia5769d932017-05-17 11:10:54 +0200247
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700248 # The two lines below may be added to eliminate "" around an option.
Sapan Bhatia5769d932017-05-17 11:10:54 +0200249 # Right now, this is handled in targets. FIXME
250 #
251 # if (v.startswith('"') and v.endswith('"')):
252 # v = v[1:-1]
253
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700254 opts[k] = v
255
256 s['options'] = opts
Sapan Bhatia8a57c662017-04-11 10:39:47 -0700257 try:
258 last_link = self.stack[-1]['_type']
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700259 if (last_link == 'link'):
Sapan Bhatia8a57c662017-04-11 10:39:47 -0700260 s['link'] = True
261 except:
262 pass
263 s['_type'] = 'field'
264
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700265 self.stack.push(s)
266 return True
267
268 def visit_EnumFieldDefinition(self, obj):
269 if self.verbose > 4:
270 print "\tEnumField: name=%s, %s" % (obj.name, obj)
271
272 return True
273
274 def visit_EnumDefinition(self, obj):
275 '''New enum definition, refactor name'''
276 if self.verbose > 3:
277 print "Enum, [%s] body=%s\n\n" % (obj.name, obj.body)
278
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700279 return True
280
281 def visit_MessageDefinition(self, obj):
Sapan Bhatia504cc972017-04-27 01:56:28 +0200282 self.current_message_name = obj.name.value.pval
283 self.message_options = {}
284 self.count_stack.push(count_fields(obj.body))
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700285 return True
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700286
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700287 def visit_MessageDefinition_post(self, obj):
288 stack_num = self.count_stack.pop()
289 fields = []
290 links = []
291 last_field = None
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200292 try:
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200293 obj.bases = map(dotname_to_fqn, obj.bases)
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200294 except AttributeError:
295 pass
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700296
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200297 last_field = {}
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700298 for i in range(0, stack_num):
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700299 f = self.stack.pop()
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700300 if (f['_type'] == 'link'):
301 f['options'] = {i: d[i] for d in [f['options'], last_field['options']] for i in d}
302 assert (last_field == fields[0])
Sapan Bhatiad022aeb2017-06-07 15:49:55 +0200303 fields[0].setdefault('options', {})['link_type'] = f['link_type']
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700304 links.insert(0, f)
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700305 else:
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700306 fields.insert(0, f)
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700307 last_field = f
308
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200309 if self.package:
310 model_name = '.'.join([self.package, obj.name.value.pval])
311 else:
312 model_name = obj.name.value.pval
313
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200314 model_def = {'name':obj.name.value.pval,'fields':fields,'links':links, 'bases':obj.bases, 'options':self.message_options, 'package':self.package, 'fqn': model_name, 'rlinks': []}
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700315 try:
316 model_def['policy'] = obj.policy.pval
317 except AttributeError:
318 model_def['policy'] = None
319
Sapan Bhatia943dad52017-05-19 18:41:01 +0200320 self.stack.push(model_def)
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200321
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200322 self.models[model_name] = model_def
323
324 # Set message options
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700325 for k, v in self.options.iteritems():
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200326 try:
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200327 if k not in self.message_options:
328 self.message_options[k] = v
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200329 except KeyError:
330 pass
331
Sapan Bhatia504cc972017-04-27 01:56:28 +0200332 self.current_message_name = None
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700333 return True
334
335 def visit_MessageExtension(self, obj):
336 return True
337
338 def visit_MethodDefinition(self, obj):
339 return True
340
341 def visit_ServiceDefinition(self, obj):
342 return True
343
344 def visit_ExtensionsDirective(self, obj):
345 return True
346
347 def visit_Literal(self, obj):
348 return True
349
350 def visit_Name(self, obj):
351 return True
352
353 def visit_DotName(self, obj):
354 return True
355
356 def visit_Proto(self, obj):
Sapan Bhatia504cc972017-04-27 01:56:28 +0200357 self.count_stack.push(count_messages(obj.body))
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700358 return True
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700359
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700360 def visit_Proto_post(self, obj):
361 count = self.count_stack.pop()
362 messages = []
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700363 for i in range(0, count):
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200364 try:
365 m = self.stack.pop()
366 except IndexError:
Sapan Bhatia504cc972017-04-27 01:56:28 +0200367 pass
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200368
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700369 messages.insert(0, m)
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700370
Scott Bakerc237f882018-09-28 14:12:47 -0700371 self.compute_rlinks(messages, self.models)
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200372
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -0700373 self.messages = messages
374 return True
375
376 def visit_LinkSpec(self, obj):
377 count = self.count_stack.pop()
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700378 self.count_stack.push(count + 1)
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700379 return True
Scott Bakerc237f882018-09-28 14:12:47 -0700380
381 def compute_rlinks(self, messages, message_dict):
382 rev_links = {}
383
384 link_opposite = {
385 'manytomany': 'manytomany',
386 'manytoone': 'onetomany',
387 'onetoone': 'onetoone',
388 'onetomany': 'manytoone'
389 }
390
391 for m in messages:
392 for l in m['links']:
393 rlink = copy.deepcopy(l)
394
395 rlink['_type'] = 'rlink' # An implicit link, not declared in the model
396 rlink['src_port'] = l['dst_port']
397 rlink['dst_port'] = l['src_port']
398 rlink['peer'] = {'name': m['name'], 'package': m['package'], 'fqn': m['fqn']}
399 rlink['link_type'] = link_opposite[l['link_type']]
400 rlink["reverse_id"] = l['reverse_id']
401
402 if (not l['reverse_id']) and (self.args.verbosity >= 1):
403 print >> sys.stderr, "WARNING: Field %s in model %s has no reverse_id" % (l["src_port"], m["name"])
404
405 if l["reverse_id"] and ((int(l["reverse_id"]) < 1000) or (int(l["reverse_id"]) >= 1900)):
406 raise Exception("reverse id for field %s in model %s should be between 1000 and 1899" % (l["src_port"], m["name"]))
407
408 try:
409 try:
410 rev_links[l['peer']['fqn']].append(rlink)
411 except TypeError:
412 pass
413 except KeyError:
414 rev_links[l['peer']['fqn']] = [rlink]
415
416 for m in messages:
417 try:
418 m['rlinks'] = rev_links[m['name']]
419 message_dict[m['name']]['rlinks'] = m['rlinks']
420 except KeyError:
421 pass