blob: e11d2ece8cc1d8edf6e11d3b25e7d9e0bed7ae5c [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 Bhatiaff1b8fa2017-04-10 19:44:38 -070017import pdb
Sapan Bhatiac4f803f2017-04-21 11:50:39 +020018import re
Scott Baker391f5d82018-10-02 16:34:41 -070019from inflect import engine as inflect_engine_class
20
21inflect_engine = inflect_engine_class()
Sapan Bhatia7886e122017-05-17 11:19:39 +020022
Sapan Bhatiaf7934b52017-06-12 05:04:23 -070023class FieldNotFound(Exception):
24 def __init__(self, message):
25 super(FieldNotFound, self).__init__(message)
26
Sapan Bhatiad4567592017-07-24 17:26:26 -040027def xproto_debug(**kwargs):
28 print kwargs
29 pdb.set_trace()
30
Sapan Bhatia943dad52017-05-19 18:41:01 +020031def xproto_unquote(s):
32 return unquote(s)
33
Sapan Bhatia49b54ae2017-05-19 17:11:32 +020034def unquote(s):
35 if (s.startswith('"') and s.endswith('"')):
36 return s[1:-1]
Matteo Scandolo292cc2a2017-07-31 19:02:12 -070037 else:
38 return s
Sapan Bhatia49b54ae2017-05-19 17:11:32 +020039
40def xproto_singularize(field):
41 try:
42 # The user has set a singular, as an exception that cannot be handled automatically
43 singular = field['options']['singular']
44 singular = unquote(singular)
45 except KeyError:
Scott Baker391f5d82018-10-02 16:34:41 -070046 singular = inflect_engine.singular_noun(field['name'])
Scott Bakera1b089a2018-10-05 09:59:17 -070047 if singular is False:
48 # singular_noun returns False on a noun it can't singularize
49 singular = field["name"]
Sapan Bhatia49b54ae2017-05-19 17:11:32 +020050
51 return singular
52
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +020053def xproto_singularize_pluralize(field):
54 try:
55 # The user has set a plural, as an exception that cannot be handled automatically
56 plural = field['options']['plural']
57 plural = unquote(plural)
58 except KeyError:
Scott Bakera1b089a2018-10-05 09:59:17 -070059 singular = inflect_engine.singular_noun(field['name'])
60 if singular is False:
61 # singular_noun returns False on a noun it can't singularize
62 singular = field["name"]
63
64 plural = inflect_engine.plural_noun(singular)
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +020065
66 return plural
67
Sapan Bhatia7886e122017-05-17 11:19:39 +020068def xproto_pluralize(field):
69 try:
70 # The user has set a plural, as an exception that cannot be handled automatically
71 plural = field['options']['plural']
Sapan Bhatia49b54ae2017-05-19 17:11:32 +020072 plural = unquote(plural)
Sapan Bhatia7886e122017-05-17 11:19:39 +020073 except KeyError:
Scott Baker391f5d82018-10-02 16:34:41 -070074 plural = inflect_engine.plural_noun(field['name'])
Sapan Bhatia7886e122017-05-17 11:19:39 +020075
76 return plural
Sapan Bhatiac4f803f2017-04-21 11:50:39 +020077
Sapan Bhatiaa3d2e622017-07-31 22:25:55 -040078def xproto_base_def(model_name, base, suffix='', suffix_list=[]):
Sapan Bhatia4e80a262017-05-19 23:10:51 +020079 if (model_name=='XOSBase'):
80 return '(models.Model, PlModelMixIn)'
81 elif (not base):
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070082 return ''
83 else:
Sapan Bhatiaa3d2e622017-07-31 22:25:55 -040084 int_base = [i['name']+suffix for i in base if i['name'] in suffix_list]
85 ext_base = [i['name'] for i in base if i['name'] not in suffix_list]
86 return '(' + ','.join(int_base + ext_base) + ')'
Sapan Bhatiaff1b8fa2017-04-10 19:44:38 -070087
Sapan Bhatia504cc972017-04-27 01:56:28 +020088def xproto_first_non_empty(lst):
89 for l in lst:
90 if l: return l
91
Sapan Bhatia943dad52017-05-19 18:41:01 +020092def xproto_api_type(field):
93 try:
94 if (unquote(field['options']['content_type'])=='date'):
Scott Baker450e5d02017-09-06 11:18:16 -070095 return 'double'
Sapan Bhatia943dad52017-05-19 18:41:01 +020096 except KeyError:
97 pass
98
99 return field['type']
100
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200101
102def xproto_base_name(n):
103 # Hack - Refactor NetworkParameter* to make this go away
104 if (n.startswith('NetworkParameter')):
105 return '_'
106
Sapan Bhatia4e80a262017-05-19 23:10:51 +0200107 expr = r'^[A-Z]+[a-z]*'
Sapan Bhatiac4f803f2017-04-21 11:50:39 +0200108
109 try:
110 match = re.findall(expr, n)[0]
111 except:
112 return '_'
113
114 return match
Sapan Bhatiaae9645c2017-05-05 15:35:54 +0200115
Sapan Bhatia943dad52017-05-19 18:41:01 +0200116def xproto_base_fields(m, table):
117 fields = []
118
119 for b in m['bases']:
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200120 option1 = b['fqn']
121 try:
122 option2 = m['package'] + '.' + b['name']
123 except TypeError:
124 option2 = option1
Sapan Bhatia943dad52017-05-19 18:41:01 +0200125
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200126 accessor = None
127 if option1 in table: accessor = option1
128 elif option2 in table: accessor = option2
129
130 if accessor:
131 base_fields = xproto_base_fields(table[accessor], table)
132
Scott Bakerc237f882018-09-28 14:12:47 -0700133 model_fields = [x.copy() for x in table[accessor]['fields']]
134 for field in model_fields:
135 field["accessor"] = accessor
136
Sapan Bhatia943dad52017-05-19 18:41:01 +0200137 fields.extend(base_fields)
138 fields.extend(model_fields)
139
Matteo Scandolo39b4a272017-11-17 11:09:21 -0800140 if 'no_sync' in m['options'] and m['options']['no_sync']:
141 fields = [f for f in fields if f['name'] != 'backend_status' and f['name'] != 'backend_code']
142
143 if 'no_policy' in m['options'] and m['options']['no_policy']:
144 fields = [f for f in fields if f['name'] != 'policy_status' and f['name'] != 'policy_code']
145
Sapan Bhatia943dad52017-05-19 18:41:01 +0200146 return fields
Sapan Bhatiad022aeb2017-06-07 15:49:55 +0200147
Scott Bakerc237f882018-09-28 14:12:47 -0700148def xproto_fields(m, table):
149 """ Generate the full list of models for the xproto message `m` including fields from the classes it inherits.
150
151 Inserts the special field "id" at the very beginning.
152
153 Each time we descend a new level of inheritance, increment the offset field numbers by 100. The base
154 class's fields will be numbered from 1-99, the first descendant will be number 100-199, the second
155 descdendant numbered from 200-299, and so on. This assumes any particular model as at most 100
156 fields.
157 """
158
159 model_fields = [x.copy() for x in m["fields"]]
160 for field in model_fields:
161 field["accessor"] = m["fqn"]
162
163 fields = xproto_base_fields(m, table) + model_fields
164
165 # The "id" field is a special field. Every model has one. Put it up front and pretend it's part of the
166
Scott Baker1f7791d2018-10-04 13:21:20 -0700167 if not fields:
168 raise Exception("Model %s has no fields. Check for missing base class." % m["name"])
169
Scott Bakerc237f882018-09-28 14:12:47 -0700170 id_field = {'type': 'int32', 'name': 'id', 'options': {}, "id": "1", "accessor": fields[0]["accessor"]}
171
172 fields = [id_field] + fields
173
174 # Walk through the list of fields. They will be in depth-first search order from the base model forward. Each time
175 # the model changes, offset the protobuf field numbers by 100.
176 offset = 0
177 last_accessor = fields[0]["accessor"]
178 for field in fields:
179 if (field["accessor"] != last_accessor):
180 last_accessor = field["accessor"]
181 offset += 100
182 field_id = int(field["id"])
183 if (field_id < 1) or (field_id >= 100):
184 raise Exception("Only field numbers from 1 to 99 are permitted, field %s in model %s" % (field["name"], field["accessor"]))
185 field["id"] = int(field["id"]) + offset
186
187 # Check for duplicates
188 fields_by_number = {}
189 for field in fields:
190 id = field["id"]
191 dup = fields_by_number.get(id)
192 if dup:
193 raise Exception("Field %s has duplicate number %d with field %s in model %s" % (field["name"], id, dup["name"], field["accessor"]))
194 fields_by_number[id] = field
195
196 return fields
197
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200198def xproto_base_rlinks(m, table):
199 links = []
200
201 for base in m['bases']:
202 b = base['name']
203 if b in table:
204 base_rlinks = xproto_base_rlinks(table[b], table)
205
Scott Bakerd87c02a2018-10-29 16:24:29 -0700206 model_rlinks = [x.copy() for x in table[b]['rlinks']]
207 for link in model_rlinks:
208 link["accessor"] = b
209
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200210 links.extend(base_rlinks)
211 links.extend(model_rlinks)
212
213 return links
214
Scott Bakerc237f882018-09-28 14:12:47 -0700215def xproto_rlinks(m, table):
216 """ Return the reverse links for the xproto message `m`.
217
Scott Bakerd87c02a2018-10-29 16:24:29 -0700218 If the link includes a reverse_id, then it will be used for the protobuf field id. Each level of inheritance
219 will add an offset of 100 to the supplied reverse_id.
220
221 If there is no reverse_id, then one will automatically be allocated started at id 1900. It is encouraged that
222 all links include reverse_ids, so that field identifiers are deterministic across all protobuf messages.
Scott Bakerc237f882018-09-28 14:12:47 -0700223 """
224
Scott Bakerd87c02a2018-10-29 16:24:29 -0700225 model_rlinks = [x.copy() for x in m["rlinks"]]
226 for link in model_rlinks:
227 link["accessor"] = m["fqn"]
228
229 links = xproto_base_rlinks(m, table) + model_rlinks
Scott Bakerc237f882018-09-28 14:12:47 -0700230
231 links = [x for x in links if ("+" not in x["src_port"]) and ("+" not in x["dst_port"])]
232
Scott Bakerd87c02a2018-10-29 16:24:29 -0700233 if links:
234 last_accessor = links[0]["accessor"]
235 offset = 0
236 index = 1900
237 for link in links:
238 if (link["accessor"] != last_accessor):
239 last_accessor = link["accessor"]
240 offset += 100
Scott Bakerc237f882018-09-28 14:12:47 -0700241
Scott Bakerd87c02a2018-10-29 16:24:29 -0700242 if link["reverse_id"]:
243 # Statically numbered reverse links. Use the id that the developer supplied, adding the offset based on
244 # inheritance depth.
245 link["id"] = int(link["reverse_id"]) + offset
246 else:
247 # Automatically numbered reverse links. These will eventually go away.
248 link["id"] = index
249 index += 1
250
251 # check for duplicates
252 links_by_number={}
253 for link in links:
254 id = link["id"]
255 dup=links_by_number.get(id)
256 if dup:
257 raise Exception("Field %s has duplicate number %d in model %s with reverse field %s" %
258 (link["src_port"], id, m["name"], dup["src_port"]))
259 links_by_number[id] = link
Scott Bakerc237f882018-09-28 14:12:47 -0700260
261 return links
262
263
Sapan Bhatiad022aeb2017-06-07 15:49:55 +0200264def xproto_base_links(m, table):
265 links = []
266
Sapan Bhatia3cfdf632017-06-08 05:14:03 +0200267 for base in m['bases']:
268 b = base['name']
Sapan Bhatiad022aeb2017-06-07 15:49:55 +0200269 if b in table:
270 base_links = xproto_base_links(table[b], table)
271
272 model_links = table[b]['links']
273 links.extend(base_links)
274 links.extend(model_links)
275 return links
276
Sapan Bhatiad022aeb2017-06-07 15:49:55 +0200277def xproto_string_type(xptags):
278 try:
279 max_length = eval(xptags['max_length'])
280 except:
281 max_length = 1024
282
283 if ('varchar' not in xptags):
284 return 'string'
285 else:
286 return 'text'
287
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700288def xproto_tuplify(nested_list_or_set):
289 if not isinstance(nested_list_or_set, list) and not isinstance(nested_list_or_set, set):
290 return nested_list_or_set
291 else:
292 return tuple([xproto_tuplify(i) for i in nested_list_or_set])
293
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700294def xproto_field_graph_components(fields, model, tag='unique_with'):
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700295 def find_components(graph):
296 pending = set(graph.keys())
297 components = []
298
299 while pending:
300 front = { pending.pop() }
301 component = set()
302
303 while front:
304 node = front.pop()
305 neighbours = graph[node]
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700306 neighbours -= component # These we have already visited
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700307 front |= neighbours
308
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700309 pending -= neighbours
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700310 component |= neighbours
311
312 components.append(component)
313
314 return components
315
316 field_graph = {}
317 field_names = {f['name'] for f in fields}
318
319 for f in fields:
320 try:
321 tagged_str = unquote(f['options'][tag])
322 tagged_fields = tagged_str.split(',')
323
324 for uf in tagged_fields:
325 if uf not in field_names:
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700326 raise FieldNotFound('Field "%s" not found in model "%s", referenced from field "%s" by option "%s"' % (uf, model['name'], f['name'], tag))
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700327
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700328 field_graph.setdefault(f['name'], set()).add(uf)
329 field_graph.setdefault(uf, set()).add(f['name'])
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700330 except KeyError:
331 pass
332
Matteo Scandoloa17e6e42018-05-25 10:28:25 -0700333 return find_components(field_graph)
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700334
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200335def xproto_api_opts(field):
336 options = []
337 if 'max_length' in field['options'] and field['type']=='string':
338 options.append('(val).maxLength = %s'%field['options']['max_length'])
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700339
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200340 try:
341 if field['options']['null'] == 'False':
342 options.append('(val).nonNull = true')
343 except KeyError:
344 pass
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700345
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200346 if 'link' in field and 'model' in field['options']:
347 options.append('(foreignKey).modelName = "%s"'%field['options']['model'])
Scott Bakerc4156c32017-12-08 10:58:21 -0800348 if ("options" in field) and ("port" in field["options"]):
349 options.append('(foreignKey).reverseFieldName = "%s"' % field['options']['port'])
Sapan Bhatiaf7934b52017-06-12 05:04:23 -0700350
Sapan Bhatiacb35e7f2017-05-24 12:17:28 +0200351 if options:
352 options_str = '[' + ', '.join(options) + ']'
353 else:
354 options_str = ''
355
356 return options_str
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700357
Matteo Scandolo431781c2017-09-06 15:33:07 -0700358def xproto_type_to_swagger_type(f):
359 try:
360 content_type = f['options']['content_type']
361 content_type = eval(content_type)
362 except:
363 content_type = None
364 pass
365
366 if 'choices' in f['options']:
367 return 'string'
368 elif content_type == 'date':
369 return 'string'
370 elif f['type'] == 'bool':
371 return 'boolean'
372 elif f['type'] == 'string':
373 return 'string'
374 elif f['type'] in ['int','uint32','int32'] or 'link' in f:
375 return 'integer'
376 elif f['type'] in ['double','float']:
377 return 'string'
378
379def xproto_field_to_swagger_enum(f):
380 if 'choices' in f['options']:
381 list = []
382
383 for c in eval(xproto_unquote(f['options']['choices'])):
384 list.append(c[0])
385
386 return list
387 else:
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800388 return False
Scott Bakera33ccb02018-01-26 13:03:28 -0800389
390def xproto_is_true(x):
391 # TODO: Audit xproto and make specification of trueness more uniform
392 if (x==True) or (x=="True") or (x=='"True"'):
393 return True
394 return False