class VmmInjectedContGroup(AciResourceBase): """Resource representing a VMM injected container group in ACI. Identity attributes: VMM domain type, VMM domain name, controller name, namespace name and group name. """ identity_attributes = t.identity( ('domain_type', t.name), ('domain_name', t.name), ('controller_name', t.name), ('namespace_name', t.name), ('name', t.name)) other_attributes = t.other( ('display_name', t.name), ('host_name', t.name), ('compute_node_name', t.name), ('replica_set_name', t.name)) db_attributes = t.db(('guid', t.string())) _aci_mo_name = 'vmmInjectedContGrp' _tree_parent = VmmInjectedNamespace def __init__(self, **kwargs): super(VmmInjectedContGroup, self).__init__({'host_name': '', 'compute_node_name': '', 'replica_set_name': '', 'guid': ''}, **kwargs)
class ApicAssignment(resource.ResourceBase): """Track the APIC to aim-aid mapping""" identity_attributes = t.identity( ('apic_host', t.string(128))) other_attributes = t.other( ('aim_aid_id', t.string(64))) db_attributes = t.db( ('last_update_timestamp', t.string())) def __init__(self, **kwargs): super(ApicAssignment, self).__init__({'aim_aid_id': ''}, **kwargs) def is_available(self, context): current = context.store.current_timestamp # When the store doesn't support time stamp, the APIC can never # be considered available. if current is None: return False result = current - self.last_update_timestamp >= datetime.timedelta( seconds=cfg.CONF.aim.apic_available_time) if result: LOG.info("APIC %s is available. Last update time was %s" % (self.apic_host, self.last_update_timestamp)) return True else: LOG.debug("APIC %s is not available. Last update time was %s" % (self.apic_host, self.last_update_timestamp)) return False
class Tree(api_res.ResourceBase): identity_attributes = t.identity(('root_rn', t.string(64))) other_attributes = t.other(('needs_reset', t.bool)) db_attributes = t.db() def __init__(self, **kwargs): super(Tree, self).__init__({}, **kwargs)
class TypeTreeBase(object): identity_attributes = t.identity(('root_rn', t.string(64))) other_attributes = t.other(('root_full_hash', t.string(256)), ('tree', t.string())) db_attributes = t.db() def __init__(self, **kwargs): super(TypeTreeBase, self).__init__({}, **kwargs)
class AciFault(resource.ResourceBase, OperationalResource): """Fault information reported by ACI.""" LC_UNKNOWN = 0x0 LC_SOAKING = 0x1 LC_RETAINING = 0x10 LC_RAISED = 0x2 LC_SOAKING_CLEARING = 0x4 LC_RAISED_CLEARING = 0x8 SEV_CLEARED = 'cleared' SEV_INFO = 'info' SEV_WARNING = 'warning' SEV_MINOR = 'minor' SEV_MAJOR = 'major' SEV_CRITICAL = 'critical' _aci_mo_name = 'faultInst' identity_attributes = t.identity(('fault_code', t.string()), ('external_identifier', t.string())) other_attributes = t.other(('severity', t.enum(SEV_CLEARED, SEV_CRITICAL, SEV_INFO, SEV_WARNING, SEV_MAJOR, SEV_MINOR)), ('status_id', t.id), ('cause', t.string()), ('description', t.string(255))) db_attributes = t.db(('last_update_timestamp', t.string())) def __eq__(self, other): try: return self.identity == other.identity except AttributeError: return False def __init__(self, **kwargs): super(AciFault, self).__init__( { 'severity': self.SEV_INFO, 'lifecycle_status': self.LC_UNKNOWN, 'cause': '', 'description': "" }, **kwargs) def is_error(self): return self.severity in [self.SEV_MAJOR, self.SEV_CRITICAL] @property def dn(self): return self.external_identifier @property def root(self): mos_and_types = utils.decompose_dn(self._aci_mo_name, self.dn) if mos_and_types: # Faults associated with unrecognized MOs will not decompose mo = apic_client.ManagedObjectClass(mos_and_types[0][0]) return (mo.rn(mos_and_types[0][1]) if mo.rn_param_count else mo.rn())
class Configuration(ResourceBase): identity_attributes = t.identity( ('key', t.string(52)), ('host', t.string(52)), ('group', t.string(52))) other_attributes = t.other(('value', t.string(512))) db_attributes = t.db(('version', t.string(36))) def __init__(self, **kwargs): super(Configuration, self).__init__({}, **kwargs)
class Agent(ResourceBase): """Resource representing an AIM Agent""" identity_attributes = t.identity(('id', t.id)) other_attributes = t.other( ('agent_type', t.string(255)), ('host', t.string(255)), ('binary_file', t.string(255)), ('admin_state_up', t.bool), ('description', t.string(255)), ('hash_trees', t.list_of_ids), ('version', t.string())) # Attrbutes completely managed by the DB (eg. timestamps) db_attributes = t.db(('heartbeat_timestamp', t.string())) def __init__(self, **kwargs): super(Agent, self).__init__({'admin_state_up': True, 'id': utils.generate_uuid()}, **kwargs) def __eq__(self, other): return self.id == other.id # An object is hashable if it has a hash value which never changes during # its lifetime (it needs a __hash__() method), and can be compared to # other objects (it needs an __eq__() or __cmp__() method). # Hashable objects which compare equal must have the same hash value. # # If you define __eq__() , the default __hash__() (namely, hashing the # address of the object in memory) goes away. # So for each class defining __eq__() we must also # define __hash__() even though parent class has __hash__(). def __hash__(self): return super(Agent, self).__hash__() def is_down(self, context): current = context.store.current_timestamp # When the store doesn't support timestamps the agent can never # be considered down. if current is None: return False result = current - self.heartbeat_timestamp >= datetime.timedelta( seconds=cfg.CONF.aim.agent_down_time) if result: LOG.warn("Agent %s is down. Last heartbeat was %s" % (self.id, self.heartbeat_timestamp)) else: LOG.debug("Agent %s is alive, its last heartbeat was %s" % (self.id, self.heartbeat_timestamp)) return result def down_time(self, context): if self.is_down(context): current = context.store.current_timestamp return (current - self.heartbeat_timestamp).seconds
class ActionLog(api_res.ResourceBase): CREATE = 'create' DELETE = 'delete' RESET = 'reset' identity_attributes = t.identity(('uuid', t.string(64))) other_attributes = t.other( ('action', t.enum(CREATE, DELETE, RESET)), ('object_type', t.string(50)), ('object_dict', t.string()), ('root_rn', t.string(64)), ) db_attributes = t.db(('timestamp', t.string()), ('id', t.integer)) def __init__(self, **kwargs): super(ActionLog, self).__init__({'uuid': utils.generate_uuid()}, **kwargs)
class Agent(ResourceBase): """Resource representing an AIM Agent""" identity_attributes = t.identity(('id', t.id)) other_attributes = t.other( ('agent_type', t.string(255)), ('host', t.string(255)), ('binary_file', t.string(255)), ('admin_state_up', t.bool), ('description', t.string(255)), ('hash_trees', t.list_of_ids), ('version', t.string())) # Attrbutes completely managed by the DB (eg. timestamps) db_attributes = t.db(('heartbeat_timestamp', t.string())) def __init__(self, **kwargs): super(Agent, self).__init__( { 'admin_state_up': True, 'id': utils.generate_uuid() }, **kwargs) def __eq__(self, other): return self.id == other.id def is_down(self, context): current = context.store.current_timestamp # When the store doesn't support timestamps the agent can never # be considered down. if current is None: return False result = current - self.heartbeat_timestamp >= datetime.timedelta( seconds=cfg.CONF.aim.agent_down_time) if result: LOG.warn("Agent %s is down. Last heartbeat was %s" % (self.id, self.heartbeat_timestamp)) else: LOG.debug("Agent %s is alive, its last heartbeat was %s" % (self.id, self.heartbeat_timestamp)) return result def down_time(self, context): if self.is_down(context): current = context.store.current_timestamp return (current - self.heartbeat_timestamp).seconds
class VmmInjectedDeployment(AciResourceBase): """Resource representing a VMM injected deployment in ACI. Identity attributes: VMM domain type, VMM domain name, controller name, namespace name and deployment name. """ identity_attributes = t.identity( ('domain_type', t.name), ('domain_name', t.name), ('controller_name', t.name), ('namespace_name', t.name), ('name', t.name)) other_attributes = t.other(('display_name', t.name), ('replicas', t.integer)) db_attributes = t.db(('guid', t.string())) _aci_mo_name = 'vmmInjectedDepl' _tree_parent = VmmInjectedNamespace def __init__(self, **kwargs): super(VmmInjectedDeployment, self).__init__({ 'replicas': 0, 'guid': '' }, **kwargs)
class VmmInjectedService(AciResourceBase): """Resource representing a VMM injected service in ACI. Identity attributes: VMM domain type, VMM domain name, controller name, namespace name and service name. """ identity_attributes = t.identity( ('domain_type', t.name), ('domain_name', t.name), ('controller_name', t.name), ('namespace_name', t.name), ('name', t.name)) other_attributes = t.other( ('display_name', t.name), ('service_type', t.enum('clusterIp', 'externalName', 'nodePort', 'loadBalancer')), ('cluster_ip', t.string()), ('load_balancer_ip', t.string()), ('service_ports', t.list_of_dicts(('port', t.ports), ('protocol', t.string(32)), ('target_port', t.string(32)), ('node_port', t.ports))), ('endpoints', t.list_of_dicts(('ip', t.string()), ('pod_name', t.name)))) db_attributes = t.db(('guid', t.string())) _aci_mo_name = 'vmmInjectedSvc' _tree_parent = VmmInjectedNamespace def __init__(self, **kwargs): super(VmmInjectedService, self).__init__( {'service_type': 'clusterIp', 'cluster_ip': '0.0.0.0', 'load_balancer_ip': '0.0.0.0', 'service_ports': [], 'endpoints': [], 'guid': ''}, **kwargs)
class AciStatus(resource.ResourceBase, OperationalResource): """Status of an AIM resource that is mapped to ACI object. Following attributes are available: * sync_status - Indicates whether ACI object was created/updated * sync_message - Informational or error message related to ACI object creation/update * health_score - Health score of ACI object as reported by APIC * health_level - Level-wise classification of health-score * faults - List of AciFault objects as reported by APIC """ # ACI object create/update is pending SYNC_PENDING = 'sync_pending' # ACI object was created/updated. It may or may not be in healthy state SYNCED = 'synced' # Create/update of ACI object failed SYNC_FAILED = 'sync_failed' SYNC_NA = 'N/A' identity_attributes = t.identity(('resource_type', t.string()), ('resource_id', t.id), ('resource_root', t.name)) other_attributes = t.other( ('sync_status', t.enum(SYNCED, SYNC_PENDING, SYNC_FAILED)), ('sync_message', t.string()), ('health_score', t.number), ('faults', t.list_of_strings)) db_attributes = t.db(('id', t.string(36))) HEALTH_POOR = "Poor Health Score" HEALTH_FAIR = "Fair Health Score" HEALTH_GOOD = "Good Health Score" HEALTH_EXCELLENT = "Excellent Health Score" def __init__(self, **kwargs): super(AciStatus, self).__init__( { 'resource_type': None, 'resource_id': None, 'sync_status': self.SYNC_NA, 'sync_message': '', 'health_score': 100, 'faults': [] }, **kwargs) self._parent_class = None @property def health_level(self): if self.health_score > 90: return self.HEALTH_EXCELLENT elif self.health_score > 75: return self.HEALTH_GOOD elif self.health_score > 50: return self.HEALTH_FAIR else: return self.HEALTH_POOR @property def parent_class(self): if not self._parent_class: for path in resource_paths: try: self._parent_class = importutils.import_class( path + '.%s' % self.resource_type) except ImportError: continue return self._parent_class @property def root(self): return self.resource_root def is_build(self): return self.sync_status in [self.SYNC_PENDING, self.SYNC_NA] def is_error(self): return (self.sync_status == self.SYNC_FAILED or self.health_level == self.HEALTH_POOR or [f for f in self.faults if f.is_error()])
class ResourceBase(object): """Base class for AIM resource. Class property 'identity_attributes' gives a list of resource attributes that uniquely identify the resource. The values of these attributes directly determines the corresponding ACI object identifier (DN). These attributes must always be specified. Class property 'other_attributes' gives a list of additional resource attributes that are defined on the resource. Class property 'db_attributes' gives a list of resource attributes that are managed by the database layer, eg: timestamp, incremental counter. """ db_attributes = t.db() common_db_attributes = t.db(('epoch', t.epoch)) sorted_attributes = [] def __init__(self, defaults, **kwargs): unset_attr = [k for k in self.identity_attributes if kwargs.get(k) is None and k not in defaults] if 'display_name' in self.other_attributes: defaults.setdefault('display_name', '') if unset_attr: raise exc.IdentityAttributesMissing(klass=type(self).__name__, attr=unset_attr) if kwargs.pop('_set_default', True): for k, v in defaults.items(): setattr(self, k, v) for k, v in kwargs.items(): setattr(self, k, v) def __getattr__(self, item): if item == 'epoch': return None super(ResourceBase, self).__getattr__(item) @property def identity(self): return [str(getattr(self, x)) for x in self.identity_attributes.keys()] @classmethod def attributes(cls): return (list(cls.identity_attributes.keys()) + list(cls.other_attributes.keys()) + list(cls.db_attributes.keys()) + list(cls.common_db_attributes.keys())) @classmethod def user_attributes(cls): return list(cls.identity_attributes.keys()) + list( cls.other_attributes.keys()) @classmethod def non_user_attributes(cls): return list(cls.db_attributes.keys()) + list( cls.common_db_attributes.keys()) @property def members(self): return {x: self.__dict__[x] for x in self.attributes() + ['pe_existing', '_error', '_pending'] if x in self.__dict__} @property def hash(self): def make_serializable(key, attr): if isinstance(attr, list) and key not in self.sorted_attributes: return sorted(make_serializable(None, x) for x in attr) if isinstance(attr, dict): return sorted([(k, make_serializable(k, v)) for k, v in attr.items()]) if isinstance(attr, set): return sorted([(make_serializable(None, x) for x in attr)]) if isinstance(attr, (int, float, bool, type(None))): return attr # Don't know the type, make it serializable anyways return str(attr) serializable = make_serializable(None, self.members) return int(md5(base64.b64encode( oslo_serialization.jsonutils.dump_as_bytes( serializable, sort_keys=True))).hexdigest(), 16) def user_equal(self, other): def sort_if_list(key, attr): # In Py3, sorting a dict w.r.t. keys first & then its values # natively is not available. So this is a fix for that. if six.PY3: if isinstance(attr, list) and key not in self.sorted_attributes: if attr and isinstance(attr[0], dict): return sorted(attr, key=lambda d: sorted(d.items())) return sorted(attr) return attr return (sorted(attr) if isinstance(attr, list) and key not in self.sorted_attributes else attr) missing = object() if type(self) != type(other): return False for attr in self.user_attributes(): if (sort_if_list(attr, getattr(self, attr, missing)) != sort_if_list(attr, getattr(other, attr, missing))): return False return True def __str__(self): return '%s(%s)' % (type(self).__name__, ','.join(self.identity)) def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return '%s(%s)' % (super(ResourceBase, self).__repr__(), self.members) # An object is hashable if it has a hash value which never changes during # its lifetime (it needs a __hash__() method), and can be compared to # other objects (it needs an __eq__() or __cmp__() method). # Hashable objects which compare equal must have the same hash value. # # If you define __eq__() , the default __hash__() (namely, hashing the # address of the object in memory) goes away. # So for each class defining __eq__() we must also # define __hash__() even though parent class has __hash__(). def __hash__(self): return self.hash
class ResourceBase(object): """Base class for AIM resource. Class property 'identity_attributes' gives a list of resource attributes that uniquely identify the resource. The values of these attributes directly determines the corresponding ACI object identifier (DN). These attributes must always be specified. Class property 'other_attributes' gives a list of additional resource attributes that are defined on the resource. Class property 'db_attributes' gives a list of resource attributes that are managed by the database layer, eg: timestamp, incremental counter. """ db_attributes = t.db() common_db_attributes = t.db(('epoch', t.epoch)) sorted_attributes = [] def __init__(self, defaults, **kwargs): unset_attr = [ k for k in self.identity_attributes if kwargs.get(k) is None and k not in defaults ] if 'display_name' in self.other_attributes: defaults.setdefault('display_name', '') if unset_attr: raise exc.IdentityAttributesMissing(klass=type(self).__name__, attr=unset_attr) if kwargs.pop('_set_default', True): for k, v in defaults.iteritems(): setattr(self, k, v) for k, v in kwargs.iteritems(): setattr(self, k, v) def __getattr__(self, item): if item == 'epoch': return None super(ResourceBase, self).__getattr__(item) @property def identity(self): return [str(getattr(self, x)) for x in self.identity_attributes.keys()] @classmethod def attributes(cls): return (cls.identity_attributes.keys() + cls.other_attributes.keys() + cls.db_attributes.keys() + cls.common_db_attributes.keys()) @classmethod def user_attributes(cls): return cls.identity_attributes.keys() + cls.other_attributes.keys() @classmethod def non_user_attributes(cls): return cls.db_attributes.keys() + cls.common_db_attributes.keys() @property def members(self): return { x: self.__dict__[x] for x in self.attributes() + ['pe_existing', '_error', '_pending'] if x in self.__dict__ } @property def hash(self): def make_serializable(key, attr): if isinstance(attr, list) and key not in self.sorted_attributes: return sorted(make_serializable(None, x) for x in attr) if isinstance(attr, dict): return sorted([(k, make_serializable(k, v)) for k, v in attr.iteritems()]) if isinstance(attr, set): return sorted([(make_serializable(None, x) for x in attr)]) if isinstance(attr, (int, float, bool, type(None))): return attr # Don't know the type, make it serializable anyways return str(attr) serializable = make_serializable(None, self.members) return int( md5(base64.b64encode(json.dumps(serializable, sort_keys=True))).hexdigest(), 16) def user_equal(self, other): def sort_if_list(key, attr): return (sorted(attr) if isinstance(attr, list) and key not in self.sorted_attributes else attr) missing = object() if type(self) != type(other): return False for attr in self.user_attributes(): if (sort_if_list(attr, getattr(self, attr, missing)) != sort_if_list(attr, getattr(other, attr, missing))): return False return True def __str__(self): return '%s(%s)' % (type(self).__name__, ','.join(self.identity)) def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return not self.__eq__(other) def __repr__(self): return '%s(%s)' % (super(ResourceBase, self).__repr__(), self.members) def __hash__(self): return self.hash
class AciFault(resource.ResourceBase, OperationalResource): """Fault information reported by ACI.""" LC_UNKNOWN = 0x0 LC_SOAKING = 0x1 LC_RETAINING = 0x10 LC_RAISED = 0x2 LC_SOAKING_CLEARING = 0x4 LC_RAISED_CLEARING = 0x8 SEV_CLEARED = 'cleared' SEV_INFO = 'info' SEV_WARNING = 'warning' SEV_MINOR = 'minor' SEV_MAJOR = 'major' SEV_CRITICAL = 'critical' _aci_mo_name = 'faultInst' identity_attributes = t.identity(('fault_code', t.string()), ('external_identifier', t.string())) other_attributes = t.other(('severity', t.enum(SEV_CLEARED, SEV_CRITICAL, SEV_INFO, SEV_WARNING, SEV_MAJOR, SEV_MINOR)), ('status_id', t.id), ('cause', t.string()), ('description', t.string(255))) db_attributes = t.db(('last_update_timestamp', t.string())) def __eq__(self, other): try: return self.identity == other.identity except AttributeError: return False # An object is hashable if it has a hash value which never changes during # its lifetime (it needs a __hash__() method), and can be compared to # other objects (it needs an __eq__() or __cmp__() method). # Hashable objects which compare equal must have the same hash value. # # If you define __eq__() , the default __hash__() (namely, hashing the # address of the object in memory) goes away. # So for each class defining __eq__() we must also # define __hash__() even though parent class has __hash__(). def __hash__(self): return super(AciFault, self).__hash__() def __init__(self, **kwargs): super(AciFault, self).__init__( { 'severity': self.SEV_INFO, 'lifecycle_status': self.LC_UNKNOWN, 'cause': '', 'description': "" }, **kwargs) def is_error(self): return self.severity in [self.SEV_MAJOR, self.SEV_CRITICAL] @property def dn(self): return self.external_identifier @property def root(self): mos_and_types = utils.decompose_dn(self._aci_mo_name, self.dn) if mos_and_types: # Faults associated with unrecognized MOs will not decompose mo = apic_client.ManagedObjectClass(mos_and_types[0][0]) return (mo.rn(mos_and_types[0][1]) if mo.rn_param_count else mo.rn())