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 # TODO remove when print is no longer needed for debugging
27 from __future__
import print_function
28 from emuvim
.api
.openstack
.resources
.router
import Router
29 from datetime
import datetime
34 import emuvim
.api
.openstack
.ip_handler
as IP
37 LOG
= logging
.getLogger("api.openstack.heat.parser")
42 The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
45 def __init__(self
, compute
):
46 self
.description
= None
47 self
.parameter_groups
= None
48 self
.parameters
= None
51 self
.compute
= compute
52 self
.bufferResource
= list()
54 def parse_input(self
, input_dict
, stack
, dc_label
, stack_update
=False):
56 It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
58 :param input_dict: Dictionary with the template version and resources.
59 :type input_dict: ``dict``
60 :param stack: Reference of the stack that should finally contain all created classes.
61 :type stack: :class:`heat.resources.stack`
62 :param dc_label: String that contains the label of the used data center.
63 :type dc_label: ``str``
64 :param stack_update: Specifies if a new stack will be created or a older one will be updated
65 :type stack_update: ``bool``
66 :return: * *True*: If the template version is supported and all resources could be created.
70 if not self
.check_template_version(
71 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
,
85 stack_update
=stack_update
)
87 # This loop tries to create all classes which had unresolved
89 unresolved_resources_last_round
= len(self
.bufferResource
) + 1
90 while len(self
.bufferResource
) > 0 and unresolved_resources_last_round
> len(
92 unresolved_resources_last_round
= len(self
.bufferResource
)
93 number_of_items
= len(self
.bufferResource
)
94 while number_of_items
> 0:
95 self
.handle_resource(self
.bufferResource
.pop(
96 0), stack
, dc_label
, stack_update
=stack_update
)
99 if len(self
.bufferResource
) > 0:
100 print(str(len(self
.bufferResource
)) +
101 ' classes of the HOT could not be created, because the dependencies could not be found.')
102 print("the problem classes are:")
103 for br
in self
.bufferResource
:
104 print("class: %s" % str(br
))
108 def handle_resource(self
, resource
, stack
, dc_label
, stack_update
=False):
110 This function will take a resource (from a heat template) and determines which type it is and creates
111 the corresponding class, with its required parameters, for further calculations (like deploying the stack).
112 If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
113 within the 'self.bufferResource' list.
115 :param resource: Dict which contains all important informations about the type and parameters.
116 :type resource: ``dict``
117 :param stack: Reference of the stack that should finally contain the created class.
118 :type stack: :class:`heat.resources.stack`
119 :param dc_label: String that contains the label of the used data center
120 :type dc_label: ``str``
121 :param stack_update: Specifies if a new stack will be created or a older one will be updated
122 :type stack_update: ``bool``
126 if "OS::Neutron::Net" in resource
['type']:
128 net_name
= resource
['properties']['name']
129 if net_name
not in stack
.nets
:
130 stack
.nets
[net_name
] = self
.compute
.create_network(
133 except Exception as e
:
134 LOG
.warning('Could not create Net: ' + str(e
))
137 if 'OS::Neutron::Subnet' in resource
['type'] and "Net" not in resource
['type']:
139 net_name
= resource
['properties']['network']['get_resource']
140 if net_name
not in stack
.nets
:
141 net
= self
.compute
.create_network(net_name
, stack_update
)
142 stack
.nets
[net_name
] = net
144 net
= stack
.nets
[net_name
]
146 net
.subnet_name
= resource
['properties']['name']
147 if 'gateway_ip' in resource
['properties']:
148 net
.gateway_ip
= resource
['properties']['gateway_ip']
149 net
.subnet_id
= resource
['properties'].get(
150 'id', str(uuid
.uuid4()))
151 net
.subnet_creation_time
= str(datetime
.now())
153 net
.set_cidr(IP
.get_new_cidr(net
.subnet_id
))
154 except Exception as e
:
155 LOG
.warning('Could not create Subnet: ' + str(e
))
158 if 'OS::Neutron::Port' in resource
['type']:
160 port_name
= resource
['properties']['name']
161 if port_name
not in stack
.ports
:
162 port
= self
.compute
.create_port(port_name
, stack_update
)
163 stack
.ports
[port_name
] = port
165 port
= stack
.ports
[port_name
]
167 if str(resource
['properties']['network']
168 ['get_resource']) in stack
.nets
:
169 net
= stack
.nets
[resource
['properties']
170 ['network']['get_resource']]
171 if net
.subnet_id
is not None:
172 port
.net_name
= net
.name
173 port
.ip_address
= net
.get_new_ip_address(port
.name
)
175 except Exception as e
:
176 LOG
.warning('Could not create Port: ' + str(e
))
177 self
.bufferResource
.append(resource
)
180 if 'OS::Nova::Server' in resource
['type']:
182 compute_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + \
183 '_' + str(resource
['properties']['name'])
184 shortened_name
= str(dc_label
) + '_' + str(stack
.stack_name
) + '_' + \
185 self
.shorten_server_name(
186 str(resource
['properties']['name']), stack
)
187 nw_list
= resource
['properties']['networks']
189 if shortened_name
not in stack
.servers
:
190 server
= self
.compute
.create_server(
191 shortened_name
, stack_update
)
192 stack
.servers
[shortened_name
] = server
194 server
= stack
.servers
[shortened_name
]
196 server
.full_name
= compute_name
197 server
.template_name
= str(resource
['properties']['name'])
198 server
.command
= resource
['properties'].get(
199 'command', '/bin/sh')
200 server
.image
= resource
['properties']['image']
201 server
.flavor
= resource
['properties']['flavor']
204 port_name
= port
['port']['get_resource']
206 # we don't know which network it belongs to yet, but the resource will appear later in a valid
208 if port_name
not in stack
.ports
:
209 stack
.ports
[port_name
] = self
.compute
.create_port(
210 port_name
, stack_update
)
211 server
.port_names
.append(port_name
)
213 except Exception as e
:
214 LOG
.warning('Could not create Server: ' + str(e
))
217 if 'OS::Neutron::RouterInterface' in resource
['type']:
220 subnet_name
= resource
['properties']['subnet']['get_resource']
222 if 'get_resource' in resource
['properties']['router']:
223 router_name
= resource
['properties']['router']['get_resource']
225 router_name
= resource
['properties']['router']
227 if router_name
not in stack
.routers
:
228 stack
.routers
[router_name
] = Router(router_name
)
230 for tmp_net
in stack
.nets
.values():
231 if tmp_net
.subnet_name
== subnet_name
:
232 stack
.routers
[router_name
].add_subnet(subnet_name
)
234 except Exception as e
:
236 'Could not create RouterInterface: ' + e
.__repr
__())
237 self
.bufferResource
.append(resource
)
240 if 'OS::Neutron::FloatingIP' in resource
['type']:
242 port_name
= resource
['properties']['port_id']['get_resource']
243 floating_network_id
= resource
['properties']['floating_network_id']
244 if port_name
not in stack
.ports
:
245 stack
.ports
[port_name
] = self
.compute
.create_port(
246 port_name
, stack_update
)
248 stack
.ports
[port_name
].floating_ip
= floating_network_id
249 except Exception as e
:
250 LOG
.warning('Could not create FloatingIP: ' + str(e
))
253 if 'OS::Neutron::Router' in resource
['type']:
255 name
= resource
['properties']['name']
256 if name
not in stack
.routers
:
257 stack
.routers
[name
] = Router(name
)
258 except Exception as e
:
259 print('Could not create Router: ' + str(e
))
262 if 'OS::Heat::ResourceGroup' in resource
['type']:
264 embedded_resource
= resource
['properties']['resource_def']
265 LOG
.debug("Found resource in resource group: {}".format(
267 # recursively parse embedded resource
268 self
.handle_resource(
269 embedded_resource
, stack
, dc_label
, stack_update
)
270 except Exception as e
:
271 print('Could not create Router: ' + str(e
))
275 'Could not determine resource type: {}'.format(resource
['type']))
278 def shorten_server_name(self
, server_name
, stack
):
280 Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
283 :param server_name: The original server name.
284 :type server_name: ``str``
285 :param stack: A reference to the used stack.
286 :type stack: :class:`heat.resources.stack`
287 :return: A string with max. 12 characters plus iterator string.
290 server_name
= self
.shorten_name(server_name
, 12)
292 while server_name
in stack
.servers
:
293 server_name
= server_name
[0:12] + str(iterator
)
297 def shorten_name(self
, name
, max_size
):
299 Shortens the name to max_size characters and replaces all '-' with '_'.
301 :param name: The original string.
303 :param max_size: The number of allowed characters.
304 :type max_size: ``int``
305 :return: String with at most max_size characters and without '-'.
308 shortened_name
= name
.split(':', 1)[0]
309 shortened_name
= shortened_name
.replace("-", "_")
310 shortened_name
= shortened_name
[0:max_size
]
311 return shortened_name
313 def check_template_version(self
, version_string
):
315 Checks if a version string is equal or later than 30-04-2015
317 :param version_string: String with the version.
318 :type version_string: ``str``
319 :return: * *True*: if the version is equal or later 30-04-2015.
323 r
= re
.compile('\d{4}-\d{2}-\d{2}')
324 if not r
.match(version_string
):
327 year
, month
, day
= map(int, version_string
.split('-', 2))
333 if month
== 0o4 and day
< 30: