e5a23b2a054b9a3ba0ba44301f24b1ce211a82bd
[laas.git] / src / workflow / 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
11 from django.contrib.auth.models import User
12 from django.db import models
13 from django.shortcuts import render
14 from django.contrib import messages
15
16 import yaml
17 import requests
18
19 from workflow.forms import ConfirmationForm
20 from api.models import *
21 from dashboard.exceptions import *
22 from resource_inventory.models import *
23 from resource_inventory.resource_manager import ResourceManager
24 from notifier.manager import NotificationHandler
25
26
27 class BookingAuthManager():
28     LFN_PROJECTS = ["opnfv"]  # TODO
29
30     def parse_url(self, info_url):
31         """
32         will return the PTL in the INFO file on success, or None
33         """
34         try:
35             parts = info_url.split("/")
36             if parts[0].find("http") > -1:  # the url include http(s)://
37                 parts = parts[2:]
38             if parts[-1] != "INFO.yaml":
39                 return None
40             if parts[0] not in ["github.com", "raw.githubusercontent.com"]:
41                 return None
42             if parts[1] not in self.LFN_PROJECTS:
43                 return None
44             # now to download and parse file
45             if parts[3] == "blob":
46                 parts[3] = "raw"
47             url = "https://" + "/".join(parts)
48             info_file = requests.get(url, timeout=15).text
49             info_parsed = yaml.load(info_file)
50             ptl = info_parsed.get('project_lead')
51             if not ptl:
52                 return None
53             return ptl
54
55         except Exception as e:
56             return None
57
58
59     def booking_allowed(self, booking, repo):
60         """
61         This is the method that will have to change whenever the booking policy changes in the Infra
62         group / LFN. This is a nice isolation of that administration crap
63         currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
64         which is checked using the provided info file
65         """
66         if len(booking.resource.template.getHosts()) < 2:
67             return True  #if they only have one server, we dont care
68         if booking.owner.userprofile.booking_privledge:
69             return True  # admin override for this user
70         if repo.BOOKING_INFO_FILE not in repo.el:
71             return False  # INFO file not provided
72         ptl_info = self.parse_url(repo.BOOKING_INFO_FILE)
73         return ptl_info and  ptl_info == booking.owner.userprofile.email_addr
74
75
76
77 class WorkflowStep(object):
78
79     template = 'bad_request.html'
80     title = "Generic Step"
81     description = "You were led here by mistake"
82     short_title = "error"
83     metastep = None
84
85     def __init__(self, id, repo=None):
86         self.repo = repo
87         self.id = id
88
89     def get_context(self):
90         context = {}
91         context['step_number'] = self.repo_get('steps')
92         context['active_step'] = self.repo_get('active_step')
93         context['render_correct'] = "true"
94         context['step_title'] = self.title
95         context['description'] = self.description
96         return context
97
98     def render(self, request):
99         self.context = self.get_context()
100         return render(request, self.template, self.context)
101
102     def post_render(self, request):
103         return self.render(request)
104
105     def test_render(self, request):
106         if request.method == "POST":
107             return self.post_render(request)
108         return self.render(request)
109
110     def validate(self, request):
111         pass
112
113     def repo_get(self, key, default=None):
114         return self.repo.get(key, default, self.id)
115
116     def repo_put(self, key, value):
117         return self.repo.put(key, value, self.id)
118
119 class Confirmation_Step(WorkflowStep):
120     template = 'workflow/confirm.html'
121     title = "Confirm Changes"
122     description = "Does this all look right?"
123
124     def get_vlan_warning(self):
125         grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
126         if not grb:
127             return 0
128         vlan_manager = grb.lab.vlan_manager
129         if vlan_manager is None:
130             return 0
131         hosts = grb.getHosts()
132         for host in hosts:
133             for interface in host.generic_interfaces.all():
134                 for vlan in interface.vlans.all():
135                     if vlan.public:
136                         if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
137                             return 1
138                     else:
139                         if not vlan_manager.is_available(vlan.vlan_id):
140                             return 1  # There is a problem with these vlans
141         return 0
142
143
144     def get_context(self):
145         context = super(Confirmation_Step, self).get_context()
146         context['form'] = ConfirmationForm()
147         context['confirmation_info'] = yaml.dump(
148                 self.repo_get(self.repo.CONFIRMATION),
149                 default_flow_style=False
150                 ).strip()
151         context['vlan_warning'] = self.get_vlan_warning()
152
153         return context
154
155     def flush_to_db(self):
156         errors = self.repo.make_models()
157         if errors:
158             return errors
159
160     def post_render(self, request):
161         form = ConfirmationForm(request.POST)
162         if form.is_valid():
163             data = form.cleaned_data['confirm']
164             context = self.get_context()
165             if data == "True":
166                 context["bypassed"] = "true"
167                 errors = self.flush_to_db()
168                 if errors:
169                     messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
170                     return render(request, self.template, context)
171                 messages.add_message(request, messages.SUCCESS, "Confirmed")
172                 return render(request, self.template, context)
173             elif data == "False":
174                 context["bypassed"] = "true"
175                 messages.add_message(request, messages.SUCCESS, "Canceled")
176                 return render(request, self.template, context)
177             else:
178                 pass
179
180         else:
181             if "vlan_input" in request.POST:
182                 if request.POST.get("vlan_input") == "True":
183                     self.translate_vlans()
184                     return self.render(request)
185             pass
186
187     def translate_vlans(self):
188         grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
189         if not grb:
190             return 0
191         vlan_manager = grb.lab.vlan_manager
192         if vlan_manager is None:
193             return 0
194         hosts = grb.getHosts()
195         for host in hosts:
196             for interface in host.generic_interfaces.all():
197                 for vlan in interface.vlans.all():
198                     if not vlan.public:
199                         if not vlan_manager.is_available(vlan.vlan_id):
200                             vlan.vlan_id = vlan_manager.get_vlan()
201                             vlan.save()
202                     else:
203                         if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
204                             pub_vlan = vlan_manager.get_public_vlan()
205                             vlan.vlan_id = pub_vlan.vlan
206                             vlan.save()
207
208
209 class Workflow():
210
211     steps = []
212     active_index = 0
213
214 class Repository():
215
216     EDIT = "editing"
217     MODELS = "models"
218     RESOURCE_SELECT = "resource_select"
219     CONFIRMATION = "confirmation"
220     SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
221     GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
222     GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
223     BOOKING = "booking"
224     LAB = "lab"
225     GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
226     BOOKING_FORMS = "booking_forms"
227     SWCONF_HOSTS = "swconf_hosts"
228     SWCONF_SELECTED_GRB = "swconf_selected_grb_pk"
229     BOOKING_SELECTED_GRB = "booking_selected_grb_pk"
230     BOOKING_MODELS = "booking models"
231     CONFIG_MODELS = "configuration bundle models"
232     SESSION_USER = "session owner user account"
233     VALIDATED_MODEL_GRB = "valid grb config model instance in db"
234     VALIDATED_MODEL_CONFIG = "valid config model instance in db"
235     VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
236     VLANS = "a list of vlans"
237     SNAPSHOT_MODELS = "the models for snapshotting"
238     SNAPSHOT_BOOKING_ID = "the booking id for snapshotting"
239     SNAPSHOT_NAME = "the name of the snapshot"
240     SNAPSHOT_DESC = "description of the snapshot"
241     BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
242
243
244     def get(self, key, default, id):
245         self.add_get_history(key, id)
246         return self.el.get(key, default)
247
248     def put(self,key,val, id):
249         self.add_put_history(key, id)
250         self.el[key] = val
251
252     def add_get_history(self, key, id):
253         self.add_history(key, id, self.get_history)
254
255     def add_put_history(self, key, id):
256         self.add_history(key, id, self.put_history)
257
258     def add_history(self, key, id, history):
259         if not key in history:
260             history[key] = [id]
261         else:
262             history[key].append(id)
263
264     def make_models(self):
265         if self.SNAPSHOT_MODELS in self.el:
266             errors = self.make_snapshot()
267             if errors:
268                 return errors
269         #if GRB WF, create it
270         if self.GRESOURCE_BUNDLE_MODELS in self.el:
271             errors = self.make_generic_resource_bundle()
272             if errors:
273                 return errors
274
275         if self.CONFIG_MODELS in self.el:
276             errors = self.make_software_config_bundle()
277             if errors:
278                 return errors
279
280         if self.BOOKING_MODELS in self.el:
281             errors = self.make_booking()
282             if errors:
283                 return errors
284             # create notification
285             booking = self.el[self.BOOKING_MODELS]['booking']
286             NotificationHandler.notify_new_booking(booking)
287
288
289     def make_snapshot(self):
290         owner = self.el[self.SESSION_USER]
291         models = self.el[self.SNAPSHOT_MODELS]
292         image = models.get('snapshot', Image())
293         booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID)
294         if not booking_id:
295             return "SNAP, No booking ID provided"
296         booking = Booking.objects.get(pk=booking_id)
297         name = self.el.get(self.SNAPSHOT_NAME)
298         if not name:
299             return "SNAP, no name provided"
300         host = models.get('host')
301         if not host:
302             return "SNAP, no host provided"
303         description = self.el.get(self.SNAPSHOT_DESC, "")
304         image.from_lab = booking.lab
305         image.name = name
306         image.description = description
307         image.public = False
308         image.lab_id = -1
309         image.owner = owner
310         image.host_type = host.profile
311         image.save()
312
313
314     def make_generic_resource_bundle(self):
315         owner = self.el[self.SESSION_USER]
316         if self.GRESOURCE_BUNDLE_MODELS in self.el:
317             models = self.el[self.GRESOURCE_BUNDLE_MODELS]
318             if 'hosts' in models:
319                 hosts = models['hosts']
320             else:
321                 return "GRB has no hosts. CODE:0x0002"
322             if 'bundle' in models:
323                 bundle = models['bundle']
324             else:
325                 return "GRB, no bundle in models. CODE:0x0003"
326
327             try:
328                 bundle.owner = owner
329                 bundle.save()
330             except Exception as e:
331                 return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
332             try:
333                 for host in hosts:
334                     genericresource = host.resource
335                     genericresource.bundle = bundle
336                     genericresource.save()
337                     host.resource = genericresource
338                     host.save()
339             except Exception as e:
340                 return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
341
342             if 'interfaces' in models:
343                 for interface_set in models['interfaces'].values():
344                     for interface in interface_set:
345                         try:
346                             interface.host = interface.host
347                             interface.save()
348                         except Exception as e:
349                             return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
350             else:
351                 return "GRB, no interface set provided. CODE:0x001a"
352
353             if 'vlans' in models:
354                 for resource_name, mapping in models['vlans'].items():
355                     for profile_name, vlan_set in mapping.items():
356                         interface = GenericInterface.objects.get(
357                                 profile__name=profile_name,
358                                 host__resource__name=resource_name,
359                                 host__resource__bundle=models['bundle']
360                                 )
361                         for vlan in vlan_set:
362                             try:
363                                 vlan.save()
364                                 interface.vlans.add(vlan)
365                             except Exception as e:
366                                 return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017"
367             else:
368                 return "GRB, no vlan set provided. CODE:0x0018"
369
370
371         else:
372             return "GRB no models given. CODE:0x0001"
373
374         self.el[self.VALIDATED_MODEL_GRB] = bundle
375         return False
376
377
378     def make_software_config_bundle(self):
379         owner = self.el[self.SESSION_USER]
380         models = self.el[self.CONFIG_MODELS]
381         if 'bundle' in models:
382             bundle = models['bundle']
383             bundle.bundle = bundle.bundle
384             try:
385                 bundle.save()
386             except Exception as e:
387                 return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007"
388
389         else:
390             return "SWC, no bundle in models. CODE:0x0006"
391         if 'host_configs' in models:
392             host_configs = models['host_configs']
393             for host_config in host_configs:
394                 host_config.bundle = host_config.bundle
395                 host_config.host = host_config.host
396                 try:
397                     host_config.save()
398                 except Exception as e:
399                     return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009"
400         else:
401             return "SWC, no host configs in models. CODE:0x0008"
402         if 'opnfv' in models:
403             opnfvconfig = models['opnfv']
404             opnfvconfig.bundle = opnfvconfig.bundle
405             if not opnfvconfig.scenario in opnfvconfig.installer.sup_scenarios.all():
406                 return "SWC, scenario not supported by installer. CODE:0x000d"
407             try:
408                 opnfvconfig.save()
409             except Exception as e:
410                 return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b"
411         else:
412             pass
413
414         self.el[self.VALIDATED_MODEL_CONFIG] = bundle
415         return False
416
417
418     def make_booking(self):
419         models = self.el[self.BOOKING_MODELS]
420         owner = self.el[self.SESSION_USER]
421
422         if self.BOOKING_SELECTED_GRB in self.el:
423             selected_grb = self.el[self.BOOKING_SELECTED_GRB]
424         else:
425             return "BOOK, no selected resource. CODE:0x000e"
426
427         if not self.reserve_vlans(selected_grb):
428             return "BOOK, vlans not available"
429
430         if 'booking' in models:
431             booking = models['booking']
432         else:
433             return "BOOK, no booking model exists. CODE:0x000f"
434
435         if not booking.start:
436             return "BOOK, booking has no start. CODE:0x0010"
437         if not booking.end:
438             return "BOOK, booking has no end. CODE:0x0011"
439         if booking.end <= booking.start:
440             return "BOOK, end before/same time as start. CODE:0x0012"
441
442         if 'collaborators' in models:
443             collaborators = models['collaborators']
444         else:
445             return "BOOK, collaborators not defined. CODE:0x0013"
446         try:
447             resource_bundle = ResourceManager.getInstance().convertResourceBundle(selected_grb, config=booking.config_bundle)
448         except ResourceAvailabilityException as e:
449             return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014"
450         except ModelValidationException as e:
451             return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b"
452
453         booking.resource = resource_bundle
454         booking.owner = owner
455         booking.config_bundle = booking.config_bundle
456         booking.lab = selected_grb.lab
457
458         is_allowed = BookingAuthManager().booking_allowed(booking, self)
459         if not is_allowed:
460             return "BOOK, you are not allowed to book the requested resources"
461
462         try:
463             booking.save()
464         except Exception as e:
465             return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015"
466
467         for collaborator in collaborators:
468             booking.collaborators.add(collaborator)
469
470         try:
471             JobFactory.makeCompleteJob(booking)
472         except Exception as e:
473             return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
474
475         try:
476             booking.save()
477         except Exception as e:
478             return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
479
480     def reserve_vlans(self, grb):
481         """
482         True is success
483         """
484         vlans = []
485         public_vlan = None
486         vlan_manager = grb.lab.vlan_manager
487         if vlan_manager is None:
488             return True
489         for host in grb.getHosts():
490             for interface in host.generic_interfaces.all():
491                 for vlan in interface.vlans.all():
492                     if vlan.public:
493                         public_vlan = vlan
494                     else:
495                         vlans.append(vlan.vlan_id)
496
497         try:
498             vlan_manager.reserve_vlans(vlans)
499             vlan_manager.reserve_public_vlan(public_vlan.vlan_id)
500             return True
501         except Exception as e:
502             return False
503
504
505     def __init__(self):
506         self.el = {}
507         self.el[self.CONFIRMATION] = {}
508         self.get_history = {}
509         self.put_history = {}