Second patch for volume support.
[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
28 __author__ = 'spisarski'
29
30 logger = logging.getLogger('heat_utils')
31
32
33 def heat_client(os_creds):
34     """
35     Retrieves the Heat client
36     :param os_creds: the OpenStack credentials
37     :return: the client
38     """
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)
43
44
45 def get_stack(heat_cli, stack_settings=None, stack_name=None):
46     """
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
51     returned, else None
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
56     """
57
58     stack_filter = dict()
59     if stack_settings:
60         stack_filter['stack_name'] = stack_settings.name
61     elif stack_name:
62         stack_filter['stack_name'] = stack_name
63
64     stacks = heat_cli.stacks.list(**stack_filter)
65     for stack in stacks:
66         return Stack(name=stack.identifier, stack_id=stack.id)
67
68
69 def get_stack_by_id(heat_cli, stack_id):
70     """
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
75     """
76     stack = heat_cli.stacks.get(stack_id)
77     return Stack(name=stack.identifier, stack_id=stack.id)
78
79
80 def get_stack_status(heat_cli, stack_id):
81     """
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
85     :return:
86     """
87     return heat_cli.stacks.get(stack_id).stack_status
88
89
90 def create_stack(heat_cli, stack_settings):
91     """
92     Executes an Ansible playbook to the given host
93     :param heat_cli: the OpenStack heat client object
94     :param stack_settings: the stack configuration
95     :return: the Stack domain object
96     """
97     args = dict()
98
99     if stack_settings.template:
100         args['template'] = stack_settings.template
101     else:
102         args['template'] = parse_heat_template_str(
103             file_utils.read_file(stack_settings.template_path))
104     args['stack_name'] = stack_settings.name
105
106     if stack_settings.env_values:
107         args['parameters'] = stack_settings.env_values
108
109     stack = heat_cli.stacks.create(**args)
110
111     return get_stack_by_id(heat_cli, stack_id=stack['stack']['id'])
112
113
114 def delete_stack(heat_cli, stack):
115     """
116     Deletes the Heat stack
117     :param heat_cli: the OpenStack heat client object
118     :param stack: the OpenStack Heat stack object
119     """
120     heat_cli.stacks.delete(stack.id)
121
122
123 def __get_os_resources(heat_cli, stack):
124     """
125     Returns all of the OpenStack resource objects for a given stack
126     :param heat_cli: the OpenStack heat client
127     :param stack: the SNAPS-OO Stack domain object
128     :return: a list
129     """
130     return heat_cli.resources.list(stack.id)
131
132
133 def get_resources(heat_cli, stack):
134     """
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
138     :return: a list
139     """
140     os_resources = __get_os_resources(heat_cli, stack)
141
142     if os_resources:
143         out = list()
144         for os_resource in os_resources:
145             out.append(Resource(resource_type=os_resource.resource_type,
146                                 resource_id=os_resource.physical_resource_id))
147         return out
148
149
150 def get_outputs(heat_cli, stack):
151     """
152     Returns all of the SNAPS-OO Output domain objects for the defined outputs
153     for given stack
154     :param heat_cli: the OpenStack heat client
155     :param stack: the SNAPS-OO Stack domain object
156     :return: a list
157     """
158     out = list()
159
160     os_stack = heat_cli.stacks.get(stack.id)
161
162     outputs = None
163     if os_stack:
164         outputs = os_stack.outputs
165
166     if outputs:
167         for output in outputs:
168             out.append(Output(**output))
169
170     return out
171
172
173 def get_stack_networks(heat_cli, neutron, stack):
174     """
175     Returns an instance of NetworkSettings for each network owned by this stack
176     :param heat_cli: the OpenStack heat client object
177     :param neutron: the OpenStack neutron client object
178     :param stack: the SNAPS-OO Stack domain object
179     :return: a list of NetworkSettings
180     """
181
182     out = list()
183     resources = get_resources(heat_cli, stack)
184     for resource in resources:
185         if resource.type == 'OS::Neutron::Net':
186             network = neutron_utils.get_network_by_id(
187                 neutron, resource.id)
188             if network:
189                 out.append(network)
190
191     return out
192
193
194 def get_stack_servers(heat_cli, nova, stack):
195     """
196     Returns an instance of NetworkSettings for each network owned by this stack
197     :param heat_cli: the OpenStack heat client object
198     :param nova: the OpenStack nova client object
199     :param stack: the SNAPS-OO Stack domain object
200     :return: a list of NetworkSettings
201     """
202
203     out = list()
204     resources = get_resources(heat_cli, stack)
205     for resource in resources:
206         if resource.type == 'OS::Nova::Server':
207             try:
208                 server = nova_utils.get_server_object_by_id(
209                     nova, resource.id)
210                 if server:
211                     out.append(server)
212             except NotFound:
213                 logger.warn(
214                     'VmInst cannot be located with ID %s', resource.id)
215
216     return out
217
218
219 def parse_heat_template_str(tmpl_str):
220     """
221     Takes a heat template string, performs some simple validation and returns a
222     dict containing the parsed structure. This function supports both JSON and
223     YAML Heat template formats.
224     """
225     if tmpl_str.startswith('{'):
226         tpl = jsonutils.loads(tmpl_str)
227     else:
228         try:
229             tpl = yaml.load(tmpl_str, Loader=yaml_loader)
230         except yaml.YAMLError as yea:
231             raise ValueError(yea)
232         else:
233             if tpl is None:
234                 tpl = {}
235     # Looking for supported version keys in the loaded template
236     if not ('HeatTemplateFormatVersion' in tpl or
237             'heat_template_version' in tpl or
238             'AWSTemplateFormatVersion' in tpl):
239         raise ValueError("Template format version not found.")
240     return tpl