Lab as a Service 2.0
[laas.git] / 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("^[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.DO_NOTHING, related_name='interfaceprofile')
43     nic_type = models.CharField(max_length=50, choices=[
44         ("onboard", "onboard"),
45         ("pcie", "pcie")
46         ], default="onboard")
47
48     def __str__(self):
49         return self.name + " for " + str(self.host)
50
51
52 class DiskProfile(models.Model):
53     id = models.AutoField(primary_key=True)
54     size = models.IntegerField()
55     media_type = models.CharField(max_length=50, choices=[
56         ("SSD", "SSD"),
57         ("HDD", "HDD")
58     ])
59     name = models.CharField(max_length=50)
60     host = models.ForeignKey(HostProfile, on_delete=models.DO_NOTHING, related_name='storageprofile')
61     rotation = models.IntegerField(default=0)
62     interface = models.CharField(max_length=50, choices=[
63             ("sata", "sata"),
64             ("sas", "sas"),
65             ("ssd", "ssd"),
66             ("nvme", "nvme"),
67             ("scsi", "scsi"),
68             ("iscsi", "iscsi"),
69         ], default="sata")
70
71     def __str__(self):
72         return self.name + " for " + str(self.host)
73
74
75 class CpuProfile(models.Model):
76     id = models.AutoField(primary_key=True)
77     cores = models.IntegerField()
78     architecture = models.CharField(max_length=50, choices=[
79         ("x86_64", "x86_64"),
80         ("aarch64", "aarch64")
81     ])
82     cpus = models.IntegerField()
83     host = models.ForeignKey(HostProfile, on_delete=models.DO_NOTHING, related_name='cpuprofile')
84     cflags = models.TextField(null=True)
85
86     def __str__(self):
87         return str(self.architecture) + " " + str(self.cpus) + "S" + str(self.cores) + " C for " + str(self.host)
88
89
90 class RamProfile(models.Model):
91     id = models.AutoField(primary_key=True)
92     amount = models.IntegerField()
93     channels = models.IntegerField()
94     host = models.ForeignKey(HostProfile, on_delete=models.DO_NOTHING, related_name='ramprofile')
95
96     def __str__(self):
97         return str(self.amount) + "G for " + str(self.host)
98
99
100 ##Networking -- located here due to import order requirements
101 class Network(models.Model):
102     id = models.AutoField(primary_key=True)
103     vlan_id = models.IntegerField()
104     name = models.CharField(max_length=100)
105
106     def __str__(self):
107         return self.name
108
109 class Vlan(models.Model):
110     id = models.AutoField(primary_key=True)
111     vlan_id = models.IntegerField()
112     tagged = models.BooleanField()
113     public = models.BooleanField(default=False)
114
115     def __str__(self):
116         return str(self.vlan_id) + ("_T" if self.tagged else "")
117
118
119 # Generic resource templates
120 class GenericResourceBundle(models.Model):
121     id = models.AutoField(primary_key=True)
122     name = models.CharField(max_length=300, unique=True)
123     xml = models.TextField()
124     owner = models.ForeignKey(User, null=True, on_delete=models.DO_NOTHING)
125     lab = models.ForeignKey(Lab, null=True, on_delete=models.DO_NOTHING)
126     description = models.CharField(max_length=1000, default="")
127
128     def getHosts(self):
129         return_hosts = []
130         for genericResource in self.generic_resources.all():
131             return_hosts.append(genericResource.getHost())
132
133         return return_hosts
134
135     def __str__(self):
136         return self.name
137
138
139 class GenericResource(models.Model):
140     bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.DO_NOTHING)
141     hostname_validchars = RegexValidator(regex='(?=^.{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 (_)")
142     name = models.CharField(max_length=200, validators=[hostname_validchars])
143
144     def getHost(self):
145         return self.generic_host
146
147     def __str__(self):
148         return self.name
149
150     def validate(self):
151         validname = re.compile('(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))')
152         if not validname.match(self.name):
153             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 (_)"
154         else:
155             return None
156
157
158 # Host template
159 class GenericHost(models.Model):
160     id = models.AutoField(primary_key=True)
161     profile = models.ForeignKey(HostProfile, on_delete=models.DO_NOTHING)
162     resource = models.OneToOneField(GenericResource, related_name='generic_host', on_delete=models.DO_NOTHING)
163
164     def __str__(self):
165         return self.resource.name
166
167
168 # Physical, actual resources
169 class ResourceBundle(models.Model):
170     id = models.AutoField(primary_key=True)
171     template = models.ForeignKey(GenericResourceBundle, on_delete=models.DO_NOTHING)
172
173     def __str__(self):
174         return "instance of " + str(self.template)
175
176
177 # Networking
178
179
180 class GenericInterface(models.Model):
181     id = models.AutoField(primary_key=True)
182     vlans = models.ManyToManyField(Vlan)
183     profile = models.ForeignKey(InterfaceProfile, on_delete=models.DO_NOTHING)
184     host = models.ForeignKey(GenericHost, on_delete=models.DO_NOTHING, related_name='generic_interfaces')
185
186     def __str__(self):
187         return "type " + str(self.profile) + " on host " + str(self.host)
188
189
190 class Scenario(models.Model):
191     id = models.AutoField(primary_key=True)
192     name = models.CharField(max_length=300)
193
194     def __str__(self):
195         return self.name
196
197 class Installer(models.Model):
198     id = models.AutoField(primary_key=True)
199     name = models.CharField(max_length=200)
200     sup_scenarios = models.ManyToManyField(Scenario, blank=True)
201
202     def __str__(self):
203         return self.name
204
205 class Opsys(models.Model):
206     id = models.AutoField(primary_key=True)
207     name = models.CharField(max_length=100)
208     sup_installers = models.ManyToManyField(Installer, blank=True)
209
210     def __str__(self):
211         return self.name
212
213 class ConfigBundle(models.Model):
214     id = models.AutoField(primary_key=True)
215     owner = models.ForeignKey(User, on_delete=models.CASCADE) #consider setting to root user?
216     name = models.CharField(max_length=200, unique=True)
217     description = models.CharField(max_length=1000, default="")
218     bundle = models.ForeignKey(GenericResourceBundle, null=True, on_delete=models.CASCADE)
219
220     def __str__(self):
221         return self.name
222
223 class OPNFVConfig(models.Model):
224     id = models.AutoField(primary_key=True)
225     installer = models.ForeignKey(Installer, on_delete=models.CASCADE)
226     scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE)
227     bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE)
228
229     def __str__(self):
230         return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario)
231
232 class OPNFVRole(models.Model):
233     id = models.AutoField(primary_key=True)
234     name = models.CharField(max_length=200)
235     description = models.TextField()
236
237     def __str__(self):
238         return self.name
239
240 class Image(models.Model):
241     """
242     model for representing OS images / snapshots of hosts
243     """
244     id = models.AutoField(primary_key=True)
245     lab_id = models.IntegerField()  # ID the lab who holds this image knows
246     from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
247     name = models.CharField(max_length=200)
248     owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
249     public = models.BooleanField(default=True)
250     host_type = models.ForeignKey(HostProfile, on_delete=models.CASCADE) #may need to change to models.SET() once images are transferrable between compatible host types
251     description = models.TextField()
252
253     def __str__(self):
254         return self.name
255
256 class HostConfiguration(models.Model):
257     """
258     model to represent a complete configuration for a single
259     physical host
260     """
261     id = models.AutoField(primary_key=True)
262     host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE)
263     image = models.ForeignKey(Image, on_delete=models.PROTECT)
264     bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE)
265     opnfvRole = models.ForeignKey(OPNFVRole, on_delete=models.PROTECT) #need protocol for phasing out a role if we are going to allow that to happen
266
267     def __str__(self):
268         return "config with " + str(self.host) + " and image " + str(self.image)
269
270
271 # Concrete host, actual machine in a lab
272 class Host(models.Model):
273     id = models.AutoField(primary_key=True)
274     template = models.ForeignKey(GenericHost, on_delete=models.SET_NULL, null=True)
275     booked = models.BooleanField(default=False)
276     name = models.CharField(max_length=200, unique=True)
277     bundle = models.ForeignKey(ResourceBundle, related_name='hosts', on_delete=models.SET_NULL, null=True)
278     config = models.ForeignKey(HostConfiguration, null=True, related_name="configuration", on_delete=models.SET_NULL)
279     labid = models.CharField(max_length=200, default="default_id")
280     profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
281     lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
282     working = models.BooleanField(default=True)
283     vendor = models.CharField(max_length=100, default="unknown")
284     model = models.CharField(max_length=150, default="unknown")
285
286     def __str__(self):
287         return self.name
288
289
290 class Interface(models.Model):
291     id = models.AutoField(primary_key=True)
292     mac_address = models.CharField(max_length=17)
293     bus_address = models.CharField(max_length=50)
294     name = models.CharField(max_length=100, default="eth0")
295     config = models.ManyToManyField(Vlan)
296     host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='interfaces')
297
298     def __str__(self):
299         return self.mac_address + " on host " + str(self.host)