class Site(models.Model): name = models.CharField(max_length=40, primary_key=True) description = models.CharField(max_length=200) parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE) config_values = MapField(blank=True) updated = models.DateTimeField(editable=False, auto_now=True) created = models.DateTimeField(editable=False, auto_now_add=True) @cinp.action('Map') def getConfig(self): return getConfig(self) @cinp.check_auth() @staticmethod def checkAuth(user, method, id_list, action=None): return True def clean(self, *args, **kwargs): super().clean(*args, **kwargs) errors = {} if not name_regex.match(self.name): errors['name'] = 'name "{0}" is invalid'.format(self.name) for name in self.config_values: if not config_name_regex.match(name): errors[ 'config_values'] = 'config item name "{0}" is invalid'.format( name) break if errors: raise ValidationError(errors) def __str__(self): return 'Site "{0}"({1})'.format(self.description, self.name)
class Box(models.Model): BOX_TYPE = (('post', 'POST'), ('call', 'call (CINP)')) url = models.CharField(max_length=2048) proxy = models.CharField(max_length=512, blank=True, null=True) type = models.CharField(max_length=4, choices=BOX_TYPE) one_shot = models.BooleanField(default=True) extra_data = MapField() expires = models.DateTimeField(blank=True, null=True) updated = models.DateTimeField(editable=False, auto_now=True) created = models.DateTimeField(editable=False, auto_now_add=True) def extend(self, additional_hours): self.expires += timedelta(hour=additional_hours) self.full_clean() self.save() def clean(self, *args, **kwargs): super().clean(*args, **kwargs) if not self.expires: self.expires = None if not self.proxy: self.proxy = None errors = {} if self.expires is not None and not self.expires > datetime.now( timezone.utc) + timedelta(hours=MAX_BOX_LIFE): errors['expires'] = 'more than "{0}" hourse in the future'.format( MAX_BOX_LIFE) if not self.one_shot and self.exires is None: errors['expires'] = 'required when not one_shot' if errors: raise ValidationError(errors) class Meta: abstract = True
class BluePrint(models.Model): name = models.CharField( max_length=40, primary_key=True) # update Architect if this changes max_length description = models.CharField(max_length=200) scripts = models.ManyToManyField('Script', through='BluePrintScript') config_values = MapField(blank=True, null=True) updated = models.DateTimeField(editable=False, auto_now=True) created = models.DateTimeField(editable=False, auto_now_add=True) def get_script(self, name): try: return self.blueprintscript_set.get(name=name).script.script except BluePrintScript.DoesNotExist: for parent in self.parent_list.all(): tmp = parent.get_script(name) if tmp is not None: return tmp return None @property def subclass(self): try: return self.foundationblueprint except AttributeError: pass try: return self.structureblueprint except AttributeError: pass return self @cinp.action('Map') def getConfig(self): return getConfig(self.subclass) @cinp.check_auth() @staticmethod def checkAuth(user, verb, id_list, action=None): if verb in ('DESCRIBE', 'CALL'): return True return False def clean(self, *args, **kwargs): super().clean(*args, **kwargs) errors = {} if not name_regex.match( self.name ): # if this regex changes, make sure to update tcalc parser in archetect errors['name'] = 'BluePrint Script name "{0}" is invalid'.format( self.name) if self.config_values is not None: for name in self.config_values: if not config_name_regex.match(name): errors[ 'config_values'] = 'config item name "{0}" is invalid'.format( name) break if errors: raise ValidationError(errors) def __str__(self): return 'BluePrint "{0}"({1})'.format(self.description, self.name)
class Site( models.Model ): name = models.CharField( max_length=40, primary_key=True ) # update Architect if this changes max_length zone = models.ForeignKey( Zone, null=True, blank=True, on_delete=models.PROTECT ) description = models.CharField( max_length=200 ) parent = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.CASCADE ) config_values = MapField( blank=True, null=True ) updated = models.DateTimeField( editable=False, auto_now=True ) created = models.DateTimeField( editable=False, auto_now_add=True ) @cinp.action( 'Map' ) def getConfig( self ): return getConfig( self ) @cinp.action( 'Map' ) def getDependencyMap( self ): from contractor.Building.models import Dependency result = {} external_list = [] structure_job_list = [ i.structurejob.structure.pk for i in self.basejob_set.filter( structurejob__isnull=False ) ] foundation_job_list = [ i.foundationjob.foundation.pk for i in self.basejob_set.filter( foundationjob__isnull=False ) ] dependency_job_list = [ i.dependencyjob.dependency.pk for i in self.basejob_set.filter( dependencyjob__isnull=False ) ] for structure in self.networked_set.filter( structure__isnull=False ).order_by( 'pk' ): structure = structure.structure dependency_list = [ structure.foundation.dependencyId ] if structure.foundation.site != self: external_list.append( structure.foundation ) result[ structure.dependencyId ] = { 'description': structure.description, 'type': 'Structure', 'state': structure.state, 'dependency_list': dependency_list, 'has_job': ( structure.pk in structure_job_list ), 'external': False } for foundation in self.foundation_set.all().order_by( 'pk' ): foundation = foundation.subclass # dependency_list = list( set( [ i.dependencyId for i in foundation.dependency_set.all() ] ) ) dependency_list = [] if foundation.dependency: dependency_list.append( foundation.dependency.dependencyId ) try: dependency_list += [ foundation.complex.dependencyId ] if foundation.complex.site != self: external_list += [ foundation.complex ] except AttributeError: pass result[ foundation.dependencyId ] = { 'description': foundation.description, 'type': 'Foundation', 'state': foundation.state, 'dependency_list': dependency_list, 'has_job': ( foundation.pk in foundation_job_list ), 'external': False } for dependency in Dependency.objects.filter( Q( foundation__site=self ) | Q( foundation__isnull=True, script_structure__site=self ) | Q( foundation__isnull=True, script_structure__isnull=True, dependency__structure__site=self ) | Q( foundation__isnull=True, script_structure__isnull=True, structure__site=self ) ).order_by( 'pk' ): if dependency.dependency is not None: dependency_list = [ dependency.dependency.dependencyId ] else: dependency_list = [ dependency.structure.dependencyId ] if dependency.site != self: external_list += [ dependency.structure ] result[ dependency.dependencyId ] = { 'description': dependency.description, 'type': 'Dependency', 'state': dependency.state, 'dependency_list': dependency_list, 'has_job': ( dependency.pk in dependency_job_list ), 'external': False } for complex in self.complex_set.all().order_by( 'pk' ): complex = complex.subclass dependency_list = [ i.structure.dependencyId for i in complex.complexstructure_set.all() ] external_list += [ i.structure if i.structure.site != self else None for i in complex.complexstructure_set.all() ] result[ complex.dependencyId ] = { 'description': complex.description, 'type': 'Complex', 'state': complex.state, 'dependency_list': dependency_list, 'external': False } external_list = list( set( external_list ) ) for external in external_list: if external is None: continue result[ external.dependencyId ] = { 'description': external.description, 'type': external.type, 'state': external.state, 'dependency_list': [], 'external': True } return result @cinp.check_auth() @staticmethod def checkAuth( user, verb, id_list, action=None ): return True def clean( self, *args, **kwargs ): super().clean( *args, **kwargs ) errors = {} if self.name and not name_regex.match( self.name ): errors[ 'name' ] = 'Invalid' if self.config_values is not None: for name in self.config_values: if not config_name_regex.match( name ): errors[ 'config_values' ] = 'config item name "{0}" is invalid'.format( name ) break if name in ( 'domain_search', 'dns_servers', 'log_servers' ): if not isinstance( self.config_values[ name ], list ): errors[ 'config_values' ] = 'config item "{0}" must be a list'.format( name ) break if errors: raise ValidationError( errors ) def __str__( self ): return 'Site "{0}"({1})'.format( self.description, self.name )
class Structure( Networked ): blueprint = models.ForeignKey( StructureBluePrint, on_delete=models.PROTECT ) # ie what to bild foundation = models.OneToOneField( Foundation, related_name='+', on_delete=models.PROTECT ) # ie what to build it on config_uuid = models.CharField( max_length=36, default=getUUID, unique=True ) # unique config_values = MapField( blank=True, null=True ) built_at = models.DateTimeField( editable=False, blank=True, null=True ) updated = models.DateTimeField( editable=False, auto_now=True ) created = models.DateTimeField( editable=False, auto_now_add=True ) def _canSetState( self, job=None ): try: return self.structurejob == job except ObjectDoesNotExist: pass return True def setBuilt( self, job=None ): if not self._canSetState( job ): raise Exception( 'All related jobs must be cleared before setting Built' ) self.built_at = timezone.now() self.full_clean() self.save() def setDestroyed( self, job=None ): if not self._canSetState( job ): raise Exception( 'All related jobs must be cleared before setting Destroyed' ) self.built_at = None self.config_uuid = str( uuid.uuid4() ) # new on destroyed, that way we can leave anything that might still be kicking arround in the dust self.full_clean() self.save() for dependency in self.dependant_dependencies: dependency.setDestroyed() def configAttributes( self ): provisioning_interface = self.provisioning_interface provisioning_address = self.provisioning_address primary_interface = self.primary_interface primary_address = self.primary_address result = { '_structure_id': self.pk, '_structure_state': self.state, '_structure_config_uuid': self.config_uuid, '_hostname': self.hostname, '_domain_name': self.domain_name, '_fqdn': self.fqdn, '_provisioning_interface': provisioning_interface.physical_location if provisioning_interface is not None else None, '_provisioning_interface_mac': provisioning_interface.mac if provisioning_interface is not None else None, '_provisioning_address': provisioning_address.as_dict if provisioning_address is not None else None, '_primary_interface': primary_interface.name if primary_interface is not None else None, '_primary_interface_mac': primary_interface.mac if primary_interface is not None else None, '_primary_address': primary_address.as_dict if primary_address is not None else None, '_interface_map': {} } for iface in self.foundation.networkinterface_set.all(): # mabey? mabey not? result[ '_interface_map' ][ iface.name ] = iface.config return result @property def state( self ): if self.built_at is not None: return 'built' return 'planned' @property def can_delete( self ): return self.state != 'build' @property def description( self ): return self.hostname @property def dependant_dependencies( self ): try: return Dependency.objects.filter( structure=self ) except Dependency.DoesNotExist: return [] @property def dependencyId( self ): return 's-{0}'.format( self.pk ) @cinp.action( return_type='Integer', paramater_type_list=[ '_USER_' ] ) def doCreate( self, user ): from contractor.Foreman.lib import createJob return createJob( 'create', self, user ) @cinp.action( return_type='Integer', paramater_type_list=[ '_USER_' ] ) def doDestroy( self, user ): from contractor.Foreman.lib import createJob return createJob( 'destroy', self, user ) @cinp.action( return_type='Integer', paramater_type_list=[ '_USER_', 'String' ] ) def doJob( self, user, name ): from contractor.Foreman.lib import createJob if name in ( 'create', 'destroy' ): raise ValueError( 'Invalid Job Name' ) return createJob( name, self, user ) @cinp.action( return_type={ 'type': 'Model', 'model': 'contractor.Foreman.models.StructureJob' } ) def getJob( self ): try: return self.structurejob except ObjectDoesNotExist: pass return None @cinp.action( return_type='Map' ) def getConfig( self ): return mergeValues( getConfig( self ) ) @cinp.action( return_type='Map', paramater_type_list=[ 'Map' ] ) def updateConfig( self, config_value_map ): # TODO: this is a bad Idea, need to figure out a better way to do this, at least restrict it to accounts that can create/updatre structures self.config_values.update( config_value_map ) self.full_clean() self.save() return mergeValues( getConfig( self ) ) @cinp.list_filter( name='site', paramater_type_list=[ { 'type': 'Model', 'model': Site } ] ) @staticmethod def filter_site( site ): return Structure.objects.filter( site=site ) @cinp.list_filter( name='complex', paramater_type_list=[ { 'type': 'Model', 'model': 'contractor.Building.models.Complex' } ] ) @staticmethod def filter_complex( complex ): return complex.members.all() @cinp.check_auth() @staticmethod def checkAuth( user, verb, id_list, action=None ): return True def clean( self, *args, **kwargs ): super().clean( *args, **kwargs ) errors = {} if self.foundation_id is not None and self.foundation.blueprint not in self.blueprint.combined_foundation_blueprint_list: errors[ 'foundation' ] = 'The blueprint "{0}" is not allowed on foundation "{1}"'.format( self.blueprint, self.foundation.blueprint ) if self.config_values is not None: for name in self.config_values: if not config_name_regex.match( name ): errors[ 'config_values' ] = 'config item name "{0}" is invalid'.format( name ) break if errors: raise ValidationError( errors ) def delete( self ): if not self.can_delete: raise models.ProtectedError( 'Structure not Deleteable', self ) super().delete() def __str__( self ): return 'Structure #{0}({1}) in "{2}"'.format( self.pk, self.hostname, self.site.pk )
class testModel( models.Model ): f = MapField( **kwargs )
class testModel( models.Model ): f = MapField( default={ 'b': 2 }, blank=True )
class testModel2( models.Model ): f = MapField()
class testModel( models.Model ): f = MapField( default={ 'b': 2 } )
class testModel( models.Model ): f = MapField( blank=True )
class testModel( models.Model ): f = MapField( default=None, null=True, blank=True )
class Foundation( models.Model ): site = models.ForeignKey( Site, on_delete=models.PROTECT ) # ie where to build it blueprint = models.ForeignKey( FoundationBluePrint, on_delete=models.PROTECT ) locator = models.CharField( max_length=100, unique=True ) config_values = MapField( blank=True ) id_map = JSONField( blank=True ) # ie a dict of asset, chassis, system, etc types interfaces = models.ManyToManyField( RealNetworkInterface, through='FoundationNetworkInterface' ) located_at = models.DateTimeField( editable=False, blank=True, null=True ) built_at = models.DateTimeField( editable=False, blank=True, null=True ) updated = models.DateTimeField( editable=False, auto_now=True ) created = models.DateTimeField( editable=False, auto_now_add=True ) def setLocated( self ): try: self.structure.setDestroyed() # TODO: this may be a little harsh except Structure.DoesNotExist: pass self.located_at = timezone.now() self.built_at = None self.save() def setBuilt( self ): if self.located_at is None: self.located_at = timezone.now() self.built_at = timezone.now() self.save() def setDestroyed( self ): try: self.structure.setDestroyed() # TODO: this may be a little harsh except Structure.DoesNotExist: pass self.built_at = None self.located_at = None self.save() @staticmethod def getTscriptValues( write_mode=False ): # locator is handled seperatly return { # none of these base items are writeable, ignore the write_mode for now 'id': ( lambda foundation: foundation.pk, None ), 'type': ( lambda foundation: foundation.subclass.type, None ), 'site': ( lambda foundation: foundation.site.pk, None ), 'blueprint': ( lambda foundation: foundation.blueprint.pk, None ), 'id_map': ( lambda foundation: foundation.ip_map, None ), 'interface_list': ( lambda foundation: [ i for i in foundation.interfaces.all() ], None ) } @staticmethod def getTscriptFunctions(): return {} def configValues( self ): return { 'foundation_id': self.pk, 'foundation_type': self.type, 'foundation_state': self.state, 'foundation_class_list': self.class_list } @property def subclass( self ): for attr in FOUNDATION_SUBCLASS_LIST: try: return getattr( self, attr ) except AttributeError: pass return self @cinp.action( 'String' ) def getRealFoundationURI( self ): # TODO: this is such a hack, figure out a better way subclass = self.subclass class_name = type( subclass ).__name__ if class_name == 'Foundation': return '/api/v1/Building/Foundation:{0}:'.format( subclass.pk ) elif class_name == 'VirtualBoxFoundation': return '/api/v1/VirtualBox/VirtualBoxFoundation:{0}:'.format( subclass.pk ) elif class_name == 'ManualFoundation': return '/api/v1/Manual/ManualFoundation:{0}:'.format( subclass.pk ) raise ValueError( 'Unknown Foundation class "{0}"'.format( class_name ) ) @cinp.action( 'Map' ) def getConfig( self ): return getConfig( self.subclass ) @property def type( self ): return 'Unknown' @property def class_list( self ): # top level generic classes: Metal, VM, Container, Switch, PDU return [] @property def can_auto_locate( self ): # child models can decide if it can auto submit job for building, ie: vm (and like foundations) are only canBuild if their structure is auto_build return False @property def state( self ): if self.located_at is not None and self.built_at is not None: return 'built' elif self.located_at is not None: return 'located' return 'planned' @cinp.list_filter( name='site', paramater_type_list=[ { 'type': 'Model', 'model': 'contractor.Site.models.Site' } ] ) @staticmethod def filter_site( site ): return Foundation.objects.filter( site=site ) @cinp.check_auth() @staticmethod def checkAuth( user, method, id_list, action=None ): return True def clean( self, *args, **kwargs ): super().clean( *args, **kwargs ) errors = {} if self.type not in self.blueprint.foundation_type_list: errors[ 'name' ] = 'Blueprint "{0}" does not list this type ({1})'.format( self.blueprint.description, self.type ) if errors: raise ValidationError( errors ) def __str__( self ): return 'Foundation #{0} of "{1}" in "{2}"'.format( self.pk, self.blueprint.pk, self.site.pk )
class Structure( Networked ): blueprint = models.ForeignKey( StructureBluePrint, on_delete=models.PROTECT ) # ie what to bild foundation = models.OneToOneField( Foundation, on_delete=models.PROTECT ) # ie what to build it on config_uuid = models.CharField( max_length=36, default=getUUID, unique=True ) # unique config_values = MapField( blank=True ) auto_build = models.BooleanField( default=True ) build_priority = models.IntegerField( default=100 ) built_at = models.DateTimeField( editable=False, blank=True, null=True ) updated = models.DateTimeField( editable=False, auto_now=True ) created = models.DateTimeField( editable=False, auto_now_add=True ) def setBuilt( self ): self.built_at = timezone.now() self.save() def setDestroyed( self ): self.built_at = None self.config_uuid = str( uuid.uuid4() ) # new on destroyed, that way we can leave anything that might still be kicking arround in the dust self.save() for dependancy in self.dependancy_set.all(): dependancy.setDestroyed() def configValues( self ): return { 'hostname': self.hostname, 'structure': self.pk, 'state': self.state, 'config_uuid': self.config_uuid } @cinp.action( 'Map' ) def getConfig( self ): return getConfig( self ) @property def state( self ): if self.built_at is not None: return 'built' return 'planned' @cinp.list_filter( name='site', paramater_type_list=[ { 'type': 'Model', 'model': 'contractor.Site.models.Site' } ] ) @staticmethod def filter_site( site ): return Structure.objects.filter( site=site ) @cinp.check_auth() @staticmethod def checkAuth( user, method, id_list, action=None ): return True def clean( self, *args, **kwargs ): super().clean( *args, **kwargs ) errors = {} if self.foundation.blueprint not in self.blueprint.combined_foundation_blueprint_list: errors[ 'foundation' ] = 'The blueprint "{0}" is not allowed on foundation "{1}"'.format( self.blueprint.description, self.foundation.blueprint.description ) if errors: raise ValidationError( errors ) def __str__( self ): return 'Structure #{0} of "{1}" in "{2}"'.format( self.pk, self.blueprint.pk, self.site.pk )