| 1 | # |
|---|
| 2 | # Configuration objects |
|---|
| 3 | # |
|---|
| 4 | |
|---|
| 5 | import sys |
|---|
| 6 | |
|---|
| 7 | class ConfigKlass: |
|---|
| 8 | """ |
|---|
| 9 | A ConfigObject is an abstract representation of something |
|---|
| 10 | that has configuration. We can perform common configuration |
|---|
| 11 | operations on ConfigObjects, based on what their configuration |
|---|
| 12 | is. |
|---|
| 13 | """ |
|---|
| 14 | def __init__(self, name): |
|---|
| 15 | # attributes that all configuration objects have |
|---|
| 16 | |
|---|
| 17 | # The generic class name of object, just a string |
|---|
| 18 | self.name = name |
|---|
| 19 | |
|---|
| 20 | # A list of attributes that this object may have. |
|---|
| 21 | # This is an attribute vocabulary. |
|---|
| 22 | self.attrib = {} |
|---|
| 23 | |
|---|
| 24 | # A list of klasses I can contain |
|---|
| 25 | self.can_contain = {} |
|---|
| 26 | |
|---|
| 27 | # A list of klasses I can connect to |
|---|
| 28 | self.can_connect = {} |
|---|
| 29 | |
|---|
| 30 | def attribute(self, name, attrib_type): |
|---|
| 31 | """ |
|---|
| 32 | Add an attribute to this config object |
|---|
| 33 | """ |
|---|
| 34 | self.attrib[name] = Attribute(name, attrib_type) |
|---|
| 35 | |
|---|
| 36 | def contains(self, klassname, min=1, max=sys.maxint): |
|---|
| 37 | """ |
|---|
| 38 | Add a containment relationship to the list |
|---|
| 39 | """ |
|---|
| 40 | r = Contains(klassname, min, max) |
|---|
| 41 | self.can_contain[klassname] = r |
|---|
| 42 | |
|---|
| 43 | def connects_to(self, klassname, min=1, max=sys.maxint): |
|---|
| 44 | r = ConnectsTo(klassname, min, max) |
|---|
| 45 | self.can_connect[klassname] = r |
|---|
| 46 | |
|---|
| 47 | def __str__(self): |
|---|
| 48 | retstr = ['ConfigKlass: %s' % self.name, ] |
|---|
| 49 | retstr += [' Attributes:',] + [ ' %s' % a for a in self.attrib.values() ] |
|---|
| 50 | retstr += [' Contains:',] + [ ' %s' % a for a in self.can_contain.values() ] |
|---|
| 51 | retstr += [' Connects to:',] + [ ' %s' % a for a in self.can_connect.values() ] |
|---|
| 52 | return '\n'.join(retstr) |
|---|
| 53 | |
|---|
| 54 | class Attribute: |
|---|
| 55 | """ |
|---|
| 56 | An Attribute is a description of a single attribute that |
|---|
| 57 | a configuration object can have. |
|---|
| 58 | |
|---|
| 59 | Attributes are stored in a database in some method. This |
|---|
| 60 | could be, in postgres, a set of tables, one for each type, |
|---|
| 61 | so the table would be something like: |
|---|
| 62 | |
|---|
| 63 | attrib_int ( object_id FOREIGN_KEY, name VARCHAR, value INTEGER ) |
|---|
| 64 | attrib_ipaddr ( object_id FOREIGN_KEY, name VARCHAR, value IPADDR ) |
|---|
| 65 | |
|---|
| 66 | which would key into the main object entity table: |
|---|
| 67 | |
|---|
| 68 | objects ( object_id PRIMARY_KEY, klass VARCHAR UNIQUE ) |
|---|
| 69 | |
|---|
| 70 | Then, to get a particular object's information: |
|---|
| 71 | |
|---|
| 72 | select * from objects LEFT JOIN attrib_int,attrib_varchar,attrib_ipaddr... etc for all tables |
|---|
| 73 | joining on the object_id |
|---|
| 74 | |
|---|
| 75 | """ |
|---|
| 76 | def __init__(self, name, type): |
|---|
| 77 | |
|---|
| 78 | # all attributes must have a name |
|---|
| 79 | self.name = name |
|---|
| 80 | |
|---|
| 81 | # Type provides an indication of the database style |
|---|
| 82 | # type that would be used for this attribute, eg: |
|---|
| 83 | # string |
|---|
| 84 | # boolean |
|---|
| 85 | # int |
|---|
| 86 | # long |
|---|
| 87 | # float |
|---|
| 88 | # ipaddr |
|---|
| 89 | # |
|---|
| 90 | self.type = type |
|---|
| 91 | |
|---|
| 92 | def __str__(self): |
|---|
| 93 | return 'Attribute: %s [%s]' % (self.name, self.type) |
|---|
| 94 | |
|---|
| 95 | class RelationshipType: |
|---|
| 96 | """ |
|---|
| 97 | Objects are related to one another by a Relationship, which |
|---|
| 98 | will be of a particular RelationshipType that constrains |
|---|
| 99 | the nature of the relationship. |
|---|
| 100 | |
|---|
| 101 | Stored in a database thus: |
|---|
| 102 | |
|---|
| 103 | relationships = ( object_id FOREIGN_KEY, relation_type, related_obj |
|---|
| 104 | """ |
|---|
| 105 | def __init__(self, klass, min, max): |
|---|
| 106 | |
|---|
| 107 | # The klass of object that this object can be related to |
|---|
| 108 | self.related_klass = klass |
|---|
| 109 | |
|---|
| 110 | # The minimum number of other objects this object should |
|---|
| 111 | # be related to. |
|---|
| 112 | self.related_min = min |
|---|
| 113 | |
|---|
| 114 | # The maximum number of other objects this object should |
|---|
| 115 | # be related to. |
|---|
| 116 | self.related_max = max |
|---|
| 117 | |
|---|
| 118 | def __str__(self): |
|---|
| 119 | return 'Relationship: %s [min: %d, max: %d]' % (self.related_klass, self.related_min, self.related_max) |
|---|
| 120 | |
|---|
| 121 | class Contains(RelationshipType): |
|---|
| 122 | """ |
|---|
| 123 | The contains relationship says that an object contains |
|---|
| 124 | some number of other objects. |
|---|
| 125 | """ |
|---|
| 126 | |
|---|
| 127 | class ConnectsTo(RelationshipType): |
|---|
| 128 | """ |
|---|
| 129 | An object connects to another by some mechanism. |
|---|
| 130 | """ |
|---|
| 131 | |
|---|
| 132 | class ConfigObject: |
|---|
| 133 | """ |
|---|
| 134 | A ConfigObject is a specific instance of a ConfigKlass. |
|---|
| 135 | |
|---|
| 136 | The ConfigKlass governs the possible configuration parameters |
|---|
| 137 | of this object. |
|---|
| 138 | """ |
|---|
| 139 | def __init__(self, klass): |
|---|
| 140 | self.klass = klass |
|---|
| 141 | |
|---|
| 142 | # name, value pairs for my attributes |
|---|
| 143 | self.attrib = {} |
|---|
| 144 | |
|---|
| 145 | # name, value pairs for my relationships |
|---|
| 146 | |
|---|
| 147 | def __setitem__(self, name, value): |
|---|
| 148 | """ |
|---|
| 149 | Set attribute named 'name' to value. |
|---|
| 150 | Performs various constraint checks on the |
|---|
| 151 | attribute first. |
|---|
| 152 | """ |
|---|
| 153 | # attempt to fetch the attribute from my klass, raising exceptions as required |
|---|
| 154 | try: |
|---|
| 155 | self.klass.attrib[name] |
|---|
| 156 | except KeyError, e: |
|---|
| 157 | raise KeyError("ConfigObject klass type '%s' does not support attribute: %s" % (self.klass.name, e)) |
|---|
| 158 | |
|---|
| 159 | print "attribute valid!" |
|---|
| 160 | |
|---|
| 161 | self.attrib[name] = value |
|---|
| 162 | |
|---|
| 163 | def __getitem__(self, name): |
|---|
| 164 | """ |
|---|
| 165 | Try fetching an attribute value |
|---|
| 166 | """ |
|---|
| 167 | try: |
|---|
| 168 | return self.attrib[name] |
|---|
| 169 | except KeyError: |
|---|
| 170 | # check to see if this attribute is available on this object's klass |
|---|
| 171 | if not self.klass.attrib.has_key(name): |
|---|
| 172 | raise KeyError("Attribute %s is not available on objects of type %s" % (name, self.klass.name)) |
|---|
| 173 | |
|---|
| 174 | if __name__ == '__main__': |
|---|
| 175 | |
|---|
| 176 | klasslist = {} |
|---|
| 177 | |
|---|
| 178 | # set up a switch object class |
|---|
| 179 | switch = ConfigKlass('switch') |
|---|
| 180 | |
|---|
| 181 | switch.attribute('name', 'varchar') |
|---|
| 182 | |
|---|
| 183 | switch.contains('port', 24, 24) |
|---|
| 184 | switch.contains('cpu', 1) |
|---|
| 185 | switch.connects_to('switch') |
|---|
| 186 | |
|---|
| 187 | # Doing this when loading, I'll have to temporarily store objects |
|---|
| 188 | # if they can contain or connect to ones that haven't been loaded yet |
|---|
| 189 | # and then fix all the relationships at the end. |
|---|
| 190 | |
|---|
| 191 | klasslist['switch'] = switch |
|---|
| 192 | |
|---|
| 193 | port = ConfigKlass('switchport') |
|---|
| 194 | |
|---|
| 195 | klasslist['port'] = port |
|---|
| 196 | |
|---|
| 197 | obj = ConfigObject(klasslist['switch']) |
|---|
| 198 | |
|---|
| 199 | for klass in klasslist.values(): |
|---|
| 200 | print '%s' % klass |
|---|
| 201 | |
|---|
| 202 | obj['name'] = 'fred' |
|---|
| 203 | obj['port'] = 'fred' |
|---|
| 204 | |
|---|