Implement OPNFV workflow
[pharos-tools.git] / dashboard / src / resource_inventory / models.py
1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 from django.contrib.auth.models import User
11 from django.db import models
12 from django.core.validators import RegexValidator
13
14 import re
15
16 from account.models import Lab
17
18
19 # profile of resources hosted by labs
20 class HostProfile(models.Model):
21     id = models.AutoField(primary_key=True)
22     host_type = models.PositiveSmallIntegerField(default=0)
23     name = models.CharField(max_length=200, unique=True)
24     description = models.TextField()
25     labs = models.ManyToManyField(Lab, related_name="hostprofiles")
26
27     def validate(self):
28         validname = re.compile(r"^[A-Za-z0-9\-\_\.\/\, ]+$")
29         if not validname.match(self.name):
30             return "Invalid host profile name given. Name must only use A-Z, a-z, 0-9, hyphens, underscores, dots, commas, or spaces."
31         else:
32             return None
33
34     def __str__(self):
35         return self.name
36
37
38 class InterfaceProfile(models.Model):
39     id = models.AutoField(primary_key=True)
40     speed = models.IntegerField()
41     name = models.CharField(max_length=100)
42     host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='interfaceprofile')
43     nic_type = models.CharField(
44         max_length=50,
45         choices=[
46             ("onboard", "onboard"),
47             ("pcie", "pcie")
48         ],
49         default="onboard"
50     )
51
52     def __str__(self):
53         return self.name + " for " + str(self.host)
54
55
56 class DiskProfile(models.Model):
57     id = models.AutoField(primary_key=True)
58     size = models.IntegerField()
59     media_type = models.CharField(max_length=50, choices=[
60         ("SSD", "SSD"),
61         ("HDD", "HDD")
62     ])
63     name = models.CharField(max_length=50)
64     host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='storageprofile')
65     rotation = models.IntegerField(default=0)
66     interface = models.CharField(
67         max_length=50,
68         choices=[
69             ("sata", "sata"),
70             ("sas", "sas"),
71             ("ssd", "ssd"),
72             ("nvme", "nvme"),
73             ("scsi", "scsi"),
74             ("iscsi", "iscsi"),
75         ],
76         default="sata"
77     )
78
79     def __str__(self):
80         return self.name + " for " + str(self.host)
81
82
83 class CpuProfile(models.Model):
84     id = models.AutoField(primary_key=True)
85     cores = models.IntegerField()
86     architecture = models.CharField(max_length=50, choices=[
87         ("x86_64", "x86_64"),
88         ("aarch64", "aarch64")
89     ])
90     cpus = models.IntegerField()
91     host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='cpuprofile')
92     cflags = models.TextField(null=True)
93
94     def __str__(self):
95         return str(self.architecture) + " " + str(self.cpus) + "S" + str(self.cores) + " C for " + str(self.host)
96
97
98 class RamProfile(models.Model):
99     id = models.AutoField(primary_key=True)
100     amount = models.IntegerField()
101     channels = models.IntegerField()
102     host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='ramprofile')
103
104     def __str__(self):
105         return str(self.amount) + "G for " + str(self.host)
106
107
108 # Generic resource templates
109 class GenericResourceBundle(models.Model):
110     id = models.AutoField(primary_key=True)
111     name = models.CharField(max_length=300, unique=True)
112     xml = models.TextField()
113     owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
114     lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL)
115     description = models.CharField(max_length=1000, default="")
116
117     def getHosts(self):
118         return_hosts = []
119         for genericResource in self.generic_resources.all():
120             return_hosts.append(genericResource.getHost())
121
122         return return_hosts
123
124     def __str__(self):
125         return self.name
126
127
128 class Network(models.Model):
129     id = models.AutoField(primary_key=True)
130     name = models.CharField(max_length=100)
131     bundle = models.ForeignKey(GenericResourceBundle, on_delete=models.CASCADE, related_name="networks")
132     is_public = models.BooleanField()
133
134     def __str__(self):
135         return self.name
136
137
138 class NetworkConnection(models.Model):
139     network = models.ForeignKey(Network, on_delete=models.CASCADE)
140     vlan_is_tagged = models.BooleanField()
141
142
143 class Vlan(models.Model):
144     id = models.AutoField(primary_key=True)
145     vlan_id = models.IntegerField()
146     tagged = models.BooleanField()
147     public = models.BooleanField(default=False)
148     network = models.ForeignKey(Network, on_delete=models.DO_NOTHING, null=True)
149
150     def __str__(self):
151         return str(self.vlan_id) + ("_T" if self.tagged else "")
152
153
154 class GenericResource(models.Model):
155     bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE)
156     hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)")
157     name = models.CharField(max_length=200, validators=[hostname_validchars])
158
159     def getHost(self):
160         return self.generic_host
161
162     def __str__(self):
163         return self.name
164
165     def validate(self):
166         validname = re.compile(r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))')
167         if not validname.match(self.name):
168             return "Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)"
169         else:
170             return None
171
172
173 # Host template
174 class GenericHost(models.Model):
175     id = models.AutoField(primary_key=True)
176     profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
177     resource = models.OneToOneField(GenericResource, related_name='generic_host', on_delete=models.CASCADE)
178
179     def __str__(self):
180         return self.resource.name
181
182
183 # Physical, actual resources
184 class ResourceBundle(models.Model):
185     id = models.AutoField(primary_key=True)
186     template = models.ForeignKey(GenericResourceBundle, on_delete=models.SET_NULL, null=True)
187
188     def __str__(self):
189         if self.template is None:
190             return "Resource bundle " + str(self.id) + " with no template"
191         return "instance of " + str(self.template)
192
193     def get_host(self, role="Jumphost"):
194         return Host.objects.filter(bundle=self, config__is_head_node=True).first()  # should only ever be one, but it is not an invariant in the models
195
196
197 class GenericInterface(models.Model):
198     id = models.AutoField(primary_key=True)
199     profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE)
200     host = models.ForeignKey(GenericHost, on_delete=models.CASCADE, related_name='generic_interfaces')
201     connections = models.ManyToManyField(NetworkConnection)
202
203     def __str__(self):
204         return "type " + str(self.profile) + " on host " + str(self.host)
205
206
207 class Scenario(models.Model):
208     id = models.AutoField(primary_key=True)
209     name = models.CharField(max_length=300)
210
211     def __str__(self):
212         return self.name
213
214
215 class Installer(models.Model):
216     id = models.AutoField(primary_key=True)
217     name = models.CharField(max_length=200)
218     sup_scenarios = models.ManyToManyField(Scenario, blank=True)
219
220     def __str__(self):
221         return self.name
222
223
224 class Opsys(models.Model):
225     id = models.AutoField(primary_key=True)
226     name = models.CharField(max_length=100)
227     sup_installers = models.ManyToManyField(Installer, blank=True)
228
229     def __str__(self):
230         return self.name
231
232
233 class NetworkRole(models.Model):
234     name = models.CharField(max_length=100)
235     network = models.ForeignKey(Network, on_delete=models.CASCADE)
236
237
238 class ConfigBundle(models.Model):
239     id = models.AutoField(primary_key=True)
240     owner = models.ForeignKey(User, on_delete=models.CASCADE)
241     name = models.CharField(max_length=200, unique=True)
242     description = models.CharField(max_length=1000, default="")
243     bundle = models.ForeignKey(GenericResourceBundle, null=True, on_delete=models.CASCADE)
244
245     def __str__(self):
246         return self.name
247
248
249 class OPNFVConfig(models.Model):
250     id = models.AutoField(primary_key=True)
251     installer = models.ForeignKey(Installer, on_delete=models.CASCADE)
252     scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE)
253     bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE)
254     networks = models.ManyToManyField(NetworkRole)
255     name = models.CharField(max_length=300, blank=True, default="")
256     description = models.CharField(max_length=600, blank=True, default="")
257
258     def __str__(self):
259         return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario)
260
261
262 class OPNFVRole(models.Model):
263     id = models.AutoField(primary_key=True)
264     name = models.CharField(max_length=200)
265     description = models.TextField()
266
267     def __str__(self):
268         return self.name
269
270
271 class Image(models.Model):
272     """
273     model for representing OS images / snapshots of hosts
274     """
275     id = models.AutoField(primary_key=True)
276     lab_id = models.IntegerField()  # ID the lab who holds this image knows
277     from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
278     name = models.CharField(max_length=200)
279     owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
280     public = models.BooleanField(default=True)
281     host_type = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
282     description = models.TextField()
283     os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE)
284
285     def __str__(self):
286         return self.name
287
288
289 def get_sentinal_opnfv_role():
290     return OPNFVRole.objects.get_or_create(name="deleted", description="Role was deleted.")
291
292
293 class HostConfiguration(models.Model):
294     """
295     model to represent a complete configuration for a single
296     physical host
297     """
298     id = models.AutoField(primary_key=True)
299     host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE)
300     image = models.ForeignKey(Image, on_delete=models.PROTECT)
301     bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE)
302     is_head_node = models.BooleanField(default=False)
303
304     def __str__(self):
305         return "config with " + str(self.host) + " and image " + str(self.image)
306
307
308 class HostOPNFVConfig(models.Model):
309     role = models.ForeignKey(OPNFVRole, related_name="host_opnfv_configs", on_delete=models.CASCADE)
310     host_config = models.ForeignKey(HostConfiguration, related_name="host_opnfv_config", on_delete=models.CASCADE)
311     opnfv_config = models.ForeignKey(OPNFVConfig, related_name="host_opnfv_config", on_delete=models.CASCADE)
312
313
314 class RemoteInfo(models.Model):
315     address = models.CharField(max_length=15)
316     mac_address = models.CharField(max_length=17)
317     password = models.CharField(max_length=100)
318     user = models.CharField(max_length=100)
319     management_type = models.CharField(max_length=50, default="ipmi")
320     versions = models.CharField(max_length=100)  # json serialized list of floats
321
322
323 def get_default_remote_info():
324     return RemoteInfo.objects.get_or_create(
325         address="default",
326         mac_address="default",
327         password="default",
328         user="default",
329         management_type="default",
330         versions="[default]"
331     )[0].pk
332
333
334 # Concrete host, actual machine in a lab
335 class Host(models.Model):
336     id = models.AutoField(primary_key=True)
337     template = models.ForeignKey(GenericHost, on_delete=models.SET_NULL, null=True)
338     booked = models.BooleanField(default=False)
339     name = models.CharField(max_length=200, unique=True)
340     bundle = models.ForeignKey(ResourceBundle, related_name='hosts', on_delete=models.SET_NULL, null=True)
341     config = models.ForeignKey(HostConfiguration, null=True, related_name="configuration", on_delete=models.SET_NULL)
342     labid = models.CharField(max_length=200, default="default_id")
343     profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
344     lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
345     working = models.BooleanField(default=True)
346     vendor = models.CharField(max_length=100, default="unknown")
347     model = models.CharField(max_length=150, default="unknown")
348     remote_management = models.ForeignKey(RemoteInfo, default=get_default_remote_info, on_delete=models.SET(get_default_remote_info))
349
350     def __str__(self):
351         return self.name
352
353
354 class Interface(models.Model):
355     id = models.AutoField(primary_key=True)
356     mac_address = models.CharField(max_length=17)
357     bus_address = models.CharField(max_length=50)
358     name = models.CharField(max_length=100, default="eth0")
359     config = models.ManyToManyField(Vlan)
360     host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='interfaces')
361
362     def __str__(self):
363         return self.mac_address + " on host " + str(self.host)
364
365
366 class OPNFV_SETTINGS():
367     """
368     This is a static configuration class
369     """
370     # all the required network types in PDF/IDF spec
371     NETWORK_ROLES = ["public", "private", "admin", "mgmt"]