2
0

models.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. from __future__ import unicode_literals
  2. from django.conf import settings
  3. from django.contrib.contenttypes.fields import GenericRelation
  4. from django.core.exceptions import ValidationError
  5. from django.db import models
  6. from django.urls import reverse
  7. from django.utils.encoding import python_2_unicode_compatible
  8. from dcim.models import Device
  9. from extras.models import CustomFieldModel, CustomFieldValue
  10. from utilities.models import CreatedUpdatedModel
  11. from utilities.utils import csv_format
  12. from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES
  13. #
  14. # Cluster types
  15. #
  16. @python_2_unicode_compatible
  17. class ClusterType(models.Model):
  18. """
  19. A type of Cluster.
  20. """
  21. name = models.CharField(
  22. max_length=50,
  23. unique=True
  24. )
  25. slug = models.SlugField(
  26. unique=True
  27. )
  28. class Meta:
  29. ordering = ['name']
  30. def __str__(self):
  31. return self.name
  32. def get_absolute_url(self):
  33. return "{}?type={}".format(reverse('virtualization:cluster_list'), self.slug)
  34. #
  35. # Cluster groups
  36. #
  37. @python_2_unicode_compatible
  38. class ClusterGroup(models.Model):
  39. """
  40. An organizational group of Clusters.
  41. """
  42. name = models.CharField(
  43. max_length=50,
  44. unique=True
  45. )
  46. slug = models.SlugField(
  47. unique=True
  48. )
  49. class Meta:
  50. ordering = ['name']
  51. def __str__(self):
  52. return self.name
  53. def get_absolute_url(self):
  54. return "{}?group={}".format(reverse('virtualization:cluster_list'), self.slug)
  55. #
  56. # Clusters
  57. #
  58. @python_2_unicode_compatible
  59. class Cluster(CreatedUpdatedModel, CustomFieldModel):
  60. """
  61. A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
  62. """
  63. name = models.CharField(
  64. max_length=100,
  65. unique=True
  66. )
  67. type = models.ForeignKey(
  68. to=ClusterType,
  69. on_delete=models.PROTECT,
  70. related_name='clusters'
  71. )
  72. group = models.ForeignKey(
  73. to=ClusterGroup,
  74. on_delete=models.PROTECT,
  75. related_name='clusters',
  76. blank=True,
  77. null=True
  78. )
  79. site = models.ForeignKey(
  80. to='dcim.Site',
  81. on_delete=models.PROTECT,
  82. related_name='clusters',
  83. blank=True,
  84. null=True
  85. )
  86. comments = models.TextField(
  87. blank=True
  88. )
  89. custom_field_values = GenericRelation(
  90. to=CustomFieldValue,
  91. content_type_field='obj_type',
  92. object_id_field='obj_id'
  93. )
  94. csv_headers = [
  95. 'name', 'type', 'group', 'site', 'comments',
  96. ]
  97. class Meta:
  98. ordering = ['name']
  99. def __str__(self):
  100. return self.name
  101. def get_absolute_url(self):
  102. return reverse('virtualization:cluster', args=[self.pk])
  103. def clean(self):
  104. # If the Cluster is assigned to a Site, verify that all host Devices belong to that Site.
  105. if self.pk and self.site:
  106. nonsite_devices = Device.objects.filter(cluster=self).exclude(site=self.site).count()
  107. if nonsite_devices:
  108. raise ValidationError({
  109. 'site': "{} devices are assigned as hosts for this cluster but are not in site {}".format(
  110. nonsite_devices, self.site
  111. )
  112. })
  113. def to_csv(self):
  114. return csv_format([
  115. self.name,
  116. self.type.name,
  117. self.group.name if self.group else None,
  118. self.site.name if self.site else None,
  119. self.comments,
  120. ])
  121. #
  122. # Virtual machines
  123. #
  124. @python_2_unicode_compatible
  125. class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
  126. """
  127. A virtual machine which runs inside a Cluster.
  128. """
  129. cluster = models.ForeignKey(
  130. to=Cluster,
  131. on_delete=models.PROTECT,
  132. related_name='virtual_machines'
  133. )
  134. tenant = models.ForeignKey(
  135. to='tenancy.Tenant',
  136. on_delete=models.PROTECT,
  137. related_name='virtual_machines',
  138. blank=True,
  139. null=True
  140. )
  141. platform = models.ForeignKey(
  142. to='dcim.Platform',
  143. on_delete=models.SET_NULL,
  144. related_name='virtual_machines',
  145. blank=True,
  146. null=True
  147. )
  148. name = models.CharField(
  149. max_length=64,
  150. unique=True
  151. )
  152. status = models.PositiveSmallIntegerField(
  153. choices=VM_STATUS_CHOICES,
  154. default=DEVICE_STATUS_ACTIVE,
  155. verbose_name='Status'
  156. )
  157. role = models.ForeignKey(
  158. to='dcim.DeviceRole',
  159. limit_choices_to={'vm_role': True},
  160. on_delete=models.PROTECT,
  161. related_name='virtual_machines',
  162. blank=True,
  163. null=True
  164. )
  165. primary_ip4 = models.OneToOneField(
  166. to='ipam.IPAddress',
  167. on_delete=models.SET_NULL,
  168. related_name='+',
  169. blank=True,
  170. null=True,
  171. verbose_name='Primary IPv4'
  172. )
  173. primary_ip6 = models.OneToOneField(
  174. to='ipam.IPAddress',
  175. on_delete=models.SET_NULL,
  176. related_name='+',
  177. blank=True,
  178. null=True,
  179. verbose_name='Primary IPv6'
  180. )
  181. vcpus = models.PositiveSmallIntegerField(
  182. blank=True,
  183. null=True,
  184. verbose_name='vCPUs'
  185. )
  186. memory = models.PositiveIntegerField(
  187. blank=True,
  188. null=True,
  189. verbose_name='Memory (MB)'
  190. )
  191. disk = models.PositiveIntegerField(
  192. blank=True,
  193. null=True,
  194. verbose_name='Disk (GB)'
  195. )
  196. comments = models.TextField(
  197. blank=True
  198. )
  199. custom_field_values = GenericRelation(
  200. to=CustomFieldValue,
  201. content_type_field='obj_type',
  202. object_id_field='obj_id'
  203. )
  204. csv_headers = [
  205. 'name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
  206. ]
  207. class Meta:
  208. ordering = ['name']
  209. def __str__(self):
  210. return self.name
  211. def get_absolute_url(self):
  212. return reverse('virtualization:virtualmachine', args=[self.pk])
  213. def to_csv(self):
  214. return csv_format([
  215. self.name,
  216. self.get_status_display(),
  217. self.cluster.name,
  218. self.tenant.name if self.tenant else None,
  219. self.platform.name if self.platform else None,
  220. self.vcpus,
  221. self.memory,
  222. self.disk,
  223. self.comments,
  224. ])
  225. def get_status_class(self):
  226. return VM_STATUS_CLASSES[self.status]
  227. @property
  228. def primary_ip(self):
  229. if settings.PREFER_IPV4 and self.primary_ip4:
  230. return self.primary_ip4
  231. elif self.primary_ip6:
  232. return self.primary_ip6
  233. elif self.primary_ip4:
  234. return self.primary_ip4
  235. else:
  236. return None
  237. def site(self):
  238. # used when a child compent (eg Interface) needs to know its parent's site but
  239. # the parent could be either a device or a virtual machine
  240. return self.cluster.site