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