1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
26 from flask
import request
, Response
27 from flask_restful
import Resource
28 from emuvim
.api
.openstack
.resources
.stack
import Stack
29 from emuvim
.api
.openstack
.openstack_dummies
.base_openstack_dummy
import BaseOpenstackDummy
30 from emuvim
.api
.openstack
.helper
import get_host
31 from datetime
import datetime
32 from emuvim
.api
.openstack
.heat_parser
import HeatParser
37 LOG
= logging
.getLogger("api.openstack.heat")
40 class HeatDummyApi(BaseOpenstackDummy
):
41 def __init__(self
, in_ip
, in_port
, compute
):
42 super(HeatDummyApi
, self
).__init
__(in_ip
, in_port
)
43 self
.compute
= compute
45 self
.api
.add_resource(HeatListAPIVersions
, "/",
46 resource_class_kwargs
={'api': self
})
47 self
.api
.add_resource(HeatCreateStack
, "/v1/<tenant_id>/stacks",
48 resource_class_kwargs
={'api': self
})
49 self
.api
.add_resource(HeatShowStack
, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
50 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
51 resource_class_kwargs
={'api': self
})
52 self
.api
.add_resource(HeatShowStackTemplate
, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/template",
53 resource_class_kwargs
={'api': self
})
54 self
.api
.add_resource(HeatShowStackResources
, "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>/resources",
55 resource_class_kwargs
={'api': self
})
56 self
.api
.add_resource(HeatUpdateStack
, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
57 "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
58 resource_class_kwargs
={'api': self
})
59 self
.api
.add_resource(HeatDeleteStack
, "/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
})
63 @self.app
.after_request
64 def add_access_control_header(response
):
65 response
.headers
['Access-Control-Allow-Origin'] = '*'
69 class HeatListAPIVersions(Resource
):
70 def __init__(self
, api
):
74 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
77 resp
['versions'] = dict()
83 "href": "http://%s:%d/v2.0" % (get_host(request
), self
.api
.port
),
89 return Response(json
.dumps(resp
), status
=200,
90 mimetype
="application/json")
93 class HeatCreateStack(Resource
):
94 def __init__(self
, api
):
97 def post(self
, tenant_id
):
99 Create and deploy a new stack.
102 :return: 409, if the stack name was already used.
103 400, if the heat template could not be parsed properly.
104 500, if any exception occurred while creation.
105 201, if everything worked out.
107 LOG
.debug("API CALL: %s POST" % str(self
.__class
__.__name
__))
110 stack_dict
= json
.loads(request
.data
)
111 for stack
in self
.api
.compute
.stacks
.values():
112 if stack
.stack_name
== stack_dict
['stack_name']:
115 stack
.stack_name
= stack_dict
['stack_name']
117 reader
= HeatParser(self
.api
.compute
)
118 if isinstance(stack_dict
['template'], str) or isinstance(
119 stack_dict
['template'], unicode):
120 stack_dict
['template'] = json
.loads(stack_dict
['template'])
121 if not reader
.parse_input(
122 stack_dict
['template'], stack
, self
.api
.compute
.dc
.label
):
123 self
.api
.compute
.clean_broken_stack(stack
)
124 return 'Could not create stack.', 400
126 stack
.template
= stack_dict
['template']
127 stack
.creation_time
= str(datetime
.now())
128 stack
.status
= "CREATE_COMPLETE"
130 return_dict
= {"stack": {"id": stack
.id,
133 "href": "http://%s:%s/v1/%s/stacks/%s"
134 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
138 self
.api
.compute
.add_stack(stack
)
139 self
.api
.compute
.deploy_stack(stack
.id)
140 return Response(json
.dumps(return_dict
), status
=201,
141 mimetype
="application/json")
143 except Exception as ex
:
144 LOG
.exception("Heat: Create Stack exception.")
145 return ex
.message
, 500
147 def get(self
, tenant_id
):
149 Calculates information about the requested stack.
152 :return: Returns a json response which contains information like the stack id, name, status, creation time.
153 500, if any exception occurred.
154 200, if everything worked out.
156 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
158 return_stacks
= dict()
159 return_stacks
['stacks'] = list()
160 for stack
in self
.api
.compute
.stacks
.values():
161 return_stacks
['stacks'].append(
162 {"creation_time": stack
.creation_time
,
163 "description": "desc of " + stack
.id,
166 "stack_name": stack
.stack_name
,
167 "stack_status": stack
.status
,
168 "stack_status_reason": "Stack CREATE completed successfully",
169 "updated_time": stack
.update_time
,
173 return Response(json
.dumps(return_stacks
),
174 status
=200, mimetype
="application/json")
175 except Exception as ex
:
176 LOG
.exception("Heat: List Stack exception.")
177 return ex
.message
, 500
180 class HeatShowStack(Resource
):
181 def __init__(self
, api
):
184 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
186 Calculates detailed information about the requested stack.
189 :param stack_name_or_id:
191 :return: Returns a json response which contains information like the stack id, name, status, creation time.
192 500, if any exception occurred.
193 200, if everything worked out.
195 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
198 if stack_name_or_id
in self
.api
.compute
.stacks
:
199 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
201 for tmp_stack
in self
.api
.compute
.stacks
.values():
202 if tmp_stack
.stack_name
== stack_name_or_id
:
205 return 'Could not resolve Stack - ID', 404
210 "creation_time": stack
.creation_time
,
211 "description": "desc of " + stack
.stack_name
,
212 "disable_rollback": True,
216 "href": "http://%s:%s/v1/%s/stacks/%s"
217 % (get_host(request
), self
.api
.port
, tenant_id
, stack
.id),
221 "notification_topics": [],
224 "OS::project_id": "3ab5b02f-a01f-4f95-afa1-e254afc4a435", # add real project id
225 "OS::stack_id": stack
.id,
226 "OS::stack_name": stack
.stack_name
228 "stack_name": stack
.stack_name
,
229 "stack_owner": "The owner of the stack.", # add stack owner
230 "stack_status": stack
.status
,
232 "stack_status_reason": "The reason for the current status of the stack.",
233 "template_description": "The description of the stack template.",
234 "stack_user_project_id": "The project UUID of the stack user.",
242 return Response(json
.dumps(return_stack
),
243 status
=200, mimetype
="application/json")
245 except Exception as ex
:
246 LOG
.exception("Heat: Show stack exception.")
247 return ex
.message
, 500
250 class HeatShowStackTemplate(Resource
):
251 def __init__(self
, api
):
254 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
256 Returns template of given stack.
259 :param stack_name_or_id:
261 :return: Returns a json response which contains the stack's template.
263 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
266 if stack_name_or_id
in self
.api
.compute
.stacks
:
267 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
269 for tmp_stack
in self
.api
.compute
.stacks
.values():
270 if tmp_stack
.stack_name
== stack_name_or_id
:
273 return 'Could not resolve Stack - ID', 404
274 # LOG.debug("STACK: {}".format(stack))
275 # LOG.debug("TEMPLATE: {}".format(stack.template))
276 return Response(json
.dumps(stack
.template
),
277 status
=200, mimetype
="application/json")
279 except Exception as ex
:
280 LOG
.exception("Heat: Show stack template exception.")
281 return ex
.message
, 500
284 class HeatShowStackResources(Resource
):
285 def __init__(self
, api
):
288 def get(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
290 Returns template of given stack.
293 :param stack_name_or_id:
295 :return: Returns a json response which contains the stack's template.
297 LOG
.debug("API CALL: %s GET" % str(self
.__class
__.__name
__))
300 if stack_name_or_id
in self
.api
.compute
.stacks
:
301 stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
303 for tmp_stack
in self
.api
.compute
.stacks
.values():
304 if tmp_stack
.stack_name
== stack_name_or_id
:
307 return 'Could not resolve Stack - ID', 404
309 response
= {"resources": []}
311 return Response(json
.dumps(response
), status
=200,
312 mimetype
="application/json")
314 except Exception as ex
:
315 LOG
.exception("Heat: Show stack template exception.")
316 return ex
.message
, 500
319 class HeatUpdateStack(Resource
):
320 def __init__(self
, api
):
323 def put(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
324 LOG
.debug("API CALL: %s PUT" % str(self
.__class
__.__name
__))
325 return self
.update_stack(tenant_id
, stack_name_or_id
, stack_id
)
327 def patch(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
328 LOG
.debug("API CALL: %s PATCH" % str(self
.__class
__.__name
__))
329 return self
.update_stack(tenant_id
, stack_name_or_id
, stack_id
)
331 def update_stack(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
333 Updates an existing stack with a new heat template.
336 :param stack_name_or_id: Specifies the stack, which should be updated.
338 :return: 404, if the requested stack could not be found.
339 400, if the stack creation (because of errors in the heat template) or the stack update failed.
340 500, if any exception occurred while updating.
341 202, if everything worked out.
345 if stack_name_or_id
in self
.api
.compute
.stacks
:
346 old_stack
= self
.api
.compute
.stacks
[stack_name_or_id
]
348 for tmp_stack
in self
.api
.compute
.stacks
.values():
349 if tmp_stack
.stack_name
== stack_name_or_id
:
350 old_stack
= tmp_stack
351 if old_stack
is None:
352 return 'Could not resolve Stack - ID', 404
354 stack_dict
= json
.loads(request
.data
)
357 stack
.stack_name
= old_stack
.stack_name
358 stack
.id = old_stack
.id
359 stack
.creation_time
= old_stack
.creation_time
360 stack
.update_time
= str(datetime
.now())
361 stack
.status
= "UPDATE_COMPLETE"
363 reader
= HeatParser(self
.api
.compute
)
364 if isinstance(stack_dict
['template'], str) or isinstance(
365 stack_dict
['template'], unicode):
366 stack_dict
['template'] = json
.loads(stack_dict
['template'])
367 if not reader
.parse_input(
368 stack_dict
['template'], stack
, self
.api
.compute
.dc
.label
, stack_update
=True):
369 return 'Could not create stack.', 400
370 stack
.template
= stack_dict
['template']
372 if not self
.api
.compute
.update_stack(old_stack
.id, stack
):
373 return 'Could not update stack.', 400
375 return Response(status
=202, mimetype
="application/json")
377 except Exception as ex
:
378 LOG
.exception("Heat: Update Stack exception")
379 return ex
.message
, 500
382 class HeatDeleteStack(Resource
):
383 def __init__(self
, api
):
386 def delete(self
, tenant_id
, stack_name_or_id
, stack_id
=None):
388 Deletes an existing stack.
391 :param stack_name_or_id: Specifies the stack, which should be deleted.
393 :return: 500, if any exception occurred while deletion.
394 204, if everything worked out.
396 LOG
.debug("API CALL: %s DELETE" % str(self
.__class
__.__name
__))
398 if stack_name_or_id
in self
.api
.compute
.stacks
:
399 self
.api
.compute
.delete_stack(stack_name_or_id
)
400 return Response("", 204,
401 mimetype
='application/json')
403 for stack
in self
.api
.compute
.stacks
.values():
404 if stack
.stack_name
== stack_name_or_id
:
405 self
.api
.compute
.delete_stack(stack
.id)
406 return Response("", 204,
407 mimetype
='application/json')
409 except Exception as ex
:
410 LOG
.exception("Heat: Delete Stack exception")
411 return ex
.message
, 500