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