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 keystone_utils, neutron_utils, nova_utils
28 __author__ = 'spisarski'
30 logger = logging.getLogger('heat_utils')
33 def heat_client(os_creds):
35 Retrieves the Heat client
36 :param os_creds: the OpenStack credentials
39 logger.debug('Retrieving Nova Client')
40 return Client(os_creds.heat_api_version,
41 session=keystone_utils.keystone_session(os_creds),
42 region_name=os_creds.region_name)
45 def get_stack(heat_cli, stack_settings=None, stack_name=None):
47 Returns the first domain Stack object found. When stack_setting
48 is not None, the filter created will take the name attribute. When
49 stack_settings is None and stack_name is not, stack_name will be used
50 instead. When both are None, the first stack object received will be
52 :param heat_cli: the OpenStack heat client
53 :param stack_settings: a StackSettings object
54 :param stack_name: the name of the heat stack to return
55 :return: the Stack domain object else None
60 stack_filter['stack_name'] = stack_settings.name
62 stack_filter['stack_name'] = stack_name
64 stacks = heat_cli.stacks.list(**stack_filter)
66 return Stack(name=stack.identifier, stack_id=stack.id)
69 def get_stack_by_id(heat_cli, stack_id):
71 Returns a domain Stack object for a given ID
72 :param heat_cli: the OpenStack heat client
73 :param stack_id: the ID of the heat stack to retrieve
74 :return: the Stack domain object else None
76 stack = heat_cli.stacks.get(stack_id)
77 return Stack(name=stack.identifier, stack_id=stack.id)
80 def get_stack_status(heat_cli, stack_id):
82 Returns the current status of the Heat stack
83 :param heat_cli: the OpenStack heat client
84 :param stack_id: the ID of the heat stack to retrieve
87 return heat_cli.stacks.get(stack_id).stack_status
90 def get_stack_status_reason(heat_cli, stack_id):
92 Returns the current status of the Heat stack
93 :param heat_cli: the OpenStack heat client
94 :param stack_id: the ID of the heat stack to retrieve
95 :return: reason for stack creation failure
97 return heat_cli.stacks.get(stack_id).stack_status_reason
100 def create_stack(heat_cli, stack_settings):
102 Executes an Ansible playbook to the given host
103 :param heat_cli: the OpenStack heat client object
104 :param stack_settings: the stack configuration
105 :return: the Stack domain object
109 if stack_settings.template:
110 args['template'] = stack_settings.template
112 args['template'] = parse_heat_template_str(
113 file_utils.read_file(stack_settings.template_path))
114 args['stack_name'] = stack_settings.name
116 if stack_settings.env_values:
117 args['parameters'] = stack_settings.env_values
119 stack = heat_cli.stacks.create(**args)
121 return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
124 def delete_stack(heat_cli, stack):
126 Deletes the Heat stack
127 :param heat_cli: the OpenStack heat client object
128 :param stack: the OpenStack Heat stack object
130 heat_cli.stacks.delete(stack.id)
133 def __get_os_resources(heat_cli, stack):
135 Returns all of the OpenStack resource objects for a given stack
136 :param heat_cli: the OpenStack heat client
137 :param stack: the SNAPS-OO Stack domain object
140 return heat_cli.resources.list(stack.id)
143 def get_resources(heat_cli, stack):
145 Returns all of the OpenStack resource objects for a given stack
146 :param heat_cli: the OpenStack heat client
147 :param stack: the SNAPS-OO Stack domain object
150 os_resources = __get_os_resources(heat_cli, stack)
154 for os_resource in os_resources:
155 out.append(Resource(resource_type=os_resource.resource_type,
156 resource_id=os_resource.physical_resource_id))
160 def get_outputs(heat_cli, stack):
162 Returns all of the SNAPS-OO Output domain objects for the defined outputs
164 :param heat_cli: the OpenStack heat client
165 :param stack: the SNAPS-OO Stack domain object
170 os_stack = heat_cli.stacks.get(stack.id)
174 outputs = os_stack.outputs
177 for output in outputs:
178 out.append(Output(**output))
183 def get_stack_networks(heat_cli, neutron, stack):
185 Returns an instance of NetworkSettings for each network owned by this stack
186 :param heat_cli: the OpenStack heat client object
187 :param neutron: the OpenStack neutron client object
188 :param stack: the SNAPS-OO Stack domain object
189 :return: a list of NetworkSettings
193 resources = get_resources(heat_cli, stack)
194 for resource in resources:
195 if resource.type == 'OS::Neutron::Net':
196 network = neutron_utils.get_network_by_id(
197 neutron, resource.id)
204 def get_stack_servers(heat_cli, nova, stack):
206 Returns an instance of NetworkSettings for each network owned by this stack
207 :param heat_cli: the OpenStack heat client object
208 :param nova: the OpenStack nova client object
209 :param stack: the SNAPS-OO Stack domain object
210 :return: a list of NetworkSettings
214 resources = get_resources(heat_cli, stack)
215 for resource in resources:
216 if resource.type == 'OS::Nova::Server':
218 server = nova_utils.get_server_object_by_id(
224 'VmInst cannot be located with ID %s', resource.id)
229 def parse_heat_template_str(tmpl_str):
231 Takes a heat template string, performs some simple validation and returns a
232 dict containing the parsed structure. This function supports both JSON and
233 YAML Heat template formats.
235 if tmpl_str.startswith('{'):
236 tpl = jsonutils.loads(tmpl_str)
239 tpl = yaml.load(tmpl_str, Loader=yaml_loader)
240 except yaml.YAMLError as yea:
241 raise ValueError(yea)
245 # Looking for supported version keys in the loaded template
246 if not ('HeatTemplateFormatVersion' in tpl or
247 'heat_template_version' in tpl or
248 'AWSTemplateFormatVersion' in tpl):
249 raise ValueError("Template format version not found.")