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 __future__
import print_function
# TODO remove when print is no longer needed for debugging
29 from resources
import *
30 from datetime
import datetime
35 import ip_handler
as IP
38 LOG
= logging
.getLogger("api.openstack.heat.parser")
43 The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
46 def __init__(self
, compute
):
47 self
.description
= None
48 self
.parameter_groups
= None
49 self
.parameters
= None
52 self
.compute
= compute
53 self
.bufferResource
= list()
55 def parse_input(self
, input_dict
, stack
, dc_label
, stack_update
=False):
57 It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
59 :param input_dict: Dictionary with the template version and resources.
60 :type input_dict: ``dict``
61 :param stack: Reference of the stack that should finally contain all created classes.
62 :type stack: :class:`heat.resources.stack`
63 :param dc_label: String that contains the label of the used data center.
64 :type dc_label: ``str``
65 :param stack_update: Specifies if a new stack will be created or a older one will be updated
66 :type stack_update: ``bool``
67 :return: * *True*: If the template version is supported and all resources could be created.
71 if not self
.check_template_version(str(input_dict
['heat_template_version'])):
72 print('Unsupported template version: ' + input_dict
['heat_template_version'], file=sys
.stderr
)
75 self
.description
= input_dict
.get('description', None)
76 self
.parameter_groups
= input_dict
.get('parameter_groups', None)
77 self
.parameters
= input_dict
.get('parameters', None)
78 self
.resources
= input_dict
.get('resources', None)
79 self
.outputs
= input_dict
.get('outputs', None)
80 # clear bufferResources
81 self
.bufferResource
= list()
83 for resource
in self
.resources
.values():
84 self
.handle_resource(resource
, stack
, dc_label
, stack_update
=stack_update
)
86 # This loop tries to create all classes which had unresolved dependencies.
87 unresolved_resources_last_round
= len(self
.bufferResource
) + 1
88 while len(self
.bufferResource
) > 0 and unresolved_resources_last_round
> len(self
.bufferResource
):
89 unresolved_resources_last_round
= len(self
.bufferResource
)
90 number_of_items
= len(self
.bufferResource
)
91 while number_of_items
> 0:
92 self
.handle_resource(self
.bufferResource
.pop(0), stack
, dc_label
, stack_update
=stack_update
)
95 if len(self
.bufferResource
) > 0:
96 print(str(len(self
.bufferResource
)) +
97 ' classes of the HOT could not be created, because the dependencies could not be found.')
98 print("the problem classes are:")
99 for br
in self
.bufferResource
:
100 print("class: %s" % str(br
))
104 def handle_resource(self
, resource
, stack
, dc_label
, stack_update
=False):
106 This function will take a resource (from a heat template) and determines which type it is and creates
107 the corresponding class, with its required parameters, for further calculations (like deploying the stack).
108 If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
109 within the 'self.bufferResource' list.
111 :param resource: Dict which contains all important informations about the type and parameters.
112 :type resource: ``dict``
113 :param stack: Reference of the stack that should finally contain the created class.
114 :type stack: :class:`heat.resources.stack`
115 :param dc_label: String that contains the label of the used data center
116 :type dc_label: ``str``
117 :param stack_update: Specifies if a new stack will be created or a older one will be updated
118 :type stack_update: ``bool``
122 if "OS::Neutron::Net" in resource
['type']:
124 net_name
= resource
['properties']['name']
125 if net_name
not in stack
.nets
:
126 stack
.nets
[net_name
] = self
.compute
.create_network(net_name
, True)
128 except Exception as e
:
129 LOG
.warning('Could not create Net: ' + e
.message
)
132 if 'OS::Neutron::Subnet' in resource
['type'] and "Net" not in resource
['type']:
134 net_name
= resource
['properties']['network']['get_resource']
135 if net_name
not in stack
.nets
:
136 net
= self
.compute
.create_network(net_name
, stack_update
)
137 stack
.nets
[net_name
] = net
139 net
= stack
.nets
[net_name
]
141 net
.subnet_name
= resource
['properties']['name']
142 if 'gateway_ip' in resource
['properties']:
143 net
.gateway_ip
= resource
['properties']['gateway_ip']
144 net
.subnet_id
= resource
['properties'].get('id', str(uuid
.uuid4()))
145 net
.subnet_creation_time
= str(datetime
.now())
147 net
.set_cidr(IP
.get_new_cidr(net
.subnet_id
))
148 except Exception as e
:
149 LOG
.warning('Could not create Subnet: ' + e
.message
)
152 if 'OS::Neutron::Port' in resource
['type']:
154 port_name
= resource
['properties']['name']
155 if port_name
not in stack
.ports
:
156 port
= self
.compute
.create_port(port_name
, stack_update
)
157 stack
.ports
[port_name
] = port
159 port
= stack
.ports
[port_name
]
161 if str(resource
['properties']['network']['get_resource']) in stack
.nets
:
162 net
= stack
.nets
[resource
['properties']['network']['get_resource']]
163 if net
.subnet_id
is not None:
164 port
.net_name
= net
.name
165 port
.ip_address
= net
.get_new_ip_address(port
.name
)
167 except Exception as e
:
168 LOG
.warning('Could not create Port: ' + e
.message
)
169 self
.bufferResource
.append(resource
)
172 if 'OS::Nova::Server' in resource
['type']:
174 compute_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + '_' + str(resource
['properties']['name'])
175 shortened_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + '_' + \
176 self
.shorten_server_name(str(resource
['properties']['name']), stack
)
177 nw_list
= resource
['properties']['networks']
179 if shortened_name
not in stack
.servers
:
180 server
= self
.compute
.create_server(shortened_name
, stack_update
)
181 stack
.servers
[shortened_name
] = server
183 server
= stack
.servers
[shortened_name
]
185 server
.full_name
= compute_name
186 server
.template_name
= str(resource
['properties']['name'])
187 server
.command
= resource
['properties'].get('command', '/bin/sh')
188 server
.image
= resource
['properties']['image']
189 server
.flavor
= resource
['properties']['flavor']
192 port_name
= port
['port']['get_resource']
194 # we don't know which network it belongs to yet, but the resource will appear later in a valid
196 if port_name
not in stack
.ports
:
197 stack
.ports
[port_name
] = self
.compute
.create_port(port_name
, stack_update
)
198 server
.port_names
.append(port_name
)
200 except Exception as e
:
201 LOG
.warning('Could not create Server: ' + e
.message
)
204 if 'OS::Neutron::RouterInterface' in resource
['type']:
207 subnet_name
= resource
['properties']['subnet']['get_resource']
209 if 'get_resource' in resource
['properties']['router']:
210 router_name
= resource
['properties']['router']['get_resource']
212 router_name
= resource
['properties']['router']
214 if router_name
not in stack
.routers
:
215 stack
.routers
[router_name
] = Router(router_name
)
217 for tmp_net
in stack
.nets
.values():
218 if tmp_net
.subnet_name
== subnet_name
:
219 stack
.routers
[router_name
].add_subnet(subnet_name
)
221 except Exception as e
:
222 LOG
.warning('Could not create RouterInterface: ' + e
.__repr
__())
223 self
.bufferResource
.append(resource
)
226 if 'OS::Neutron::FloatingIP' in resource
['type']:
228 port_name
= resource
['properties']['port_id']['get_resource']
229 floating_network_id
= resource
['properties']['floating_network_id']
230 if port_name
not in stack
.ports
:
231 stack
.ports
[port_name
] = self
.compute
.create_port(port_name
, stack_update
)
233 stack
.ports
[port_name
].floating_ip
= floating_network_id
234 except Exception as e
:
235 LOG
.warning('Could not create FloatingIP: ' + e
.message
)
238 if 'OS::Neutron::Router' in resource
['type']:
240 name
= resource
['properties']['name']
241 if name
not in stack
.routers
:
242 stack
.routers
[name
] = Router(name
)
243 except Exception as e
:
244 print('Could not create Router: ' + e
.message
)
247 if 'OS::Heat::ResourceGroup' in resource
['type']:
249 embedded_resource
= resource
['properties']['resource_def']
250 LOG
.debug("Found resource in resource group: {}".format(embedded_resource
))
251 # recursively parse embedded resource
252 self
.handle_resource(embedded_resource
, stack
, dc_label
, stack_update
)
253 except Exception as e
:
254 print('Could not create Router: ' + e
.message
)
257 LOG
.warning('Could not determine resource type: {}'.format(resource
['type']))
260 def shorten_server_name(self
, server_name
, stack
):
262 Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
265 :param server_name: The original server name.
266 :type server_name: ``str``
267 :param stack: A reference to the used stack.
268 :type stack: :class:`heat.resources.stack`
269 :return: A string with max. 12 characters plus iterator string.
272 server_name
= self
.shorten_name(server_name
, 12)
274 while server_name
in stack
.servers
:
275 server_name
= server_name
[0:12] + str(iterator
)
279 def shorten_name(self
, name
, max_size
):
281 Shortens the name to max_size characters and replaces all '-' with '_'.
283 :param name: The original string.
285 :param max_size: The number of allowed characters.
286 :type max_size: ``int``
287 :return: String with at most max_size characters and without '-'.
290 shortened_name
= name
.split(':', 1)[0]
291 shortened_name
= shortened_name
.replace("-", "_")
292 shortened_name
= shortened_name
[0:max_size
]
293 return shortened_name
295 def check_template_version(self
, version_string
):
297 Checks if a version string is equal or later than 30-04-2015
299 :param version_string: String with the version.
300 :type version_string: ``str``
301 :return: * *True*: if the version is equal or later 30-04-2015.
305 r
= re
.compile('\d{4}-\d{2}-\d{2}')
306 if not r
.match(version_string
):
309 year
, month
, day
= map(int, version_string
.split('-', 2))
315 if month
== 04 and day
< 30: