Esempio n. 1
0
class GenericComponent(Component):
    label = models.CharField(
        verbose_name=_('label'), max_length=255, blank=True,
        null=True, default=None,
    )
    sn = NullableCharField(
        verbose_name=_('vendor SN'), max_length=255, unique=True, null=True,
        blank=True, default=None,
    )

    class Meta:
        verbose_name = _('generic component')
        verbose_name_plural = _('generic components')
Esempio n. 2
0
class DiskShare(Component):
    share_id = models.PositiveIntegerField(
        verbose_name=_('share identifier'),
        null=True,
        blank=True,
    )
    label = models.CharField(
        verbose_name=_('name'),
        max_length=255,
        blank=True,
        null=True,
        default=None,
    )
    size = models.PositiveIntegerField(
        verbose_name=_('size (MiB)'),
        null=True,
        blank=True,
    )
    snapshot_size = models.PositiveIntegerField(
        verbose_name=_('size for snapshots (MiB)'),
        null=True,
        blank=True,
    )
    wwn = NullableCharField(
        verbose_name=_('Volume serial'),
        max_length=33,
        unique=True,
    )
    full = models.BooleanField(default=True)

    def get_total_size(self):
        return (self.size or 0) + (self.snapshot_size or 0)

    class Meta:
        verbose_name = _('disk share')
        verbose_name_plural = _('disk shares')

    def __str__(self):
        return '%s (%s)' % (self.label, self.wwn)
Esempio n. 3
0
class Service(PermByFieldMixin, AdminAbsoluteUrlMixin, NamedMixin,
              TimeStampMixin, models.Model):
    # Fixme: let's do service catalog replacement from that
    _allow_in_dashboard = True

    active = models.BooleanField(default=True)
    uid = NullableCharField(max_length=40, unique=True, blank=True, null=True)
    profit_center = models.ForeignKey(ProfitCenter, null=True, blank=True)
    business_segment = models.ForeignKey(BusinessSegment,
                                         null=True,
                                         blank=True)
    cost_center = models.CharField(max_length=100, blank=True)
    environments = models.ManyToManyField('Environment',
                                          through='ServiceEnvironment')
    business_owners = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='services_business_owner',
        blank=True,
    )
    technical_owners = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='services_technical_owner',
        blank=True,
    )
    support_team = models.ForeignKey(
        Team,
        null=True,
        blank=True,
        related_name='services',
    )

    def __str__(self):
        return '{}'.format(self.name)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.filter(active=True)
Esempio n. 4
0
class Transition(models.Model):
    name = models.CharField(max_length=50)
    model = models.ForeignKey(TransitionModel)
    run_asynchronously = models.BooleanField(
        default=False,
        help_text=_(
            'Run this transition in the background (this could be enforced if '
            'you choose at least one asynchronous action)'))
    async_service_name = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        default=DEFAULT_ASYNC_TRANSITION_SERVICE_NAME,
        help_text=_(
            'Name of asynchronous (internal) service to run this transition. '
            'Fill this field only if you want to run this transition in the '
            'background.'))
    source = JSONField()
    target = models.CharField(max_length=50)
    actions = models.ManyToManyField('Action')
    template_name = models.CharField(max_length=255, blank=True, default='')
    success_url = NullableCharField(max_length=255,
                                    blank=True,
                                    null=True,
                                    default=None)

    class Meta:
        unique_together = ('name', 'model')
        app_label = 'transitions'

    def __str__(self):
        return self.name

    @property
    def permission_info(self):
        return {
            'name': 'Can run {} transition'.format(self.name.lower()),
            'content_type': self.model.content_type,
            'codename': 'can_run_{}_transition'.format(slugify(self.name))
        }

    @property
    def model_cls(self):
        return self.model.content_type.model_class()

    @property
    def is_async(self):
        return (self.run_asynchronously
                or any([func.is_async for func in self.get_pure_actions()]))

    @classmethod
    def transitions_for_model(cls, model, user=None):
        content_type = ContentType.objects.get_for_model(model)
        transitions = cls.objects.filter(model__content_type=content_type)
        return [
            transition for transition in transitions
            if _check_user_perm_for_transition(user, transition)
        ]

    def _get_metric_name(self):
        return '{}.{}'.format(self.name, self.model.content_type.model)

    def get_pure_actions(self):
        return [
            getattr(self.model_cls, action.name)
            for action in self.actions.all()
        ]

    def has_form(self):
        for action in self.get_pure_actions():
            if getattr(action, 'form_fields', None):
                return True
        return False
Esempio n. 5
0
class Asset(AdminAbsoluteUrlMixin, BaseObject):
    model = models.ForeignKey(AssetModel,
                              related_name='assets',
                              on_delete=models.PROTECT)
    # TODO: unify hostname for DCA, VirtualServer, Cluster and CloudHost
    # (use another model?)
    hostname = NullableCharField(
        blank=True,
        default=None,
        max_length=255,
        null=True,
        verbose_name=_('hostname'),  # TODO: unique
    )
    sn = NullableCharField(
        blank=True,
        max_length=200,
        null=True,
        verbose_name=_('SN'),
        unique=True,
    )
    barcode = NullableCharField(blank=True,
                                default=None,
                                max_length=200,
                                null=True,
                                unique=True,
                                verbose_name=_('barcode'))
    niw = NullableCharField(
        blank=True,
        default=None,
        max_length=200,
        null=True,
        verbose_name=_('inventory number'),
    )
    required_support = models.BooleanField(default=False)

    order_no = models.CharField(
        verbose_name=_('order number'),
        blank=True,
        max_length=50,
        null=True,
    )
    invoice_no = models.CharField(
        verbose_name=_('invoice number'),
        blank=True,
        db_index=True,
        max_length=128,
        null=True,
    )
    invoice_date = models.DateField(blank=True, null=True)
    price = models.DecimalField(
        blank=True,
        decimal_places=2,
        default=0,
        max_digits=10,
        null=True,
    )
    # to discuss: foreign key?
    provider = models.CharField(
        blank=True,
        max_length=100,
        null=True,
    )
    depreciation_rate = models.DecimalField(
        blank=True,
        decimal_places=2,
        default=settings.DEFAULT_DEPRECIATION_RATE,
        help_text=_(
            'This value is in percentage.'
            ' For example value: "100" means it depreciates during a year.'
            ' Value: "25" means it depreciates during 4 years, and so on... .'
        ),
        max_digits=5,
    )
    force_depreciation = models.BooleanField(
        help_text=('Check if you no longer want to bill for this asset'),
        default=False,
    )
    depreciation_end_date = models.DateField(blank=True, null=True)
    buyout_date = models.DateField(blank=True, null=True, db_index=True)
    task_url = models.URLField(
        blank=True,
        help_text=('External workflow system URL'),
        max_length=2048,
        null=True,
    )
    budget_info = models.ForeignKey(
        BudgetInfo,
        blank=True,
        default=None,
        null=True,
        on_delete=models.PROTECT,
    )
    property_of = models.ForeignKey(
        AssetHolder,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )
    start_usage = models.DateField(
        blank=True,
        null=True,
        help_text=(
            'Fill it if date of first usage is different then date of creation'
        ))

    def __str__(self):
        return self.hostname or ''

    def calculate_buyout_date(self):
        """
        Get buyout date.

        Calculate buyout date invoice_date + depreciation_rate months

        Returns:
            Deprecation date
        """
        if self.depreciation_end_date:
            return self.depreciation_end_date
        elif self.invoice_date:
            return self.invoice_date + relativedelta(
                months=self.get_depreciation_months() + 1)
        else:
            return None

    def get_depreciation_months(self):
        return int((1 / (self.depreciation_rate / 100) *
                    12) if self.depreciation_rate else 0)

    def is_depreciated(self, date=None):
        date = date or datetime.date.today()
        if self.force_depreciation or not self.invoice_date:
            return True
        if self.depreciation_end_date:
            deprecation_date = self.deprecation_end_date
        else:
            deprecation_date = self.invoice_date + relativedelta(
                months=self.get_depreciation_months(), )
        return deprecation_date < date

    def get_depreciated_months(self):
        # DEPRECATED
        # BACKWARD_COMPATIBILITY
        return self.get_depreciation_months()

    def is_deprecated(self, date=None):
        # DEPRECATED
        # BACKWARD_COMPATIBILITY
        return self.is_depreciated()

    def _liquidated_at(self, date):
        liquidated_history = self.get_history().filter(
            new_value='liquidated',
            field_name='status',
        ).order_by('-date')[:1]
        return liquidated_history and liquidated_history[0].date.date() <= date

    def clean(self):
        if not self.sn and not self.barcode:
            error_message = [_('SN or BARCODE field is required')]
            raise ValidationError({
                'sn': error_message,
                'barcode': error_message
            })

    def save(self, *args, **kwargs):
        # if you save barcode as empty string (instead of None) you could have
        # only one asset with empty barcode (because of `unique` constraint)
        # if you save barcode as None you could have many assets with empty
        # barcode (becasue `unique` constrainst is skipped)
        for unique_field in ['barcode', 'sn']:
            value = getattr(self, unique_field, None)
            if value == '':
                value = None
            setattr(self, unique_field, value)

        self.buyout_date = self.calculate_buyout_date()
        return super(Asset, self).save(*args, **kwargs)
Esempio n. 6
0
class VirtualServer(PreviousStateMixin, DNSaaSPublisherMixin,
                    AdminAbsoluteUrlMixin, NetworkableBaseObject, BaseObject):
    # parent field for VirtualServer is hypervisor!
    # TODO: limit parent to DataCenterAsset and CloudHost
    status = TransitionField(
        default=VirtualServerStatus.new.id,
        choices=VirtualServerStatus(),
    )
    type = models.ForeignKey(VirtualServerType, related_name='virtual_servers')
    hostname = NullableCharField(
        blank=True,
        default=None,
        max_length=255,
        null=True,
        verbose_name=_('hostname'),
        unique=True,
    )
    sn = NullableCharField(
        max_length=200,
        verbose_name=_('SN'),
        blank=True,
        default=None,
        null=True,
        unique=True,
    )
    # TODO: remove this field
    cluster = models.ForeignKey(Cluster, blank=True, null=True)

    previous_dc_host_update_fields = ['hostname']
    _allow_in_dashboard = True

    @cached_property
    def polymorphic_parent(self):
        return self.parent.last_descendant if self.parent_id else None

    def get_location(self):
        if (self.parent_id and self.parent.content_type_id
                == ContentType.objects.get_for_model(DataCenterAsset).id):
            parent = self.parent.asset.datacenterasset
            location = parent.get_location()
            if parent.hostname:
                location.append(parent.hostname)
        else:
            location = []
        return location

    @property
    def model(self):
        return self.type

    @cached_property
    def rack_id(self):
        return self.rack.id if self.rack else None

    @cached_property
    def rack(self):
        if self.parent_id:
            polymorphic_parent = self.polymorphic_parent.last_descendant
            if (isinstance(polymorphic_parent, (DataCenterAsset, CloudHost))):
                return polymorphic_parent.rack
        return None

    class Meta:
        verbose_name = _('Virtual server (VM)')
        verbose_name_plural = _('Virtual servers (VM)')

    def __str__(self):
        return 'VirtualServer: {} ({})'.format(self.hostname, self.sn)
Esempio n. 7
0
class BackOfficeAsset(Regionalizable, Asset):
    _allow_in_dashboard = True

    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        related_name='assets_as_owner',
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        related_name='assets_as_user',
    )
    location = models.CharField(max_length=128, null=True, blank=True)
    purchase_order = models.CharField(max_length=50, null=True, blank=True)
    loan_end_date = models.DateField(
        null=True,
        blank=True,
        default=None,
        verbose_name=_('Loan end date'),
    )
    status = TransitionField(
        default=BackOfficeAssetStatus.new.id,
        choices=BackOfficeAssetStatus(),
    )
    imei = NullableCharField(max_length=18,
                             null=True,
                             blank=True,
                             unique=True,
                             verbose_name=_('IMEI'))
    imei2 = NullableCharField(max_length=18,
                              null=True,
                              blank=True,
                              unique=True,
                              verbose_name=_('IMEI 2'))
    office_infrastructure = models.ForeignKey(OfficeInfrastructure,
                                              null=True,
                                              blank=True)

    class Meta:
        verbose_name = _('Back Office Asset')
        verbose_name_plural = _('Back Office Assets')

    @property
    def country_code(self):
        iso2 = Country.name_from_id(int(self.region.country)).upper()
        return iso2_to_iso3(iso2)

    def __str__(self):
        return '{} (BC: {} / SN: {})'.format(self.hostname or '-', self.barcode
                                             or '-', self.sn or '-')

    def __repr__(self):
        return '<BackOfficeAsset: {}>'.format(self.id)

    def validate_imei(self, imei):
        return IMEI_SINCE_2003.match(imei) or IMEI_UNTIL_2003.match(imei)

    def clean(self):
        super().clean()
        if self.imei and not self.validate_imei(self.imei):
            raise ValidationError({
                'imei': _('%(imei)s is not IMEI format') % {
                    'imei': self.imei
                }
            })
        if self.imei2 and not self.validate_imei(self.imei2):
            raise ValidationError({
                'imei2': _('%(imei)s is not IMEI format') % {
                    'imei': self.imei2
                }  # noqa
            })

    def is_liquidated(self, date=None):
        date = date or datetime.date.today()
        # check if asset has status 'liquidated' and if yes, check if it has
        # this status on given date
        if (self.status == BackOfficeAssetStatus.liquidated
                and self._liquidated_at(date)):
            return True
        return False

    def generate_hostname(self, commit=True, template_vars=None):
        def render_template(template):
            template = Template(template)
            context = Context(template_vars or {})
            return template.render(context)

        logger.warning(
            'Generating new hostname for {} using {} old hostname {}'.format(
                self, template_vars, self.hostname))
        prefix = render_template(ASSET_HOSTNAME_TEMPLATE.get('prefix', ''), )
        postfix = render_template(ASSET_HOSTNAME_TEMPLATE.get('postfix', ''), )
        counter_length = ASSET_HOSTNAME_TEMPLATE.get('counter_length', 5)
        last_hostname = AssetLastHostname.increment_hostname(prefix, postfix)
        self.hostname = last_hostname.formatted_hostname(fill=counter_length)
        if commit:
            self.save()

    # TODO: add message when hostname was changed
    def _try_assign_hostname(self, commit=False, country=None, force=False):
        if self.model.category and self.model.category.code:
            template_vars = {
                'code': self.model.category.code,
                'country_code': country or self.country_code,
            }
            if (force or not self.hostname
                    or self.country_code not in self.hostname):
                self.generate_hostname(commit, template_vars)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.exclude(
            status=BackOfficeAssetStatus.liquidated.id)

    @classmethod
    @transition_action(form_fields={
        'user': {
            'field': forms.CharField(label=_('User')),
            'autocomplete_field': 'user',
            'default_value': partial(autocomplete_user, field_name='user')
        }
    },
                       run_after=['unassign_user'])
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(
        form_fields={
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'default_value': partial(autocomplete_user, field_name='owner')
            }
        },
        help_text=_(
            'During this transition owner will be assigned as well as new '
            'hostname might be generated for asset (only for particular model '
            'categories and only if owner\'s country has changed)'),
        run_after=['unassign_owner'])
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action(form_fields={
        'licences': {
            'field':
            forms.ModelMultipleChoiceField(
                queryset=Licence.objects.all(),
                label=_('Licence'),
                required=False,
            ),
            'autocomplete_field':
            'licence',
            'autocomplete_model':
            'licences.BaseObjectLicence',
            'widget_options': {
                'multi': True
            },
        }
    },
                       run_after=['unassign_licences'])
    def assign_licence(cls, instances, **kwargs):
        for instance in instances:
            for obj in kwargs['licences']:
                BaseObjectLicence.objects.get_or_create(
                    base_object=instance,
                    licence_id=obj.id,
                )

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_owner'] = str(
                instance.owner)
            instance.owner = None

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_user'] = str(
                instance.user)
            instance.user = None

    @classmethod
    @transition_action(
        form_fields={
            'loan_end_date': {
                'field':
                forms.DateField(
                    label=_('Loan end date'),
                    widget=forms.TextInput(attrs={'class': 'datepicker'}))
            }
        }, )
    def assign_loan_end_date(cls, instances, **kwargs):
        for instance in instances:
            instance.loan_end_date = kwargs['loan_end_date']

    @classmethod
    @transition_action()
    def unassign_loan_end_date(cls, instances, **kwargs):
        for instance in instances:
            instance.loan_end_date = None

    @classmethod
    @transition_action(
        form_fields={
            'warehouse': {
                'field': forms.CharField(label=_('Warehouse')),
                'autocomplete_field': 'warehouse'
            }
        })
    def assign_warehouse(cls, instances, **kwargs):
        warehouse = Warehouse.objects.get(pk=int(kwargs['warehouse']))
        for instance in instances:
            instance.warehouse = warehouse

    @classmethod
    @transition_action(
        form_fields={
            'office_infrastructure': {
                'field': forms.CharField(label=_('Office infrastructure')),
                'autocomplete_field': 'office_infrastructure'
            }
        }, )
    def assign_office_infrastructure(cls, instances, **kwargs):
        office_inf = OfficeInfrastructure.objects.get(
            pk=int(kwargs['office_infrastructure']))
        for instance in instances:
            instance.office_infrastructure = office_inf

    @classmethod
    @transition_action(form_fields={
        'remarks': {
            'field': forms.CharField(label=_('Remarks')),
        }
    })
    def add_remarks(cls, instances, **kwargs):
        for instance in instances:
            instance.remarks = '{}\n{}'.format(instance.remarks,
                                               kwargs['remarks'])

    @classmethod
    @transition_action(form_fields={
        'task_url': {
            'field': forms.URLField(label=_('task URL')),
        }
    })
    def assign_task_url(cls, instances, **kwargs):
        for instance in instances:
            instance.task_url = kwargs['task_url']

    @classmethod
    @transition_action()
    def unassign_licences(cls, instances, **kwargs):
        BaseObjectLicence.objects.filter(base_object__in=instances).delete()

    @classmethod
    @transition_action(
        form_fields={
            'country': {
                'field':
                forms.ChoiceField(
                    label=_('Country'),
                    choices=Country(),
                    **{
                        'initial':
                        Country.from_name(
                            settings.CHANGE_HOSTNAME_ACTION_DEFAULT_COUNTRY.
                            lower()  # noqa: E501
                        ).id
                    } if settings.CHANGE_HOSTNAME_ACTION_DEFAULT_COUNTRY else
                    {}),
            }
        }, )
    def change_hostname(cls, instances, **kwargs):
        country_id = kwargs['country']
        country_name = Country.name_from_id(int(country_id)).upper()
        iso3_country_name = iso2_to_iso3(country_name)
        for instance in instances:
            instance._try_assign_hostname(country=iso3_country_name,
                                          force=True)

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
            },
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'condition': lambda obj, actions: bool(obj.owner),
            }
        })
    def change_user_and_owner(cls, instances, **kwargs):
        UserModel = get_user_model()  # noqa
        user_id = kwargs.get('user', None)
        user = UserModel.objects.get(id=user_id)
        owner_id = kwargs.get('owner', None)
        for instance in instances:
            instance.user = user
            if not owner_id:
                instance.owner = user
            else:
                instance.owner = UserModel.objects.get(id=owner_id)
            instance.location = user.location

    @classmethod
    def _get_report_context(cls, instances):
        data_instances = [{
            'sn': obj.sn,
            'model': str(obj.model),
            'imei': obj.imei,
            'imei2': obj.imei2,
            'barcode': obj.barcode,
        } for obj in instances]
        return data_instances

    @classmethod
    @transition_action(precondition=_check_assets_owner)
    def must_be_owner_of_asset(cls, instances, **kwargs):
        """Only a precondition matters"""
        pass

    @classmethod
    @transition_action(precondition=_check_assets_user)
    def must_be_user_of_asset(cls, instances, **kwargs):
        """Only a precondition matters"""
        pass

    @classmethod
    @transition_action(
        form_fields={
            'accept': {
                'field':
                forms.BooleanField(
                    label=_('I have read and fully understand and '
                            'accept the agreement.'))
            },
        })
    def accept_asset_release_agreement(cls, instances, requester, **kwargs):
        pass

    @classmethod
    @transition_action(run_after=['release_report'])
    def assign_requester_as_an_owner(cls, instances, requester, **kwargs):
        """Assign current user as an owner"""
        for instance in instances:
            instance.owner = requester
            instance.save()

    @classmethod
    @transition_action(form_fields={
        'report_language': {
            'field':
            forms.ModelChoiceField(
                label=_('Release report language'),
                queryset=ReportLanguage.objects.all().order_by('-default'),
                empty_label=None),
            'exclude_from_history':
            True
        }
    },
                       return_attachment=True,
                       run_after=['assign_owner', 'assign_user'])
    def release_report(cls, instances, requester, transition_id, **kwargs):
        report_name = get_report_name_for_transition_id(transition_id)
        return generate_report(instances=instances,
                               name=report_name,
                               requester=requester,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(
        run_after=['release_report', 'return_report', 'loan_report'])
    def send_attachments_to_user(cls, requester, transition_id, **kwargs):
        context_func = get_hook('back_office.transition_action.email_context')
        send_transition_attachments_to_user(requester=requester,
                                            transition_id=transition_id,
                                            context_func=context_func,
                                            **kwargs)

    @classmethod
    @transition_action(
        form_fields={
            'report_language': {
                'field':
                forms.ModelChoiceField(
                    label=_('Return report language'),
                    queryset=ReportLanguage.objects.all().order_by('-default'),
                    empty_label=None),
                'exclude_from_history':
                True
            },
        },
        return_attachment=True,
        precondition=_check_user_assigned,
    )
    def return_report(cls, instances, requester, **kwargs):
        return generate_report(instances=instances,
                               name='return',
                               requester=requester,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(
        form_fields={
            'report_language': {
                'field':
                forms.ModelChoiceField(
                    label=_('Loan report language'),
                    queryset=ReportLanguage.objects.all().order_by('-default'),
                    empty_label=None),
                'exclude_from_history':
                True
            }
        },
        return_attachment=True,
        run_after=['assign_owner', 'assign_user', 'assign_loan_end_date'])
    def loan_report(cls, instances, requester, **kwargs):
        return generate_report(name='loan',
                               requester=requester,
                               instances=instances,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(verbose_name=_('Convert to DataCenter Asset'),
                       disable_save_object=True,
                       only_one_action=True,
                       form_fields={
                           'rack': {
                               'field': forms.CharField(label=_('Rack')),
                               'autocomplete_field': 'rack',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           },
                           'position': {
                               'field':
                               forms.IntegerField(label=_('Position')),
                           },
                           'model': {
                               'field': forms.CharField(label=_('Model')),
                               'autocomplete_field': 'model',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           },
                           'service_env': {
                               'field':
                               forms.CharField(label=_('Service env')),
                               'autocomplete_field': 'service_env',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           }
                       })
    def convert_to_data_center_asset(cls, instances, **kwargs):
        from ralph.data_center.models.physical import DataCenterAsset, Rack  # noqa
        from ralph.back_office.helpers import bo_asset_to_dc_asset_status_converter  # noqa
        with transaction.atomic():
            for i, instance in enumerate(instances):
                data_center_asset = DataCenterAsset()
                data_center_asset.rack = Rack.objects.get(pk=kwargs['rack'])
                data_center_asset.position = kwargs['position']
                data_center_asset.service_env = ServiceEnvironment.objects.get(
                    pk=kwargs['service_env'])
                data_center_asset.model = AssetModel.objects.get(
                    pk=kwargs['model'])
                target_status = int(
                    Transition.objects.values_list('target', flat=True).get(
                        pk=kwargs['transition_id'])  # noqa
                )
                data_center_asset.status = bo_asset_to_dc_asset_status_converter(  # noqa
                    instance.status, target_status)
                move_parents_models(instance,
                                    data_center_asset,
                                    exclude_copy_fields=[
                                        'rack', 'model', 'service_env',
                                        'status'
                                    ])
                data_center_asset.save()
                # Save new asset to list, required to redirect url.
                # RunTransitionView.get_success_url()
                instances[i] = data_center_asset

    @classmethod
    @transition_action()
    def assign_hostname_if_empty_or_country_not_match(cls, instances,
                                                      **kwargs):
        for instance in instances:
            instance._try_assign_hostname(commit=False, force=False)
Esempio n. 8
0
class Cluster(
    PreviousStateMixin,
    DNSaaSPublisherMixin,
    AdminAbsoluteUrlMixin,
    WithManagementIPMixin,
    NetworkableBaseObject,
    BaseObject,
    models.Model
):
    name = models.CharField(_('name'), max_length=255, blank=True, null=True)
    hostname = NullableCharField(
        unique=True,
        null=True,
        blank=True,
        max_length=255,
        verbose_name=_('hostname')
    )
    type = models.ForeignKey(ClusterType)
    base_objects = models.ManyToManyField(
        BaseObject,
        verbose_name=_('Assigned base objects'),
        through='BaseObjectCluster',
        related_name='+',
    )
    status = TransitionField(
        default=ClusterStatus.in_use.id,
        choices=ClusterStatus(),
    )

    def __str__(self):
        return '{} ({})'.format(self.name or self.hostname, self.type)

    def get_location(self):
        return self.masters[0].get_location() if self.masters else []

    @property
    def model(self):
        return self.type

    @cached_property
    def masters(self):
        return self.get_masters(cast_base_object=True)

    def get_masters(self, cast_base_object=False):
        # prevents cyclic import
        from ralph.virtual.models import CloudHost, VirtualServer  # noqa

        result = []
        for obj in self.baseobjectcluster_set.all():
            if obj.is_master:
                bo = obj.base_object
                # fetch final object if it's base object
                if cast_base_object and not isinstance(
                    bo,
                    # list equal to BaseObjectCluster.base_object.limit_models
                    (Database, DataCenterAsset, CloudHost, VirtualServer)
                ):
                    bo = bo.last_descendant
                result.append(bo)
        return result

    @cached_property
    def rack_id(self):
        return self.rack.id if self.rack else None

    @cached_property
    def rack(self):
        for master in self.masters:
            if isinstance(master, DataCenterAsset) and master.rack_id:
                return master.rack
        return None

    def _validate_name_hostname(self):
        if not self.name and not self.hostname:
            error_message = [_('At least one of name or hostname is required')]
            raise ValidationError(
                {'name': error_message, 'hostname': error_message}
            )

    def clean(self):
        errors = {}
        for validator in [
            super().clean,
            self._validate_name_hostname,
        ]:
            try:
                validator()
            except ValidationError as e:
                e.update_error_dict(errors)
        if errors:
            raise ValidationError(errors)
Esempio n. 9
0
class NetworkEnvironment(AdminAbsoluteUrlMixin, TimeStampMixin, NamedMixin,
                         models.Model):
    data_center = models.ForeignKey('data_center.DataCenter',
                                    verbose_name=_('data center'))
    queue = models.ForeignKey(
        'DiscoveryQueue',
        verbose_name=_('discovery queue'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    hostname_template_counter_length = models.PositiveIntegerField(
        verbose_name=_('hostname template counter length'),
        default=4,
    )
    hostname_template_prefix = models.CharField(
        verbose_name=_('hostname template prefix'),
        max_length=30,
    )
    hostname_template_postfix = models.CharField(
        verbose_name=_('hostname template postfix'),
        max_length=30,
        help_text=_(
            'This value will be used as a postfix when generating new hostname '
            'in this network environment. For example, when prefix is "s1", '
            'postfix is ".mydc.net" and counter length is 4, following '
            ' hostnames will be generated: s10000.mydc.net, s10001.mydc.net, ..'
            ', s19999.mydc.net.'))
    domain = NullableCharField(
        verbose_name=_('domain'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('Used in DHCP configuration.'),
    )
    remarks = models.TextField(
        verbose_name=_('remarks'),
        help_text=_('Additional information.'),
        blank=True,
        null=True,
    )
    use_hostname_counter = models.BooleanField(
        default=True,
        help_text='If set to false hostname based on already added hostnames.')

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name', )

    @property
    def HOSTNAME_MODELS(self):
        from ralph.data_center.models.virtual import Cluster
        from ralph.data_center.models.physical import DataCenterAsset
        from ralph.virtual.models import VirtualServer

        return (DataCenterAsset, VirtualServer, Cluster, IPAddress)

    @property
    def next_free_hostname(self):
        """
        Retrieve next free hostname
        """
        if self.use_hostname_counter:
            return AssetLastHostname.get_next_free_hostname(
                self.hostname_template_prefix, self.hostname_template_postfix,
                self.hostname_template_counter_length,
                self.check_hostname_is_available)
        else:
            result = self.next_hostname_without_model_counter()
        return result

    def check_hostname_is_available(self, hostname):

        if not hostname:
            return False

        for model_class in self.HOSTNAME_MODELS:
            if model_class.objects.filter(hostname=hostname).exists():
                return False

        return True

    def issue_next_free_hostname(self):
        """
        Retrieve and reserve next free hostname
        """
        if self.use_hostname_counter:
            hostname = None

            while not self.check_hostname_is_available(hostname):
                hostname = AssetLastHostname.increment_hostname(
                    self.hostname_template_prefix,
                    self.hostname_template_postfix,
                ).formatted_hostname(self.hostname_template_counter_length)

            return hostname

        return self.next_hostname_without_model_counter()

    def current_counter_without_model(self):
        """
        Return current counter based on already added hostnames

        Returns:
            counter int
        """
        start = len(self.hostname_template_prefix)
        stop = -len(self.hostname_template_postfix)
        hostnames = []
        for model_class in self.HOSTNAME_MODELS:
            item = model_class.objects.filter(
                hostname__iregex='{}[0-9]+{}'.format(
                    self.hostname_template_prefix, self.
                    hostname_template_postfix)).order_by('-hostname').first()
            if item and item.hostname:
                hostnames.append(item.hostname[start:stop])
        counter = 0
        if hostnames:
            # queryset guarantees that hostnames are valid number
            # therefore we can skip ValueError
            counter = int(sorted(hostnames, reverse=True)[0])
        return counter

    def next_counter_without_model(self):
        """
        Return next counter based on already added hostnames

        Returns:
            counter int
        """
        return self.current_counter_without_model() + 1

    def next_hostname_without_model_counter(self):
        """
        Return hostname based on already added hostnames

        Returns:
            hostname string
        """
        hostname = AssetLastHostname(prefix=self.hostname_template_prefix,
                                     counter=self.next_counter_without_model(),
                                     postfix=self.hostname_template_postfix)
        return hostname.formatted_hostname(
            self.hostname_template_counter_length)
Esempio n. 10
0
class Job(TimeStampMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    username = NullableCharField(max_length=200, null=True, blank=True)
    service_name = models.CharField(max_length=200, null=False, blank=False)
    _dumped_params = JSONField()
    status = models.PositiveIntegerField(
        verbose_name=_('job status'),
        choices=JobStatus(),
        default=JobStatus.QUEUED.id,
    )
    _params = None
    objects = JobQuerySet.as_manager()

    class Meta:
        app_label = 'external_services'

    def __str__(self):
        return '{} ({})'.format(self.service_name, self.id)

    @property
    def user(self):
        try:
            return self.params['_request__user']
        except KeyError:
            if self.username:
                try:
                    return get_user_model()._default_manager.get(
                        username=self.username
                    )
                except get_user_model().DoesNotExist:
                    pass
        return None

    @property
    def is_running(self):
        """
        Return True if job is not ended.
        """
        return self.status in JOB_NOT_ENDED_STATUSES

    @property
    def is_frozen(self):
        """
        Return True if job is frozen.
        """
        return self.status == JobStatus.FROZEN

    @property
    def is_killed(self):
        """
        Return True if job is killed.
        """
        return self.status == JobStatus.KILLED

    @property
    def params(self):
        """
        Should be called on job-side to extract params as they were passed to
        job.
        """
        if self._params is None:
            self._params = self._restore_params(self._dumped_params)
            logger.debug('{} restored into {}'.format(
                self._dumped_params, self._params
            ))
        return self._params

    def _get_metric_name(self):
        return self.service_name

    def _update_dumped_params(self):
        # re-save job to store updated params in DB
        self._dumped_params = self.prepare_params(**self.params)
        logger.debug('Updating _dumped_params to {}'.format(
            self._dumped_params
        ))
        self.save()

    @collect_metrics('start')
    def start(self):
        """
        Mark job as started.
        """
        logger.info('Starting job {}'.format(self))
        self.status = JobStatus.STARTED
        self.save()

    @collect_metrics('reschedule')
    def reschedule(self):
        """
        Reschedule the same job again.
        """
        # TODO: use rq scheduler
        self._update_dumped_params()
        logger.info('Rescheduling {}'.format(self))
        service = InternalService(self.service_name)
        job = service.run_async(job_id=self.id)
        return job

    @collect_metrics('freeze')
    def freeze(self):
        self._update_dumped_params()
        logger.info('Freezing job {}'.format(self))
        self.status = JobStatus.FROZEN
        self.save()

    @collect_metrics('unfreeze')
    def unfreeze(self):
        logger.info('Unfreezing {}'.format(self))
        service = InternalService(self.service_name)
        job = service.run_async(job_id=self.id)
        return job

    @collect_metrics('kill')
    def kill(self):
        logger.info('Kill job {}'.format(self))
        self.status = JobStatus.KILLED
        self.save()

    @collect_metrics('fail')
    def fail(self, reason=''):
        """
        Mark job as failed.
        """
        self._update_dumped_params()
        logger.info('Job {} has failed. Reason: {}'.format(self, reason))
        self.status = JobStatus.FAILED
        self.save()

    @collect_metrics('success')
    def success(self):
        """
        Mark job as successfuly ended.
        """
        self._update_dumped_params()
        logger.info('Job {} has succeeded'.format(self))
        self.status = JobStatus.FINISHED
        self.save()

    @classmethod
    def prepare_params(cls, **kwargs):
        user = kwargs.pop('requester', None)
        result = cls.dump_obj_to_jsonable(kwargs)
        result['_request__user'] = (
            result.get('_request__user') or
            (cls.dump_obj_to_jsonable(user) if user else None)
        )
        logger.debug('{} prepared into {}'.format(kwargs, result))
        return result

    @classmethod
    def run(cls, service_name, requester, defaults=None, **kwargs):
        """
        Run Job asynchronously in internal service (with DB and models access).
        """
        service = InternalService(service_name)
        obj = cls._default_manager.create(
            service_name=service_name,
            username=requester.username if requester else None,
            _dumped_params=cls.prepare_params(requester=requester, **kwargs),
            **(defaults or {})
        )
        # commit transaction to allow worker to fetch it using job id
        transaction.commit()
        service.run_async(job_id=obj.id)
        return obj.id, obj

    @classmethod
    def dump_obj_to_jsonable(cls, obj):
        """
        Dump obj to JSON-acceptable format
        """
        result = obj
        if isinstance(obj, (list, tuple, set)):
            result = [cls.dump_obj_to_jsonable(p) for p in obj]
        elif isinstance(obj, QuerySet):
            result = {
                '__django_queryset': True,
                'value': [i.pk for i in obj],
                'content_type_id': ContentType.objects.get_for_model(
                    obj.model
                ).pk
            }
        elif isinstance(obj, date):
            result = {
                '__date': True,
                'value': str(obj)
            }
        elif isinstance(obj, dict):
            result = {}
            for k, v in obj.items():
                result[k] = cls.dump_obj_to_jsonable(v)
        elif isinstance(obj, models.Model):
            # save Django object as 3-items dict with content type and object id
            result = {
                '__django_model': True,
                'content_type_id': ContentType.objects.get_for_model(obj).pk,
                'object_pk': obj.pk,
            }
        return result

    @classmethod
    def _restore_params(cls, obj):
        return cls._restore_django_models(obj)

    @classmethod
    def _restore_django_models(cls, obj):
        """
        Restore Django objects from dump created with `dump_obj_to_jsonable`
        """
        result = obj
        if isinstance(obj, (list, tuple)):
            result = [cls._restore_django_models(p) for p in obj]
        elif isinstance(obj, dict):
            if obj.get('__date') is True:
                result = parse(obj.get('value')).date()
            elif obj.get('__django_queryset') is True:
                ct = ContentType.objects.get_for_id(obj['content_type_id'])
                result = ct.model_class().objects.filter(
                    pk__in=obj.get('value')
                )
            elif obj.get('__django_model') is True:
                ct = ContentType.objects.get_for_id(obj['content_type_id'])
                result = ct.get_object_for_this_type(pk=obj['object_pk'])
            else:
                result = {}
                for k, v in obj.items():
                    result[k] = cls._restore_django_models(v)
        return result
Esempio n. 11
0
class IPAddress(AdminAbsoluteUrlMixin, LastSeenMixin, TimeStampMixin,
                PreviousStateMixin, NetworkMixin, models.Model):
    _parent_attr = 'network'

    ethernet = models.OneToOneField(
        Ethernet,
        null=True,
        default=None,
        blank=True,
        on_delete=models.CASCADE,
    )
    network = models.ForeignKey(
        Network,
        null=True,
        default=None,
        editable=False,
        related_name='ips',
        on_delete=models.SET_NULL,
    )
    address = models.GenericIPAddressField(
        verbose_name=_('IP address'),
        help_text=_('Presented as string.'),
        unique=True,
        blank=False,
        null=False,
    )
    hostname = NullableCharField(
        verbose_name=_('hostname'),
        max_length=255,
        null=True,
        blank=True,
        default=None,
        # TODO: unique
    )
    number = models.DecimalField(
        verbose_name=_('IP address'),
        help_text=_('Presented as int.'),
        editable=False,
        unique=True,
        max_digits=39,
        decimal_places=0,
        default=None,
    )
    is_management = models.BooleanField(
        verbose_name=_('Is management address'),
        default=False,
    )
    is_public = models.BooleanField(
        verbose_name=_('Is public'),
        default=False,
        editable=False,
    )
    is_gateway = models.BooleanField(
        verbose_name=_('Is gateway'),
        default=False,
    )
    status = models.PositiveSmallIntegerField(
        default=IPAddressStatus.used.id,
        choices=IPAddressStatus(),
    )
    dhcp_expose = models.BooleanField(
        default=False,
        verbose_name=_('Expose in DHCP'),
    )
    objects = IPAddressQuerySet.as_manager()

    class Meta:
        verbose_name = _('IP address')
        verbose_name_plural = _('IP addresses')

    def __str__(self):
        return self.address

    def _hostname_is_unique_in_dc(self, hostname, dc):
        from ralph.dhcp.models import DHCPEntry
        entries_with_hostname = DHCPEntry.objects.filter(
            hostname=hostname, network__network_environment__data_center=dc)
        if self.pk:
            entries_with_hostname = entries_with_hostname.exclude(pk=self.pk)
        return not entries_with_hostname.exists()

    def validate_hostname_uniqueness_in_dc(self, hostname):
        network = self.get_network()
        if network and network.network_environment:
            dc = network.network_environment.data_center
            if not self._hostname_is_unique_in_dc(hostname, dc):
                raise ValidationError(
                    'Hostname "{hostname}" is already exposed in DHCP in {dc}.'
                    .format(hostname=self.hostname, dc=dc))

    def _validate_hostname_uniqueness_in_dc(self):
        if not self.dhcp_expose:
            return
        self.validate_hostname_uniqueness_in_dc(self.hostname)

    def _validate_expose_in_dhcp_and_mac(self):
        if ((not self.ethernet_id or (self.ethernet and not self.ethernet.mac))
                and  # noqa
                self.dhcp_expose):
            raise ValidationError(
                {'dhcp_expose': ('Cannot expose in DHCP without MAC address')})

    def _validate_expose_in_dhcp_and_hostname(self):
        if not self.hostname and self.dhcp_expose:
            raise ValidationError(
                {'hostname': ('Cannot expose in DHCP without hostname')})

    def _validate_change_when_exposing_in_dhcp(self):
        """
        Check if one of hostname, address or ethernet is changed
        when entry is exposed in DHCP.
        """
        if self.pk and settings.DHCP_ENTRY_FORBID_CHANGE:
            old_obj = self.__class__._default_manager.get(pk=self.pk)
            if old_obj.dhcp_expose:
                for attr_name, field_name in [
                    ('hostname', 'hostname'),
                    ('address', 'address'),
                    ('ethernet_id', 'ethernet'),
                ]:
                    if getattr(old_obj, attr_name) != getattr(self, attr_name):
                        raise ValidationError(
                            'Cannot change {} when exposing in DHCP'.format(
                                field_name))

    def clean(self):
        errors = {}
        for validator in [
                super().clean,
                self._validate_expose_in_dhcp_and_mac,
                self._validate_expose_in_dhcp_and_hostname,
                self._validate_change_when_exposing_in_dhcp,
                self._validate_hostname_uniqueness_in_dc,
        ]:
            try:
                validator()
            except ValidationError as e:
                e.update_error_dict(errors)
        if errors:
            raise ValidationError(errors)

    def save(self, *args, **kwargs):
        if settings.CHECK_IP_HOSTNAME_ON_SAVE:
            if not self.address and self.hostname:
                self.address = network_tools.hostname(self.hostname,
                                                      reverse=True)
            if not self.hostname and self.address:
                self.hostname = network_tools.hostname(self.address)
        if self.number and not self.address:
            self.address = ipaddress.ip_address(int(self.number))
        else:
            self.number = int(ipaddress.ip_address(self.address or 0))
        self._assign_parent()
        self.is_public = not self.ip.is_private
        # TODO: if not reserved, check for ethernet
        super(IPAddress, self).save(*args, **kwargs)

    @property
    def ip(self):
        return ipaddress.ip_address(self.address)

    @property
    def base_object(self):
        if not self.ethernet:
            return None
        return self.ethernet.base_object

    @base_object.setter
    def base_object(self, value):
        if self.ethernet:
            self.ethernet.base_object = value
            self.ethernet.save()
        else:
            eth = Ethernet.objects.create(base_object=value)
            self.ethernet = eth
            self.save()

    def search_networks(self):
        """
        Search networks (ancestors) order first by min_ip descending,
        then by max_ip ascending, to get smallest ancestor network
        containing current network.
        """
        int_value = int(self.ip)
        nets = Network.objects.filter(min_ip__lte=int_value,
                                      max_ip__gte=int_value).order_by(
                                          '-min_ip', 'max_ip')
        return nets