Exemplo n.º 1
0
class FoundationBluePrint(BluePrint):
    parent = models.ForeignKey('self',
                               null=True,
                               blank=True,
                               on_delete=models.CASCADE)
    foundation_type_list = StringListField(
        max_length=200
    )  # list of the foundation types this blueprint can be used for
    template = JSONField(default={}, blank=True)
    physical_interface_names = StringListField(max_length=200, blank=True)

    @cinp.action('Map')
    def getConfig(self):
        return getConfig(self)

    @property
    def subcontractor(self):
        return {'type': None}

    @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 isinstance(self.template, dict):
            errors['template'] = 'template must be a dict'

        if errors:
            raise ValidationError(errors)

    def __str__(self):
        return 'FoundationBluePrint "{0}"({1})'.format(self.description,
                                                       self.name)
Exemplo n.º 2
0
class BaseJob(models.Model):
    JOB_STATE_CHOICES = (('queued', 'queued'), ('waiting', 'waiting'),
                         ('done', 'done'), ('paused', 'paused'),
                         ('error', 'error'), ('aborted', 'aborted'))
    site = models.ForeignKey(Site, editable=False, on_delete=models.CASCADE)
    state = models.CharField(max_length=10, choices=JOB_STATE_CHOICES)
    status = JSONField(default=[], blank=True)
    message = models.CharField(max_length=1024, default='', blank=True)
    script_runner = models.BinaryField(editable=False)
    script_name = models.CharField(max_length=40,
                                   editable=False,
                                   default=False)
    updated = models.DateTimeField(editable=False, auto_now=True)
    created = models.DateTimeField(editable=False, auto_now_add=True)

    @property
    def realJob(self):
        try:
            return self.foundationjob
        except ObjectDoesNotExist:
            pass

        try:
            return self.structurejob
        except ObjectDoesNotExist:
            pass

        try:
            return self.dependancyjob
        except ObjectDoesNotExist:
            pass

        return self

    def pause(self):
        if self.state != 'queued':
            raise ValueError('Can only pause a job if it is queued')

        self.state = 'paused'
        self.save()

    def resume(self):
        if self.state != 'paused':
            raise ValueError('Can only resume a job if it is paused')

        self.state = 'queued'
        self.save()

    def reset(self):
        if self.state != 'error':
            raise ValueError('Can only reset a job if it is in error')

        runner = pickle.loads(self.script_runner)
        runner.clearDispatched()
        self.status = runner.status
        self.script_runner = pickle.dumps(runner)

        self.state = 'queued'
        self.save()

    def rollback(self):
        if self.state != 'error':
            raise ValueError('Can only rollback a job if it is in error')

        runner = pickle.loads(self.script_runner)
        msg = runner.rollback()
        if msg != 'Done':
            raise ValueError('Unable to rollback "{0}"'.format(msg))

        self.status = runner.status
        self.script_runner = pickle.dumps(runner)
        self.state = 'queued'
        self.save()

    @property
    def progress(self):
        try:
            return self.status[0][0]
        except IndexError:
            return 0.0

    @cinp.check_auth()
    @staticmethod
    def checkAuth(user, method, id_list, action=None):
        if method == 'DESCRIBE':
            return True

        return False

    def clean(self, *args, **kwargs
              ):  # also need to make sure a Structure is in only one complex
        super().clean(*args, **kwargs)
        errors = {}

        if self.state not in self.JOB_STATE_CHOICES:
            errors['state'] = 'Invalid state "{0}"'.format(self.state)

        if errors:
            raise ValidationError(errors)

    def __str__(self):
        return 'BaseJob #{0} in "{1}"'.format(self.pk, self.site.pk)
Exemplo n.º 3
0
class Foundation( models.Model ):
  locator = models.CharField( max_length=100, primary_key=True )  # if this changes make sure to update architect - instance - foundation_id
  site = models.ForeignKey( Site, on_delete=models.PROTECT )
  blueprint = models.ForeignKey( FoundationBluePrint, on_delete=models.PROTECT )
  id_map = JSONField( blank=True, null=True )  # ie a dict of asset, chassis, system, etc types
  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 )

  @cinp.action( return_type={ 'type': 'String' }, paramater_type_list=[ 'Map' ] )
  def setIdMap( self, id_map ):
    error = self.blueprint.validateIdMap( id_map )
    if error is not None:
      return error

    self.id_map = id_map
    self.full_clean()
    self.save()

    for iface in RealNetworkInterface.objects.filter( foundation=self ):
      if iface.physical_location in id_map[ 'network' ]:
        iface.mac = id_map[ 'network' ][ iface.physical_location ][ 'mac' ]
        iface.full_clean()
        iface.save()

    return None

  def _canSetState( self, job=None ):  # You can't set state if there is a related job, unless that job (that is hopfully being passed in from the job that is calling this) is that related job
    try:
      self.cartographer
      return False
    except ObjectDoesNotExist:
      pass

    try:
      return self.foundationjob == job
    except ObjectDoesNotExist:
      pass

    try:
      self.structure.structurejob
      return False
    except ( ObjectDoesNotExist, AttributeError ):
      pass

    return True

  def setLocated( self ):
    """
    Sets the Foundation to 'located' state.  This will not create/destroy any jobs.

    NOTE: This will set the attached structure (if there is one) to 'planned' without running a job to destroy the structure.
    """
    try:
      job = self.foundationjob
      if job.script_name != 'create':
        job = None

    except ObjectDoesNotExist:
      job = None

    if not self._canSetState( job ):
      raise Exception( 'All related jobs and cartographer instances must be cleared before setting Located' )

    if self.structure is not None and self.structure.state != 'planned':
      raise Exception( 'Attached Structure must be in Planned state' )

    template = self.blueprint.getTemplate()
    if template is not None and not self.id_map:
      raise Exception( 'Foundations with blueprints, which specify templates, require id_map to be set before setting to Located' )

    self.located_at = timezone.now()
    self.built_at = None
    self.full_clean()
    self.save()

  def setBuilt( self, job=None ):
    """
    Set the Foundation to 'built' state.  This will not create/destroy any jobs.
    """
    if not self._canSetState( job ):
      raise Exception( 'All related jobs and cartographer instances must be cleared before setting Built' )

    if self.located_at is None:
      if self.blueprint.getTemplate() is not None:
        raise Exception( 'Foundation with Blueprints with templates must be located first' )
      self.located_at = timezone.now()

    self.built_at = timezone.now()
    self.full_clean()
    self.save()

  def setDestroyed( self, job=None ):
    """
    Sets the Foundation to 'destroyed' state.  This will not create/destroy any jobs.

    NOTE: This will set the attached structure (if there is one) to 'planned' without running a job to destroy the structure.
    """
    if not self._canSetState( job ):
      raise Exception( 'All related jobs and cartographer instances must be cleared before setting Destroyed' )

    try:
      self.structure.setDestroyed()  # TODO: this may be a little harsh
    except AttributeError:
      pass

    for iface in RealNetworkInterface.objects.filter( foundation=self ):
      iface.mac = None
      iface.full_clean()
      iface.save()

    self.built_at = None
    self.located_at = None
    self.id_map = None
    self.full_clean()
    self.save()

  @cinp.action( return_type='Integer', paramater_type_list=[ '_USER_' ]  )
  def doCreate( self, user ):
    """
    This will submit a job to run the create script.
    """
    from contractor.Foreman.lib import createJob
    return createJob( 'create', self, user )

  @cinp.action( return_type='Integer', paramater_type_list=[ '_USER_' ]  )
  def doDestroy( self, user ):
    """
    This will submit a job to run the destroy script.
    """
    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 ):
    """
    This will submit a job to run the specified script.
    """
    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.FoundationJob' }  )
  def getJob( self ):
    """
    Return the Job for this Foundation if there is one
    """
    try:
      return self.foundationjob
    except ObjectDoesNotExist:
      pass

    return None

  @staticmethod
  def getTscriptValues( write_mode=False ):  # locator is handled seperatly
    return {  # none of these base items are writeable, ignore the write_mode for now
              'locator': ( lambda foundation: foundation.locator, None ),  # redundant?
              'type': ( lambda foundation: foundation.subclass.type, None ),  # redudnant?
              'site': ( lambda foundation: foundation.site.pk, None ),
              'blueprint': ( lambda foundation: foundation.blueprint.pk, None ),
              # 'ip_map': ( lambda foundation: foundation.ip_map, None ),
              'interface_list': ( lambda foundation: [ i for i in foundation.networkinterface_set.all().order_by( 'physical_location' ) ], None )  # redudntant?
            }

  @staticmethod
  def getTscriptFunctions():
    return {}

  def configAttributes( self ):
    provisioning_interface = self.provisioning_interface
    return {
              '_provisioning_interface': provisioning_interface.physical_location if provisioning_interface is not None else None,  # what ever deals with the provisioning interface will have to deal with the physical_location, otherwise tools that are not the final target OS will be confused
              '_provisioning_interface_mac': provisioning_interface.mac if provisioning_interface is not None else None,
              '_foundation_id': self.pk,
              '_foundation_type': self.type,
              '_foundation_state': self.state,
              '_foundation_class_list': self.class_list,
              '_foundation_locator': self.locator,
              '_foundation_id_map': self.id_map,
              '_foundation_interface_list': [ i.config for i in self.networkinterface_set.all().order_by( 'physical_location' ) ]
            }

  @property
  def provisioning_interface( self ):
    try:
      return self.networkinterface_set.get( is_provisioning=True )
    except ObjectDoesNotExist:
      return None

  @property
  def subclass( self ):
    for attr in FOUNDATION_SUBCLASS_LIST:
      try:
        return getattr( self, attr )
      except AttributeError:
        pass

    return self

  @property
  def type( self ):
    real = self.subclass
    if real != self:
      return real.type

    return 'Unknown'

  @property
  def class_list( self ):
    # top level generic classes: Metal, VM, Container, Switch, PDU
    return []

  @property
  def complex( self ):
    return None

  @property
  def can_delete( self ):
    try:
      return self.structure.state != 'build'
    except AttributeError:
      pass

    return True

  @property
  def structure( self ):
    try:
      return Structure.objects.get( foundation=self )
    except Structure.DoesNotExist:
      pass

    return None

  @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'

  @property
  def description( self ):
    return self.locator

  @property
  def dependency( self ):
    try:
      return Dependency.objects.get( foundation=self )
    except Dependency.DoesNotExist:
      return None

  @property
  def dependencyId( self ):
    return 'f-{0}'.format( self.pk )

  @cinp.action( { 'type': 'String', 'is_array': True } )
  @staticmethod
  def getFoundationTypes():
    return FOUNDATION_SUBCLASS_LIST

  @cinp.action( return_type='Map' )
  def getConfig( self ):
    """
    returns the computed config for this foundation
    """
    return mergeValues( getConfig( self.subclass ) )

  @cinp.action( return_type={ 'type': 'Map', 'is_array': True } )
  def getInterfaceList( self ):
    """
    returns the computed config for this foundation
    """
    return [ { 'name': i.name, 'physical_location': i.physical_location, 'is_provisioning': i.is_provisioning, 'mac': i.mac, 'pxe': i.pxe } for i in self.networkinterface_set.all().order_by( 'physical_location' ) ]

  @cinp.list_filter( name='site', paramater_type_list=[ { 'type': 'Model', 'model': Site } ] )
  @staticmethod
  def filter_site( site ):
    return Foundation.objects.filter( site=site )

  @cinp.list_filter( name='todo', paramater_type_list=[ { 'type': 'Model', 'model': Site }, 'Boolean', 'String' ] )
  @staticmethod
  def filter_todo( site, has_dependancies, foundation_class ):
    args = {}
    args[ 'site' ] = site
    if has_dependancies:
      args[ 'dependency' ] = True

    if foundation_class is not None:
      if foundation_class not in FOUNDATION_SUBCLASS_LIST:
        raise ValueError( 'Invalid foundation class' )

      args[ foundation_class ] = True

    return Foundation.objects.filter( **args )

  @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 not name_regex.match( self.locator ):
      errors[ 'locator' ] = 'Invalid'

    if self.blueprint_id is not None and self.type not in self.blueprint.foundation_type_list:
        errors[ 'name' ] = 'Blueprint "{0}" does not list this type ({1})'.format( self.blueprint, self.type )

    if errors:
      raise ValidationError( errors )

  def delete( self ):
    if not self.can_delete:
      raise models.ProtectedError( 'Structure not Deleatable', self )

    subclass = self.subclass

    if self == subclass:
      super().delete()
    else:
      subclass.delete()

  def __str__( self ):
    return 'Foundation #{0}({1}) in "{2}"'.format( self.pk, self.locator, self.site.pk )
Exemplo n.º 4
0
 class testModel( models.Model ):
   f = JSONField()
Exemplo n.º 5
0
 class testModel( models.Model ):
   f = JSONField( default=None, null=True, blank=True )
Exemplo n.º 6
0
class BaseJob(models.Model):
    JOB_STATE_CHOICES = ('queued', 'waiting', 'done', 'paused', 'error',
                         'aborted')
    site = models.ForeignKey(Site, editable=False, on_delete=models.CASCADE)
    state = models.CharField(max_length=10,
                             choices=[(i, i) for i in JOB_STATE_CHOICES])
    status = JSONField(default=[], blank=True)
    message = models.CharField(max_length=1024, default='', blank=True)
    script_runner = models.BinaryField(editable=False)
    script_name = models.CharField(max_length=40,
                                   editable=False,
                                   default=False)
    updated = models.DateTimeField(editable=False, auto_now=True)
    created = models.DateTimeField(editable=False, auto_now_add=True)

    @property
    def realJob(self):
        try:
            return self.foundationjob
        except ObjectDoesNotExist:
            pass

        try:
            return self.structurejob
        except ObjectDoesNotExist:
            pass

        try:
            return self.dependencyjob
        except ObjectDoesNotExist:
            pass

        return self

    @property
    def progress(self):
        try:
            return self.status[0][0]
        except IndexError:
            return 0.0

    @property
    def can_start(self):
        return False

    @cinp.action()
    def pause(self):
        """
    Pause a job that is in 'queued' state state.

    Errors:
      NOT_PAUSEABLE - Job is not in state 'queued'.
    """
        if self.state != 'queued':
            raise ForemanException('NOT_PAUSEABLE',
                                   'Can only pause a job if it is queued')

        self.state = 'paused'
        self.full_clean()
        self.save()

    @cinp.action()
    def resume(self):
        """
    Resume a job that is in 'paused' state state.

    Errors:
      NOT_PAUSED - Job is not in state 'paused'.
    """
        if self.state != 'paused':
            raise ForemanException('NOT_PAUSED',
                                   'Can only resume a job if it is paused')

        self.state = 'queued'
        self.full_clean()
        self.save()

    @cinp.action()
    def reset(self):
        """
    Resets a job that is in 'error' state, this allows the job to try the failed step again.

    Errors:
      NOT_ERRORED - Job is not in state 'error'.
    """
        if self.state != 'error':
            raise ForemanException('NOT_ERRORED',
                                   'Can only reset a job if it is in error')

        runner = pickle.loads(self.script_runner)
        runner.clearDispatched()
        self.status = runner.status
        self.script_runner = pickle.dumps(runner, protocol=PICKLE_PROTOCOL)

        self.state = 'queued'
        self.full_clean()
        self.save()

    @cinp.action()
    def rollback(self):
        """
    Starts the rollback for jobs that are in state 'error'.

    Errors:
      NOT_ERRORED - Job is not in state 'error'.
    """
        if self.state != 'error':
            raise ForemanException(
                'NOT_ERRORED', 'Can only rollback a job if it is in error')

        runner = pickle.loads(self.script_runner)
        msg = runner.rollback()
        if msg != 'Done':
            raise ValueError('Unable to rollback "{0}"'.format(msg))

        self.status = runner.status
        self.script_runner = pickle.dumps(runner, protocol=PICKLE_PROTOCOL)
        self.state = 'queued'
        self.full_clean()
        self.save()

    @cinp.action()
    def clearDispatched(self):
        """
    Resets a job that is in 'queued' state, and subcontractor lost the job.  Make
    sure to verify that subcontractor has lost the job results before calling this.

    Errors:
      NOT_ERRORED - Job is not in state 'queued'.
    """
        if self.state != 'queued':
            raise ForemanException(
                'NOT_ERRORED',
                'Can only clear the dispatched flag a job if it is in queued state'
            )

        runner = pickle.loads(self.script_runner)
        runner.clearDispatched()
        self.status = runner.status
        self.script_runner = pickle.dumps(runner, protocol=PICKLE_PROTOCOL)

        self.full_clean()
        self.save()

    @cinp.action(return_type={'type': 'Map'},
                 paramater_type_list=[{
                     'type': 'Model',
                     'model': Site
                 }])
    @staticmethod
    def jobStats(site):
        """
    Returns the job status
    """
        return {
            'running':
            BaseJob.objects.filter(site=site).count(),
            'error':
            BaseJob.objects.filter(site=site,
                                   state__in=('error', 'aborted',
                                              'paused')).count()
        }

    @cinp.action(return_type={'type': 'Map'})
    def jobRunnerVariables(self):
        """
    Returns variables internal to the job script
    """
        result = {}
        runner = pickle.loads(self.script_runner)

        for module in runner.value_map:
            for name in runner.value_map[module]:
                result['{0}.{1}'.format(module, name)] = str(
                    runner.value_map[module][name][0]())

        result.update(runner.variable_map)

        return result

    @cinp.action(return_type={'type': 'Map'})
    def jobRunnerState(self):
        """
    Returns the state of the job script
    """
        result = {}
        runner = pickle.loads(self.script_runner)

        blueprint = None

        try:
            blueprint = self.foundationjob.foundation.blueprint
        except ObjectDoesNotExist:
            pass

        try:
            blueprint = self.structurejob.structure.blueprint
        except ObjectDoesNotExist:
            pass

        try:
            dependency = self.dependencyjob.dependency
            if dependency.script_structure is not None:
                blueprint = dependency.script_structure.blueprint
            else:
                blueprint = dependency.structure.blueprint
        except ObjectDoesNotExist:
            pass

        if blueprint is not None:
            result['script'] = blueprint.get_script(self.script_name)

        result['cur_line'] = runner.cur_line
        result['state'] = runner.state

        return result

    @cinp.check_auth()
    @staticmethod
    def checkAuth(user, verb, id_list, action=None):
        if verb == 'DESCRIBE':
            return True

        if verb == 'CALL':
            return True

        return False

    def clean(
        self, *args, **kwargs
    ):  # TODO: also need to make sure a Structure is in only one complex
        super().clean(*args, **kwargs)
        errors = {}

        if self.state not in self.JOB_STATE_CHOICES:
            errors['state'] = 'Invalid state "{0}"'.format(self.state)

        if errors:
            raise ValidationError(errors)

    def __str__(self):
        return 'BaseJob #{0} in "{1}"'.format(self.pk, self.site.pk)
Exemplo n.º 7
0
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 )