2022-11-04 13:49:31 -04:00
|
|
|
from django.contrib.contenttypes.fields import GenericRelation
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from django.db import models
|
|
|
|
from django.urls import reverse
|
2023-07-31 22:28:07 +07:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2022-11-04 13:49:31 -04:00
|
|
|
|
|
|
|
from dcim.models import Device
|
|
|
|
from netbox.models import OrganizationalModel, PrimaryModel
|
2023-08-04 09:43:44 -04:00
|
|
|
from netbox.models.features import ContactsMixin
|
2022-11-04 13:49:31 -04:00
|
|
|
from virtualization.choices import *
|
|
|
|
|
|
|
|
__all__ = (
|
|
|
|
'Cluster',
|
|
|
|
'ClusterGroup',
|
|
|
|
'ClusterType',
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class ClusterType(OrganizationalModel):
|
|
|
|
"""
|
|
|
|
A type of Cluster.
|
|
|
|
"""
|
2023-08-03 10:41:10 -04:00
|
|
|
class Meta:
|
|
|
|
ordering = ('name',)
|
|
|
|
verbose_name = _('cluster type')
|
|
|
|
verbose_name_plural = _('cluster types')
|
|
|
|
|
2022-11-04 13:49:31 -04:00
|
|
|
def get_absolute_url(self):
|
|
|
|
return reverse('virtualization:clustertype', args=[self.pk])
|
|
|
|
|
|
|
|
|
2023-08-04 09:43:44 -04:00
|
|
|
class ClusterGroup(ContactsMixin, OrganizationalModel):
|
2022-11-04 13:49:31 -04:00
|
|
|
"""
|
|
|
|
An organizational group of Clusters.
|
|
|
|
"""
|
|
|
|
vlan_groups = GenericRelation(
|
|
|
|
to='ipam.VLANGroup',
|
|
|
|
content_type_field='scope_type',
|
|
|
|
object_id_field='scope_id',
|
|
|
|
related_query_name='cluster_group'
|
|
|
|
)
|
|
|
|
|
2023-08-03 10:41:10 -04:00
|
|
|
class Meta:
|
|
|
|
ordering = ('name',)
|
|
|
|
verbose_name = _('cluster group')
|
|
|
|
verbose_name_plural = _('cluster groups')
|
|
|
|
|
2022-11-04 13:49:31 -04:00
|
|
|
def get_absolute_url(self):
|
|
|
|
return reverse('virtualization:clustergroup', args=[self.pk])
|
|
|
|
|
|
|
|
|
2023-08-04 09:43:44 -04:00
|
|
|
class Cluster(ContactsMixin, PrimaryModel):
|
2022-11-04 13:49:31 -04:00
|
|
|
"""
|
|
|
|
A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
|
|
|
|
"""
|
|
|
|
name = models.CharField(
|
2023-07-31 22:28:07 +07:00
|
|
|
verbose_name=_('name'),
|
2022-11-04 13:49:31 -04:00
|
|
|
max_length=100
|
|
|
|
)
|
|
|
|
type = models.ForeignKey(
|
2023-07-31 22:28:07 +07:00
|
|
|
verbose_name=_('type'),
|
2022-11-04 13:49:31 -04:00
|
|
|
to=ClusterType,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='clusters'
|
|
|
|
)
|
|
|
|
group = models.ForeignKey(
|
|
|
|
to=ClusterGroup,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='clusters',
|
|
|
|
blank=True,
|
|
|
|
null=True
|
|
|
|
)
|
|
|
|
status = models.CharField(
|
2023-07-31 22:28:07 +07:00
|
|
|
verbose_name=_('status'),
|
2022-11-04 13:49:31 -04:00
|
|
|
max_length=50,
|
|
|
|
choices=ClusterStatusChoices,
|
|
|
|
default=ClusterStatusChoices.STATUS_ACTIVE
|
|
|
|
)
|
|
|
|
tenant = models.ForeignKey(
|
|
|
|
to='tenancy.Tenant',
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='clusters',
|
|
|
|
blank=True,
|
|
|
|
null=True
|
|
|
|
)
|
|
|
|
site = models.ForeignKey(
|
|
|
|
to='dcim.Site',
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='clusters',
|
|
|
|
blank=True,
|
|
|
|
null=True
|
|
|
|
)
|
|
|
|
|
|
|
|
# Generic relations
|
|
|
|
vlan_groups = GenericRelation(
|
|
|
|
to='ipam.VLANGroup',
|
|
|
|
content_type_field='scope_type',
|
|
|
|
object_id_field='scope_id',
|
|
|
|
related_query_name='cluster'
|
|
|
|
)
|
|
|
|
|
|
|
|
clone_fields = (
|
|
|
|
'type', 'group', 'status', 'tenant', 'site',
|
|
|
|
)
|
2022-11-16 17:22:09 -05:00
|
|
|
prerequisite_models = (
|
|
|
|
'virtualization.ClusterType',
|
|
|
|
)
|
2022-11-04 13:49:31 -04:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
ordering = ['name']
|
|
|
|
constraints = (
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=('group', 'name'),
|
|
|
|
name='%(app_label)s_%(class)s_unique_group_name'
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=('site', 'name'),
|
|
|
|
name='%(app_label)s_%(class)s_unique_site_name'
|
|
|
|
),
|
|
|
|
)
|
2023-08-03 10:41:10 -04:00
|
|
|
verbose_name = _('cluster')
|
|
|
|
verbose_name_plural = _('clusters')
|
2022-11-04 13:49:31 -04:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def get_absolute_url(self):
|
|
|
|
return reverse('virtualization:cluster', args=[self.pk])
|
|
|
|
|
|
|
|
def get_status_color(self):
|
|
|
|
return ClusterStatusChoices.colors.get(self.status)
|
|
|
|
|
|
|
|
def clean(self):
|
|
|
|
super().clean()
|
|
|
|
|
|
|
|
# If the Cluster is assigned to a Site, verify that all host Devices belong to that Site.
|
|
|
|
if self.pk and self.site:
|
2023-10-30 13:38:03 -04:00
|
|
|
if nonsite_devices := Device.objects.filter(cluster=self).exclude(site=self.site).count():
|
2022-11-04 13:49:31 -04:00
|
|
|
raise ValidationError({
|
2023-10-30 13:38:03 -04:00
|
|
|
'site': _(
|
|
|
|
"{count} devices are assigned as hosts for this cluster but are not in site {site}"
|
|
|
|
).format(count=nonsite_devices, site=self.site)
|
2022-11-04 13:49:31 -04:00
|
|
|
})
|