c431017c6c64506fc107b59184dc5bf0dbb2edcf
[laas.git] / src / booking / quick_deployer.py
1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, 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
11 import json
12 import uuid
13 import re
14 from django.db.models import Q
15 from django.contrib.auth.models import User
16 from datetime import timedelta
17 from django.utils import timezone
18 from account.models import Lab
19
20 from resource_inventory.models import (
21     Installer,
22     Image,
23     GenericResourceBundle,
24     ConfigBundle,
25     Vlan,
26     Host,
27     HostProfile,
28     HostConfiguration,
29     GenericResource,
30     GenericHost,
31     GenericInterface,
32     OPNFVRole,
33     OPNFVConfig
34 )
35 from resource_inventory.resource_manager import ResourceManager
36 from booking.models import Booking
37 from dashboard.exceptions import (
38     InvalidHostnameException,
39     ResourceAvailabilityException,
40     ModelValidationException
41 )
42 from api.models import JobFactory
43
44
45 # model validity exceptions
46 class IncompatibleInstallerForOS(Exception):
47     pass
48
49
50 class IncompatibleScenarioForInstaller(Exception):
51     pass
52
53
54 class IncompatibleImageForHost(Exception):
55     pass
56
57
58 class ImageOwnershipInvalid(Exception):
59     pass
60
61
62 class ImageNotAvailableAtLab(Exception):
63     pass
64
65
66 class LabDNE(Exception):
67     pass
68
69
70 class HostProfileDNE(Exception):
71     pass
72
73
74 class HostNotAvailable(Exception):
75     pass
76
77
78 class NoLabSelectedError(Exception):
79     pass
80
81
82 class OPNFVRoleDNE(Exception):
83     pass
84
85
86 class NoRemainingPublicNetwork(Exception):
87     pass
88
89
90 def create_from_form(form, request):
91     quick_booking_id = str(uuid.uuid4())
92
93     host_field = form.cleaned_data['filter_field']
94     host_json = json.loads(host_field)
95     purpose_field = form.cleaned_data['purpose']
96     project_field = form.cleaned_data['project']
97     users_field = form.cleaned_data['users']
98     host_name = form.cleaned_data['hostname']
99     length = form.cleaned_data['length']
100
101     image = form.cleaned_data['image']
102     scenario = form.cleaned_data['scenario']
103     installer = form.cleaned_data['installer']
104
105     # get all initial info we need to validate
106     lab_dict = host_json['labs'][0]
107     lab_id = list(lab_dict.keys())[0]
108     lab_user_id = int(lab_id.split("_")[-1])
109     lab = Lab.objects.get(lab_user__id=lab_user_id)
110
111     host_dict = host_json['hosts'][0]
112     profile_id = list(host_dict.keys())[0]
113     profile_id = int(profile_id.split("_")[-1])
114     profile = HostProfile.objects.get(id=profile_id)
115
116     # check validity of field data before trying to apply to models
117     if not lab:
118         raise LabDNE("Lab with provided ID does not exist")
119     if not profile:
120         raise HostProfileDNE("Host type with provided ID does not exist")
121
122     # check that hostname is valid
123     if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", host_name):
124         raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
125     # check that image os is compatible with installer
126     if installer in image.os.sup_installers.all():
127         #if installer not here, we can omit that and not check for scenario
128         if not scenario:
129             raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
130         if scenario not in installer.sup_scenarios.all():
131             raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
132     if image.from_lab != lab:
133         raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
134     if image.host_type != profile:
135         raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
136     if not image.public and image.owner != request.user:
137         raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
138
139     # check if host type is available
140     #ResourceManager.getInstance().acquireHost(ghost, lab.name)
141     available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
142     if not profile in available_host_types:
143         # TODO: handle deleting generic resource in this instance along with grb
144         raise HostNotAvailable("Could not book selected host due to changed availability. Try again later")
145
146     # check if any hosts with profile at lab are still available
147     hostset = Host.objects.filter(lab=lab, profile=profile).filter(booked=False).filter(working=True)
148     if not hostset.first():
149         raise HostNotAvailable("Couldn't find any matching unbooked hosts")
150
151     # generate GenericResourceBundle
152     if len(host_json['labs']) != 1:
153         raise NoLabSelectedError("No lab was selected")
154
155     grbundle = GenericResourceBundle(owner=request.user)
156     grbundle.lab = lab
157     grbundle.name = "grbundle for quick booking with uid " + quick_booking_id
158     grbundle.description = "grbundle created for quick-deploy booking"
159     grbundle.save()
160
161     # generate GenericResource, GenericHost
162     gresource = GenericResource(bundle=grbundle, name=host_name)
163     gresource.save()
164
165     ghost = GenericHost()
166     ghost.resource = gresource
167     ghost.profile = profile
168     ghost.save()
169
170     # generate config bundle
171     cbundle = ConfigBundle()
172     cbundle.owner = request.user
173     cbundle.name = "configbundle for quick booking  with uid " + quick_booking_id
174     cbundle.description = "configbundle created for quick-deploy booking"
175     cbundle.bundle = grbundle
176     cbundle.save()
177
178     # generate OPNFVConfig pointing to cbundle
179     if installer:
180         opnfvconfig = OPNFVConfig()
181         opnfvconfig.scenario = scenario
182         opnfvconfig.installer = installer
183         opnfvconfig.bundle = cbundle
184         opnfvconfig.save()
185
186     # generate HostConfiguration pointing to cbundle
187     hconf = HostConfiguration()
188     hconf.host = ghost
189     hconf.image = image
190     hconf.opnfvRole = OPNFVRole.objects.get(name="Jumphost")
191     if not hconf.opnfvRole:
192         raise OPNFVRoleDNE("No jumphost role was found")
193     hconf.bundle = cbundle
194     hconf.save()
195
196     # construct generic interfaces
197     for interface_profile in profile.interfaceprofile.all():
198         generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
199         generic_interface.save()
200     ghost.save()
201
202     # get vlan, assign to first interface
203     publicnetwork = lab.vlan_manager.get_public_vlan()
204     publicvlan = publicnetwork.vlan
205     if not publicnetwork:
206         raise NoRemainingPublicNetwork("No public networks were available for your pod")
207     lab.vlan_manager.reserve_public_vlan(publicvlan)
208
209     vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
210     vlan.save()
211     ghost.generic_interfaces.first().vlans.add(vlan)
212     ghost.generic_interfaces.first().save()
213
214     # generate resource bundle
215     try:
216         resource_bundle = ResourceManager.getInstance().convertResourceBundle(grbundle, config=cbundle)
217     except ResourceAvailabilityException:
218         raise ResourceAvailabilityException("Requested resources not available")
219     except ModelValidationException:
220         raise ModelValidationException("Encountered error while saving grbundle")
221
222     # generate booking
223     booking = Booking()
224     booking.purpose = purpose_field
225     booking.project = project_field
226     booking.lab = lab
227     booking.owner = request.user
228     booking.start = timezone.now()
229     booking.end = timezone.now() + timedelta(days=int(length))
230     booking.resource = resource_bundle
231     booking.pdf = ResourceManager().makePDF(booking.resource)
232     booking.config_bundle = cbundle
233     booking.save()
234     print("users field:")
235     print(users_field)
236     print(type(users_field))
237     #users_field = json.loads(users_field)
238     users_field = users_field[2:-2]
239     if users_field: #may be empty after split, if no collaborators entered
240         users_field = json.loads(users_field)
241         for collaborator in users_field:
242             user = User.objects.get(id=collaborator['id'])
243             booking.collaborators.add(user)
244         booking.save()
245
246     # generate job
247     JobFactory.makeCompleteJob(booking)
248
249
250 def drop_filter(user):
251     installer_filter = {}
252     for image in Image.objects.all():
253         installer_filter[image.id] = {}
254         for installer in image.os.sup_installers.all():
255             installer_filter[image.id][installer.id] = 1
256
257     scenario_filter = {}
258     for installer in Installer.objects.all():
259         scenario_filter[installer.id] = {}
260         for scenario in installer.sup_scenarios.all():
261             scenario_filter[installer.id][scenario.id] = 1
262
263     images = Image.objects.filter(Q(public=True) | Q(owner=user))
264     image_filter = {}
265     for image in images:
266         image_filter[image.id] = {}
267         image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
268         image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
269         image_filter[image.id]['name'] = image.name
270
271     return {'installer_filter': json.dumps(installer_filter),
272             'scenario_filter': json.dumps(scenario_filter),
273             'image_filter': json.dumps(image_filter)}