Merge "bugfix: OperationFailure: the limit must be positive"
[releng.git] / utils / test / testapi / opnfv_testapi / resources / scenario_handlers.py
1 import functools
2
3 from opnfv_testapi.common import message
4 from opnfv_testapi.common import raises
5 from opnfv_testapi.resources import handlers
6 import opnfv_testapi.resources.scenario_models as models
7 from opnfv_testapi.tornado_swagger import swagger
8
9
10 class GenericScenarioHandler(handlers.GenericApiHandler):
11     def __init__(self, application, request, **kwargs):
12         super(GenericScenarioHandler, self).__init__(application,
13                                                      request,
14                                                      **kwargs)
15         self.table = self.db_scenarios
16         self.table_cls = models.Scenario
17
18     def set_query(self, locators):
19         query = dict()
20         elem_query = dict()
21         for k, v in locators.iteritems():
22             if k == 'scenario':
23                 query['name'] = v
24             elif k == 'installer':
25                 elem_query["installer"] = v
26             elif k == 'version':
27                 elem_query["versions.version"] = v
28             elif k == 'project':
29                 elem_query["versions.projects.project"] = v
30             else:
31                 query[k] = v
32         if elem_query:
33             query['installers'] = {'$elemMatch': elem_query}
34         return query
35
36
37 class ScenariosCLHandler(GenericScenarioHandler):
38     @swagger.operation(nickname="queryScenarios")
39     def get(self):
40         """
41             @description: Retrieve scenario(s).
42             @notes: Retrieve scenario(s)
43                 Available filters for this request are :
44                  - name : scenario name
45
46                 GET /scenarios?name=scenario_1
47             @param name: scenario name
48             @type name: L{string}
49             @in name: query
50             @required name: False
51             @param installer: installer type
52             @type installer: L{string}
53             @in installer: query
54             @required installer: False
55             @param version: version
56             @type version: L{string}
57             @in version: query
58             @required version: False
59             @param project: project name
60             @type project: L{string}
61             @in project: query
62             @required project: False
63             @return 200: all scenarios satisfy queries,
64                          empty list if no scenario is found
65             @rtype: L{Scenarios}
66         """
67
68         def _set_query():
69             query = dict()
70             elem_query = dict()
71             for k in self.request.query_arguments.keys():
72                 v = self.get_query_argument(k)
73                 if k == 'installer':
74                     elem_query["installer"] = v
75                 elif k == 'version':
76                     elem_query["versions.version"] = v
77                 elif k == 'project':
78                     elem_query["versions.projects.project"] = v
79                 else:
80                     query[k] = v
81             if elem_query:
82                 query['installers'] = {'$elemMatch': elem_query}
83             return query
84
85         self._list(query=_set_query())
86
87     @swagger.operation(nickname="createScenario")
88     def post(self):
89         """
90             @description: create a new scenario by name
91             @param body: scenario to be created
92             @type body: L{ScenarioCreateRequest}
93             @in body: body
94             @rtype: L{CreateResponse}
95             @return 200: scenario is created.
96             @raise 403: scenario already exists
97             @raise 400:  body or name not provided
98         """
99         def query():
100             return {'name': self.json_args.get('name')}
101         miss_fields = ['name']
102         self._create(miss_fields=miss_fields, query=query)
103
104
105 class ScenarioGURHandler(GenericScenarioHandler):
106     @swagger.operation(nickname='getScenarioByName')
107     def get(self, name):
108         """
109             @description: get a single scenario by name
110             @rtype: L{Scenario}
111             @return 200: scenario exist
112             @raise 404: scenario not exist
113         """
114         self._get_one(query={'name': name})
115         pass
116
117     def put(self, name):
118         pass
119
120     @swagger.operation(nickname="deleteScenarioByName")
121     def delete(self, name):
122         """
123         @description: delete a scenario by name
124         @return 200: delete success
125         @raise 404: scenario not exist:
126         """
127         self._delete(query={'name': name})
128
129
130 class ScenarioUpdater(object):
131     def __init__(self, data, body=None,
132                  installer=None, version=None, project=None):
133         self.data = data
134         self.body = body
135         self.installer = installer
136         self.version = version
137         self.project = project
138
139     def update(self, item, action):
140         updates = {
141             ('scores', 'post'): self._update_requests_add_score,
142             ('trust_indicators', 'post'): self._update_requests_add_ti,
143             ('customs', 'post'): self._update_requests_add_customs,
144             ('customs', 'put'): self._update_requests_update_customs,
145             ('customs', 'delete'): self._update_requests_delete_customs,
146             ('projects', 'post'): self._update_requests_add_projects,
147             ('projects', 'put'): self._update_requests_update_projects,
148             ('projects', 'delete'): self._update_requests_delete_projects,
149             ('owner', 'put'): self._update_requests_change_owner,
150         }
151         updates[(item, action)](self.data)
152
153         return self.data.format()
154
155     def iter_installers(xstep):
156         @functools.wraps(xstep)
157         def magic(self, data):
158             [xstep(self, installer)
159              for installer in self._filter_installers(data.installers)]
160         return magic
161
162     def iter_versions(xstep):
163         @functools.wraps(xstep)
164         def magic(self, installer):
165             [xstep(self, version)
166              for version in (self._filter_versions(installer.versions))]
167         return magic
168
169     def iter_projects(xstep):
170         @functools.wraps(xstep)
171         def magic(self, version):
172             [xstep(self, project)
173              for project in (self._filter_projects(version.projects))]
174         return magic
175
176     @iter_installers
177     @iter_versions
178     @iter_projects
179     def _update_requests_add_score(self, project):
180         project.scores.append(
181             models.ScenarioScore.from_dict(self.body))
182
183     @iter_installers
184     @iter_versions
185     @iter_projects
186     def _update_requests_add_ti(self, project):
187         project.trust_indicators.append(
188             models.ScenarioTI.from_dict(self.body))
189
190     @iter_installers
191     @iter_versions
192     @iter_projects
193     def _update_requests_add_customs(self, project):
194         project.customs = list(set(project.customs + self.body))
195
196     @iter_installers
197     @iter_versions
198     @iter_projects
199     def _update_requests_update_customs(self, project):
200         project.customs = list(set(self.body))
201
202     @iter_installers
203     @iter_versions
204     @iter_projects
205     def _update_requests_delete_customs(self, project):
206         project.customs = filter(
207             lambda f: f not in self.body,
208             project.customs)
209
210     @iter_installers
211     @iter_versions
212     def _update_requests_add_projects(self, version):
213         exists = list()
214         malformat = list()
215         for n in self.body:
216             try:
217                 f_n = models.ScenarioProject.from_dict_with_raise(n)
218                 if not any(o.project == f_n.project for o in version.projects):
219                     version.projects.append(f_n)
220                 else:
221                     exists.append(n['project'])
222             except Exception as e:
223                 malformat.append(e.message)
224         if malformat:
225             raises.BadRequest(message.bad_format(malformat))
226         elif exists:
227             raises.Conflict(message.exist('projects', exists))
228
229     @iter_installers
230     @iter_versions
231     def _update_requests_update_projects(self, version):
232         exists = list()
233         malformat = list()
234         projects = list()
235         for n in self.body:
236             try:
237                 f_n = models.ScenarioProject.from_dict_with_raise(n)
238                 if not any(o.project == f_n.project for o in projects):
239                     projects.append(models.ScenarioProject.from_dict(n))
240                 else:
241                     exists.append(n['project'])
242             except:
243                 malformat.append(n)
244         if malformat:
245             raises.BadRequest(message.bad_format(malformat))
246         elif exists:
247             raises.Forbidden(message.exist('projects', exists))
248         version.projects = projects
249
250     @iter_installers
251     @iter_versions
252     def _update_requests_delete_projects(self, version):
253         version.projects = self._remove_projects(version.projects)
254
255     @iter_installers
256     @iter_versions
257     def _update_requests_change_owner(self, version):
258         version.owner = self.body
259
260     def _filter_installers(self, installers):
261         return self._filter('installer', installers)
262
263     def _filter_versions(self, versions):
264         return self._filter('version', versions)
265
266     def _filter_projects(self, projects):
267         return self._filter('project', projects)
268
269     def _remove_projects(self, projects):
270         return self._remove('project', projects)
271
272     def _filter(self, item, items):
273         return filter(
274             lambda f: getattr(f, item) == getattr(self, item),
275             items)
276
277     def _remove(self, field, fields):
278         return filter(
279             lambda f: getattr(f, field) not in self.body,
280             fields)
281
282
283 class GenericScenarioUpdateHandler(GenericScenarioHandler):
284     def __init__(self, application, request, **kwargs):
285         super(GenericScenarioUpdateHandler, self).__init__(application,
286                                                            request,
287                                                            **kwargs)
288         self.installer = None
289         self.version = None
290         self.project = None
291         self.item = None
292         self.action = None
293
294     def do_update(self, item, action, locators):
295         self.item = item
296         self.action = action
297         for k, v in locators.iteritems():
298             if not v:
299                 v = self.get_query_argument(k)
300                 setattr(self, k, v)
301                 locators[k] = v
302         self.pure_update(query=self.set_query(locators=locators))
303
304     def _update_requests(self, data):
305         return ScenarioUpdater(data,
306                                self.json_args,
307                                self.installer,
308                                self.version,
309                                self.project).update(self.item, self.action)
310
311
312 class ScenarioScoresHandler(GenericScenarioUpdateHandler):
313     @swagger.operation(nickname="addScoreRecord")
314     def post(self, scenario):
315         """
316         @description: add a new score record
317         @notes: add a new score record to a project
318             POST /api/v1/scenarios/<scenario_name>/scores? \
319                 installer=<installer_name>& \
320                 version=<version_name>& \
321                 project=<project_name>
322         @param body: score to be added
323         @type body: L{ScenarioScore}
324         @in body: body
325         @param installer: installer type
326         @type installer: L{string}
327         @in installer: query
328         @required installer: True
329         @param version: version
330         @type version: L{string}
331         @in version: query
332         @required version: True
333         @param project: project name
334         @type project: L{string}
335         @in project: query
336         @required project: True
337         @return 200: score is created.
338         @raise 404:  scenario/installer/version/project not existed
339         """
340         self.do_update('scores',
341                        'post',
342                        locators={'scenario': scenario,
343                                  'installer': None,
344                                  'version': None,
345                                  'project': None})
346
347
348 class ScenarioTIsHandler(GenericScenarioUpdateHandler):
349     @swagger.operation(nickname="addTrustIndicatorRecord")
350     def post(self, scenario):
351         """
352         @description: add a new trust indicator record
353         @notes: add a new trust indicator record to a project
354             POST /api/v1/scenarios/<scenario_name>/trust_indicators? \
355                 installer=<installer_name>& \
356                 version=<version_name>& \
357                 project=<project_name>
358         @param body: trust indicator to be added
359         @type body: L{ScenarioTI}
360         @in body: body
361         @param installer: installer type
362         @type installer: L{string}
363         @in installer: query
364         @required installer: True
365         @param version: version
366         @type version: L{string}
367         @in version: query
368         @required version: True
369         @param project: project name
370         @type project: L{string}
371         @in project: query
372         @required project: True
373         @return 200: trust indicator is added.
374         @raise 404:  scenario/installer/version/project not existed
375         """
376         self.do_update('trust_indicators',
377                        'post',
378                        locators={'scenario': scenario,
379                                  'installer': None,
380                                  'version': None,
381                                  'project': None})
382
383
384 class ScenarioCustomsHandler(GenericScenarioUpdateHandler):
385     @swagger.operation(nickname="addCustomizedTestCases")
386     def post(self, scenario):
387         """
388         @description: add customized test cases
389         @notes: add several test cases to a project
390             POST /api/v1/scenarios/<scenario_name>/customs? \
391                 installer=<installer_name>& \
392                 version=<version_name>& \
393                 project=<project_name>
394         @param body: test cases to be added
395         @type body: C{list} of L{string}
396         @in body: body
397         @param installer: installer type
398         @type installer: L{string}
399         @in installer: query
400         @required installer: True
401         @param version: version
402         @type version: L{string}
403         @in version: query
404         @required version: True
405         @param project: project name
406         @type project: L{string}
407         @in project: query
408         @required project: True
409         @return 200: test cases are added.
410         @raise 404:  scenario/installer/version/project not existed
411         """
412         self.do_update('customs',
413                        'post',
414                        locators={'scenario': scenario,
415                                  'installer': None,
416                                  'version': None,
417                                  'project': None})
418
419     @swagger.operation(nickname="updateCustomizedTestCases")
420     def put(self, scenario):
421         """
422         @description: update customized test cases
423         @notes: substitute all the customized test cases
424             PUT /api/v1/scenarios/<scenario_name>/customs? \
425                 installer=<installer_name>& \
426                 version=<version_name>& \
427                 project=<project_name>
428         @param body: new supported test cases
429         @type body: C{list} of L{string}
430         @in body: body
431         @param installer: installer type
432         @type installer: L{string}
433         @in installer: query
434         @required installer: True
435         @param version: version
436         @type version: L{string}
437         @in version: query
438         @required version: True
439         @param project: project name
440         @type project: L{string}
441         @in project: query
442         @required project: True
443         @return 200: substitute test cases success.
444         @raise 404:  scenario/installer/version/project not existed
445         """
446         self.do_update('customs',
447                        'put',
448                        locators={'scenario': scenario,
449                                  'installer': None,
450                                  'version': None,
451                                  'project': None})
452
453     @swagger.operation(nickname="deleteCustomizedTestCases")
454     def delete(self, scenario):
455         """
456         @description: delete one or several customized test cases
457         @notes: delete one or some customized test cases
458             DELETE /api/v1/scenarios/<scenario_name>/customs? \
459                 installer=<installer_name>& \
460                 version=<version_name>& \
461                 project=<project_name>
462         @param body: test case(s) to be deleted
463         @type body: C{list} of L{string}
464         @in body: body
465         @param installer: installer type
466         @type installer: L{string}
467         @in installer: query
468         @required installer: True
469         @param version: version
470         @type version: L{string}
471         @in version: query
472         @required version: True
473         @param project: project name
474         @type project: L{string}
475         @in project: query
476         @required project: True
477         @return 200: delete test case(s) success.
478         @raise 404:  scenario/installer/version/project not existed
479         """
480         self.do_update('customs',
481                        'delete',
482                        locators={'scenario': scenario,
483                                  'installer': None,
484                                  'version': None,
485                                  'project': None})
486
487
488 class ScenarioProjectsHandler(GenericScenarioUpdateHandler):
489     @swagger.operation(nickname="addProjectsUnderScenario")
490     def post(self, scenario):
491         """
492         @description: add projects to scenario
493         @notes: add one or multiple projects
494             POST /api/v1/scenarios/<scenario_name>/projects? \
495                 installer=<installer_name>& \
496                 version=<version_name>
497         @param body: projects to be added
498         @type body: C{list} of L{ScenarioProject}
499         @in body: body
500         @param installer: installer type
501         @type installer: L{string}
502         @in installer: query
503         @required installer: True
504         @param version: version
505         @type version: L{string}
506         @in version: query
507         @required version: True
508         @return 200: projects are added.
509         @raise 400: bad schema
510         @raise 409: conflict, project already exists
511         @raise 404:  scenario/installer/version not existed
512         """
513         self.do_update('projects',
514                        'post',
515                        locators={'scenario': scenario,
516                                  'installer': None,
517                                  'version': None})
518
519     @swagger.operation(nickname="updateScenarioProjects")
520     def put(self, scenario):
521         """
522         @description: replace all projects
523         @notes: substitute all projects, delete existed ones with new provides
524             PUT /api/v1/scenarios/<scenario_name>/projects? \
525                 installer=<installer_name>& \
526                 version=<version_name>
527         @param body: new projects
528         @type body: C{list} of L{ScenarioProject}
529         @in body: body
530         @param installer: installer type
531         @type installer: L{string}
532         @in installer: query
533         @required installer: True
534         @param version: version
535         @type version: L{string}
536         @in version: query
537         @required version: True
538         @return 200: replace projects success.
539         @raise 400: bad schema
540         @raise 404:  scenario/installer/version not existed
541         """
542         self.do_update('projects',
543                        'put',
544                        locators={'scenario': scenario,
545                                  'installer': None,
546                                  'version': None})
547
548     @swagger.operation(nickname="deleteProjectsUnderScenario")
549     def delete(self, scenario):
550         """
551         @description: delete one or multiple projects
552         @notes: delete one or multiple projects
553             DELETE /api/v1/scenarios/<scenario_name>/projects? \
554                 installer=<installer_name>& \
555                 version=<version_name>
556         @param body: projects(names) to be deleted
557         @type body: C{list} of L{string}
558         @in body: body
559         @param installer: installer type
560         @type installer: L{string}
561         @in installer: query
562         @required installer: True
563         @param version: version
564         @type version: L{string}
565         @in version: query
566         @required version: True
567         @return 200: delete project(s) success.
568         @raise 404:  scenario/installer/version not existed
569         """
570         self.do_update('projects',
571                        'delete',
572                        locators={'scenario': scenario,
573                                  'installer': None,
574                                  'version': None})
575
576
577 class ScenarioOwnerHandler(GenericScenarioUpdateHandler):
578     @swagger.operation(nickname="changeScenarioOwner")
579     def put(self, scenario):
580         """
581         @description: change scenario owner
582         @notes: substitute all projects, delete existed ones with new provides
583             PUT /api/v1/scenarios/<scenario_name>/owner? \
584                 installer=<installer_name>& \
585                 version=<version_name>
586         @param body: new owner
587         @type body: L{string}
588         @in body: body
589         @param installer: installer type
590         @type installer: L{string}
591         @in installer: query
592         @required installer: True
593         @param version: version
594         @type version: L{string}
595         @in version: query
596         @required version: True
597         @return 200: change owner success.
598         @raise 404:  scenario/installer/version not existed
599         """
600         self.do_update('owner',
601                        'put',
602                        locators={'scenario': scenario,
603                                  'installer': None,
604                                  'version': None})