Implement OPNFV workflow
[laas.git] / src / workflow / sw_bundle_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
11 from django.forms import formset_factory
12
13 from workflow.models import WorkflowStep
14 from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
15 from workflow.booking_workflow import Resource_Select
16 from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration
17
18
19 # resource selection step is reused from Booking workflow
20 class SWConf_Resource_Select(Resource_Select):
21     def __init__(self, *args, **kwargs):
22         super(SWConf_Resource_Select, self).__init__(*args, **kwargs)
23         self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
24         self.confirm_key = "configuration"
25
26     def post_render(self, request):
27         response = super(SWConf_Resource_Select, self).post_render(request)
28         models = self.repo_get(self.repo.CONFIG_MODELS, {})
29         bundle = models.get("bundle", ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER)))
30         bundle.bundle = self.repo_get(self.repo_key)  # super put grb here
31         models['bundle'] = bundle
32         self.repo_put(self.repo.CONFIG_MODELS, models)
33         return response
34
35
36 class Define_Software(WorkflowStep):
37     template = 'config_bundle/steps/define_software.html'
38     title = "Pick Software"
39     description = "Choose the opnfv and image of your machines"
40     short_title = "host config"
41
42     def build_filter_data(self, hosts_data):
43         """
44         returns a 2D array of images to exclude
45         based on the ordering of the passed
46         hosts_data
47         """
48         filter_data = []
49         user = self.repo_get(self.repo.SESSION_USER)
50         lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab
51         for i, host_data in enumerate(hosts_data):
52             host = GenericHost.objects.get(pk=host_data['host_id'])
53             wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
54             wrong_host = Image.objects.exclude(host_type=host.profile)
55             wrong_lab = Image.objects.exclude(from_lab=lab)
56             excluded_images = wrong_owner | wrong_host | wrong_lab
57             filter_data.append([])
58             for image in excluded_images:
59                 filter_data[i].append(image.pk)
60         return filter_data
61
62     def create_hostformset(self, hostlist, data=None):
63         hosts_initial = []
64         host_configs = self.repo_get(self.repo.CONFIG_MODELS, {}).get("host_configs", False)
65         if host_configs:
66             for config in host_configs:
67                 hosts_initial.append({
68                     'host_id': config.host.id,
69                     'host_name': config.host.resource.name,
70                     'headnode': config.is_head_node,
71                     'image': config.image
72                 })
73         else:
74             for host in hostlist:
75                 hosts_initial.append({
76                     'host_id': host.id,
77                     'host_name': host.resource.name
78                 })
79
80         HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
81         filter_data = self.build_filter_data(hosts_initial)
82
83         class SpecialHostFormset(HostFormset):
84             def get_form_kwargs(self, index):
85                 kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
86                 if index is not None:
87                     kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
88                 return kwargs
89
90         if data:
91             return SpecialHostFormset(data, initial=hosts_initial)
92         return SpecialHostFormset(initial=hosts_initial)
93
94     def get_host_list(self, grb=None):
95         if grb is None:
96             grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
97             if not grb:
98                 return []
99         if grb.id:
100             return GenericHost.objects.filter(resource__bundle=grb)
101         generic_hosts = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("hosts", [])
102         return generic_hosts
103
104     def get_context(self):
105         context = super(Define_Software, self).get_context()
106
107         grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
108
109         if grb:
110             context["grb"] = grb
111             formset = self.create_hostformset(self.get_host_list(grb))
112             context["formset"] = formset
113             context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1)
114         else:
115             context["error"] = "Please select a resource first"
116             self.metastep.set_invalid("Step requires information that is not yet provided by previous step")
117
118         return context
119
120     def post_render(self, request):
121         models = self.repo_get(self.repo.CONFIG_MODELS, {})
122         if "bundle" not in models:
123             models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
124
125         confirm = self.repo_get(self.repo.CONFIRMATION, {})
126
127         hosts = self.get_host_list()
128         models['headnode_index'] = request.POST.get("headnode", 1)
129         formset = self.create_hostformset(hosts, data=request.POST)
130         has_headnode = False
131         if formset.is_valid():
132             models['host_configs'] = []
133             confirm_hosts = []
134             for i, form in enumerate(formset):
135                 host = hosts[i]
136                 image = form.cleaned_data['image']
137                 headnode = form.cleaned_data['headnode']
138                 if headnode:
139                     has_headnode = True
140                 bundle = models['bundle']
141                 hostConfig = HostConfiguration(
142                     host=host,
143                     image=image,
144                     bundle=bundle,
145                     is_head_node=headnode
146                 )
147                 models['host_configs'].append(hostConfig)
148                 confirm_hosts.append({
149                     "name": host.resource.name,
150                     "image": image.name,
151                     "headnode": headnode
152                 })
153
154             if not has_headnode:
155                 self.metastep.set_invalid('Must have one "Headnode" per POD')
156                 return self.render(request)
157
158             self.repo_put(self.repo.CONFIG_MODELS, models)
159             if "configuration" not in confirm:
160                 confirm['configuration'] = {}
161             confirm['configuration']['hosts'] = confirm_hosts
162             self.repo_put(self.repo.CONFIRMATION, confirm)
163             self.metastep.set_valid("Completed")
164         else:
165             self.metastep.set_invalid("Please complete all fields")
166
167         return self.render(request)
168
169
170 class Config_Software(WorkflowStep):
171     template = 'config_bundle/steps/config_software.html'
172     title = "Other Info"
173     description = "Give your software config a name, description, and other stuff"
174     short_title = "config info"
175
176     def get_context(self):
177         context = super(Config_Software, self).get_context()
178
179         initial = {}
180         models = self.repo_get(self.repo.CONFIG_MODELS, {})
181         bundle = models.get("bundle", False)
182         if bundle:
183             initial['name'] = bundle.name
184             initial['description'] = bundle.description
185         context["form"] = BasicMetaForm(initial=initial)
186         return context
187
188     def post_render(self, request):
189         models = self.repo_get(self.repo.CONFIG_MODELS, {})
190         if "bundle" not in models:
191             models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
192
193         confirm = self.repo_get(self.repo.CONFIRMATION, {})
194         if "configuration" not in confirm:
195             confirm['configuration'] = {}
196
197         form = BasicMetaForm(request.POST)
198         if form.is_valid():
199             models['bundle'].name = form.cleaned_data['name']
200             models['bundle'].description = form.cleaned_data['description']
201
202             confirm['configuration']['name'] = form.cleaned_data['name']
203             confirm['configuration']['description'] = form.cleaned_data['description']
204             self.metastep.set_valid("Complete")
205         else:
206             self.metastep.set_invalid("Please correct the errors shown below")
207
208         self.repo_put(self.repo.CONFIG_MODELS, models)
209         self.repo_put(self.repo.CONFIRMATION, confirm)
210
211         return self.render(request)