blob: 4bb156d4f0e0cf4b1dc57b965e0e00ed158bfbb3 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2011,2012,2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
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
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17import sys
18# from midw import *
19
20#
21# MODel Information (modi)
22#
23
24class Modi():
25
26 def __init__(self, sdnsh, cli_model_info):
27 self.sdnsh = sdnsh
28
29 self.obj_type_info_dict = {}
30 self.obj_types = []
31 self.obj_keys = {}
32
33 # self.cli_model_info = CliModelInfo()
34 self.cli_model_info = cli_model_info
35
36 self.init_obj_type_info_dict(cli_model_info)
37 self.init_foreign_key_xref()
38 self.init_alias_obj_type_xref()
39
40 @staticmethod
41 def _line():
42 # pylint: disable=W0212
43 f = sys._getframe().f_back
44 return '[%s:%d]' % (f.f_code.co_filename, f.f_lineno)
45 #
46 # --------------------------------------------------------------------------------
47
48 def init_obj_type_info_dict(self, cli_model_info):
49 """
50 When obj_type_info_dict is indexed by the table/model name,
51 the value is a dictionary. That dictionary has several keys:
52
53 - 'cascade_delete' True/False, set to true when foreign key's in this
54 obj_type will be used o identify rows to delete based
55 on rows removed from the parent table.
56 - 'force_delete' True/False, set to true when foreign keys in this
57 obj_type are allowed to be null, but when they are not,
58 the rows identified by cascade_delete must be deleted
59 when rows are removed from the parent table
60 - 'source' set to 'user-config' for obj_types which user's configure
61 set to 'debug-only' to enable viewing object only in debug mode
62
63 The 'fields' indexed result returns a dictionary, each of which
64 is indexed by the names of the fields within the table/model. further
65 indicies describe details about that field within the table:
66
67 Some of the key's are intended to be added to the dictionary
68 to further decorate details about the field. The objective is
69 to modify cli behavior, or override the model details. These
70 values, tho, ought to really come from the model description.
71
72 - 'verbose-name' identfies an additonal name for the field, inteneded
73 to be more descriptive
74
75 - 'hex' True/False, True for fields when a hex value can
76 be replaced with a decimal value
77 - 'edit' True/Falue, False when a field cannot be edited within
78 the nested config mode.
79
80 - 'validate' function to call to validate the field value
81
82 Various key's use '_' vs the typical '-' since they're
83 currently constructed via the django model description
84
85 - 'primary_key' identifes this field as the key for the table/model.
86 - 'has_rest_model' True/False
87 - 'json_serialize_string' True/False
88 - 'help_text' String providing more clues about the intent of this field
89 - 'type' various values, intended to be populated by tools/extract_model.py
90 allowing access of the type to the cli. the allows the
91 cli to find foreign_keys, and possibly determine
92 - 'max_length'
93
94 Other key decorations: sorting
95
96 The 'field_ordering' dictionary associated with the name of the table/model
97 is used to order the output.
98
99 Also populates the self.obj_keys[], which is a dictionary mapping the table/model
100 name to the name of the storage key (table/model's column) for that table.
101
102 @param cli_model_info instantiated class CliModelInfo()
103 """
104 self.obj_type_info_dict = self.cli_model_info.get_complete_obj_type_info_dict()
105 self.obj_types = [k for (k, v) in self.obj_type_info_dict.items()
106 if 'has_rest_model' in v]
107 for (k, d) in self.obj_type_info_dict.items():
108 if not 'fields' in d:
109 print '%s: Missing "fields"' % k
110 continue
111 candidate_keys = [f for f in d['fields'].keys()
112 if d['fields'][f].get('primary_key', False)]
113 if len(candidate_keys) > 0:
114 self.obj_keys[k] = candidate_keys[0]
115
116 #
117 # --------------------------------------------------------------------------------
118
119 def init_foreign_key_xref(self):
120 """
121 Walk through the obj_type_info_dict, looking for foreign keys.
122 Build a cross references, so that a <obj_type, field> can be
123 used to identify all the obj_types which reference that field.
124
125 To allow for both <obj_type, field> to be used to index
126 self.foreign_key_xref, two levels of dictionaries are built.
127 The first maps from obj_type to a dictionary of fields, and
128 the fields dictionary has values which are lists. Each of the
129 lists has two members: the <obj_type, field>, which will be
130 a foreign key to the original pair.
131
132 This allows identificaion of fields which have single foreign
133 key xref's, which can then be used to identify "sub-modes"
134 for particular obj_types.
135 """
136
137 self.foreign_key_xref = {}
138 for (obj_type, obj_type_dict) in self.obj_type_info_dict.items():
139 if not 'fields' in obj_type_dict:
140 print '%s: Missing "fields"' % obj_type
141 continue
142 for (fn, fd) in obj_type_dict['fields'].items():
143 if 'type' in fd:
144 if fd['type'] == 'ForeignKey':
145 ref_foreign_obj_type = fd['rel_obj_type']
146 ref_foreign_field = fd['rel_field_name']
147 if not ref_foreign_obj_type in self.foreign_key_xref:
148 self.foreign_key_xref[ref_foreign_obj_type] = {}
149 if not ref_foreign_field in self.foreign_key_xref[ref_foreign_obj_type]:
150 self.foreign_key_xref[ref_foreign_obj_type][ref_foreign_field] = []
151 self.foreign_key_xref[ref_foreign_obj_type][ref_foreign_field].append(
152 [obj_type, fn])
153
154 #
155 # --------------------------------------------------------------------------------
156
157 def init_alias_obj_type_xref(self):
158 """
159 Alias obj types have a non-compound primary key, a foreign key, and
160 possibly 'DateTimeField' fields, but no other fields. These can
161 be identified by scanning the dictionary.
162
163 The alias_obj_type_xref dictionary is indexed by the obj_type referenced
164 by the foreign key of the original obj_type (its a cross ref)
165 """
166
167 self.alias_obj_type_xref = {}
168 self.alias_obj_types = []
169 for (obj_type, obj_type_dict) in self.obj_type_info_dict.items():
170 if not 'fields' in obj_type_dict:
171 print '%s: Missing "fields"' % obj_type
172 continue
173 foreign_key_obj_type = None
174 foreign_key_count = 0
175 other_types = False
176 for (fn, fd) in obj_type_dict['fields'].items():
177 # 'Idx' field is only for display counting of rows
178 if fn != 'Idx' and 'type' in fd:
179 if fd['type'] == 'ForeignKey':
180 foreign_key_count += 1
181 foreign_key_obj_type = fd['rel_obj_type']
182 elif 'primary_key' in fd:
183 if self.is_compound_key(obj_type, fn):
184 other_types = True
185 elif fd['type'] != 'DateTimeField':
186 other_types = True
187 if foreign_key_count == 1 and other_types == False:
188 self.alias_obj_types.append(obj_type)
189 if not foreign_key_obj_type in self.alias_obj_type_xref:
190 self.alias_obj_type_xref[foreign_key_obj_type] = []
191 self.alias_obj_type_xref[foreign_key_obj_type].append(
192 obj_type
193 )
194 #
195 # if the primariy key is a compound key, and the first item
196 # is also a foreign key here, then allow the first item to
197 # match up with the alias. currenly, only use the first item
198 # since the 'startswith' can use used to find the assocaited
199 # members
200 #
201 elif foreign_key_count >= 1:
202 key = self.obj_keys[obj_type]
203 compound_fields = self.compound_key_fields(obj_type, key)
204 if compound_fields:
205 first_field = compound_fields[0]
206 if first_field in self.obj_type_foreign_keys(obj_type):
207 #
208 # This is nasty -- assuming that the foreign_key's table
209 # name will then have an alias table associated with it.
210 # Perhaps some model field can help describe these
211 # associations, something like 'allow-alias: <obj_type>'
212 if first_field == 'vns':
213 pass
214 if not "%s-alias" % first_field in self.obj_type_info_dict:
215 # only build references to tables which exist.
216 pass
217 elif obj_type in self.alias_obj_type_xref:
218 self.alias_obj_type_xref[obj_type] += ["%s-alias" % first_field]
219 else:
220 self.alias_obj_type_xref[obj_type] = ["%s-alias" % first_field]
221
222 #
223 # --------------------------------------------------------------------------------
224
225 def pk(self, obj_type):
226 """
227 Return the primary key name for the object
228
229 @param obj_type string, name of the object-type (ie: 'host', 'switch')
230 """
231
232 # Raise an exception when the name doesn't exist?
233 return self.obj_keys.get(obj_type, None)
234
235 #
236 # --------------------------------------------------------------------------------
237
238 def obj_type_exists(self, obj_type):
239 """
240 Return True if there's details about obj_type in obj_types_info_dict
241 """
242 return obj_type in self.obj_type_info_dict.keys()
243
244 #
245 # --------------------------------------------------------------------------------
246
247 def obj_type_has_model(self, obj_type):
248 """
249 Return True if the obj_type is serviced via the model rest api
250 (in other words, there's a db table which supports this model)
251 """
252 return obj_type in self.obj_types
253
254 #
255 # --------------------------------------------------------------------------------
256
257 def obj_type_has_url(self, obj_type):
258 """
259 Returns a url suffix describing a path which returns the
260 data associated with thie obj_type
261 """
262 if obj_type in self.obj_type_info_dict:
263 return self.obj_type_info_dict[obj_type].get('url', None)
264 return None
265
266 #
267 # --------------------------------------------------------------------------------
268
269 def is_foreign_key(self, obj_type, field):
270 """
271 Return True when the field within the obj_type is a foreign key.
272
273 @param obj_type string, name of the object-type
274 @param field string, field within the object-type
275 """
276 if not obj_type in self.obj_type_info_dict:
277 return False
278 if not 'fields' in self.obj_type_info_dict[obj_type]:
279 return False
280
281 field_info = self.obj_type_info_dict[obj_type]['fields'].get(field, [])
282 if 'type' in field_info:
283 if field_info['type'] == 'ForeignKey':
284 return True
285 return False
286
287 #
288 # --------------------------------------------------------------------------------
289
290 def is_integer_field(self, obj_type, field):
291 """
292 Return True when the type associated with the obj_type's field is an integer
293
294 @param obj_type string, name of the object-type
295 @param field string, field within the object-type
296 """
297
298 if not obj_type in self.obj_type_info_dict:
299 return False
300 if not 'fields' in self.obj_type_info_dict[obj_type]:
301 return False
302
303 field_info = self.obj_type_info_dict[obj_type].get('fields', [])[field]
304 if 'type' in field_info:
305 if field_info['type'] == 'IntegerField':
306 return True
307 return False
308
309
310 #
311 # --------------------------------------------------------------------------------
312
313 def is_primary_key(self, obj_type, field):
314 """
315 Return true when the obj_type's field is a primary key
316
317 @param obj_type string, name of the object-type
318 @param field string, field within the object-type
319 """
320
321 if not obj_type in self.obj_type_info_dict:
322 return False
323 if not 'fields' in self.obj_type_info_dict[obj_type]:
324 return False
325
326 field_info = self.obj_type_info_dict[obj_type].get('fields',[])[field]
327 if 'primary_key' in field_info:
328 return field_info['primary_key'] # should be true if exists
329 return False
330
331 #
332 # --------------------------------------------------------------------------------
333
334 def is_field_editable(self, obj_type, field):
335 """
336 Return True if the field is editable. Default is True.
337
338 @param obj_type string, name of the object-type
339 @param field string, field within the object-type
340 """
341
342 if not obj_type in self.obj_type_info_dict:
343 return False
344 if not 'fields' in self.obj_type_info_dict[obj_type]:
345 return False
346
347 field_info = self.obj_type_info_dict[obj_type].get('fields',[])[field]
348 if 'edit' in field_info:
349 return field_info['edit'] # should be False if exists
350 return True
351
352 #
353 # --------------------------------------------------------------------------------
354
355 def is_editable(self, obj_type, field):
356 """
357 Return true if the obj_type/field is available for editing
358 Excludes foreign keys, and primary keys (unless edit: True
359 is specifically enabled for the field)
360
361 @param obj_type string, name of the object-type
362 @param field string, field within the object-type
363 """
364
365 if not obj_type in self.obj_type_info_dict:
366 return False
367 if not 'fields' in self.obj_type_info_dict[obj_type]:
368 return False
369
370 if not field in self.obj_type_info_dict[obj_type]['fields']:
371 return False
372 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
373 if 'edit' in field_info and field_info['edit'] == True:
374 return True
375 if self.is_foreign_key(obj_type, field):
376 return False
377 if self.is_primary_key(obj_type, field):
378 return False
379 if not self.is_field_editable(obj_type, field):
380 return False
381 return True
382
383 #
384 # --------------------------------------------------------------------------------
385
386 def obj_type_disable_edit(self, obj_type, field):
387 """
388 Mark an obj_type's field as not being directly editable.
389 When the command descriptions are used for all obj_type's edit, the need
390 for 'disabling edit' ought to disappear
391 """
392 if not obj_type in self.obj_type_info_dict:
393 return False
394 if not 'fields' in self.obj_type_info_dict[obj_type]:
395 self.obj_type_info_dict[obj_type]['fields'] = {}
396 if not field in self.obj_type_info_dict[obj_type]['fields']:
397 self.obj_type_info_dict[obj_type]['fields'][field] = {}
398 self.obj_type_info_dict[obj_type]['fields'][field]['edit'] = False
399
400 #
401 # --------------------------------------------------------------------------------
402
403 def is_marked_searchable(self, obj_type, field):
404 """
405 Return true if a field is searchable.
406
407 This ought to be true for any fields which is part of the
408 primary key construction.
409
410 This predicate, however, in intended to look for fields
411 which are identified by the model as being searchable,
412 even when the field doesn't appear in the primary key.
413
414 @param obj_type
415 @param field
416 """
417
418 if not obj_type in self.obj_type_info_dict:
419 return False
420 if not 'fields' in self.obj_type_info_dict[obj_type]:
421 return False
422 if not field in self.obj_type_info_dict[obj_type]['fields']:
423 return False
424
425 key = self.pk(obj_type)
426 if self.is_compound_key(obj_type, key):
427 if field in self.compound_key_fields(obj_type, key):
428 return True
429
430 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
431 if 'searchable' in field_info:
432 return field_info['searchable'] == True
433 return False
434
435 #
436 # --------------------------------------------------------------------------------
437
438 def get_obj_type_field_case_sensitive(self, obj_type, field):
439 """
440 Return true if a field is case sensitive.
441
442 @param obj_type
443 @param field
444 """
445
446 if not obj_type in self.obj_type_info_dict:
447 return None
448 if not 'fields' in self.obj_type_info_dict[obj_type]:
449 return None
450 if not field in self.obj_type_info_dict[obj_type]['fields']:
451 return None
452
453 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
454 if 'case' in field_info:
455 return field_info['case']
456 return None
457
458
459 #
460 # --------------------------------------------------------------------------------
461
462 def set_obj_type_field_case_sensitive(self, obj_type, field, case):
463 """
464 Set case sensitivity for a field in an obj_type
465
466 @param obj_type
467 @param field
468 @param case either 'upper', or 'lower'
469 """
470
471 if case not in ['upper', 'lower']:
472 print 'set_case_sensitive: obj_type %s field %s case %s ' \
473 'case not upper/lower' % (obj_type, field, case)
474 return
475
476 if not obj_type in self.obj_type_info_dict:
477 self.obj_type_info_dict[obj_type] = {}
478 if not 'fields' in self.obj_type_info_dict[obj_type]:
479 self.obj_type_info_dict[obj_type]['fields'] = {}
480 if not field in self.obj_type_info_dict[obj_type]['fields']:
481 self.obj_type_info_dict[obj_type]['fields'][field] = {}
482 self.obj_type_info_dict[obj_type]['fields'][field]['case'] = case
483
484
485 #
486 # --------------------------------------------------------------------------------
487
488 def is_hex_allowed(self, obj_type, field):
489 """
490 Return true if the obj_type/field allows hex instead of decimal
491
492 @param obj_type string, name of the object-type
493 @param field string, field within the object-type
494 """
495
496 if not obj_type in self.obj_type_info_dict:
497 return False
498 if not 'fields' in self.obj_type_info_dict[obj_type]:
499 return False
500
501 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
502 if 'hex' in field_info:
503 return field_info['hex'] # likely False if exists
504 if self.is_integer_field(obj_type, field):
505 return True
506 return False
507
508 #
509 # --------------------------------------------------------------------------------
510
511 def is_field_boolean(self, obj_type, field):
512 """
513 Return true if the obj_type/field is a boolean type
514
515 @param obj_type string, name of the object-type
516 @param field string, field within the object-type
517 """
518
519 if not obj_type in self.obj_type_info_dict:
520 return False
521 if not 'fields' in self.obj_type_info_dict[obj_type]:
522 return False
523
524 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
525 if 'type' in field_info:
526 return field_info['type'] == 'BooleanField'
527 return False
528
529 #
530 # --------------------------------------------------------------------------------
531 # is_field_string
532 # Return true if the obj_type/field is a character (CharType) type
533 #
534 def is_field_string(self, obj_type, field):
535 """
536
537 @param obj_type string, name of the object-type
538 @param field string, field within the object-type
539 """
540
541 if not obj_type in self.obj_type_info_dict:
542 return False
543 if not 'fields' in self.obj_type_info_dict[obj_type]:
544 return False
545
546 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
547 if 'type' in field_info:
548 return field_info['type'] == 'CharField'
549 return False
550
551 #
552 # --------------------------------------------------------------------------------
553 # is_null_allowed
554 # Return true if the obj_type/field is allowed to be null.
555 #
556 def is_null_allowed(self, obj_type, field):
557 """
558
559 @param obj_type string, name of the object-type
560 @param field string, field within the object-type
561 """
562
563 if not obj_type in self.obj_type_info_dict:
564 return False
565 if not 'fields' in self.obj_type_info_dict[obj_type]:
566 return False
567
568 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
569 if 'null' in field_info:
570 return field_info['null']
571 return False
572
573 #
574 # --------------------------------------------------------------------------------
575
576 def obj_type_fields(self, obj_type):
577 """
578 Return a list of field names (strings) for the obj_type,
579 includes all fields, including primary keys and foreign keys
580 """
581
582 if obj_type in self.obj_type_info_dict:
583 if not 'fields' in self.obj_type_info_dict[obj_type]:
584 return []
585 return self.obj_type_info_dict[obj_type]['fields'].keys()
586 return []
587 #
588 # --------------------------------------------------------------------------------
589
590 def obj_type_has_field(self, obj_type, field):
591 """
592 Return true if the obj_type has a field named 'field'
593
594 @param obj_type string, name of the object-type
595 @param field string, field within the object-type
596 """
597
598 if not obj_type in self.obj_type_info_dict:
599 return False
600 if not 'fields' in self.obj_type_info_dict[obj_type]:
601 return False
602
603 if obj_type in self.obj_type_info_dict:
604 fields_info = self.obj_type_info_dict[obj_type]['fields']
605 if field in fields_info:
606 return True
607 return False
608
609 #
610 # --------------------------------------------------------------------------------
611
612 def obj_type_config_fields(self, obj_type):
613 """
614 For an obj_type, return a list of fields which are possibly user configurable.
615
616 @param obj_type string, name of the object-type
617 """
618
619 if obj_type in self.obj_type_info_dict:
620 if not 'fields' in self.obj_type_info_dict[obj_type]:
621 return []
622 fields_info = self.obj_type_info_dict[obj_type]['fields']
623 return [x for x in fields_info if self.is_editable(obj_type, x)]
624 return []
625
626
627 #
628 # --------------------------------------------------------------------------------
629
630 def obj_type_show_this(self, obj_type):
631 """
632 Return a list of addition types to display for 'show this'
633
634 @param obj_type string, name of the object-type
635 """
636
637 if obj_type in self.obj_type_info_dict:
638 if 'show-this' in self.obj_type_info_dict[obj_type]:
639 result = self.obj_type_info_dict[obj_type]['show-this']
640 if type(result) == str or type(result) == 'unicode':
641 return [result]
642 return result
643 return []
644
645 #
646 # --------------------------------------------------------------------------------
647
648 def is_cascade_delete_enabled(self, obj_type):
649 """
650 Cascade is enabled for an obj_type by setting 'cascade_enabled': True for
651 the primary key of an obj_type.
652
653 @param obj_type string, name of the object-type
654 """
655
656 if not obj_type in self.obj_type_info_dict:
657 return False
658
659 if 'cascade_delete' in self.obj_type_info_dict[obj_type]:
660 return self.obj_type_info_dict[obj_type]['cascade_delete']
661 return False
662
663 def is_force_delete_enabled(self, obj_type):
664 """
665 Force delete is enabled for an obj_type by setting 'force_delete': True
666 for the primary key of an obj_type.
667
668 @param obj_type string, name of the object-type
669 """
670
671 if not obj_type in self.obj_type_info_dict:
672 return False
673
674 if 'force_delete' in self.obj_type_info_dict[obj_type]:
675 return self.obj_type_info_dict[obj_type]['force_delete']
676 return False
677
678 #
679 # --------------------------------------------------------------------------------
680
681 def cascade_delete_set_enable(self, obj_type):
682 """
683 Enable cascade_delete for an obj_type
684 """
685 if not obj_type in self.obj_type_info_dict:
686 self.obj_type_info_dict[obj_type] = {}
687 self.obj_type_info_dict[obj_type]['cascade_delete'] = True
688
689 def cascade_delete_enable_force(self, obj_type):
690 """
691 Force cascade_delete for an obj_type
692 """
693 if not obj_type in self.obj_type_info_dict:
694 self.obj_type_info_dict[obj_type] = {}
695 self.obj_type_info_dict[obj_type]['cascade_delete'] = True
696 self.obj_type_info_dict[obj_type]['force_delete'] = True
697
698 #
699 # --------------------------------------------------------------------------------
700
701 def has_display_field(self, obj_type, field):
702 """
703 Determine if a particular obj_type has a particular field on the
704 display (ie: during a show command). Uses the 'field_orderings'
705 of the obj_type_info_dict.
706
707 Currently used to deterine whether an alias table needs to be
708 re-cached in preparation for the display of some obj_type
709
710 @param obj_type string, name of the object-type
711 """
712
713 if obj_type in self.obj_type_info_dict and \
714 'field_orderings' in obj_type in self.obj_type_info_dict[obj_type]:
715 order = self.obj_type_info_dict[obj_type]['field_orderings']['default']
716 return field in order
717 return False
718
719 #
720 # --------------------------------------------------------------------------------
721
722 def is_obj_type_source_not_user_config(self, obj_type):
723 """
724 Return True if the obj_type is intended to be configured,
725 some tables are intended to be written by sdnplatform as a way
726 of presenting information; it makes no sense for the cli
727 to present these tables to the user as configurable.
728
729 keep in mind that this returns True only if 'source' exists,
730 and the source isn't get to user-config. This means that
731 for a table to be excluded, 'source' must be added, and
732 it must be set to something other than 'user-config'
733
734 @param obj_type string, name of the object-type
735 """
736
737 if obj_type in self.obj_type_info_dict and \
738 'source' in self.obj_type_info_dict[obj_type]:
739 if self.obj_type_info_dict[obj_type]['source'] != 'user-config':
740 if self.sdnsh.debug and \
741 self.obj_type_info_dict[obj_type]['source'] == 'debug-only':
742 return False
743 return True
744 return False
745
746 #
747 # --------------------------------------------------------------------------------
748
749 def is_obj_type_source_debug_only(self, obj_type):
750 """
751 Return True if the obj_type is marked to be viewed only in debug mode
752
753 @param obj_type string, name of the object-type
754 """
755
756 if obj_type in self.obj_type_info_dict and \
757 'source' in self.obj_type_info_dict[obj_type]:
758 if self.obj_type_info_dict[obj_type]['source'] == 'debug-only':
759 return True
760 return False
761
762 #
763 # --------------------------------------------------------------------------------
764
765 def obj_type_source_set_debug_only(self, obj_type):
766 """
767 Set the source for the obj-type
768 """
769
770 if obj_type in self.obj_type_info_dict:
771 self.obj_type_info_dict[obj_type]['source'] = 'debug-only'
772
773 #
774 # --------------------------------------------------------------------------------
775
776 def compound_key_text(self, obj_type, field):
777 """
778 Return a text string which describes the construction of a compound key.
779 The first character is a '#' when this returned field describes a
780 compound key. The second character is the separator for the field
781 itself (not the separator for the fields described by this text
782 field). The remainder is a concatenation of the fields names which
783 are used to construct this field.
784
785 Currently compound keys are identified through the help_text.
786 The help text isn't displayed to the user for primary since the value
787 of the key for compound key's must be constructed by the cli, and then
788 the user doesn't directly modify or search the compound value.
789
790 When the field is itself a foreign key, and no text string exists to
791 describe the construction of the compound key, its possible that the foreign
792 key's value is itself a compound key. Peek at the original foreign
793 key to see if it has a field identifying the layout of the compound key.
794
795 @param obj_type string, name of the object-type
796 @param field string, field within the object-type
797 """
798
799 if not obj_type in self.obj_type_info_dict:
800 return None
801 if not 'fields' in self.obj_type_info_dict[obj_type]:
802 return None
803 if not field in self.obj_type_info_dict[obj_type]['fields']:
804 return None
805
806 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
807 if 'help_text' in field_info:
808 if field_info['help_text'][0] == '#':
809 return field_info['help_text']
810 if self.is_foreign_key(obj_type, field):
811 (fk_obj_type, fk_name) = self.foreign_key_references(obj_type, field)
812 field_info = self.obj_type_info_dict[fk_obj_type]['fields'][fk_name]
813 if field_info.get('help_text', ' ')[0] == '#':
814 return field_info['help_text']
815 return None
816
817 #
818 # --------------------------------------------------------------------------------
819
820 def is_compound_key(self, obj_type, field):
821 """
822 Return true if the obj_type/field is a compound key,
823
824 The first character of the compound key's text '#', the
825 second character identifies the separator characer for the
826 fields value. The fields are separated by '|' within the text.
827
828 @param obj_type string, name of the object-type
829 @param field string, field within the object-type
830 """
831
832 text = self.compound_key_text(obj_type, field)
833 if text:
834 return True
835 return False
836
837 #
838 # --------------------------------------------------------------------------------
839 # is_primitive_compound_key
840 #
841 def is_primitive_compound_key(self, obj_type, field):
842 """
843 Returns True for a primitive compound key.
844
845 Primitive means compound-keys which don't use CassandraSetting's
846 COMPOUND_KEY_FIELDS, rather the help text describes the fields which
847 are cobbled together to create a primary key.
848
849 For the cassandraSetting's COMPOUND_KEY_FIELDS, searches for
850 rows can be done by setting values of the fields of the
851 COMPOUND_KEY_FIELDS, for primitive_compound_keys,
852
853
854 @param obj_type string, name of the object-type
855 @param field string, field within the object-type
856 """
857
858 if self.is_compound_key(obj_type, field):
859 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
860 if 'type' in field_info:
861 if field_info['type'] != 'compound-key':
862 return True
863 return False
864
865 #
866 # --------------------------------------------------------------------------------
867 # compound_key_separator
868 #
869 def compound_key_separator(self, obj_type, field):
870 """
871 Return the single character which is used to separate the values
872 of the different field parts for a field which is a compound key.
873
874 Return None when the
875
876 @param obj_type string, name of the object-type
877 @param field string, field within the object-type
878 """
879
880 text = self.compound_key_text(obj_type, field)
881 if text:
882 return text[1]
883 return None
884
885 #
886 # --------------------------------------------------------------------------------
887 # compound_key_fields
888 #
889 def compound_key_fields(self, obj_type, field):
890 """
891 Return's a list of strings, where each is intended to be the
892 name of a field within the obj_typ (this is not validated)
893
894 @param obj_type string, name of the object-type
895 @param field string, field within the object-type
896 """
897
898 text = self.compound_key_text(obj_type, field)
899 if text:
900 return text[2:].split('|')
901 return None
902
903 #
904 # --------------------------------------------------------------------------------
905
906 def deep_compound_key_fields(self, obj_type, field):
907 """
908 Similar to compound_key_fields(), but when any field is also a foreign
909 key, the references obj_type's field is checked, and if that field is
910 also a compound key, it is also expanded.
911
912 @param obj_type string, name of the object-type
913 @param field string, field within the object-type
914 """
915
916 def recurse_compound_key_fields(obj_type, field, parts):
917 if self.is_foreign_key(obj_type, field):
918 (fk_ot, fk_fn) = self.foreign_key_references(obj_type, field)
919 if self.is_compound_key(fk_ot, fk_fn):
920 recurse_compound_key_fields(fk_ot, fk_fn, parts)
921 elif self.is_compound_key(obj_type, field):
922 for cf in self.compound_key_fields(obj_type, field):
923 if (self.obj_type_has_field(obj_type, cf) and
924 self.is_foreign_key(obj_type, cf)):
925
926 (fk_ot, fk_fn) = self.foreign_key_references(obj_type, cf)
927 if self.is_compound_key(fk_ot, fk_fn):
928 recurse_compound_key_fields(fk_ot, fk_fn, parts)
929 else:
930 parts.append(cf)
931 else:
932 parts.append(cf)
933 return parts
934 return recurse_compound_key_fields(obj_type, field, [])
935
936
937 #
938 # --------------------------------------------------------------------------------
939
940 def split_compound_into_dict(self, obj_type, key, target_dict, is_prefix = False):
941 """
942 To be used to convert a compound key in a row intended for display,
943 into separate component name:value pairs.
944
945 The original dict is from a row of a table. the 'key' parameter
946 identifies a compound key in the row, the procedure splits the
947 value of that key/field, and uses the compound_key_fields() to
948 determine what names ought to be associated with the field's
949 values, and add's these fields into the original dict.
950 """
951 def add_to_dict(a, b):
952 if a in target_dict and a != key:
953 if str(target_dict[a]) != b:
954 if self.is_foreign_key(obj_type, a):
955 target_dict[a] = b
956 else:
957 print self.sdnsh.error_msg("compound split dict has different value: "
958 "%s found %s expected %s" %
959 (a, target_dict[a], b))
960 else:
961 target_dict[a] = b
962
963 names = self.deep_compound_key_fields(obj_type, key)
964 separator = self.compound_key_separator(obj_type, key)
965 values = target_dict[key].split(separator)
966
967 if len(names) != len(values):
968 if not is_prefix:
969 print self.sdnsh.error_msg("%s: %s: compound length mismatch: %s %s" %
970 (obj_type, key, names, values))
971 min_len = len(names)
972 if len(values) < min_len:
973 min_len = len(values)
974 map(add_to_dict, names[:min_len], values[:min_len])
975 else:
976 map(add_to_dict, names, values)
977
978 #
979 # --------------------------------------------------------------------------------
980
981 def foreign_key_references(self, obj_type, field):
982 """
983 For a field which is a foreign key, return the pair of
984 [obj_type, field] describing where the foreign key references
985
986 @param obj_type string, name of the object-type
987 @param field string, field within the object-type
988 """
989 if not obj_type in self.obj_type_info_dict:
990 return None
991 if not 'fields' in self.obj_type_info_dict[obj_type]:
992 return None
993 if not field in self.obj_type_info_dict[obj_type]['fields']:
994 return None
995
996 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
997 if 'type' in field_info:
998 if field_info['type'] == 'ForeignKey':
999 return [field_info['rel_obj_type'],
1000 field_info['rel_field_name']]
1001 return False
1002
1003 #
1004 # --------------------------------------------------------------------------------
1005
1006 def obj_type_foreign_keys(self, obj_type):
1007 """
1008 Return a list of foreign keys for this obj_type
1009
1010 @param field string, field within the object-type
1011 """
1012 if not obj_type in self.obj_type_info_dict:
1013 return []
1014 return [x for x in self.obj_type_info_dict[obj_type]['fields']
1015 if self.is_foreign_key(obj_type, x)]
1016
1017 #
1018 # --------------------------------------------------------------------------------
1019
1020 def obj_type_show_title(self, obj_type):
1021 """
1022 Return a string, the display title for this table,
1023 never return None, only displayable values
1024 """
1025 if not obj_type in self.obj_type_info_dict:
1026 return ''
1027 if 'title' in self.obj_type_info_dict[obj_type]:
1028 return self.obj_type_info_dict[obj_type]['title']
1029 return obj_type
1030
1031 #
1032 # --------------------------------------------------------------------------------
1033
1034 def obj_type_set_title(self, obj_type, title):
1035 if not obj_type in self.obj_type_info_dict:
1036 self.obj_type_info_dict[obj_type] = {}
1037 self.obj_type_info_dict[obj_type]['title'] = title
1038
1039 #
1040 # --------------------------------------------------------------------------------
1041
1042 def obj_type_set_show_this(self, obj_type, this_list):
1043 if not obj_type in self.obj_type_info_dict:
1044 self.obj_type_info_dict[obj_type] = {}
1045 self.obj_type_info_dict[obj_type]['show-this'] = this_list
1046
1047 #
1048 # --------------------------------------------------------------------------------
1049
1050 def field_default_value(self, obj_type, field):
1051 """
1052 Return non null value of the default value of a field if its configured
1053
1054 @param obj_type string, name of the object-type
1055 @param field string, field within the object-type
1056 """
1057 if not obj_type in self.obj_type_info_dict:
1058 return None
1059 if not field in self.obj_type_info_dict[obj_type]['fields']:
1060 return None
1061
1062 field_info = self.obj_type_info_dict[obj_type]['fields'][field]
1063 if 'default' in field_info:
1064 return field_info['default']
1065 return None
1066
1067 #
1068 # --------------------------------------------------------------------------------
1069
1070 def field_current_obj_type_default_value(self, field):
1071 """
1072 Return non null value of the default value of a field if its configured
1073
1074 @param field string, field within the object-type
1075 """
1076 current_obj_type = self.sdnsh.get_current_mode_obj_type()
1077
1078 if current_obj_type:
1079 if self.obj_type_has_field(current_obj_type, field):
1080 default = self.field_default_value(current_obj_type, field)
1081 if default == '':
1082 return None
1083 return default
1084 return None
1085
1086 #
1087 # --------------------------------------------------------------------------------
1088
1089 def field_validation(self, obj_type, field):
1090 """
1091 Return the field validation function
1092
1093 @param obj_type string, name of the object-type
1094 @param field string, field within the object-type
1095 """
1096 if 'validate' in self.obj_type_info_dict[obj_type]['fields'][field]:
1097 validate = self.obj_type_info_dict[obj_type]['fields'][field]['validate']
1098 return getattr(self, validate, None)
1099 return None
1100
1101 #
1102 # --------------------------------------------------------------------------------
1103
1104 def obj_type_prepare_row_update(self, obj_type):
1105 """
1106 Return the row update function is one is described in climodelinfo
1107 These callouts are intended to do fixup for the primary key's
1108 in a table where the field members are used to build the primary key
1109
1110 @param field string, field within the object-type
1111 """
1112 if not obj_type in self.obj_type_info_dict:
1113 return None
1114
1115 if 'update' in self.obj_type_info_dict[obj_type]:
1116 update = self.obj_type_info_dict[obj_type]['update']
1117 return getattr(self, update, None)
1118 return None
1119
1120
1121 #
1122 # --------------------------------------------------------------------------------
1123
1124 def obj_type_show_sort(self, obj_type):
1125 """
1126 Return a sort-type for the obj-type during show. The value is extracted
1127 from the obj_type_info_dict, and configured for the primary key for an
1128 obj-type, for example vns-access-list-entry's 'id' field shows
1129 'sort' : 'integer' to describe that the table's items
1130 are sorted by integer.
1131
1132 Currently the sort-by field is not described in the
1133 cassandra model description
1134
1135 @param field string, field within the object-type
1136 """
1137
1138 if not obj_type in self.obj_keys:
1139 return None
1140
1141 key = self.obj_keys[obj_type]
1142 if 'sort' in self.obj_type_info_dict[obj_type].get('fields', [])[key]:
1143 return self.obj_type_info_dict[obj_type]['fields'][key]['sort']
1144 return None
1145
1146 #
1147 # --------------------------------------------------------------------------------
1148
1149 def alias_obj_type_field(self, alias_obj_type):
1150 """
1151 Return a single field name for an obj_type, usually a foreign key
1152 in the model, which is the field associated with an alias.
1153 """
1154 foreign_keys = self.obj_type_foreign_keys(alias_obj_type)
1155 if len(foreign_keys) == 1:
1156 return foreign_keys[0]
1157 return None
1158
1159
1160 #
1161 # --------------------------------------------------------------------------------
1162
1163 def not_default_value(self, obj_type, field, value):
1164 """
1165 Return True when the value passed in is not the default value.
1166 """
1167 default_value = self.field_default_value(obj_type, field)
1168
1169 if (self.is_null_allowed(obj_type, field) and value != '') or \
1170 (not self.is_null_allowed(obj_type, field) and default_value != None
1171 and (default_value != value)):
1172 return True
1173 return False
1174
1175 #
1176 # --------------------------------------------------------------------------------
1177
1178 def obj_type_related_config_obj_type(self, obj_type):
1179 """
1180 If an obj-type doesn't have a rest model, for example - host, the obj_type
1181 may have a related config-table in the database, where additional configured
1182 data for the discovered data can be found. Return the name of that table,
1183 for host, its host-condig
1184 """
1185 if not obj_type in self.obj_type_info_dict:
1186 return None
1187
1188 if self.obj_type_has_model(obj_type):
1189 return obj_type
1190
1191 if 'config-obj-type' in self.obj_type_info_dict[obj_type]:
1192 return self.obj_type_info_dict[obj_type]['config-obj-type']
1193 return None
1194
1195 #
1196 # --------------------------------------------------------------------------------
1197
1198 def obj_type_in_use_as_related_config_type(self, config_obj_type):
1199 """
1200 Return the obj_type is in use by the pased in config_obj_type
1201 or None otherwise. Inverse of obj_type_related_config_obj_type
1202 """
1203 if config_obj_type == None:
1204 return None
1205
1206 if not config_obj_type in self.obj_type_info_dict:
1207 return None
1208
1209 for (ot, ov) in self.obj_type_info_dict.items():
1210 if ov.get('config-obj-type') == config_obj_type:
1211 return ot
1212 return None