OverHaul the Snapshot Workflow
[pharos-tools.git] / dashboard / src / workflow / booking_workflow.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 import messages
11 from django.shortcuts import render
12 from django.contrib.auth.models import User
13 from django.utils import timezone
14
15 import json
16 from datetime import timedelta
17
18 from booking.models import Booking
19 from workflow.models import WorkflowStep
20 from workflow.forms import ResourceSelectorForm, SWConfigSelectorForm, BookingMetaForm
21 from resource_inventory.models import GenericResourceBundle, ResourceBundle, ConfigBundle
22
23
24 class Resource_Select(WorkflowStep):
25     template = 'booking/steps/resource_select.html'
26     title = "Select Resource"
27     description = "Select a resource template to use for your deployment"
28     short_title = "pod select"
29
30     def __init__(self, *args, **kwargs):
31         super(Resource_Select, self).__init__(*args, **kwargs)
32         self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
33         self.repo_check_key = False
34         self.confirm_key = "booking"
35
36     def get_context(self):
37         context = super(Resource_Select, self).get_context()
38         default = []
39
40         chosen_bundle = self.repo_get(self.repo_key, False)
41         if chosen_bundle:
42             default.append(chosen_bundle.id)
43
44         bundle = chosen_bundle
45         edit = self.repo_get(self.repo.EDIT, False)
46         user = self.repo_get(self.repo.SESSION_USER)
47         context['form'] = ResourceSelectorForm(
48             data={"user": user},
49             chosen_resource=default,
50             bundle=bundle,
51             edit=edit
52         )
53         return context
54
55     def post_render(self, request):
56         form = ResourceSelectorForm(request.POST)
57         context = self.get_context()
58         if form.is_valid():
59             data = form.cleaned_data['generic_resource_bundle']
60             data = data[2:-2]
61             if not data:
62                 self.metastep.set_invalid("Please select a valid bundle")
63                 return render(request, self.template, context)
64             selected_bundle = json.loads(data)
65             if len(selected_bundle) < 1:
66                 self.metastep.set_invalid("Please select a valid bundle")
67                 return render(request, self.template, context)
68             selected_id = selected_bundle[0]['id']
69             gresource_bundle = None
70             try:
71                 selected_id = int(selected_id)
72                 gresource_bundle = GenericResourceBundle.objects.get(id=selected_id)
73             except ValueError:
74                 # we want the bundle in the repo
75                 gresource_bundle = self.repo_get(
76                     self.repo.GRESOURCE_BUNDLE_MODELS,
77                     {}
78                 ).get("bundle", GenericResourceBundle())
79             self.repo_put(
80                 self.repo_key,
81                 gresource_bundle
82             )
83             confirm = self.repo_get(self.repo.CONFIRMATION)
84             if self.confirm_key not in confirm:
85                 confirm[self.confirm_key] = {}
86             confirm[self.confirm_key]["resource name"] = gresource_bundle.name
87             self.repo_put(self.repo.CONFIRMATION, confirm)
88             messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
89             self.metastep.set_valid("Step Completed")
90             return render(request, self.template, context)
91         else:
92             messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
93             self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
94             return render(request, self.template, context)
95
96
97 class Booking_Resource_Select(Resource_Select):
98
99     def __init__(self, *args, **kwargs):
100         super(Booking_Resource_Select, self).__init__(*args, **kwargs)
101         self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
102         self.confirm_key = "booking"
103
104     def get_context(self):
105         context = super(Booking_Resource_Select, self).get_context()
106         return context
107
108     def post_render(self, request):
109         response = super(Booking_Resource_Select, self).post_render(request)
110         models = self.repo_get(self.repo.BOOKING_MODELS, {})
111         if "booking" not in models:
112             models['booking'] = Booking()
113         booking = models['booking']
114         resource = self.repo_get(self.repo_key, False)
115         if resource:
116             try:
117                 booking.resource.template = resource
118             except:
119                 booking.resource = ResourceBundle(template=resource)
120         models['booking'] = booking
121         self.repo_put(self.repo.BOOKING_MODELS, models)
122         return response
123
124
125 class SWConfig_Select(WorkflowStep):
126     template = 'booking/steps/swconfig_select.html'
127     title = "Select Software Configuration"
128     description = "Choose the software and related configurations you want to have used for your deployment"
129     short_title = "pod config"
130
131     def post_render(self, request):
132         form = SWConfigSelectorForm(request.POST)
133         if form.is_valid():
134
135             bundle_json = form.cleaned_data['software_bundle']
136             bundle_json = bundle_json[2:-2]  # Stupid django string bug
137             if not bundle_json:
138                 self.metastep.set_invalid("Please select a valid config")
139                 return self.render(request)
140             bundle_json = json.loads(bundle_json)
141             if len(bundle_json) < 1:
142                 self.metastep.set_invalid("Please select a valid config")
143                 return self.render(request)
144             bundle = None
145             id = int(bundle_json[0]['id'])
146             bundle = ConfigBundle.objects.get(id=id)
147
148             grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
149
150             if grb and bundle.bundle != grb:
151                 self.metastep.set_invalid("Incompatible config selected for resource bundle")
152                 return self.render(request)
153             if not grb:
154                 self.repo_set(self.repo.SELECTED_GRESOURCE_BUNDLE, bundle.bundle)
155
156             models = self.repo_get(self.repo.BOOKING_MODELS, {})
157             if "booking" not in models:
158                 models['booking'] = Booking()
159             models['booking'].config_bundle = bundle
160             self.repo_put(self.repo.BOOKING_MODELS, models)
161             confirm = self.repo_get(self.repo.CONFIRMATION)
162             if "booking" not in confirm:
163                 confirm['booking'] = {}
164             confirm['booking']["configuration name"] = bundle.name
165             self.repo_put(self.repo.CONFIRMATION, confirm)
166             self.metastep.set_valid("Step Completed")
167             messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
168         else:
169             self.metastep.set_invalid("Please select or create a valid config")
170             messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
171
172         return self.render(request)
173
174     def get_context(self):
175         context = super(SWConfig_Select, self).get_context()
176         default = []
177         bundle = None
178         chosen_bundle = None
179         created_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
180         booking = self.repo_get(self.repo.BOOKING_MODELS, {}).get("booking", False)
181         try:
182             chosen_bundle = booking.config_bundle
183             default.append(chosen_bundle.id)
184             bundle = chosen_bundle
185         except:
186             if created_bundle:
187                 default.append(created_bundle.id)
188                 bundle = created_bundle
189         edit = self.repo_get(self.repo.EDIT, False)
190         grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
191         context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=bundle, edit=edit, resource=grb)
192         return context
193
194
195 class Booking_Meta(WorkflowStep):
196     template = 'booking/steps/booking_meta.html'
197     title = "Extra Info"
198     description = "Tell us how long you want your booking, what it is for, and who else should have access to it"
199     short_title = "booking info"
200
201     def get_context(self):
202         context = super(Booking_Meta, self).get_context()
203         initial = {}
204         default = []
205         try:
206             models = self.repo_get(self.repo.BOOKING_MODELS, {})
207             booking = models.get("booking")
208             if booking:
209                 initial['purpose'] = booking.purpose
210                 initial['project'] = booking.project
211                 initial['length'] = (booking.end - booking.start).days
212             info = self.repo_get(self.repo.BOOKING_INFO_FILE, False)
213             if info:
214                 initial['info_file'] = info
215             users = models.get("collaborators", [])
216             for user in users:
217                 default.append(user.id)
218         except Exception:
219             pass
220
221         default_user = self.repo_get(self.repo.SESSION_USER)
222         if default_user is None:
223             # TODO: error
224             default_user = "you"
225         else:
226             default_user = default_user.username
227
228         context['form'] = BookingMetaForm(initial=initial, chosen_users=default, default_user=default_user)
229         return context
230
231     def post_render(self, request):
232         form = BookingMetaForm(data=request.POST)
233         context = self.get_context()
234
235         forms = self.repo_get(self.repo.BOOKING_FORMS, {})
236
237         forms["meta_form"] = form
238         self.repo_put(self.repo.BOOKING_FORMS, forms)
239
240         if form.is_valid():
241             models = self.repo_get(self.repo.BOOKING_MODELS, {})
242             if "booking" not in models:
243                 models['booking'] = Booking()
244             models['collaborators'] = []
245             confirm = self.repo_get(self.repo.CONFIRMATION)
246             if "booking" not in confirm:
247                 confirm['booking'] = {}
248
249             models['booking'].start = timezone.now()
250             models['booking'].end = timezone.now() + timedelta(days=int(form.cleaned_data['length']))
251             models['booking'].purpose = form.cleaned_data['purpose']
252             models['booking'].project = form.cleaned_data['project']
253             for key in ['length', 'project', 'purpose']:
254                 confirm['booking'][key] = form.cleaned_data[key]
255
256             user_data = form.cleaned_data['users']
257             confirm['booking']['collaborators'] = []
258             user_data = user_data[2:-2]  # fixes malformed string from querydict
259             if user_data:
260                 form_users = json.loads(user_data)
261                 for user_json in form_users:
262                     user = User.objects.get(pk=user_json['id'])
263                     models['collaborators'].append(user)
264                     confirm['booking']['collaborators'].append(user.username)
265
266             info_file = form.cleaned_data.get("info_file", False)
267             if info_file:
268                 self.repo_put(self.repo.BOOKING_INFO_FILE, info_file)
269
270             self.repo_put(self.repo.BOOKING_MODELS, models)
271             self.repo_put(self.repo.CONFIRMATION, confirm)
272             messages.add_message(request, messages.SUCCESS, 'Form Validated', fail_silently=True)
273             self.metastep.set_valid("Step Completed")
274         else:
275             messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True)
276             self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
277             context['form'] = form  # TODO: store this form
278         return render(request, self.template, context)