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 __future__
import print_function
# TODO remove when print is no longer needed for debugging
27 from resources
.router
import Router
28 from datetime
import datetime
33 import ip_handler
as IP
36 LOG
= logging
.getLogger("api.openstack.heat.parser")
41 The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
44 def __init__(self
, compute
):
45 self
.description
= None
46 self
.parameter_groups
= None
47 self
.parameters
= None
50 self
.compute
= compute
51 self
.bufferResource
= list()
53 def parse_input(self
, input_dict
, stack
, dc_label
, stack_update
=False):
55 It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
57 :param input_dict: Dictionary with the template version and resources.
58 :type input_dict: ``dict``
59 :param stack: Reference of the stack that should finally contain all created classes.
60 :type stack: :class:`heat.resources.stack`
61 :param dc_label: String that contains the label of the used data center.
62 :type dc_label: ``str``
63 :param stack_update: Specifies if a new stack will be created or a older one will be updated
64 :type stack_update: ``bool``
65 :return: * *True*: If the template version is supported and all resources could be created.
69 if not self
.check_template_version(
70 str(input_dict
['heat_template_version'])):
71 print('Unsupported template version: ' + input_dict
['heat_template_version'], file=sys
.stderr
)
74 self
.description
= input_dict
.get('description', None)
75 self
.parameter_groups
= input_dict
.get('parameter_groups', None)
76 self
.parameters
= input_dict
.get('parameters', None)
77 self
.resources
= input_dict
.get('resources', None)
78 self
.outputs
= input_dict
.get('outputs', None)
79 # clear bufferResources
80 self
.bufferResource
= list()
82 for resource
in self
.resources
.values():
83 self
.handle_resource(resource
, stack
, dc_label
,
84 stack_update
=stack_update
)
86 # This loop tries to create all classes which had unresolved
88 unresolved_resources_last_round
= len(self
.bufferResource
) + 1
89 while len(self
.bufferResource
) > 0 and unresolved_resources_last_round
> len(
91 unresolved_resources_last_round
= len(self
.bufferResource
)
92 number_of_items
= len(self
.bufferResource
)
93 while number_of_items
> 0:
94 self
.handle_resource(self
.bufferResource
.pop(
95 0), stack
, dc_label
, stack_update
=stack_update
)
98 if len(self
.bufferResource
) > 0:
99 print(str(len(self
.bufferResource
)) +
100 ' classes of the HOT could not be created, because the dependencies could not be found.')
101 print("the problem classes are:")
102 for br
in self
.bufferResource
:
103 print("class: %s" % str(br
))
107 def handle_resource(self
, resource
, stack
, dc_label
, stack_update
=False):
109 This function will take a resource (from a heat template) and determines which type it is and creates
110 the corresponding class, with its required parameters, for further calculations (like deploying the stack).
111 If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
112 within the 'self.bufferResource' list.
114 :param resource: Dict which contains all important informations about the type and parameters.
115 :type resource: ``dict``
116 :param stack: Reference of the stack that should finally contain the created class.
117 :type stack: :class:`heat.resources.stack`
118 :param dc_label: String that contains the label of the used data center
119 :type dc_label: ``str``
120 :param stack_update: Specifies if a new stack will be created or a older one will be updated
121 :type stack_update: ``bool``
125 if "OS::Neutron::Net" in resource
['type']:
127 net_name
= resource
['properties']['name']
128 if net_name
not in stack
.nets
:
129 stack
.nets
[net_name
] = self
.compute
.create_network(
132 except Exception as e
:
133 LOG
.warning('Could not create Net: ' + e
.message
)
136 if 'OS::Neutron::Subnet' in resource
['type'] and "Net" not in resource
['type']:
138 net_name
= resource
['properties']['network']['get_resource']
139 if net_name
not in stack
.nets
:
140 net
= self
.compute
.create_network(net_name
, stack_update
)
141 stack
.nets
[net_name
] = net
143 net
= stack
.nets
[net_name
]
145 net
.subnet_name
= resource
['properties']['name']
146 if 'gateway_ip' in resource
['properties']:
147 net
.gateway_ip
= resource
['properties']['gateway_ip']
148 net
.subnet_id
= resource
['properties'].get(
149 'id', str(uuid
.uuid4()))
150 net
.subnet_creation_time
= str(datetime
.now())
152 net
.set_cidr(IP
.get_new_cidr(net
.subnet_id
))
153 except Exception as e
:
154 LOG
.warning('Could not create Subnet: ' + e
.message
)
157 if 'OS::Neutron::Port' in resource
['type']:
159 port_name
= resource
['properties']['name']
160 if port_name
not in stack
.ports
:
161 port
= self
.compute
.create_port(port_name
, stack_update
)
162 stack
.ports
[port_name
] = port
164 port
= stack
.ports
[port_name
]
166 if str(resource
['properties']['network']
167 ['get_resource']) in stack
.nets
:
168 net
= stack
.nets
[resource
['properties']
169 ['network']['get_resource']]
170 if net
.subnet_id
is not None:
171 port
.net_name
= net
.name
172 port
.ip_address
= net
.get_new_ip_address(port
.name
)
174 except Exception as e
:
175 LOG
.warning('Could not create Port: ' + e
.message
)
176 self
.bufferResource
.append(resource
)
179 if 'OS::Nova::Server' in resource
['type']:
181 compute_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + \
182 '_' + str(resource
['properties']['name'])
183 shortened_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + '_' + \
184 self
.shorten_server_name(
185 str(resource
['properties']['name']), stack
)
186 nw_list
= resource
['properties']['networks']
188 if shortened_name
not in stack
.servers
:
189 server
= self
.compute
.create_server(
190 shortened_name
, stack_update
)
191 stack
.servers
[shortened_name
] = server
193 server
= stack
.servers
[shortened_name
]
195 server
.full_name
= compute_name
196 server
.template_name
= str(resource
['properties']['name'])
197 server
.command
= resource
['properties'].get(
198 'command', '/bin/sh')
199 server
.image
= resource
['properties']['image']
200 server
.flavor
= resource
['properties']['flavor']
203 port_name
= port
['port']['get_resource']
205 # we don't know which network it belongs to yet, but the resource will appear later in a valid
207 if port_name
not in stack
.ports
:
208 stack
.ports
[port_name
] = self
.compute
.create_port(
209 port_name
, stack_update
)
210 server
.port_names
.append(port_name
)
212 except Exception as e
:
213 LOG
.warning('Could not create Server: ' + e
.message
)
216 if 'OS::Neutron::RouterInterface' in resource
['type']:
219 subnet_name
= resource
['properties']['subnet']['get_resource']
221 if 'get_resource' in resource
['properties']['router']:
222 router_name
= resource
['properties']['router']['get_resource']
224 router_name
= resource
['properties']['router']
226 if router_name
not in stack
.routers
:
227 stack
.routers
[router_name
] = Router(router_name
)
229 for tmp_net
in stack
.nets
.values():
230 if tmp_net
.subnet_name
== subnet_name
:
231 stack
.routers
[router_name
].add_subnet(subnet_name
)
233 except Exception as e
:
235 'Could not create RouterInterface: ' + e
.__repr
__())
236 self
.bufferResource
.append(resource
)
239 if 'OS::Neutron::FloatingIP' in resource
['type']:
241 port_name
= resource
['properties']['port_id']['get_resource']
242 floating_network_id
= resource
['properties']['floating_network_id']
243 if port_name
not in stack
.ports
:
244 stack
.ports
[port_name
] = self
.compute
.create_port(
245 port_name
, stack_update
)
247 stack
.ports
[port_name
].floating_ip
= floating_network_id
248 except Exception as e
:
249 LOG
.warning('Could not create FloatingIP: ' + e
.message
)
252 if 'OS::Neutron::Router' in resource
['type']:
254 name
= resource
['properties']['name']
255 if name
not in stack
.routers
:
256 stack
.routers
[name
] = Router(name
)
257 except Exception as e
:
258 print('Could not create Router: ' + e
.message
)
261 if 'OS::Heat::ResourceGroup' in resource
['type']:
263 embedded_resource
= resource
['properties']['resource_def']
264 LOG
.debug("Found resource in resource group: {}".format(
266 # recursively parse embedded resource
267 self
.handle_resource(
268 embedded_resource
, stack
, dc_label
, stack_update
)
269 except Exception as e
:
270 print('Could not create Router: ' + e
.message
)
274 'Could not determine resource type: {}'.format(resource
['type']))
277 def shorten_server_name(self
, server_name
, stack
):
279 Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
282 :param server_name: The original server name.
283 :type server_name: ``str``
284 :param stack: A reference to the used stack.
285 :type stack: :class:`heat.resources.stack`
286 :return: A string with max. 12 characters plus iterator string.
289 server_name
= self
.shorten_name(server_name
, 12)
291 while server_name
in stack
.servers
:
292 server_name
= server_name
[0:12] + str(iterator
)
296 def shorten_name(self
, name
, max_size
):
298 Shortens the name to max_size characters and replaces all '-' with '_'.
300 :param name: The original string.
302 :param max_size: The number of allowed characters.
303 :type max_size: ``int``
304 :return: String with at most max_size characters and without '-'.
307 shortened_name
= name
.split(':', 1)[0]
308 shortened_name
= shortened_name
.replace("-", "_")
309 shortened_name
= shortened_name
[0:max_size
]
310 return shortened_name
312 def check_template_version(self
, version_string
):
314 Checks if a version string is equal or later than 30-04-2015
316 :param version_string: String with the version.
317 :type version_string: ``str``
318 :return: * *True*: if the version is equal or later 30-04-2015.
322 r
= re
.compile('\d{4}-\d{2}-\d{2}')
323 if not r
.match(version_string
):
326 year
, month
, day
= map(int, version_string
.split('-', 2))
332 if month
== 0o4 and day
< 30: