e95d9d6918158dcb5d70a56325e5b2de1c3c3a9e
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(Shutdown
, "/shutdown")
48 self
.api
.add_resource(HeatListAPIVersions
, "/",
49 resource_class_kwargs
={'api': self
})
50 self
.api
.add_resource(HeatCreateStack
, "/v1/<tenant_id>/stacks",
51 resource_class_kwargs
={'api': self
})
52 self
.api
.add_resource(HeatShowStack
, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
53 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
54 resource_class_kwargs
={'api': self
})
55 self
.api
.add_resource(HeatShowStackTemplate
, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/template",
56 resource_class_kwargs
={'api': self
})
57 self
.api
.add_resource(HeatShowStackResources
, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/resources",
58 resource_class_kwargs
={'api': self
})
59 self
.api
.add_resource(HeatUpdateStack
, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
60 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
61 resource_class_kwargs
={'api': self
})
62 self
.api
.add_resource(HeatDeleteStack
, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
63 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
64 resource_class_kwargs
={'api': self
})
66 @self.app
.after_request
67 def add_access_control_header(response
):
68 response
.headers
['Access-Control-Allow-Origin'] = '*'
72 def _start_flask(self
):
73 LOG
.info("Starting %s endpoint @ http://%s:%d" % (__name__
, self
.ip
, self
.port
))
74 if self
.app
is not None:
75 self
.app
.before_request(self
.dump_playbook
)
76 self
.app
.run(self
.ip
, self
.port
, debug
=True, use_reloader
=False)
79 class Shutdown(Resource
):
81 A get request to /shutdown will shut down this endpoint.
85 LOG
.debug(("%s is beeing shut down") % (__name__
))
86 func
= request
.environ
.get('werkzeug.server.shutdown')
88 raise RuntimeError('Not running with the Werkzeug Server')
92 class HeatListAPIVersions(Resource
):
93 def __init__(self
, api
):
97 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
100 resp
['versions'] = dict()
101 resp
['versions'] = [{
106 "href": "http://%s:%d/v2.0" % (get_host(request
), self
.api
.port
),
112 return Response(json
.dumps(resp
), status
=200, mimetype
="application/json")
115 class HeatCreateStack(Resource
):
116 def __init__(self
, api
):
119 def post(self
, tenant_id
):
121 Create and deploy a new stack.
124 :return: 409, if the stack name was already used.
125 400, if the heat template could not be parsed properly.
126 500, if any exception occurred while creation.
127 201, if everything worked out.
129 LOG
.debug("API CALL: %s POST" % str(self
.__class
__.__name
__))
132 stack_dict
= json
.loads(request
.data
)
133 for stack
in self
.api
.compute
.stacks
.values():
134 if stack
.stack_name
== stack_dict
['stack_name']:
137 stack
.stack_name
= stack_dict
['stack_name']
139 reader
= HeatParser(self
.api
.compute
)
140 if isinstance(stack_dict
['template'], str) or isinstance(stack_dict
['template'], unicode):
141 stack_dict
['template'] = json
.loads(stack_dict
['template'])
142 if not reader
.parse_input(stack_dict
['template'], stack
, self
.api
.compute
.dc
.label
):
143 self
.api
.compute
.clean_broken_stack(stack
)
144 return 'Could not create stack.', 400
146 stack
.template
= stack_dict
['template']
147 stack
.creation_time
= str(datetime
.now())
148 stack
.status
= "CREATE_COMPLETE"
150 return_dict
= {"stack": {"id": stack
.id,
153 "href": "http://%s:%s/v1/%s/stacks/%s"
154 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
158 self
.api
.compute
.add_stack(stack
)
159 self
.api
.compute
.deploy_stack(stack
.id)
160 return Response(json
.dumps(return_dict
), status
=201, mimetype
="application/json")
162 except Exception as ex
:
163 LOG
.exception("Heat: Create Stack exception.")
164 return ex
.message
, 500
166 def get(self
, tenant_id
):
168 Calculates information about the requested stack.
171 :return: Returns a json response which contains information like the stack id, name, status, creation time.
172 500, if any exception occurred.
173 200, if everything worked out.
175 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
177 return_stacks
= dict()
178 return_stacks
['stacks'] = list()
179 for stack
in self
.api
.compute
.stacks
.values():
180 return_stacks
['stacks'].append(
181 {"creation_time": stack
.creation_time
,
182 "description": "desc of " + stack
.id,
185 "stack_name": stack
.stack_name
,
186 "stack_status": stack
.status
,
187 "stack_status_reason": "Stack CREATE completed successfully",
188 "updated_time": stack
.update_time
,
192 return Response(json
.dumps(return_stacks
), status
=200, mimetype
="application/json")
193 except Exception as ex
:
194 LOG
.exception("Heat: List Stack exception.")
195 return ex
.message
, 500
198 class HeatShowStack(Resource
):
199 def __init__(self
, api
):
202 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
204 Calculates detailed information about the requested stack.
207 :param stack_name_or_id:
209 :return: Returns a json response which contains information like the stack id, name, status, creation time.
210 500, if any exception occurred.
211 200, if everything worked out.
213 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
216 if stack_name_or_id
in self
.api
.compute
.stacks
:
217 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
219 for tmp_stack
in self
.api
.compute
.stacks
.values():
220 if tmp_stack
.stack_name
== stack_name_or_id
:
223 return 'Could not resolve Stack - ID', 404
228 "creation_time": stack
.creation_time
,
229 "description": "desc of " + stack
.stack_name
,
230 "disable_rollback": True,
234 "href": "http://%s:%s/v1/%s/stacks/%s"
235 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
239 "notification_topics": [],
242 "OS::project_id": "3ab5b02f-a01f-4f95-afa1-e254afc4a435", # add real project id
243 "OS::stack_id": stack
.id,
244 "OS::stack_name": stack
.stack_name
246 "stack_name": stack
.stack_name
,
247 "stack_owner": "The owner of the stack.", # add stack owner
248 "stack_status": stack
.status
,
249 "stack_status_reason": "The reason for the current status of the stack.", # add status reason
250 "template_description": "The description of the stack template.",
251 "stack_user_project_id": "The project UUID of the stack user.",
259 return Response(json
.dumps(return_stack
), status
=200, mimetype
="application/json")
261 except Exception as ex
:
262 LOG
.exception("Heat: Show stack exception.")
263 return ex
.message
, 500
266 class HeatShowStackTemplate(Resource
):
267 def __init__(self
, api
):
270 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
272 Returns template of given stack.
275 :param stack_name_or_id:
277 :return: Returns a json response which contains the stack's template.
279 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
282 if stack_name_or_id
in self
.api
.compute
.stacks
:
283 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
285 for tmp_stack
in self
.api
.compute
.stacks
.values():
286 if tmp_stack
.stack_name
== stack_name_or_id
:
289 return 'Could not resolve Stack - ID', 404
290 #LOG.debug("STACK: {}".format(stack))
291 #LOG.debug("TEMPLATE: {}".format(stack.template))
292 return Response(json
.dumps(stack
.template
), status
=200, mimetype
="application/json")
294 except Exception as ex
:
295 LOG
.exception("Heat: Show stack template exception.")
296 return ex
.message
, 500
299 class HeatShowStackResources(Resource
):
300 def __init__(self
, api
):
303 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
305 Returns template of given stack.
308 :param stack_name_or_id:
310 :return: Returns a json response which contains the stack's template.
312 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
315 if stack_name_or_id
in self
.api
.compute
.stacks
:
316 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
318 for tmp_stack
in self
.api
.compute
.stacks
.values():
319 if tmp_stack
.stack_name
== stack_name_or_id
:
322 return 'Could not resolve Stack - ID', 404
324 response
= {"resources": []}
326 return Response(json
.dumps(response
), status
=200, mimetype
="application/json")
328 except Exception as ex
:
329 LOG
.exception("Heat: Show stack template exception.")
330 return ex
.message
, 500
333 class HeatUpdateStack(Resource
):
334 def __init__(self
, api
):
337 def put(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
338 LOG
.debug("API CALL: %s PUT" % str(self
.__class
__.__name
__))
339 return self
.update_stack(tenant_id
, stack_name_or_id
, stack_id
)
341 def patch(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
342 LOG
.debug("API CALL: %s PATCH" % str(self
.__class
__.__name
__))
343 return self
.update_stack(tenant_id
, stack_name_or_id
, stack_id
)
345 def update_stack(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
347 Updates an existing stack with a new heat template.
350 :param stack_name_or_id: Specifies the stack, which should be updated.
352 :return: 404, if the requested stack could not be found.
353 400, if the stack creation (because of errors in the heat template) or the stack update failed.
354 500, if any exception occurred while updating.
355 202, if everything worked out.
359 if stack_name_or_id
in self
.api
.compute
.stacks
:
360 old_stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
362 for tmp_stack
in self
.api
.compute
.stacks
.values():
363 if tmp_stack
.stack_name
== stack_name_or_id
:
364 old_stack
= tmp_stack
365 if old_stack
is None:
366 return 'Could not resolve Stack - ID', 404
368 stack_dict
= json
.loads(request
.data
)
371 stack
.stack_name
= old_stack
.stack_name
372 stack
.id = old_stack
.id
373 stack
.creation_time
= old_stack
.creation_time
374 stack
.update_time
= str(datetime
.now())
375 stack
.status
= "UPDATE_COMPLETE"
377 reader
= HeatParser(self
.api
.compute
)
378 if isinstance(stack_dict
['template'], str) or isinstance(stack_dict
['template'], unicode):
379 stack_dict
['template'] = json
.loads(stack_dict
['template'])
380 if not reader
.parse_input(stack_dict
['template'], stack
, self
.api
.compute
.dc
.label
, stack_update
=True):
381 return 'Could not create stack.', 400
382 stack
.template
= stack_dict
['template']
384 if not self
.api
.compute
.update_stack(old_stack
.id, stack
):
385 return 'Could not update stack.', 400
387 return Response(status
=202, mimetype
="application/json")
389 except Exception as ex
:
390 LOG
.exception("Heat: Update Stack exception")
391 return ex
.message
, 500
394 class HeatDeleteStack(Resource
):
395 def __init__(self
, api
):
398 def delete(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
400 Deletes an existing stack.
403 :param stack_name_or_id: Specifies the stack, which should be deleted.
405 :return: 500, if any exception occurred while deletion.
406 204, if everything worked out.
408 LOG
.debug("API CALL: %s DELETE" % str(self
.__class
__.__name
__))
410 if stack_name_or_id
in self
.api
.compute
.stacks
:
411 self
.api
.compute
.delete_stack(stack_name_or_id
)
412 return Response('Deleted Stack: ' + stack_name_or_id
, 204)
414 for stack
in self
.api
.compute
.stacks
.values():
415 if stack
.stack_name
== stack_name_or_id
:
416 self
.api
.compute
.delete_stack(stack
.id)
417 return Response('Deleted Stack: ' + stack_name_or_id
, 204)
419 except Exception as ex
:
420 LOG
.exception("Heat: Delete Stack exception")
421 return ex
.message
, 500