Adding ONOS Segment Routing CLI files to new repo
diff --git a/cli/modi.py b/cli/modi.py
new file mode 100755
index 0000000..4bb156d
--- /dev/null
+++ b/cli/modi.py
@@ -0,0 +1,1212 @@
+#
+# Copyright (c) 2011,2012,2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+import sys
+# from midw import *
+
+#
+# MODel Information (modi)
+#
+
+class Modi():
+
+    def __init__(self, sdnsh, cli_model_info):
+        self.sdnsh = sdnsh
+
+        self.obj_type_info_dict = {}
+        self.obj_types = []
+        self.obj_keys = {}
+
+        # self.cli_model_info = CliModelInfo()
+        self.cli_model_info = cli_model_info
+
+        self.init_obj_type_info_dict(cli_model_info)
+        self.init_foreign_key_xref()
+        self.init_alias_obj_type_xref()
+
+    @staticmethod
+    def _line():
+        # pylint: disable=W0212
+        f = sys._getframe().f_back
+        return '[%s:%d]' % (f.f_code.co_filename, f.f_lineno)
+    #
+    # --------------------------------------------------------------------------------
+
+    def init_obj_type_info_dict(self, cli_model_info):
+        """
+        When obj_type_info_dict is indexed by the table/model name,
+        the value is a dictionary.  That dictionary has several keys:
+        
+        - 'cascade_delete' True/False, set to true when foreign key's in this
+                           obj_type will be used o identify rows to delete based
+                           on rows removed from the parent table.
+        - 'force_delete' True/False, set to true when foreign keys in this
+                         obj_type are allowed to be null, but when they are not,
+                         the rows identified by cascade_delete must be deleted
+                         when rows are removed from the parent table
+        - 'source' set to 'user-config' for obj_types which user's configure
+                   set to 'debug-only' to enable viewing object only in debug mode
+      
+        The 'fields' indexed result returns a dictionary, each of which
+        is indexed by the names of the fields within the table/model. further
+        indicies describe details about that field within the table:
+        
+        Some of the key's are intended to be added to the dictionary
+        to further decorate details about the field.  The objective is
+        to modify cli behavior, or override the model details.  These
+        values, tho, ought to really come from the model description.
+
+        - 'verbose-name' identfies an additonal name for the field, inteneded
+                         to be more descriptive
+
+        - 'hex' True/False, True for fields when a hex value can
+                         be replaced with a decimal value
+        - 'edit' True/Falue,  False when a field cannot be edited within
+                         the nested config mode.
+
+        - 'validate' function to call to validate the field value
+
+        Various key's use '_' vs the typical '-' since they're 
+        currently constructed via the django model description
+
+        - 'primary_key' identifes this field as the key for the table/model.
+        - 'has_rest_model'  True/False
+        - 'json_serialize_string'  True/False
+        - 'help_text'  String providing more clues about the intent of this field
+        - 'type' various values, intended to be populated by tools/extract_model.py
+                         allowing access of the type to the cli.  the allows the
+                         cli to find foreign_keys, and possibly determine
+        - 'max_length' 
+        
+        Other key decorations:  sorting
+
+        The 'field_ordering' dictionary associated with the name of the table/model
+        is used to order the output.
+        
+        Also populates the self.obj_keys[], which is a dictionary mapping the table/model
+        name to the name of the storage key (table/model's column) for that table.
+
+        @param cli_model_info instantiated class CliModelInfo()
+        """
+        self.obj_type_info_dict = self.cli_model_info.get_complete_obj_type_info_dict()
+        self.obj_types = [k for (k, v) in self.obj_type_info_dict.items()
+                          if 'has_rest_model' in v]
+        for (k, d) in self.obj_type_info_dict.items():
+            if not 'fields' in d:
+                print '%s: Missing "fields"' % k
+                continue
+            candidate_keys = [f for f in d['fields'].keys()
+                              if d['fields'][f].get('primary_key', False)]
+            if len(candidate_keys) > 0:
+                self.obj_keys[k] = candidate_keys[0]
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def init_foreign_key_xref(self):
+        """
+        Walk through the obj_type_info_dict, looking for foreign keys.
+        Build a cross references, so that a <obj_type, field> can be
+        used to identify all the obj_types which reference that field.
+      
+        To allow for both <obj_type, field> to be used to index
+        self.foreign_key_xref, two levels of dictionaries are built.
+        The first maps from obj_type to a dictionary of fields, and
+        the fields dictionary has values which are lists.  Each of the
+        lists has two members: the <obj_type, field>, which will be
+        a foreign key to the original pair.
+      
+        This allows identificaion of fields which have single foreign
+        key xref's, which can then be used to identify "sub-modes"
+        for particular obj_types.
+        """
+
+        self.foreign_key_xref = {}
+        for (obj_type, obj_type_dict) in self.obj_type_info_dict.items():
+            if not 'fields' in obj_type_dict:
+                print '%s: Missing "fields"' % obj_type
+                continue
+            for (fn, fd) in obj_type_dict['fields'].items():
+                if 'type' in fd:
+                    if fd['type'] == 'ForeignKey':
+                        ref_foreign_obj_type = fd['rel_obj_type']
+                        ref_foreign_field = fd['rel_field_name']
+                        if not ref_foreign_obj_type in self.foreign_key_xref:
+                            self.foreign_key_xref[ref_foreign_obj_type] = {}
+                        if not ref_foreign_field in self.foreign_key_xref[ref_foreign_obj_type]:
+                            self.foreign_key_xref[ref_foreign_obj_type][ref_foreign_field] = []
+                        self.foreign_key_xref[ref_foreign_obj_type][ref_foreign_field].append(
+                            [obj_type, fn])
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def init_alias_obj_type_xref(self):
+        """
+        Alias obj types have a non-compound primary key, a foreign key, and
+        possibly 'DateTimeField' fields, but no other fields.  These can
+        be identified by scanning the dictionary.
+
+        The alias_obj_type_xref dictionary is indexed by the obj_type referenced
+        by the foreign key of the original obj_type (its a cross ref)
+        """
+
+        self.alias_obj_type_xref = {}
+        self.alias_obj_types = []
+        for (obj_type, obj_type_dict) in self.obj_type_info_dict.items():
+            if not 'fields' in obj_type_dict:
+                print '%s: Missing "fields"' % obj_type
+                continue
+            foreign_key_obj_type = None
+            foreign_key_count = 0
+            other_types = False
+            for (fn, fd) in obj_type_dict['fields'].items():
+                # 'Idx' field is only for display counting of rows
+                if fn != 'Idx' and 'type' in fd:
+                    if fd['type'] == 'ForeignKey':
+                        foreign_key_count += 1
+                        foreign_key_obj_type = fd['rel_obj_type']
+                    elif 'primary_key' in fd:
+                        if self.is_compound_key(obj_type, fn):
+                            other_types = True
+                    elif fd['type'] != 'DateTimeField':
+                        other_types = True
+            if foreign_key_count == 1 and other_types == False:
+                self.alias_obj_types.append(obj_type)
+                if not foreign_key_obj_type in self.alias_obj_type_xref:
+                    self.alias_obj_type_xref[foreign_key_obj_type] = []
+                self.alias_obj_type_xref[foreign_key_obj_type].append(
+                    obj_type
+                )
+            #
+            # if the primariy key is a compound key, and the first item
+            # is also a foreign key here, then allow the first item to
+            # match up with the alias.  currenly, only use the first item
+            # since the 'startswith' can use used to find the assocaited
+            # members
+            #
+            elif foreign_key_count >= 1:
+                key = self.obj_keys[obj_type]
+                compound_fields = self.compound_key_fields(obj_type, key)
+                if compound_fields:
+                    first_field = compound_fields[0]
+                    if first_field in self.obj_type_foreign_keys(obj_type):
+                        #
+                        # This is nasty -- assuming that the foreign_key's table
+                        # name will then have an alias table associated with it.
+                        # Perhaps some model field can help describe these
+                        # associations, something like 'allow-alias: <obj_type>'
+                        if first_field == 'vns':
+                            pass
+                        if not "%s-alias" % first_field in self.obj_type_info_dict:
+                            # only build references to tables which exist.
+                            pass
+                        elif obj_type in self.alias_obj_type_xref:
+                            self.alias_obj_type_xref[obj_type] += ["%s-alias" % first_field]
+                        else:
+                            self.alias_obj_type_xref[obj_type] = ["%s-alias" % first_field]
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def pk(self, obj_type):
+        """
+        Return the primary key name for the object
+
+        @param obj_type string, name of the object-type (ie: 'host', 'switch')
+        """
+
+        # Raise an exception when the name doesn't exist?
+        return self.obj_keys.get(obj_type, None)
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_exists(self, obj_type):
+        """
+        Return True if there's details about obj_type in obj_types_info_dict
+        """
+        return obj_type in self.obj_type_info_dict.keys()
+    
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_has_model(self, obj_type):
+        """
+        Return True if the obj_type is serviced via the model rest api
+        (in other words, there's a db table which supports this model)
+        """
+        return obj_type in self.obj_types
+    
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_has_url(self, obj_type):
+        """
+        Returns a url suffix describing a path which returns the
+        data associated with thie obj_type
+        """
+        if obj_type in self.obj_type_info_dict:
+            return self.obj_type_info_dict[obj_type].get('url', None)
+        return None
+    
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_foreign_key(self, obj_type, field):
+        """
+        Return True when the field within the obj_type is a foreign key.
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'].get(field, [])
+        if 'type' in field_info:
+            if field_info['type'] == 'ForeignKey':
+                return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_integer_field(self, obj_type, field):
+        """
+        Return True when the type associated with the obj_type's field is an integer
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type].get('fields', [])[field]
+        if 'type' in field_info:
+            if field_info['type'] == 'IntegerField':
+                return True
+        return False
+
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def is_primary_key(self, obj_type, field):
+        """
+        Return true when the obj_type's field is a primary key
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type].get('fields',[])[field]
+        if 'primary_key' in field_info:
+            return field_info['primary_key'] # should be true if exists
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_field_editable(self, obj_type, field):
+        """
+        Return True if the field is editable.  Default is True.
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type].get('fields',[])[field]
+        if 'edit' in field_info:
+            return field_info['edit'] # should be False if exists
+        return True
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_editable(self, obj_type, field):
+        """
+        Return true if the obj_type/field is available for editing
+        Excludes foreign keys, and primary keys (unless edit: True
+        is specifically enabled for the field)
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return False
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'edit' in field_info and field_info['edit'] == True:
+            return True
+        if self.is_foreign_key(obj_type, field):
+            return False
+        if self.is_primary_key(obj_type, field):
+            return False
+        if not self.is_field_editable(obj_type, field):
+            return False
+        return True
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_disable_edit(self, obj_type, field):
+        """
+        Mark an obj_type's field as not being directly editable.
+        When the command descriptions are used for all obj_type's edit, the need
+        for 'disabling edit' ought to disappear
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            self.obj_type_info_dict[obj_type]['fields'] = {}
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            self.obj_type_info_dict[obj_type]['fields'][field] = {}
+        self.obj_type_info_dict[obj_type]['fields'][field]['edit'] = False
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def is_marked_searchable(self, obj_type, field):
+        """
+        Return true if a field is searchable.
+
+        This ought to be true for any fields which is part of the
+        primary key construction.
+
+        This predicate, however, in intended to look for fields
+        which are identified by the model as being searchable,
+        even when the field doesn't appear in the primary key.
+
+        @param obj_type
+        @param field
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return False
+
+        key = self.pk(obj_type)
+        if self.is_compound_key(obj_type, key):
+            if field in self.compound_key_fields(obj_type, key):
+                return True
+            
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'searchable' in field_info:
+            return field_info['searchable'] == True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def get_obj_type_field_case_sensitive(self, obj_type, field):
+        """
+        Return true if a field is case sensitive.
+
+        @param obj_type
+        @param field
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return None
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return None
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return None
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'case' in field_info:
+            return field_info['case']
+        return None
+
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def set_obj_type_field_case_sensitive(self, obj_type, field, case):
+        """
+        Set case sensitivity for a field in an obj_type
+
+        @param obj_type
+        @param field
+        @param case either 'upper', or 'lower'
+        """
+
+        if case not in ['upper', 'lower']:
+            print 'set_case_sensitive: obj_type %s field %s case %s ' \
+                'case not upper/lower' % (obj_type, field, case)
+            return
+
+        if not obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type] = {}
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            self.obj_type_info_dict[obj_type]['fields'] = {}
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            self.obj_type_info_dict[obj_type]['fields'][field] = {}
+        self.obj_type_info_dict[obj_type]['fields'][field]['case'] = case
+
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_hex_allowed(self, obj_type, field):
+        """
+        Return true if the obj_type/field allows hex instead of decimal
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'hex' in field_info:
+            return field_info['hex'] # likely False if exists
+        if self.is_integer_field(obj_type, field):
+            return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def is_field_boolean(self, obj_type, field):
+        """
+        Return true if the obj_type/field is a boolean type
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'type' in field_info:
+            return field_info['type'] == 'BooleanField'
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    # is_field_string
+    #  Return true if the obj_type/field is a character (CharType) type
+    #
+    def is_field_string(self, obj_type, field):
+        """
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'type' in field_info:
+            return field_info['type'] == 'CharField'
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    # is_null_allowed
+    #  Return true if the obj_type/field is allowed to be null.
+    #
+    def is_null_allowed(self, obj_type, field):
+        """
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'null' in field_info:
+            return field_info['null']
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_fields(self, obj_type):
+        """
+        Return a list of field names (strings) for the obj_type,
+        includes all fields, including primary keys and foreign keys
+        """
+
+        if obj_type in self.obj_type_info_dict:
+            if not 'fields' in self.obj_type_info_dict[obj_type]:
+                return []
+            return self.obj_type_info_dict[obj_type]['fields'].keys()
+        return []
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_has_field(self, obj_type, field):
+        """
+        Return true if the obj_type has a field named 'field'
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return False
+
+        if obj_type in self.obj_type_info_dict:
+            fields_info = self.obj_type_info_dict[obj_type]['fields']
+            if field in fields_info:
+                return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_config_fields(self, obj_type):
+        """
+        For an obj_type, return a list of fields which are possibly user configurable.
+
+        @param obj_type string, name of the object-type
+        """
+
+        if obj_type in self.obj_type_info_dict:
+            if not 'fields' in self.obj_type_info_dict[obj_type]:
+                return []
+            fields_info = self.obj_type_info_dict[obj_type]['fields']
+            return [x for x in fields_info if self.is_editable(obj_type, x)]
+        return []
+
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_show_this(self, obj_type):
+        """
+        Return a list of addition types to display for 'show this'
+
+        @param obj_type string, name of the object-type
+        """
+
+        if obj_type in self.obj_type_info_dict:
+            if 'show-this' in self.obj_type_info_dict[obj_type]:
+                result = self.obj_type_info_dict[obj_type]['show-this']
+                if type(result) == str or type(result) == 'unicode':
+                    return [result]
+                return result
+        return []
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_cascade_delete_enabled(self, obj_type):
+        """
+        Cascade is enabled for an obj_type by setting 'cascade_enabled': True for
+        the primary key of an obj_type.
+
+        @param obj_type string, name of the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+
+        if 'cascade_delete' in self.obj_type_info_dict[obj_type]:
+            return self.obj_type_info_dict[obj_type]['cascade_delete']
+        return False
+
+    def is_force_delete_enabled(self, obj_type):
+        """
+        Force delete is enabled for an obj_type by setting 'force_delete': True
+        for the primary key of an obj_type.
+
+        @param obj_type string, name of the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return False
+
+        if 'force_delete' in self.obj_type_info_dict[obj_type]:
+            return self.obj_type_info_dict[obj_type]['force_delete']
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def cascade_delete_set_enable(self, obj_type):
+        """
+        Enable cascade_delete for an obj_type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type] = {}
+        self.obj_type_info_dict[obj_type]['cascade_delete'] = True
+
+    def cascade_delete_enable_force(self, obj_type):
+        """
+        Force cascade_delete for an obj_type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type] = {}
+        self.obj_type_info_dict[obj_type]['cascade_delete'] = True
+        self.obj_type_info_dict[obj_type]['force_delete'] = True
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def has_display_field(self, obj_type, field):
+        """
+        Determine if a particular obj_type has a particular field on the
+        display (ie: during a show command).  Uses the 'field_orderings'
+        of the obj_type_info_dict.
+      
+        Currently used to deterine whether an alias table needs to be
+        re-cached in preparation for the display of some obj_type
+
+        @param obj_type string, name of the object-type
+        """
+
+        if obj_type in self.obj_type_info_dict and \
+          'field_orderings' in obj_type in self.obj_type_info_dict[obj_type]:
+            order = self.obj_type_info_dict[obj_type]['field_orderings']['default']
+            return field in order
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_obj_type_source_not_user_config(self, obj_type):
+        """
+        Return True if the obj_type is intended to be configured,
+        some tables are intended to be written by sdnplatform as a way
+        of presenting information; it makes no sense for the cli
+        to present these tables to the user as configurable.
+      
+        keep in mind that this returns True only if 'source' exists,
+        and the source isn't get to user-config.  This means that
+        for a table to be excluded, 'source' must be added, and
+        it must be set to something other than 'user-config'
+
+        @param obj_type string, name of the object-type
+        """
+
+        if obj_type in self.obj_type_info_dict and \
+          'source' in self.obj_type_info_dict[obj_type]:
+            if self.obj_type_info_dict[obj_type]['source'] != 'user-config':
+                if self.sdnsh.debug and \
+                  self.obj_type_info_dict[obj_type]['source'] == 'debug-only':
+                    return False
+                return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_obj_type_source_debug_only(self, obj_type):
+        """
+        Return True if the obj_type is marked to be viewed only in debug mode
+
+        @param obj_type string, name of the object-type
+        """
+
+        if obj_type in self.obj_type_info_dict and \
+          'source' in self.obj_type_info_dict[obj_type]:
+            if self.obj_type_info_dict[obj_type]['source'] == 'debug-only':
+                return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_source_set_debug_only(self, obj_type):
+        """
+        Set the source for the obj-type
+        """
+
+        if obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type]['source'] = 'debug-only'
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def compound_key_text(self, obj_type, field):
+        """
+        Return a text string which describes the construction of a compound key.
+        The first character is a '#' when this returned field describes a
+        compound key.  The second character is the separator for the field
+        itself (not the separator for the fields described by this text
+        field).  The remainder is a concatenation of the fields names which
+        are used to construct this field.
+      
+        Currently compound keys are identified through the help_text.
+        The help text isn't displayed to the user for primary since the value
+        of the key for compound key's must be constructed by the cli, and then
+        the user doesn't directly modify or search the compound value.
+      
+        When the field is itself a foreign key, and no text string exists to
+        describe the construction of the compound key, its possible that the foreign
+        key's value is itself a compound key.  Peek at the original foreign
+        key to see if it has a field identifying the layout of the compound key.
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_type_info_dict:
+            return None
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return None
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return None
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'help_text' in field_info:
+            if field_info['help_text'][0] == '#':
+                return field_info['help_text']
+        if self.is_foreign_key(obj_type, field):
+            (fk_obj_type, fk_name) = self.foreign_key_references(obj_type, field)
+            field_info = self.obj_type_info_dict[fk_obj_type]['fields'][fk_name]
+            if field_info.get('help_text', ' ')[0] == '#':
+                return field_info['help_text']
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def is_compound_key(self, obj_type, field):
+        """
+        Return true if the obj_type/field is a compound key,
+      
+        The first character of the compound key's text '#', the
+        second character identifies the separator characer for the
+        fields value.  The fields are separated by '|' within the text.
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        text = self.compound_key_text(obj_type, field)
+        if text:
+            return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    # is_primitive_compound_key
+    #
+    def is_primitive_compound_key(self, obj_type, field):
+        """
+        Returns True for a primitive compound key.
+
+        Primitive means compound-keys which don't use CassandraSetting's
+        COMPOUND_KEY_FIELDS, rather the help text describes the fields which
+        are cobbled together to create a primary key.
+
+        For the cassandraSetting's COMPOUND_KEY_FIELDS, searches for 
+        rows can be done by setting values of the fields of the
+        COMPOUND_KEY_FIELDS, for primitive_compound_keys, 
+
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        if self.is_compound_key(obj_type, field):
+            field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+            if 'type' in field_info:
+                if field_info['type'] != 'compound-key':
+                    return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    # compound_key_separator
+    #
+    def compound_key_separator(self, obj_type, field):
+        """
+        Return the single character which is used to separate the values
+        of the different field parts for a field which is a compound key.
+
+        Return None when the
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        text = self.compound_key_text(obj_type, field)
+        if text:
+            return text[1]
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    # compound_key_fields
+    #
+    def compound_key_fields(self, obj_type, field):
+        """
+        Return's a list of strings, where each is intended to be the
+        name of a field within the obj_typ (this is not validated)
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        text = self.compound_key_text(obj_type, field)
+        if text:
+            return text[2:].split('|')
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def deep_compound_key_fields(self, obj_type, field):
+        """
+        Similar to compound_key_fields(), but when any field is also a foreign
+        key, the references obj_type's field is checked, and if that field is
+        also a compound key, it is also expanded.
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+
+        def recurse_compound_key_fields(obj_type, field, parts):
+            if self.is_foreign_key(obj_type, field):
+                (fk_ot, fk_fn) = self.foreign_key_references(obj_type, field)
+                if self.is_compound_key(fk_ot, fk_fn):
+                    recurse_compound_key_fields(fk_ot, fk_fn, parts)
+            elif self.is_compound_key(obj_type, field):
+                for cf in self.compound_key_fields(obj_type, field):
+                    if (self.obj_type_has_field(obj_type, cf) and
+                        self.is_foreign_key(obj_type, cf)):
+
+                        (fk_ot, fk_fn) = self.foreign_key_references(obj_type, cf)
+                        if self.is_compound_key(fk_ot, fk_fn):
+                            recurse_compound_key_fields(fk_ot, fk_fn, parts)
+                        else:
+                            parts.append(cf)
+                    else:
+                        parts.append(cf)
+            return parts
+        return recurse_compound_key_fields(obj_type, field, [])
+
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def split_compound_into_dict(self, obj_type, key, target_dict, is_prefix = False):
+        """
+        To be used to convert a compound key in a row intended for display,
+        into separate component name:value pairs.
+
+        The original dict is from a row of a table. the 'key' parameter
+        identifies a compound key in the row, the procedure splits the
+        value of that key/field, and uses the compound_key_fields() to
+        determine what names ought to be associated with the field's
+        values, and add's these fields into the original dict.
+        """
+        def add_to_dict(a, b):
+            if a in target_dict and a != key:
+                if str(target_dict[a]) != b:
+                    if self.is_foreign_key(obj_type, a):
+                        target_dict[a] = b
+                    else:
+                        print self.sdnsh.error_msg("compound split dict has different value: "
+                                                   "%s found %s expected %s" %
+                                                   (a, target_dict[a], b))
+            else:
+                target_dict[a] = b
+
+        names = self.deep_compound_key_fields(obj_type, key)
+        separator = self.compound_key_separator(obj_type, key)
+        values = target_dict[key].split(separator)
+
+        if len(names) != len(values):
+            if not is_prefix:
+                print self.sdnsh.error_msg("%s: %s: compound length mismatch: %s %s" %
+                                           (obj_type, key, names, values))
+            min_len = len(names)
+            if len(values) < min_len:
+                min_len = len(values)
+            map(add_to_dict, names[:min_len], values[:min_len])
+        else:
+            map(add_to_dict, names, values)
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def foreign_key_references(self, obj_type, field):
+        """
+        For a field which is a foreign key, return the pair of
+        [obj_type, field] describing where the foreign key references
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return None
+        if not 'fields' in self.obj_type_info_dict[obj_type]:
+            return None
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return None
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'type' in field_info:
+            if field_info['type'] == 'ForeignKey':
+                return [field_info['rel_obj_type'],
+                        field_info['rel_field_name']]
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_foreign_keys(self, obj_type):
+        """
+        Return a list of foreign keys for this obj_type
+
+        @param field string, field within the object-type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return []
+        return [x for x in self.obj_type_info_dict[obj_type]['fields']
+                if self.is_foreign_key(obj_type, x)]
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_show_title(self, obj_type):
+        """
+        Return a string, the display title for this table,
+        never return None, only displayable values
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return ''
+        if 'title' in self.obj_type_info_dict[obj_type]:
+            return self.obj_type_info_dict[obj_type]['title']
+        return obj_type
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_set_title(self, obj_type, title):
+        if not obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type] = {}
+        self.obj_type_info_dict[obj_type]['title'] = title
+        
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_set_show_this(self, obj_type, this_list):
+        if not obj_type in self.obj_type_info_dict:
+            self.obj_type_info_dict[obj_type] = {}
+        self.obj_type_info_dict[obj_type]['show-this'] = this_list
+        
+    #
+    # --------------------------------------------------------------------------------
+    
+    def field_default_value(self, obj_type, field):
+        """
+        Return non null value of the default value of a field if its configured
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return None
+        if not field in self.obj_type_info_dict[obj_type]['fields']:
+            return None
+
+        field_info = self.obj_type_info_dict[obj_type]['fields'][field]
+        if 'default' in field_info:
+            return field_info['default']
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def field_current_obj_type_default_value(self, field):
+        """
+        Return non null value of the default value of a field if its configured
+
+        @param field string, field within the object-type
+        """
+        current_obj_type = self.sdnsh.get_current_mode_obj_type()
+
+        if current_obj_type:
+            if self.obj_type_has_field(current_obj_type, field):
+                default = self.field_default_value(current_obj_type, field)
+                if default == '':
+                    return None
+                return default
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def field_validation(self, obj_type, field):
+        """
+        Return the field validation function
+
+        @param obj_type string, name of the object-type
+        @param field string, field within the object-type
+        """
+        if 'validate' in self.obj_type_info_dict[obj_type]['fields'][field]:
+            validate = self.obj_type_info_dict[obj_type]['fields'][field]['validate']
+            return getattr(self, validate, None)
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_prepare_row_update(self, obj_type):
+        """
+        Return the row update function is one is described in climodelinfo
+        These callouts are intended to do fixup for the primary key's
+        in a table where the field members are used to build the primary key
+
+        @param field string, field within the object-type
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return None
+
+        if 'update' in self.obj_type_info_dict[obj_type]:
+            update = self.obj_type_info_dict[obj_type]['update']
+            return getattr(self, update, None)
+        return None
+
+    
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_show_sort(self, obj_type):
+        """
+        Return a sort-type for the obj-type during show.  The value is extracted
+        from the obj_type_info_dict, and configured for the primary key for an
+        obj-type, for example vns-access-list-entry's 'id' field shows
+        'sort' : 'integer' to describe that the table's items
+        are sorted by integer.
+     
+        Currently the sort-by field is not described in the
+        cassandra model description
+
+        @param field string, field within the object-type
+        """
+
+        if not obj_type in self.obj_keys:
+            return None
+
+        key = self.obj_keys[obj_type]
+        if 'sort' in self.obj_type_info_dict[obj_type].get('fields', [])[key]:
+            return self.obj_type_info_dict[obj_type]['fields'][key]['sort']
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def alias_obj_type_field(self, alias_obj_type):
+        """
+        Return a single field name for an obj_type, usually a foreign key
+        in the model, which is the field associated with an alias.
+        """
+        foreign_keys = self.obj_type_foreign_keys(alias_obj_type)
+        if len(foreign_keys) == 1:
+            return foreign_keys[0]
+        return None
+
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def not_default_value(self, obj_type, field, value):
+        """
+        Return True when the value passed in is not the default value.
+        """
+        default_value = self.field_default_value(obj_type, field)
+    
+        if (self.is_null_allowed(obj_type, field) and value != '') or \
+          (not self.is_null_allowed(obj_type, field) and default_value != None
+          and (default_value != value)):
+            return True
+        return False
+
+    #
+    # --------------------------------------------------------------------------------
+
+    def obj_type_related_config_obj_type(self, obj_type):
+        """
+        If an obj-type doesn't have a rest model, for example - host, the obj_type
+        may have a related config-table in the database, where additional configured
+        data for the discovered data can be found.  Return the name of that table,
+        for host, its host-condig
+        """
+        if not obj_type in self.obj_type_info_dict:
+            return None
+
+        if self.obj_type_has_model(obj_type):
+            return obj_type
+
+        if 'config-obj-type' in self.obj_type_info_dict[obj_type]:
+            return self.obj_type_info_dict[obj_type]['config-obj-type']
+        return None
+
+    #
+    # --------------------------------------------------------------------------------
+    
+    def obj_type_in_use_as_related_config_type(self, config_obj_type):
+        """
+        Return the obj_type is in use by the pased in config_obj_type
+        or None otherwise.    Inverse of obj_type_related_config_obj_type
+        """
+        if config_obj_type == None:
+            return None
+
+        if not config_obj_type in self.obj_type_info_dict:
+            return None
+
+        for (ot, ov) in self.obj_type_info_dict.items():
+            if ov.get('config-obj-type') == config_obj_type:
+                return ot
+        return None