e319229f8bb34149764f9a482626f772a21bb3d9
[snaps.git] / snaps / openstack / utils / heat_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import logging
16
17 import yaml
18 from heatclient.client import Client
19 from heatclient.common.template_format import yaml_loader
20 from novaclient.exceptions import NotFound
21 from oslo_serialization import jsonutils
22
23 from snaps import file_utils
24 from snaps.domain.stack import Stack, Resource, Output
25
26 from snaps.openstack.utils import (
27     keystone_utils, neutron_utils, nova_utils, cinder_utils)
28
29 __author__ = 'spisarski'
30
31 logger = logging.getLogger('heat_utils')
32
33
34 def heat_client(os_creds):
35     """
36     Retrieves the Heat client
37     :param os_creds: the OpenStack credentials
38     :return: the client
39     """
40     logger.debug('Retrieving Heat Client')
41     return Client(os_creds.heat_api_version,
42                   session=keystone_utils.keystone_session(os_creds),
43                   region_name=os_creds.region_name)
44
45
46 def get_stack(heat_cli, stack_settings=None, stack_name=None):
47     """
48     Returns the first domain Stack object found. When stack_setting
49     is not None, the filter created will take the name attribute. When
50     stack_settings is None and stack_name is not, stack_name will be used
51     instead. When both are None, the first stack object received will be
52     returned, else None
53     :param heat_cli: the OpenStack heat client
54     :param stack_settings: a StackSettings object
55     :param stack_name: the name of the heat stack to return
56     :return: the Stack domain object else None
57     """
58
59     stack_filter = dict()
60     if stack_settings:
61         stack_filter['stack_name'] = stack_settings.name
62     elif stack_name:
63         stack_filter['stack_name'] = stack_name
64
65     stacks = heat_cli.stacks.list(**stack_filter)
66     for stack in stacks:
67         return Stack(name=stack.identifier, stack_id=stack.id)
68
69
70 def get_stack_by_id(heat_cli, stack_id):
71     """
72     Returns a domain Stack object for a given ID
73     :param heat_cli: the OpenStack heat client
74     :param stack_id: the ID of the heat stack to retrieve
75     :return: the Stack domain object else None
76     """
77     stack = heat_cli.stacks.get(stack_id)
78     return Stack(name=stack.identifier, stack_id=stack.id)
79
80
81 def get_stack_status(heat_cli, stack_id):
82     """
83     Returns the current status of the Heat stack
84     :param heat_cli: the OpenStack heat client
85     :param stack_id: the ID of the heat stack to retrieve
86     :return:
87     """
88     return heat_cli.stacks.get(stack_id).stack_status
89
90
91 def get_stack_status_reason(heat_cli, stack_id):
92     """
93     Returns the current status of the Heat stack
94     :param heat_cli: the OpenStack heat client
95     :param stack_id: the ID of the heat stack to retrieve
96     :return: reason for stack creation failure
97     """
98     return heat_cli.stacks.get(stack_id).stack_status_reason
99
100
101 def create_stack(heat_cli, stack_settings):
102     """
103     Executes an Ansible playbook to the given host
104     :param heat_cli: the OpenStack heat client object
105     :param stack_settings: the stack configuration
106     :return: the Stack domain object
107     """
108     args = dict()
109
110     if stack_settings.template:
111         args['template'] = stack_settings.template
112     else:
113         args['template'] = parse_heat_template_str(
114             file_utils.read_file(stack_settings.template_path))
115     args['stack_name'] = stack_settings.name
116
117     if stack_settings.env_values:
118         args['parameters'] = stack_settings.env_values
119
120     stack = heat_cli.stacks.create(**args)
121
122     return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
123
124
125 def delete_stack(heat_cli, stack):
126     """
127     Deletes the Heat stack
128     :param heat_cli: the OpenStack heat client object
129     :param stack: the OpenStack Heat stack object
130     """
131     heat_cli.stacks.delete(stack.id)
132
133
134 def __get_os_resources(heat_cli, stack):
135     """
136     Returns all of the OpenStack resource objects for a given stack
137     :param heat_cli: the OpenStack heat client
138     :param stack: the SNAPS-OO Stack domain object
139     :return: a list
140     """
141     return heat_cli.resources.list(stack.id)
142
143
144 def get_resources(heat_cli, stack, res_type=None):
145     """
146     Returns all of the OpenStack resource objects for a given stack
147     :param heat_cli: the OpenStack heat client
148     :param stack: the SNAPS-OO Stack domain object
149     :param res_type: the type name to filter
150     :return: a list of Resource domain objects
151     """
152     os_resources = __get_os_resources(heat_cli, stack)
153
154     if os_resources:
155         out = list()
156         for os_resource in os_resources:
157             if ((res_type and os_resource.resource_type == res_type)
158                     or not res_type):
159                 out.append(Resource(
160                     name=os_resource.resource_name,
161                     resource_type=os_resource.resource_type,
162                     resource_id=os_resource.physical_resource_id,
163                     status=os_resource.resource_status,
164                     status_reason=os_resource.resource_status_reason))
165         return out
166
167
168 def get_outputs(heat_cli, stack):
169     """
170     Returns all of the SNAPS-OO Output domain objects for the defined outputs
171     for given stack
172     :param heat_cli: the OpenStack heat client
173     :param stack: the SNAPS-OO Stack domain object
174     :return: a list of Output domain objects
175     """
176     out = list()
177
178     os_stack = heat_cli.stacks.get(stack.id)
179
180     outputs = None
181     if os_stack:
182         outputs = os_stack.outputs
183
184     if outputs:
185         for output in outputs:
186             out.append(Output(**output))
187
188     return out
189
190
191 def get_stack_networks(heat_cli, neutron, stack):
192     """
193     Returns a list of Network domain objects deployed by this stack
194     :param heat_cli: the OpenStack heat client object
195     :param neutron: the OpenStack neutron client object
196     :param stack: the SNAPS-OO Stack domain object
197     :return: a list of Network objects
198     """
199
200     out = list()
201     resources = get_resources(heat_cli, stack, 'OS::Neutron::Net')
202     for resource in resources:
203         network = neutron_utils.get_network_by_id(neutron, resource.id)
204         if network:
205             out.append(network)
206
207     return out
208
209
210 def get_stack_routers(heat_cli, neutron, stack):
211     """
212     Returns a list of Network domain objects deployed by this stack
213     :param heat_cli: the OpenStack heat client object
214     :param neutron: the OpenStack neutron client object
215     :param stack: the SNAPS-OO Stack domain object
216     :return: a list of Network objects
217     """
218
219     out = list()
220     resources = get_resources(heat_cli, stack, 'OS::Neutron::Router')
221     for resource in resources:
222         router = neutron_utils.get_router_by_id(neutron, resource.id)
223         if router:
224             out.append(router)
225
226     return out
227
228
229 def get_stack_security_groups(heat_cli, neutron, stack):
230     """
231     Returns a list of SecurityGroup domain objects deployed by this stack
232     :param heat_cli: the OpenStack heat client object
233     :param neutron: the OpenStack neutron client object
234     :param stack: the SNAPS-OO Stack domain object
235     :return: a list of SecurityGroup objects
236     """
237
238     out = list()
239     resources = get_resources(heat_cli, stack, 'OS::Neutron::SecurityGroup')
240     for resource in resources:
241         security_group = neutron_utils.get_security_group_by_id(
242             neutron, resource.id)
243         if security_group:
244             out.append(security_group)
245
246     return out
247
248
249 def get_stack_servers(heat_cli, nova, stack):
250     """
251     Returns a list of VMInst domain objects associated with a Stack
252     :param heat_cli: the OpenStack heat client object
253     :param nova: the OpenStack nova client object
254     :param stack: the SNAPS-OO Stack domain object
255     :return: a list of VMInst domain objects
256     """
257
258     out = list()
259     resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
260     for resource in resources:
261         try:
262             server = nova_utils.get_server_object_by_id(nova, resource.id)
263             if server:
264                 out.append(server)
265         except NotFound:
266             logger.warn('VmInst cannot be located with ID %s', resource.id)
267
268     return out
269
270
271 def get_stack_keypairs(heat_cli, nova, stack):
272     """
273     Returns a list of Keypair domain objects associated with a Stack
274     :param heat_cli: the OpenStack heat client object
275     :param nova: the OpenStack nova client object
276     :param stack: the SNAPS-OO Stack domain object
277     :return: a list of VMInst domain objects
278     """
279
280     out = list()
281     resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
282     for resource in resources:
283         try:
284             keypair = nova_utils.get_keypair_by_id(nova, resource.id)
285             if keypair:
286                 out.append(keypair)
287         except NotFound:
288             logger.warn('Keypair cannot be located with ID %s', resource.id)
289
290     return out
291
292
293 def get_stack_volumes(heat_cli, cinder, stack):
294     """
295     Returns an instance of Volume domain objects created by this stack
296     :param heat_cli: the OpenStack heat client object
297     :param cinder: the OpenStack cinder client object
298     :param stack: the SNAPS-OO Stack domain object
299     :return: a list of Volume domain objects
300     """
301
302     out = list()
303     resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
304     for resource in resources:
305         try:
306             server = cinder_utils.get_volume_by_id(cinder, resource.id)
307             if server:
308                 out.append(server)
309         except NotFound:
310             logger.warn('Volume cannot be located with ID %s', resource.id)
311
312     return out
313
314
315 def get_stack_volume_types(heat_cli, cinder, stack):
316     """
317     Returns an instance of VolumeType domain objects created by this stack
318     :param heat_cli: the OpenStack heat client object
319     :param cinder: the OpenStack cinder client object
320     :param stack: the SNAPS-OO Stack domain object
321     :return: a list of VolumeType domain objects
322     """
323
324     out = list()
325     resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
326     for resource in resources:
327         try:
328             vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
329             if vol_type:
330                 out.append(vol_type)
331         except NotFound:
332             logger.warn('VolumeType cannot be located with ID %s', resource.id)
333
334     return out
335
336
337 def get_stack_flavors(heat_cli, nova, stack):
338     """
339     Returns an instance of Flavor SNAPS domain object for each flavor created
340     by this stack
341     :param heat_cli: the OpenStack heat client object
342     :param nova: the OpenStack cinder client object
343     :param stack: the SNAPS-OO Stack domain object
344     :return: a list of Volume domain objects
345     """
346
347     out = list()
348     resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
349     for resource in resources:
350         try:
351             flavor = nova_utils.get_flavor_by_id(nova, resource.id)
352             if flavor:
353                 out.append(flavor)
354         except NotFound:
355             logger.warn('Flavor cannot be located with ID %s', resource.id)
356
357     return out
358
359
360 def parse_heat_template_str(tmpl_str):
361     """
362     Takes a heat template string, performs some simple validation and returns a
363     dict containing the parsed structure. This function supports both JSON and
364     YAML Heat template formats.
365     """
366     if tmpl_str.startswith('{'):
367         tpl = jsonutils.loads(tmpl_str)
368     else:
369         try:
370             tpl = yaml.load(tmpl_str, Loader=yaml_loader)
371         except yaml.YAMLError as yea:
372             raise ValueError(yea)
373         else:
374             if tpl is None:
375                 tpl = {}
376     # Looking for supported version keys in the loaded template
377     if not ('HeatTemplateFormatVersion' in tpl or
378             'heat_template_version' in tpl or
379             'AWSTemplateFormatVersion' in tpl):
380         raise ValueError("Template format version not found.")
381     return tpl