OpenStack API: Replaced Flask with WSGI
[osm/vim-emu.git] / src / emuvim / api / openstack / openstack_dummies / heat_dummy_api.py
1 """
2 Copyright (c) 2017 SONATA-NFV and Paderborn University
3 ALL RIGHTS RESERVED.
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 Neither the name of the SONATA-NFV, Paderborn University
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
20 permission.
21
22 This work has been performed in the framework of the SONATA project,
23 funded by the European Commission under Grant number 671517 through
24 the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 acknowledge the contributions of their colleagues of the SONATA
26 partner consortium (www.sonata-nfv.eu).
27 """
28 from flask import request, Response
29 from flask_restful import Resource
30 from emuvim.api.openstack.resources import Stack
31 from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
32 from emuvim.api.openstack.helper import get_host
33 from datetime import datetime
34 from emuvim.api.openstack.heat_parser import HeatParser
35 import logging
36 import json
37
38
39 LOG = logging.getLogger("api.openstack.heat")
40
41
42 class HeatDummyApi(BaseOpenstackDummy):
43 def __init__(self, in_ip, in_port, compute):
44 super(HeatDummyApi, self).__init__(in_ip, in_port)
45 self.compute = compute
46
47 self.api.add_resource(HeatListAPIVersions, "/",
48 resource_class_kwargs={'api': self})
49 self.api.add_resource(HeatCreateStack, "/v1/<tenant_id>/stacks",
50 resource_class_kwargs={'api': self})
51 self.api.add_resource(HeatShowStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
52 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
53 resource_class_kwargs={'api': self})
54 self.api.add_resource(HeatShowStackTemplate, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/template",
55 resource_class_kwargs={'api': self})
56 self.api.add_resource(HeatShowStackResources, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/resources",
57 resource_class_kwargs={'api': self})
58 self.api.add_resource(HeatUpdateStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
59 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
60 resource_class_kwargs={'api': self})
61 self.api.add_resource(HeatDeleteStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
62 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
63 resource_class_kwargs={'api': self})
64
65 @self.app.after_request
66 def add_access_control_header(response):
67 response.headers['Access-Control-Allow-Origin'] = '*'
68 return response
69
70
71 class HeatListAPIVersions(Resource):
72 def __init__(self, api):
73 self.api = api
74
75 def get(self):
76 LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
77 resp = dict()
78
79 resp['versions'] = dict()
80 resp['versions'] = [{
81 "status": "CURRENT",
82 "id": "v1.0",
83 "links": [
84 {
85 "href": "http://%s:%d/v2.0" % (get_host(request), self.api.port),
86 "rel": "self"
87 }
88 ]
89 }]
90
91 return Response(json.dumps(resp), status=200, mimetype="application/json")
92
93
94 class HeatCreateStack(Resource):
95 def __init__(self, api):
96 self.api = api
97
98 def post(self, tenant_id):
99 """
100 Create and deploy a new stack.
101
102 :param tenant_id:
103 :return: 409, if the stack name was already used.
104 400, if the heat template could not be parsed properly.
105 500, if any exception occurred while creation.
106 201, if everything worked out.
107 """
108 LOG.debug("API CALL: %s POST" % str(self.__class__.__name__))
109
110 try:
111 stack_dict = json.loads(request.data)
112 for stack in self.api.compute.stacks.values():
113 if stack.stack_name == stack_dict['stack_name']:
114 return [], 409
115 stack = Stack()
116 stack.stack_name = stack_dict['stack_name']
117
118 reader = HeatParser(self.api.compute)
119 if isinstance(stack_dict['template'], str) or isinstance(stack_dict['template'], unicode):
120 stack_dict['template'] = json.loads(stack_dict['template'])
121 if not reader.parse_input(stack_dict['template'], stack, self.api.compute.dc.label):
122 self.api.compute.clean_broken_stack(stack)
123 return 'Could not create stack.', 400
124
125 stack.template = stack_dict['template']
126 stack.creation_time = str(datetime.now())
127 stack.status = "CREATE_COMPLETE"
128
129 return_dict = {"stack": {"id": stack.id,
130 "links": [
131 {
132 "href": "http://%s:%s/v1/%s/stacks/%s"
133 % (get_host(request), self.api.port, tenant_id, stack.id),
134 "rel": "self"
135 }]}}
136
137 self.api.compute.add_stack(stack)
138 self.api.compute.deploy_stack(stack.id)
139 return Response(json.dumps(return_dict), status=201, mimetype="application/json")
140
141 except Exception as ex:
142 LOG.exception("Heat: Create Stack exception.")
143 return ex.message, 500
144
145 def get(self, tenant_id):
146 """
147 Calculates information about the requested stack.
148
149 :param tenant_id:
150 :return: Returns a json response which contains information like the stack id, name, status, creation time.
151 500, if any exception occurred.
152 200, if everything worked out.
153 """
154 LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
155 try:
156 return_stacks = dict()
157 return_stacks['stacks'] = list()
158 for stack in self.api.compute.stacks.values():
159 return_stacks['stacks'].append(
160 {"creation_time": stack.creation_time,
161 "description": "desc of " + stack.id,
162 "id": stack.id,
163 "links": [],
164 "stack_name": stack.stack_name,
165 "stack_status": stack.status,
166 "stack_status_reason": "Stack CREATE completed successfully",
167 "updated_time": stack.update_time,
168 "tags": ""
169 })
170
171 return Response(json.dumps(return_stacks), status=200, mimetype="application/json")
172 except Exception as ex:
173 LOG.exception("Heat: List Stack exception.")
174 return ex.message, 500
175
176
177 class HeatShowStack(Resource):
178 def __init__(self, api):
179 self.api = api
180
181 def get(self, tenant_id, stack_name_or_id, stack_id=None):
182 """
183 Calculates detailed information about the requested stack.
184
185 :param tenant_id:
186 :param stack_name_or_id:
187 :param stack_id:
188 :return: Returns a json response which contains information like the stack id, name, status, creation time.
189 500, if any exception occurred.
190 200, if everything worked out.
191 """
192 LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
193 try:
194 stack = None
195 if stack_name_or_id in self.api.compute.stacks:
196 stack = self.api.compute.stacks[stack_name_or_id]
197 else:
198 for tmp_stack in self.api.compute.stacks.values():
199 if tmp_stack.stack_name == stack_name_or_id:
200 stack = tmp_stack
201 if stack is None:
202 return 'Could not resolve Stack - ID', 404
203
204 return_stack = {
205 "stack": {
206 "capabilities": [],
207 "creation_time": stack.creation_time,
208 "description": "desc of " + stack.stack_name,
209 "disable_rollback": True,
210 "id": stack.id,
211 "links": [
212 {
213 "href": "http://%s:%s/v1/%s/stacks/%s"
214 % (get_host(request), self.api.port, tenant_id, stack.id),
215 "rel": "self"
216 }
217 ],
218 "notification_topics": [],
219 "outputs": [],
220 "parameters": {
221 "OS::project_id": "3ab5b02f-a01f-4f95-afa1-e254afc4a435", # add real project id
222 "OS::stack_id": stack.id,
223 "OS::stack_name": stack.stack_name
224 },
225 "stack_name": stack.stack_name,
226 "stack_owner": "The owner of the stack.", # add stack owner
227 "stack_status": stack.status,
228 "stack_status_reason": "The reason for the current status of the stack.", # add status reason
229 "template_description": "The description of the stack template.",
230 "stack_user_project_id": "The project UUID of the stack user.",
231 "timeout_mins": "",
232 "updated_time": "",
233 "parent": "",
234 "tags": ""
235 }
236 }
237
238 return Response(json.dumps(return_stack), status=200, mimetype="application/json")
239
240 except Exception as ex:
241 LOG.exception("Heat: Show stack exception.")
242 return ex.message, 500
243
244
245 class HeatShowStackTemplate(Resource):
246 def __init__(self, api):
247 self.api = api
248
249 def get(self, tenant_id, stack_name_or_id, stack_id=None):
250 """
251 Returns template of given stack.
252
253 :param tenant_id:
254 :param stack_name_or_id:
255 :param stack_id:
256 :return: Returns a json response which contains the stack's template.
257 """
258 LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
259 try:
260 stack = None
261 if stack_name_or_id in self.api.compute.stacks:
262 stack = self.api.compute.stacks[stack_name_or_id]
263 else:
264 for tmp_stack in self.api.compute.stacks.values():
265 if tmp_stack.stack_name == stack_name_or_id:
266 stack = tmp_stack
267 if stack is None:
268 return 'Could not resolve Stack - ID', 404
269 #LOG.debug("STACK: {}".format(stack))
270 #LOG.debug("TEMPLATE: {}".format(stack.template))
271 return Response(json.dumps(stack.template), status=200, mimetype="application/json")
272
273 except Exception as ex:
274 LOG.exception("Heat: Show stack template exception.")
275 return ex.message, 500
276
277
278 class HeatShowStackResources(Resource):
279 def __init__(self, api):
280 self.api = api
281
282 def get(self, tenant_id, stack_name_or_id, stack_id=None):
283 """
284 Returns template of given stack.
285
286 :param tenant_id:
287 :param stack_name_or_id:
288 :param stack_id:
289 :return: Returns a json response which contains the stack's template.
290 """
291 LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
292 try:
293 stack = None
294 if stack_name_or_id in self.api.compute.stacks:
295 stack = self.api.compute.stacks[stack_name_or_id]
296 else:
297 for tmp_stack in self.api.compute.stacks.values():
298 if tmp_stack.stack_name == stack_name_or_id:
299 stack = tmp_stack
300 if stack is None:
301 return 'Could not resolve Stack - ID', 404
302
303 response = {"resources": []}
304
305 return Response(json.dumps(response), status=200, mimetype="application/json")
306
307 except Exception as ex:
308 LOG.exception("Heat: Show stack template exception.")
309 return ex.message, 500
310
311
312 class HeatUpdateStack(Resource):
313 def __init__(self, api):
314 self.api = api
315
316 def put(self, tenant_id, stack_name_or_id, stack_id=None):
317 LOG.debug("API CALL: %s PUT" % str(self.__class__.__name__))
318 return self.update_stack(tenant_id, stack_name_or_id, stack_id)
319
320 def patch(self, tenant_id, stack_name_or_id, stack_id=None):
321 LOG.debug("API CALL: %s PATCH" % str(self.__class__.__name__))
322 return self.update_stack(tenant_id, stack_name_or_id, stack_id)
323
324 def update_stack(self, tenant_id, stack_name_or_id, stack_id=None):
325 """
326 Updates an existing stack with a new heat template.
327
328 :param tenant_id:
329 :param stack_name_or_id: Specifies the stack, which should be updated.
330 :param stack_id:
331 :return: 404, if the requested stack could not be found.
332 400, if the stack creation (because of errors in the heat template) or the stack update failed.
333 500, if any exception occurred while updating.
334 202, if everything worked out.
335 """
336 try:
337 old_stack = None
338 if stack_name_or_id in self.api.compute.stacks:
339 old_stack = self.api.compute.stacks[stack_name_or_id]
340 else:
341 for tmp_stack in self.api.compute.stacks.values():
342 if tmp_stack.stack_name == stack_name_or_id:
343 old_stack = tmp_stack
344 if old_stack is None:
345 return 'Could not resolve Stack - ID', 404
346
347 stack_dict = json.loads(request.data)
348
349 stack = Stack()
350 stack.stack_name = old_stack.stack_name
351 stack.id = old_stack.id
352 stack.creation_time = old_stack.creation_time
353 stack.update_time = str(datetime.now())
354 stack.status = "UPDATE_COMPLETE"
355
356 reader = HeatParser(self.api.compute)
357 if isinstance(stack_dict['template'], str) or isinstance(stack_dict['template'], unicode):
358 stack_dict['template'] = json.loads(stack_dict['template'])
359 if not reader.parse_input(stack_dict['template'], stack, self.api.compute.dc.label, stack_update=True):
360 return 'Could not create stack.', 400
361 stack.template = stack_dict['template']
362
363 if not self.api.compute.update_stack(old_stack.id, stack):
364 return 'Could not update stack.', 400
365
366 return Response(status=202, mimetype="application/json")
367
368 except Exception as ex:
369 LOG.exception("Heat: Update Stack exception")
370 return ex.message, 500
371
372
373 class HeatDeleteStack(Resource):
374 def __init__(self, api):
375 self.api = api
376
377 def delete(self, tenant_id, stack_name_or_id, stack_id=None):
378 """
379 Deletes an existing stack.
380
381 :param tenant_id:
382 :param stack_name_or_id: Specifies the stack, which should be deleted.
383 :param stack_id:
384 :return: 500, if any exception occurred while deletion.
385 204, if everything worked out.
386 """
387 LOG.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
388 try:
389 if stack_name_or_id in self.api.compute.stacks:
390 self.api.compute.delete_stack(stack_name_or_id)
391 return Response('Deleted Stack: ' + stack_name_or_id, 204)
392
393 for stack in self.api.compute.stacks.values():
394 if stack.stack_name == stack_name_or_id:
395 self.api.compute.delete_stack(stack.id)
396 return Response('Deleted Stack: ' + stack_name_or_id, 204)
397
398 except Exception as ex:
399 LOG.exception("Heat: Delete Stack exception")
400 return ex.message, 500