Esempio n. 1
0
class Rack(CreatedUpdatedModel, CustomFieldModel):
    """
    Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
    Each Rack is assigned to a Site and (optionally) a RackGroup.
    """
    name = models.CharField(max_length=50)
    facility_id = NullableCharField(max_length=30,
                                    blank=True,
                                    null=True,
                                    verbose_name='Facility ID')
    site = models.ForeignKey('Site',
                             related_name='racks',
                             on_delete=models.PROTECT)
    group = models.ForeignKey('RackGroup',
                              related_name='racks',
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL)
    tenant = models.ForeignKey(Tenant,
                               blank=True,
                               null=True,
                               related_name='racks',
                               on_delete=models.PROTECT)
    role = models.ForeignKey('RackRole',
                             related_name='racks',
                             blank=True,
                             null=True,
                             on_delete=models.PROTECT)
    type = models.PositiveSmallIntegerField(choices=RACK_TYPE_CHOICES,
                                            blank=True,
                                            null=True,
                                            verbose_name='Type')
    width = models.PositiveSmallIntegerField(choices=RACK_WIDTH_CHOICES,
                                             default=RACK_WIDTH_19IN,
                                             verbose_name='Width',
                                             help_text='Rail-to-rail width')
    u_height = models.PositiveSmallIntegerField(
        default=42,
        verbose_name='Height (U)',
        validators=[MinValueValidator(1),
                    MaxValueValidator(100)])
    comments = models.TextField(blank=True)
    custom_field_values = GenericRelation(CustomFieldValue,
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    objects = RackManager()

    class Meta:
        ordering = ['site', 'name']
        unique_together = [
            ['site', 'name'],
            ['site', 'facility_id'],
        ]

    def __unicode__(self):
        return self.display_name

    def get_absolute_url(self):
        return reverse('dcim:rack', args=[self.pk])

    def clean(self):

        # Validate that Rack is tall enough to house the installed Devices
        if self.pk:
            top_device = Device.objects.filter(rack=self).exclude(
                position__isnull=True).order_by('-position').first()
            if top_device:
                min_height = top_device.position + top_device.device_type.u_height - 1
                if self.u_height < min_height:
                    raise ValidationError(
                        "Rack must be at least {}U tall with currently installed devices."
                        .format(min_height))

    def to_csv(self):
        return ','.join([
            self.site.name,
            self.group.name if self.group else '',
            self.name,
            self.facility_id or '',
            self.tenant.name if self.tenant else '',
            self.role.name if self.role else '',
            self.get_type_display() if self.type else '',
            str(self.width),
            str(self.u_height),
        ])

    @property
    def units(self):
        return reversed(range(1, self.u_height + 1))

    @property
    def display_name(self):
        if self.facility_id:
            return u"{} ({})".format(self.name, self.facility_id)
        return self.name

    def get_rack_units(self,
                       face=RACK_FACE_FRONT,
                       exclude=None,
                       remove_redundant=False):
        """
        Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
        Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.

        :param face: Rack face (front or rear)
        :param exclude: PK of a Device to exclude (optional); helpful when relocating a Device within a Rack
        :param remove_redundant: If True, rack units occupied by a device already listed will be omitted
        """

        elevation = OrderedDict()
        for u in reversed(range(1, self.u_height + 1)):
            elevation[u] = {
                'id': u,
                'name': 'U{}'.format(u),
                'face': face,
                'device': None
            }

        # Add devices to rack units list
        if self.pk:
            for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
                    .annotate(devicebay_count=Count('device_bays'))\
                    .exclude(pk=exclude)\
                    .filter(rack=self, position__gt=0)\
                    .filter(Q(face=face) | Q(device_type__is_full_depth=True)):
                if remove_redundant:
                    elevation[device.position]['device'] = device
                    for u in range(
                            device.position + 1,
                            device.position + device.device_type.u_height):
                        elevation.pop(u, None)
                else:
                    for u in range(
                            device.position,
                            device.position + device.device_type.u_height):
                        elevation[u]['device'] = device

        return [u for u in elevation.values()]

    def get_front_elevation(self):
        return self.get_rack_units(face=RACK_FACE_FRONT, remove_redundant=True)

    def get_rear_elevation(self):
        return self.get_rack_units(face=RACK_FACE_REAR, remove_redundant=True)

    def get_available_units(self, u_height=1, rack_face=None, exclude=list()):
        """
        Return a list of units within the rack available to accommodate a device of a given U height (default 1).
        Optionally exclude one or more devices when calculating empty units (needed when moving a device from one
        position to another within a rack).

        :param u_height: Minimum number of contiguous free units required
        :param rack_face: The face of the rack (front or rear) required; 'None' if device is full depth
        :param exclude: List of devices IDs to exclude (useful when moving a device within a rack)
        """

        # Gather all devices which consume U space within the rack
        devices = self.devices.select_related().filter(
            position__gte=1).exclude(pk__in=exclude)

        # Initialize the rack unit skeleton
        units = range(1, self.u_height + 1)

        # Remove units consumed by installed devices
        for d in devices:
            if rack_face is None or d.face == rack_face or d.device_type.is_full_depth:
                for u in range(d.position,
                               d.position + d.device_type.u_height):
                    try:
                        units.remove(u)
                    except ValueError:
                        # Found overlapping devices in the rack!
                        pass

        # Remove units without enough space above them to accommodate a device of the specified height
        available_units = []
        for u in units:
            if set(range(u, u + u_height)).issubset(units):
                available_units.append(u)

        return list(reversed(available_units))

    def get_0u_devices(self):
        return self.devices.filter(position=0)

    def get_utilization(self):
        """
        Determine the utilization rate of the rack and return it as a percentage.
        """
        if self.u_consumed is None:
            self.u_consumed = 0
        u_available = self.u_height - self.u_consumed
        return int(float(self.u_height - u_available) / self.u_height * 100)
Esempio n. 2
0
class Device(CreatedUpdatedModel, CustomFieldModel):
    """
    A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
    DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.

    Each Device must be assigned to a Rack, although associating it with a particular rack face or unit is optional (for
    example, vertically mounted PDUs do not consume rack units).

    When a new Device is created, console/power/interface components are created along with it as dictated by the
    component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the
    creation of a Device.
    """
    device_type = models.ForeignKey('DeviceType',
                                    related_name='instances',
                                    on_delete=models.PROTECT)
    device_role = models.ForeignKey('DeviceRole',
                                    related_name='devices',
                                    on_delete=models.PROTECT)
    tenant = models.ForeignKey(Tenant,
                               blank=True,
                               null=True,
                               related_name='devices',
                               on_delete=models.PROTECT)
    platform = models.ForeignKey('Platform',
                                 related_name='devices',
                                 blank=True,
                                 null=True,
                                 on_delete=models.SET_NULL)
    name = NullableCharField(max_length=50, blank=True, null=True, unique=True)
    serial = models.CharField(max_length=50,
                              blank=True,
                              verbose_name='Serial number')
    asset_tag = NullableCharField(
        max_length=50,
        blank=True,
        null=True,
        unique=True,
        verbose_name='Asset tag',
        help_text='A unique tag used to identify this device')
    rack = models.ForeignKey('Rack',
                             related_name='devices',
                             on_delete=models.PROTECT)
    position = models.PositiveSmallIntegerField(
        blank=True,
        null=True,
        validators=[MinValueValidator(1)],
        verbose_name='Position (U)',
        help_text='Number of the lowest U position occupied by the device')
    face = models.PositiveSmallIntegerField(blank=True,
                                            null=True,
                                            choices=RACK_FACE_CHOICES,
                                            verbose_name='Rack face')
    status = models.BooleanField(choices=STATUS_CHOICES,
                                 default=STATUS_ACTIVE,
                                 verbose_name='Status')
    primary_ip4 = models.OneToOneField('ipam.IPAddress',
                                       related_name='primary_ip4_for',
                                       on_delete=models.SET_NULL,
                                       blank=True,
                                       null=True,
                                       verbose_name='Primary IPv4')
    primary_ip6 = models.OneToOneField('ipam.IPAddress',
                                       related_name='primary_ip6_for',
                                       on_delete=models.SET_NULL,
                                       blank=True,
                                       null=True,
                                       verbose_name='Primary IPv6')
    comments = models.TextField(blank=True)
    custom_field_values = GenericRelation(CustomFieldValue,
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    objects = DeviceManager()

    class Meta:
        ordering = ['name']
        unique_together = ['rack', 'position', 'face']

    def __unicode__(self):
        return self.display_name

    def get_absolute_url(self):
        return reverse('dcim:device', args=[self.pk])

    def clean(self):

        # Validate device type assignment
        if not hasattr(self, 'device_type'):
            raise ValidationError("Must specify device type.")

        # Child devices cannot be assigned to a rack face/unit
        if self.device_type.is_child_device and (self.face is not None
                                                 or self.position):
            raise ValidationError(
                "Child device types cannot be assigned a rack face or position."
            )

        # Validate position/face combination
        if self.position and self.face is None:
            raise ValidationError("Must specify rack face with rack position.")

        # Validate rack space
        rack_face = self.face if not self.device_type.is_full_depth else None
        exclude_list = [self.pk] if self.pk else []
        try:
            available_units = self.rack.get_available_units(
                u_height=self.device_type.u_height,
                rack_face=rack_face,
                exclude=exclude_list)
            if self.position and self.position not in available_units:
                raise ValidationError(
                    "U{} is already occupied or does not have sufficient space to accommodate a(n) "
                    "{} ({}U).".format(self.position, self.device_type,
                                       self.device_type.u_height))
        except Rack.DoesNotExist:
            pass

    def save(self, *args, **kwargs):

        is_new = not bool(self.pk)

        super(Device, self).save(*args, **kwargs)

        # If this is a new Device, instantiate all of the related components per the DeviceType definition
        if is_new:
            ConsolePort.objects.bulk_create([
                ConsolePort(device=self, name=template.name)
                for template in self.device_type.console_port_templates.all()
            ])
            ConsoleServerPort.objects.bulk_create([
                ConsoleServerPort(device=self, name=template.name)
                for template in self.device_type.cs_port_templates.all()
            ])
            PowerPort.objects.bulk_create([
                PowerPort(device=self, name=template.name)
                for template in self.device_type.power_port_templates.all()
            ])
            PowerOutlet.objects.bulk_create([
                PowerOutlet(device=self, name=template.name)
                for template in self.device_type.power_outlet_templates.all()
            ])
            Interface.objects.bulk_create([
                Interface(device=self,
                          name=template.name,
                          form_factor=template.form_factor,
                          mgmt_only=template.mgmt_only)
                for template in self.device_type.interface_templates.all()
            ])
            DeviceBay.objects.bulk_create([
                DeviceBay(device=self, name=template.name)
                for template in self.device_type.device_bay_templates.all()
            ])

        # Update Rack assignment for any child Devices
        Device.objects.filter(parent_bay__device=self).update(rack=self.rack)

    def to_csv(self):
        return ','.join([
            self.name or '',
            self.device_role.name,
            self.tenant.name if self.tenant else '',
            self.device_type.manufacturer.name,
            self.device_type.model,
            self.platform.name if self.platform else '',
            self.serial,
            self.asset_tag if self.asset_tag else '',
            self.rack.site.name,
            self.rack.name,
            str(self.position) if self.position else '',
            self.get_face_display() or '',
        ])

    @property
    def display_name(self):
        if self.name:
            return self.name
        elif self.position:
            return u"{} ({} U{})".format(self.device_type, self.rack.name,
                                         self.position)
        else:
            return u"{} ({})".format(self.device_type, self.rack.name)

    @property
    def identifier(self):
        """
        Return the device name if set; otherwise return the Device's primary key as {pk}
        """
        if self.name is not None:
            return self.name
        return '{{{}}}'.format(self.pk)

    @property
    def primary_ip(self):
        if settings.PREFER_IPV4 and self.primary_ip4:
            return self.primary_ip4
        elif self.primary_ip6:
            return self.primary_ip6
        elif self.primary_ip4:
            return self.primary_ip4
        else:
            return None

    def get_children(self):
        """
        Return the set of child Devices installed in DeviceBays within this Device.
        """
        return Device.objects.filter(parent_bay__device=self.pk)

    def get_rpc_client(self):
        """
        Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined.
        """
        if not self.platform:
            return None
        return RPC_CLIENTS.get(self.platform.rpc_client)
Esempio n. 3
0
class Rack(CreatedUpdatedModel):
    """
    Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
    Each Rack is assigned to a Site and (optionally) a RackGroup.
    """
    name = models.CharField(max_length=50)
    facility_id = NullableCharField(max_length=30,
                                    blank=True,
                                    null=True,
                                    verbose_name='Facility ID')
    site = models.ForeignKey('Site',
                             related_name='racks',
                             on_delete=models.PROTECT)
    group = models.ForeignKey('RackGroup',
                              related_name='racks',
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL)
    u_height = models.PositiveSmallIntegerField(default=42,
                                                verbose_name='Height (U)')
    comments = models.TextField(blank=True)

    class Meta:
        ordering = ['site', 'name']
        unique_together = [
            ['site', 'name'],
            ['site', 'facility_id'],
        ]

    def __unicode__(self):
        return self.display_name

    def get_absolute_url(self):
        return reverse('dcim:rack', args=[self.pk])

    def to_csv(self):
        return ','.join([
            self.site.name,
            self.group.name if self.group else '',
            self.name,
            self.facility_id or '',
            str(self.u_height),
        ])

    @property
    def units(self):
        return reversed(range(1, self.u_height + 1))

    @property
    def display_name(self):
        if self.facility_id:
            return "{} ({})".format(self.name, self.facility_id)
        return self.name

    def get_rack_units(self, face=RACK_FACE_FRONT, remove_redundant=False):
        """
        Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
        Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.

        :param face: Rack face (front or rear)
        :param remove_redundant: If True, rack units occupied by a device already listed will be omitted
        """

        elevation = OrderedDict()
        for u in reversed(range(1, self.u_height + 1)):
            elevation[u] = {
                'id': u,
                'name': 'U{}'.format(u),
                'face': face,
                'device': None
            }

        # Add devices to rack units list
        if self.pk:
            for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
                    .filter(rack=self, position__gt=0).filter(Q(face=face) | Q(device_type__is_full_depth=True)):
                if remove_redundant:
                    elevation[device.position]['device'] = device
                    for u in range(
                            device.position + 1,
                            device.position + device.device_type.u_height):
                        elevation.pop(u, None)
                else:
                    for u in range(
                            device.position,
                            device.position + device.device_type.u_height):
                        elevation[u]['device'] = device

        return [u for u in elevation.values()]

    def get_front_elevation(self):
        return self.get_rack_units(face=RACK_FACE_FRONT, remove_redundant=True)

    def get_rear_elevation(self):
        return self.get_rack_units(face=RACK_FACE_REAR, remove_redundant=True)

    def get_available_units(self, u_height=1, rack_face=None, exclude=list()):
        """
        Return a list of units within the rack available to accommodate a device of a given U height (default 1).
        Optionally exclude one or more devices when calculating empty units (needed when moving a device from one
        position to another within a rack).

        :param u_height: Minimum number of contiguous free units required
        :param rack_face: The face of the rack (front or rear) required; 'None' if device is full depth
        :param exclude: List of devices IDs to exclude (useful when moving a device within a rack)
        """

        # Gather all devices which consume U space within the rack
        devices = self.devices.select_related().filter(
            position__gte=1).exclude(pk__in=exclude)

        # Initialize the rack unit skeleton
        units = range(1, self.u_height + 1)

        # Remove units consumed by installed devices
        for d in devices:
            if rack_face is None or d.face == rack_face or d.device_type.is_full_depth:
                for u in range(d.position,
                               d.position + d.device_type.u_height):
                    try:
                        units.remove(u)
                    except ValueError:
                        # Found overlapping devices in the rack!
                        pass

        # Remove units without enough space above them to accommodate a device of the specified height
        available_units = []
        for u in units:
            if set(range(u, u + u_height)).issubset(units):
                available_units.append(u)

        return list(reversed(available_units))

    def get_0u_devices(self):
        return self.devices.filter(position=0)