Refactored VmInst domain class for Ports.
[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, neutron, 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 neutron: the OpenStack neutron client object
255     :param stack: the SNAPS-OO Stack domain object
256     :return: a list of VMInst domain objects
257     """
258
259     out = list()
260     resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
261     for resource in resources:
262         try:
263             server = nova_utils.get_server_object_by_id(
264                 nova, neutron, resource.id)
265             if server:
266                 out.append(server)
267         except NotFound:
268             logger.warn('VmInst cannot be located with ID %s', resource.id)
269
270     return out
271
272
273 def get_stack_keypairs(heat_cli, nova, stack):
274     """
275     Returns a list of Keypair domain objects associated with a Stack
276     :param heat_cli: the OpenStack heat client object
277     :param nova: the OpenStack nova client object
278     :param stack: the SNAPS-OO Stack domain object
279     :return: a list of VMInst domain objects
280     """
281
282     out = list()
283     resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
284     for resource in resources:
285         try:
286             keypair = nova_utils.get_keypair_by_id(nova, resource.id)
287             if keypair:
288                 out.append(keypair)
289         except NotFound:
290             logger.warn('Keypair cannot be located with ID %s', resource.id)
291
292     return out
293
294
295 def get_stack_volumes(heat_cli, cinder, stack):
296     """
297     Returns an instance of Volume domain objects created by this stack
298     :param heat_cli: the OpenStack heat client object
299     :param cinder: the OpenStack cinder client object
300     :param stack: the SNAPS-OO Stack domain object
301     :return: a list of Volume domain objects
302     """
303
304     out = list()
305     resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
306     for resource in resources:
307         try:
308             server = cinder_utils.get_volume_by_id(cinder, resource.id)
309             if server:
310                 out.append(server)
311         except NotFound:
312             logger.warn('Volume cannot be located with ID %s', resource.id)
313
314     return out
315
316
317 def get_stack_volume_types(heat_cli, cinder, stack):
318     """
319     Returns an instance of VolumeType domain objects created by this stack
320     :param heat_cli: the OpenStack heat client object
321     :param cinder: the OpenStack cinder client object
322     :param stack: the SNAPS-OO Stack domain object
323     :return: a list of VolumeType domain objects
324     """
325
326     out = list()
327     resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
328     for resource in resources:
329         try:
330             vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
331             if vol_type:
332                 out.append(vol_type)
333         except NotFound:
334             logger.warn('VolumeType cannot be located with ID %s', resource.id)
335
336     return out
337
338
339 def get_stack_flavors(heat_cli, nova, stack):
340     """
341     Returns an instance of Flavor SNAPS domain object for each flavor created
342     by this stack
343     :param heat_cli: the OpenStack heat client object
344     :param nova: the OpenStack cinder client object
345     :param stack: the SNAPS-OO Stack domain object
346     :return: a list of Volume domain objects
347     """
348
349     out = list()
350     resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
351     for resource in resources:
352         try:
353             flavor = nova_utils.get_flavor_by_id(nova, resource.id)
354             if flavor:
355                 out.append(flavor)
356         except NotFound:
357             logger.warn('Flavor cannot be located with ID %s', resource.id)
358
359     return out
360
361
362 def parse_heat_template_str(tmpl_str):
363     """
364     Takes a heat template string, performs some simple validation and returns a
365     dict containing the parsed structure. This function supports both JSON and
366     YAML Heat template formats.
367     """
368     if tmpl_str.startswith('{'):
369         tpl = jsonutils.loads(tmpl_str)
370     else:
371         try:
372             tpl = yaml.load(tmpl_str, Loader=yaml_loader)
373         except yaml.YAMLError as yea:
374             raise ValueError(yea)
375         else:
376             if tpl is None:
377                 tpl = {}
378     # Looking for supported version keys in the loaded template
379     if not ('HeatTemplateFormatVersion' in tpl or
380             'heat_template_version' in tpl or
381             'AWSTemplateFormatVersion' in tpl):
382         raise ValueError("Template format version not found.")
383     return tpl