2 Copyright (c) 2017 SONATA-NFV and Paderborn University
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
9 http://www.apache.org/licenses/LICENSE-2.0
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.
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
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).
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
39 LOG
= logging
.getLogger("api.openstack.heat")
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
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
})
65 @self.app
.after_request
66 def add_access_control_header(response
):
67 response
.headers
['Access-Control-Allow-Origin'] = '*'
71 class HeatListAPIVersions(Resource
):
72 def __init__(self
, api
):
76 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
79 resp
['versions'] = dict()
85 "href": "http://%s:%d/v2.0" % (get_host(request
), self
.api
.port
),
91 return Response(json
.dumps(resp
), status
=200, mimetype
="application/json")
94 class HeatCreateStack(Resource
):
95 def __init__(self
, api
):
98 def post(self
, tenant_id
):
100 Create and deploy a new stack.
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.
108 LOG
.debug("API CALL: %s POST" % str(self
.__class
__.__name
__))
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']:
116 stack
.stack_name
= stack_dict
['stack_name']
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
125 stack
.template
= stack_dict
['template']
126 stack
.creation_time
= str(datetime
.now())
127 stack
.status
= "CREATE_COMPLETE"
129 return_dict
= {"stack": {"id": stack
.id,
132 "href": "http://%s:%s/v1/%s/stacks/%s"
133 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
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")
141 except Exception as ex
:
142 LOG
.exception("Heat: Create Stack exception.")
143 return ex
.message
, 500
145 def get(self
, tenant_id
):
147 Calculates information about the requested stack.
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.
154 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
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,
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
,
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
177 class HeatShowStack(Resource
):
178 def __init__(self
, api
):
181 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
183 Calculates detailed information about the requested stack.
186 :param stack_name_or_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.
192 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
195 if stack_name_or_id
in self
.api
.compute
.stacks
:
196 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
198 for tmp_stack
in self
.api
.compute
.stacks
.values():
199 if tmp_stack
.stack_name
== stack_name_or_id
:
202 return 'Could not resolve Stack - ID', 404
207 "creation_time": stack
.creation_time
,
208 "description": "desc of " + stack
.stack_name
,
209 "disable_rollback": True,
213 "href": "http://%s:%s/v1/%s/stacks/%s"
214 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
218 "notification_topics": [],
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
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.",
238 return Response(json
.dumps(return_stack
), status
=200, mimetype
="application/json")
240 except Exception as ex
:
241 LOG
.exception("Heat: Show stack exception.")
242 return ex
.message
, 500
245 class HeatShowStackTemplate(Resource
):
246 def __init__(self
, api
):
249 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
251 Returns template of given stack.
254 :param stack_name_or_id:
256 :return: Returns a json response which contains the stack's template.
258 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
261 if stack_name_or_id
in self
.api
.compute
.stacks
:
262 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
264 for tmp_stack
in self
.api
.compute
.stacks
.values():
265 if tmp_stack
.stack_name
== stack_name_or_id
:
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")
273 except Exception as ex
:
274 LOG
.exception("Heat: Show stack template exception.")
275 return ex
.message
, 500
278 class HeatShowStackResources(Resource
):
279 def __init__(self
, api
):
282 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
284 Returns template of given stack.
287 :param stack_name_or_id:
289 :return: Returns a json response which contains the stack's template.
291 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
294 if stack_name_or_id
in self
.api
.compute
.stacks
:
295 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
297 for tmp_stack
in self
.api
.compute
.stacks
.values():
298 if tmp_stack
.stack_name
== stack_name_or_id
:
301 return 'Could not resolve Stack - ID', 404
303 response
= {"resources": []}
305 return Response(json
.dumps(response
), status
=200, mimetype
="application/json")
307 except Exception as ex
:
308 LOG
.exception("Heat: Show stack template exception.")
309 return ex
.message
, 500
312 class HeatUpdateStack(Resource
):
313 def __init__(self
, api
):
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
)
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
)
324 def update_stack(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
326 Updates an existing stack with a new heat template.
329 :param stack_name_or_id: Specifies the stack, which should be updated.
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.
338 if stack_name_or_id
in self
.api
.compute
.stacks
:
339 old_stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
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
347 stack_dict
= json
.loads(request
.data
)
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"
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']
363 if not self
.api
.compute
.update_stack(old_stack
.id, stack
):
364 return 'Could not update stack.', 400
366 return Response(status
=202, mimetype
="application/json")
368 except Exception as ex
:
369 LOG
.exception("Heat: Update Stack exception")
370 return ex
.message
, 500
373 class HeatDeleteStack(Resource
):
374 def __init__(self
, api
):
377 def delete(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
379 Deletes an existing stack.
382 :param stack_name_or_id: Specifies the stack, which should be deleted.
384 :return: 500, if any exception occurred while deletion.
385 204, if everything worked out.
387 LOG
.debug("API CALL: %s DELETE" % str(self
.__class
__.__name
__))
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)
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)
398 except Exception as ex
:
399 LOG
.exception("Heat: Delete Stack exception")
400 return ex
.message
, 500