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