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.
19 from heatclient.client import Client
20 from heatclient.common.template_format import yaml_loader
21 from novaclient.exceptions import NotFound
22 from oslo_serialization import jsonutils
24 from snaps import file_utils
25 from snaps.domain.stack import Stack, Resource, Output
27 from snaps.openstack.utils import (
28 keystone_utils, neutron_utils, nova_utils, cinder_utils)
30 __author__ = 'spisarski'
32 logger = logging.getLogger('heat_utils')
35 def heat_client(os_creds, session=None):
37 Retrieves the Heat client
38 :param os_creds: the OpenStack credentials
41 logger.debug('Retrieving Heat Client')
43 session = keystone_utils.keystone_session(os_creds)
44 return Client(os_creds.heat_api_version,
46 region_name=os_creds.region_name)
49 def get_stack(heat_cli, stack_settings=None, stack_name=None):
51 Returns the first domain Stack object found. When stack_setting
52 is not None, the filter created will take the name attribute. When
53 stack_settings is None and stack_name is not, stack_name will be used
54 instead. When both are None, the first stack object received will be
56 :param heat_cli: the OpenStack heat client
57 :param stack_settings: a StackSettings object
58 :param stack_name: the name of the heat stack to return
59 :return: the Stack domain object else None
64 stack_filter['stack_name'] = stack_settings.name
66 stack_filter['stack_name'] = stack_name
68 stacks = heat_cli.stacks.list(**stack_filter)
71 name=stack.stack_name, stack_id=stack.id,
72 stack_project_id=stack.stack_user_project_id,
73 status=stack.stack_status,
74 status_reason=stack.stack_status_reason)
77 def get_stack_by_id(heat_cli, stack_id):
79 Returns a domain Stack object for a given ID
80 :param heat_cli: the OpenStack heat client
81 :param stack_id: the ID of the heat stack to retrieve
82 :return: the Stack domain object else None
84 stack = heat_cli.stacks.get(stack_id)
86 name=stack.stack_name, stack_id=stack.id,
87 stack_project_id=stack.stack_user_project_id,
88 status=stack.stack_status,
89 status_reason=stack.stack_status_reason)
92 def get_stack_status(heat_cli, stack_id):
94 Returns the current status of the Heat stack
95 :param heat_cli: the OpenStack heat client
96 :param stack_id: the ID of the heat stack to retrieve
99 return heat_cli.stacks.get(stack_id).stack_status
102 def get_stack_status_reason(heat_cli, stack_id):
104 Returns the current status of the Heat stack
105 :param heat_cli: the OpenStack heat client
106 :param stack_id: the ID of the heat stack to retrieve
107 :return: reason for stack creation failure
109 return heat_cli.stacks.get(stack_id).stack_status_reason
112 def create_stack(heat_cli, stack_settings):
114 Executes an Ansible playbook to the given host
115 :param heat_cli: the OpenStack heat client object
116 :param stack_settings: the stack configuration
117 :return: the Stack domain object
121 if stack_settings.template:
122 args['template'] = stack_settings.template
124 args['template'] = parse_heat_template_str(
125 file_utils.read_file(stack_settings.template_path))
126 args['stack_name'] = stack_settings.name
128 if stack_settings.env_values:
129 args['parameters'] = stack_settings.env_values
131 if stack_settings.resource_files:
133 for res_file in stack_settings.resource_files:
134 heat_resource_contents = file_utils.read_file(res_file)
135 base_filename = os.path.basename(res_file)
137 if heat_resource_contents and base_filename:
138 resources[base_filename] = heat_resource_contents
139 args['files'] = resources
141 stack = heat_cli.stacks.create(**args)
143 return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
146 def delete_stack(heat_cli, stack):
148 Deletes the Heat stack
149 :param heat_cli: the OpenStack heat client object
150 :param stack: the OpenStack Heat stack object
152 heat_cli.stacks.delete(stack.id)
155 def __get_os_resources(heat_cli, res_id):
157 Returns all of the OpenStack resource objects for a given stack
158 :param heat_cli: the OpenStack heat client
159 :param res_id: the resource ID
162 return heat_cli.resources.list(res_id)
165 def get_resources(heat_cli, res_id, res_type=None):
167 Returns all of the OpenStack resource objects for a given stack
168 :param heat_cli: the OpenStack heat client
169 :param res_id: the SNAPS-OO Stack domain object
170 :param res_type: the type name to filter
171 :return: a list of Resource domain objects
173 os_resources = __get_os_resources(heat_cli, res_id)
177 for os_resource in os_resources:
178 if ((res_type and os_resource.resource_type == res_type)
181 name=os_resource.resource_name,
182 resource_type=os_resource.resource_type,
183 resource_id=os_resource.physical_resource_id,
184 status=os_resource.resource_status,
185 status_reason=os_resource.resource_status_reason))
189 def get_outputs(heat_cli, stack):
191 Returns all of the SNAPS-OO Output domain objects for the defined outputs
193 :param heat_cli: the OpenStack heat client
194 :param stack: the SNAPS-OO Stack domain object
195 :return: a list of Output domain objects
199 os_stack = heat_cli.stacks.get(stack.id)
203 outputs = os_stack.outputs
206 for output in outputs:
207 out.append(Output(**output))
212 def get_stack_networks(heat_cli, neutron, stack):
214 Returns a list of Network domain objects deployed by this stack
215 :param heat_cli: the OpenStack heat client object
216 :param neutron: the OpenStack neutron client object
217 :param stack: the SNAPS-OO Stack domain object
218 :return: a list of Network objects
222 resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Net')
223 for resource in resources:
224 network = neutron_utils.get_network_by_id(neutron, resource.id)
231 def get_stack_routers(heat_cli, neutron, stack):
233 Returns a list of Network domain objects deployed by this stack
234 :param heat_cli: the OpenStack heat client object
235 :param neutron: the OpenStack neutron client object
236 :param stack: the SNAPS-OO Stack domain object
237 :return: a list of Network objects
241 resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Router')
242 for resource in resources:
243 router = neutron_utils.get_router_by_id(neutron, resource.id)
250 def get_stack_security_groups(heat_cli, neutron, stack):
252 Returns a list of SecurityGroup domain objects deployed by this stack
253 :param heat_cli: the OpenStack heat client object
254 :param neutron: the OpenStack neutron client object
255 :param stack: the SNAPS-OO Stack domain object
256 :return: a list of SecurityGroup objects
260 resources = get_resources(heat_cli, stack.id, 'OS::Neutron::SecurityGroup')
261 for resource in resources:
262 security_group = neutron_utils.get_security_group_by_id(
263 neutron, resource.id)
265 out.append(security_group)
270 def get_stack_servers(heat_cli, nova, neutron, keystone, stack, project_name):
272 Returns a list of VMInst domain objects associated with a Stack
273 :param heat_cli: the OpenStack heat client object
274 :param nova: the OpenStack nova client object
275 :param neutron: the OpenStack neutron client object
276 :param keystone: the OpenStack keystone client object
277 :param stack: the SNAPS-OO Stack domain object
278 :param project_name: the associated project ID
279 :return: a list of VMInst domain objects
283 srvr_res = get_resources(heat_cli, stack.id, 'OS::Nova::Server')
284 for resource in srvr_res:
286 server = nova_utils.get_server_object_by_id(
287 nova, neutron, keystone, resource.id, project_name)
291 logger.warn('VmInst cannot be located with ID %s', resource.id)
293 res_grps = get_resources(heat_cli, stack.id, 'OS::Heat::ResourceGroup')
294 for res_grp in res_grps:
295 res_ress = get_resources(heat_cli, res_grp.id)
296 for res_res in res_ress:
297 res_res_srvrs = get_resources(
298 heat_cli, res_res.id, 'OS::Nova::Server')
299 for res_srvr in res_res_srvrs:
300 server = nova_utils.get_server_object_by_id(
301 nova, neutron, keystone, res_srvr.id, project_name)
308 def get_stack_keypairs(heat_cli, nova, stack):
310 Returns a list of Keypair domain objects associated with a Stack
311 :param heat_cli: the OpenStack heat client object
312 :param nova: the OpenStack nova client object
313 :param stack: the SNAPS-OO Stack domain object
314 :return: a list of VMInst domain objects
318 resources = get_resources(heat_cli, stack.id, 'OS::Nova::KeyPair')
319 for resource in resources:
321 keypair = nova_utils.get_keypair_by_id(nova, resource.id)
325 logger.warn('Keypair cannot be located with ID %s', resource.id)
330 def get_stack_volumes(heat_cli, cinder, stack):
332 Returns an instance of Volume domain objects created by this stack
333 :param heat_cli: the OpenStack heat client object
334 :param cinder: the OpenStack cinder client object
335 :param stack: the SNAPS-OO Stack domain object
336 :return: a list of Volume domain objects
340 resources = get_resources(heat_cli, stack.id, 'OS::Cinder::Volume')
341 for resource in resources:
343 server = cinder_utils.get_volume_by_id(cinder, resource.id)
347 logger.warn('Volume cannot be located with ID %s', resource.id)
352 def get_stack_volume_types(heat_cli, cinder, stack):
354 Returns an instance of VolumeType domain objects created by this stack
355 :param heat_cli: the OpenStack heat client object
356 :param cinder: the OpenStack cinder client object
357 :param stack: the SNAPS-OO Stack domain object
358 :return: a list of VolumeType domain objects
362 resources = get_resources(heat_cli, stack.id, 'OS::Cinder::VolumeType')
363 for resource in resources:
365 vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id)
369 logger.warn('VolumeType cannot be located with ID %s', resource.id)
374 def get_stack_flavors(heat_cli, nova, stack):
376 Returns an instance of Flavor SNAPS domain object for each flavor created
378 :param heat_cli: the OpenStack heat client object
379 :param nova: the OpenStack cinder client object
380 :param stack: the SNAPS-OO Stack domain object
381 :return: a list of Volume domain objects
385 resources = get_resources(heat_cli, stack.id, 'OS::Nova::Flavor')
386 for resource in resources:
388 flavor = nova_utils.get_flavor_by_id(nova, resource.id)
392 logger.warn('Flavor cannot be located with ID %s', resource.id)
397 def parse_heat_template_str(tmpl_str):
399 Takes a heat template string, performs some simple validation and returns a
400 dict containing the parsed structure. This function supports both JSON and
401 YAML Heat template formats.
403 if tmpl_str.startswith('{'):
404 tpl = jsonutils.loads(tmpl_str)
407 tpl = yaml.load(tmpl_str, Loader=yaml_loader)
408 except yaml.YAMLError as yea:
409 raise ValueError(yea)
413 # Looking for supported version keys in the loaded template
414 if not ('HeatTemplateFormatVersion' in tpl or
415 'heat_template_version' in tpl or
416 'AWSTemplateFormatVersion' in tpl):
417 raise ValueError("Template format version not found.")