1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
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.
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
23 from snaps import file_utils
24 from snaps.domain.stack import Stack, Resource, Output
26 from snaps.openstack.utils import (
27 keystone_utils, neutron_utils, nova_utils, cinder_utils)
29 __author__ = 'spisarski'
31 logger = logging.getLogger('heat_utils')
34 def heat_client(os_creds):
36 Retrieves the Heat client
37 :param os_creds: the OpenStack credentials
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)
46 def get_stack(heat_cli, stack_settings=None, stack_name=None):
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
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
61 stack_filter['stack_name'] = stack_settings.name
63 stack_filter['stack_name'] = stack_name
65 stacks = heat_cli.stacks.list(**stack_filter)
67 return Stack(name=stack.identifier, stack_id=stack.id)
70 def get_stack_by_id(heat_cli, stack_id):
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
77 stack = heat_cli.stacks.get(stack_id)
78 return Stack(name=stack.identifier, stack_id=stack.id)
81 def get_stack_status(heat_cli, stack_id):
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
88 return heat_cli.stacks.get(stack_id).stack_status
91 def get_stack_status_reason(heat_cli, stack_id):
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
98 return heat_cli.stacks.get(stack_id).stack_status_reason
101 def create_stack(heat_cli, stack_settings):
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
110 if stack_settings.template:
111 args['template'] = stack_settings.template
113 args['template'] = parse_heat_template_str(
114 file_utils.read_file(stack_settings.template_path))
115 args['stack_name'] = stack_settings.name
117 if stack_settings.env_values:
118 args['parameters'] = stack_settings.env_values
120 if stack_settings.files:
121 args['files'] = stack_settings.files
123 stack = heat_cli.stacks.create(**args)
125 return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
128 def delete_stack(heat_cli, stack):
130 Deletes the Heat stack
131 :param heat_cli: the OpenStack heat client object
132 :param stack: the OpenStack Heat stack object
134 heat_cli.stacks.delete(stack.id)
137 def __get_os_resources(heat_cli, stack):
139 Returns all of the OpenStack resource objects for a given stack
140 :param heat_cli: the OpenStack heat client
141 :param stack: the SNAPS-OO Stack domain object
144 return heat_cli.resources.list(stack.id)
147 def get_resources(heat_cli, stack, res_type=None):
149 Returns all of the OpenStack resource objects for a given stack
150 :param heat_cli: the OpenStack heat client
151 :param stack: the SNAPS-OO Stack domain object
152 :param res_type: the type name to filter
153 :return: a list of Resource domain objects
155 os_resources = __get_os_resources(heat_cli, stack)
159 for os_resource in os_resources:
160 if ((res_type and os_resource.resource_type == res_type)
163 name=os_resource.resource_name,
164 resource_type=os_resource.resource_type,
165 resource_id=os_resource.physical_resource_id,
166 status=os_resource.resource_status,
167 status_reason=os_resource.resource_status_reason))
171 def get_outputs(heat_cli, stack):
173 Returns all of the SNAPS-OO Output domain objects for the defined outputs
175 :param heat_cli: the OpenStack heat client
176 :param stack: the SNAPS-OO Stack domain object
177 :return: a list of Output domain objects
181 os_stack = heat_cli.stacks.get(stack.id)
185 outputs = os_stack.outputs
188 for output in outputs:
189 out.append(Output(**output))
194 def get_stack_networks(heat_cli, neutron, stack):
196 Returns a list of Network domain objects deployed by this stack
197 :param heat_cli: the OpenStack heat client object
198 :param neutron: the OpenStack neutron client object
199 :param stack: the SNAPS-OO Stack domain object
200 :return: a list of Network objects
204 resources = get_resources(heat_cli, stack, 'OS::Neutron::Net')
205 for resource in resources:
206 network = neutron_utils.get_network_by_id(neutron, resource.id)
213 def get_stack_routers(heat_cli, neutron, stack):
215 Returns a list of Network domain objects deployed by this stack
216 :param heat_cli: the OpenStack heat client object
217 :param neutron: the OpenStack neutron client object
218 :param stack: the SNAPS-OO Stack domain object
219 :return: a list of Network objects
223 resources = get_resources(heat_cli, stack, 'OS::Neutron::Router')
224 for resource in resources:
225 router = neutron_utils.get_router_by_id(neutron, resource.id)
232 def get_stack_security_groups(heat_cli, neutron, stack):
234 Returns a list of SecurityGroup domain objects deployed by this stack
235 :param heat_cli: the OpenStack heat client object
236 :param neutron: the OpenStack neutron client object
237 :param stack: the SNAPS-OO Stack domain object
238 :return: a list of SecurityGroup objects
242 resources = get_resources(heat_cli, stack, 'OS::Neutron::SecurityGroup')
243 for resource in resources:
244 security_group = neutron_utils.get_security_group_by_id(
245 neutron, resource.id)
247 out.append(security_group)
252 def get_stack_servers(heat_cli, nova, neutron, stack):
254 Returns a list of VMInst domain objects associated with a Stack
255 :param heat_cli: the OpenStack heat client object
256 :param nova: the OpenStack nova client object
257 :param neutron: the OpenStack neutron client object
258 :param stack: the SNAPS-OO Stack domain object
259 :return: a list of VMInst domain objects
263 resources = get_resources(heat_cli, stack, 'OS::Nova::Server')
264 for resource in resources:
266 server = nova_utils.get_server_object_by_id(
267 nova, neutron, resource.id)
271 logger.warn('VmInst cannot be located with ID %s', resource.id)
276 def get_stack_keypairs(heat_cli, nova, stack):
278 Returns a list of Keypair domain objects associated with a Stack
279 :param heat_cli: the OpenStack heat client object
280 :param nova: the OpenStack nova client object
281 :param stack: the SNAPS-OO Stack domain object
282 :return: a list of VMInst domain objects
286 resources = get_resources(heat_cli, stack, 'OS::Nova::KeyPair')
287 for resource in resources:
289 keypair = nova_utils.get_keypair_by_id(nova, resource.id)
293 logger.warn('Keypair cannot be located with ID %s', resource.id)
298 def get_stack_volumes(heat_cli, cinder, stack):
300 Returns an instance of Volume domain objects created by this stack
301 :param heat_cli: the OpenStack heat client object
302 :param cinder: the OpenStack cinder client object
303 :param stack: the SNAPS-OO Stack domain object
304 :return: a list of Volume domain objects
308 resources = get_resources(heat_cli, stack, 'OS::Cinder::Volume')
309 for resource in resources:
311 server = cinder_utils.get_volume_by_id(cinder, resource.id)
315 logger.warn('Volume cannot be located with ID %s', resource.id)
320 def get_stack_volume_types(heat_cli, cinder, stack):
322 Returns an instance of VolumeType domain objects created by this stack
323 :param heat_cli: the OpenStack heat client object
324 :param cinder: the OpenStack cinder client object
325 :param stack: the SNAPS-OO Stack domain object
326 :return: a list of VolumeType domain objects
330 resources = get_resources(heat_cli, stack, 'OS::Cinder::VolumeType')
331 for resource in resources:
333 vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
337 logger.warn('VolumeType cannot be located with ID %s', resource.id)
342 def get_stack_flavors(heat_cli, nova, stack):
344 Returns an instance of Flavor SNAPS domain object for each flavor created
346 :param heat_cli: the OpenStack heat client object
347 :param nova: the OpenStack cinder client object
348 :param stack: the SNAPS-OO Stack domain object
349 :return: a list of Volume domain objects
353 resources = get_resources(heat_cli, stack, 'OS::Nova::Flavor')
354 for resource in resources:
356 flavor = nova_utils.get_flavor_by_id(nova, resource.id)
360 logger.warn('Flavor cannot be located with ID %s', resource.id)
365 def parse_heat_template_str(tmpl_str):
367 Takes a heat template string, performs some simple validation and returns a
368 dict containing the parsed structure. This function supports both JSON and
369 YAML Heat template formats.
371 if tmpl_str.startswith('{'):
372 tpl = jsonutils.loads(tmpl_str)
375 tpl = yaml.load(tmpl_str, Loader=yaml_loader)
376 except yaml.YAMLError as yea:
377 raise ValueError(yea)
381 # Looking for supported version keys in the loaded template
382 if not ('HeatTemplateFormatVersion' in tpl or
383 'heat_template_version' in tpl or
384 'AWSTemplateFormatVersion' in tpl):
385 raise ValueError("Template format version not found.")