Added method to OpenStackHeatStack to return OpenStackRouter objects.
[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 keystone_utils, neutron_utils, nova_utils, \
27     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 Nova 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                     resource_type=os_resource.resource_type,
161                     resource_id=os_resource.physical_resource_id))
162         return out
163
164
165 def get_outputs(heat_cli, stack):
166     """
167     Returns all of the SNAPS-OO Output domain objects for the defined outputs
168     for given stack
169     :param heat_cli: the OpenStack heat client
170     :param stack: the SNAPS-OO Stack domain object
171     :return: a list of Output domain objects
172     """
173     out = list()
174
175     os_stack = heat_cli.stacks.get(stack.id)
176
177     outputs = None
178     if os_stack:
179         outputs = os_stack.outputs
180
181     if outputs:
182         for output in outputs:
183             out.append(Output(**output))
184
185     return out
186
187
188 def get_stack_networks(heat_cli, neutron, stack):
189     """
190     Returns a list of Network domain objects deployed by this stack
191     :param heat_cli: the OpenStack heat client object
192     :param neutron: the OpenStack neutron client object
193     :param stack: the SNAPS-OO Stack domain object
194     :return: a list of Network objects
195     """
196
197     out = list()
198     resources = get_resources(heat_cli, stack, 'OS::Neutron::Net')
199     for resource in resources:
200         network = neutron_utils.get_network_by_id(neutron, resource.id)
201         if network:
202             out.append(network)
203
204     return out
205
206
207 def get_stack_routers(heat_cli, neutron, stack):
208     """
209     Returns a list of Network domain objects deployed by this stack
210     :param heat_cli: the OpenStack heat client object
211     :param neutron: the OpenStack neutron client object
212     :param stack: the SNAPS-OO Stack domain object
213     :return: a list of Network objects
214     """
215
216     out = list()
217     resources = get_resources(heat_cli, stack, 'OS::Neutron::Router')
218     for resource in resources:
219         router = neutron_utils.get_router_by_id(neutron, resource.id)
220         if router:
221             out.append(router)
222
223     return out
224
225
226 def get_stack_servers(heat_cli, nova, stack):
227     """
228     Returns a list of VMInst domain objects associated with a Stack
229     :param heat_cli: the OpenStack heat client object
230     :param nova: the OpenStack nova client object
231     :param stack: the SNAPS-OO Stack domain object
232     :return: a list of VMInst domain objects
233     """
234
235     out = list()
236     resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
237     for resource in resources:
238         try:
239             server = nova_utils.get_server_object_by_id(nova, resource.id)
240             if server:
241                 out.append(server)
242         except NotFound:
243             logger.warn('VmInst cannot be located with ID %s', resource.id)
244
245     return out
246
247
248 def get_stack_keypairs(heat_cli, nova, stack):
249     """
250     Returns a list of Keypair domain objects associated with a Stack
251     :param heat_cli: the OpenStack heat client object
252     :param nova: the OpenStack nova client object
253     :param stack: the SNAPS-OO Stack domain object
254     :return: a list of VMInst domain objects
255     """
256
257     out = list()
258     resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
259     for resource in resources:
260         try:
261             keypair = nova_utils.get_keypair_by_id(nova, resource.id)
262             if keypair:
263                 out.append(keypair)
264         except NotFound:
265             logger.warn('Keypair cannot be located with ID %s', resource.id)
266
267     return out
268
269
270 def get_stack_volumes(heat_cli, cinder, stack):
271     """
272     Returns an instance of Volume domain objects created by this stack
273     :param heat_cli: the OpenStack heat client object
274     :param cinder: the OpenStack cinder client object
275     :param stack: the SNAPS-OO Stack domain object
276     :return: a list of Volume domain objects
277     """
278
279     out = list()
280     resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
281     for resource in resources:
282         try:
283             server = cinder_utils.get_volume_by_id(cinder, resource.id)
284             if server:
285                 out.append(server)
286         except NotFound:
287             logger.warn('Volume cannot be located with ID %s', resource.id)
288
289     return out
290
291
292 def get_stack_volume_types(heat_cli, cinder, stack):
293     """
294     Returns an instance of VolumeType domain objects created by this stack
295     :param heat_cli: the OpenStack heat client object
296     :param cinder: the OpenStack cinder client object
297     :param stack: the SNAPS-OO Stack domain object
298     :return: a list of VolumeType domain objects
299     """
300
301     out = list()
302     resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
303     for resource in resources:
304         try:
305             vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
306             if vol_type:
307                 out.append(vol_type)
308         except NotFound:
309             logger.warn('VolumeType cannot be located with ID %s', resource.id)
310
311     return out
312
313
314 def get_stack_flavors(heat_cli, nova, stack):
315     """
316     Returns an instance of Flavor SNAPS domain object for each flavor created
317     by this stack
318     :param heat_cli: the OpenStack heat client object
319     :param nova: the OpenStack cinder client object
320     :param stack: the SNAPS-OO Stack domain object
321     :return: a list of Volume domain objects
322     """
323
324     out = list()
325     resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
326     for resource in resources:
327         try:
328             flavor = nova_utils.get_flavor_by_id(nova, resource.id)
329             if flavor:
330                 out.append(flavor)
331         except NotFound:
332             logger.warn('Flavor cannot be located with ID %s', resource.id)
333
334     return out
335
336
337 def parse_heat_template_str(tmpl_str):
338     """
339     Takes a heat template string, performs some simple validation and returns a
340     dict containing the parsed structure. This function supports both JSON and
341     YAML Heat template formats.
342     """
343     if tmpl_str.startswith('{'):
344         tpl = jsonutils.loads(tmpl_str)
345     else:
346         try:
347             tpl = yaml.load(tmpl_str, Loader=yaml_loader)
348         except yaml.YAMLError as yea:
349             raise ValueError(yea)
350         else:
351             if tpl is None:
352                 tpl = {}
353     # Looking for supported version keys in the loaded template
354     if not ('HeatTemplateFormatVersion' in tpl or
355             'heat_template_version' in tpl or
356             'AWSTemplateFormatVersion' in tpl):
357         raise ValueError("Template format version not found.")
358     return tpl