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.exc import HTTPNotFound
21 from snaps.openstack.create_network import (
22 OpenStackNetwork, NetworkSettings, SubnetSettings)
23 from snaps.openstack.utils import heat_utils, neutron_utils
25 __author__ = 'spisarski'
27 logger = logging.getLogger('create_stack')
29 STACK_COMPLETE_TIMEOUT = 1200
31 STATUS_CREATE_FAILED = 'CREATE_FAILED'
32 STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE'
33 STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE'
36 class OpenStackHeatStack:
38 Class responsible for creating an heat stack in OpenStack
41 def __init__(self, os_creds, stack_settings):
44 :param os_creds: The OpenStack connection credentials
45 :param stack_settings: The stack settings
48 self.__os_creds = os_creds
49 self.stack_settings = stack_settings
51 self.__heat_cli = None
53 def create(self, cleanup=False):
55 Creates the heat stack in OpenStack if it does not already exist and
56 returns the domain Stack object
57 :param cleanup: When true, this object is initialized only via queries,
58 else objects will be created when the queries return
59 None. The name of this parameter should be changed to
60 something like 'readonly' as the same goes with all of
61 the other creator classes.
62 :return: The OpenStack Stack object
64 self.__heat_cli = heat_utils.heat_client(self.__os_creds)
65 self.__stack = heat_utils.get_stack(
66 self.__heat_cli, stack_settings=self.stack_settings)
68 logger.info('Found stack with name - ' + self.stack_settings.name)
71 self.__stack = heat_utils.create_stack(self.__heat_cli,
74 'Created stack with name - ' + self.stack_settings.name)
75 if self.__stack and self.stack_complete(block=True):
77 'Stack is now active with name - ' +
78 self.stack_settings.name)
81 raise StackCreationError(
82 'Stack was not created or activated in the alloted amount '
85 logger.info('Did not create stack due to cleanup mode')
91 Cleanse environment of all artifacts
96 heat_utils.delete_stack(self.__heat_cli, self.__stack)
104 Returns the domain Stack object as it was populated when create() was
110 def get_outputs(self):
112 Returns the list of outputs as contained on the OpenStack Heat Stack
116 return heat_utils.get_stack_outputs(self.__heat_cli, self.__stack.id)
118 def get_status(self):
120 Returns the list of outputs as contained on the OpenStack Heat Stack
124 return heat_utils.get_stack_status(self.__heat_cli, self.__stack.id)
126 def stack_complete(self, block=False, timeout=None,
127 poll_interval=POLL_INTERVAL):
129 Returns true when the stack status returns the value of
131 :param block: When true, thread will block until active or timeout
132 value in seconds has been exceeded (False)
133 :param timeout: The timeout value
134 :param poll_interval: The polling interval in seconds
138 timeout = self.stack_settings.stack_create_timeout
139 return self._stack_status_check(STATUS_CREATE_COMPLETE, block, timeout,
142 def get_network_creators(self):
144 Returns a list of network creator objects as configured by the heat
146 :return: list() of OpenStackNetwork objects
149 neutron = neutron_utils.neutron_client(self.__os_creds)
152 stack_networks = heat_utils.get_stack_networks(
153 self.__heat_cli, neutron, self.__stack)
155 for stack_network in stack_networks:
156 net_settings = self.__create_network_settings(
157 neutron, stack_network)
158 net_creator = OpenStackNetwork(self.__os_creds, net_settings)
159 out.append(net_creator)
160 net_creator.create(cleanup=True)
164 def __create_network_settings(self, neutron, network):
166 Returns a NetworkSettings object
167 :param neutron: the neutron client
168 :param network: a SNAPS-OO Network domain object
171 return NetworkSettings(
172 name=network.name, network_type=network.type,
173 subnet_settings=self.__create_subnet_settings(neutron, network))
175 def __create_subnet_settings(self, neutron, network):
177 Returns a list of SubnetSettings objects for a given network
178 :param neutron: the OpenStack neutron client
179 :param network: the SNAPS-OO Network domain object
184 subnets = neutron_utils.get_subnets_by_network(neutron, network)
185 for subnet in subnets:
187 kwargs['cidr'] = subnet.cidr
188 kwargs['ip_version'] = subnet.ip_version
189 kwargs['name'] = subnet.name
190 kwargs['start'] = subnet.start
191 kwargs['end'] = subnet.end
192 kwargs['gateway_ip'] = subnet.gateway_ip
193 kwargs['enable_dhcp'] = subnet.enable_dhcp
194 kwargs['dns_nameservers'] = subnet.dns_nameservers
195 kwargs['host_routes'] = subnet.host_routes
196 kwargs['ipv6_ra_mode'] = subnet.ipv6_ra_mode
197 kwargs['ipv6_address_mode'] = subnet.ipv6_address_mode
198 out.append(SubnetSettings(**kwargs))
201 def _stack_status_check(self, expected_status_code, block, timeout,
204 Returns true when the stack status returns the value of
206 :param expected_status_code: stack status evaluated with this string
208 :param block: When true, thread will block until active or timeout
209 value in seconds has been exceeded (False)
210 :param timeout: The timeout value
211 :param poll_interval: The polling interval in seconds
214 # sleep and wait for stack status change
218 start = time.time() - timeout
220 while timeout > time.time() - start:
221 status = self._status(expected_status_code)
224 'Stack is active with name - ' + self.stack_settings.name)
227 logger.debug('Retry querying stack status in ' + str(
228 poll_interval) + ' seconds')
229 time.sleep(poll_interval)
230 logger.debug('Stack status query timeout in ' + str(
231 timeout - (time.time() - start)))
234 'Timeout checking for stack status for ' + expected_status_code)
237 def _status(self, expected_status_code):
239 Returns True when active else False
240 :param expected_status_code: stack status evaluated with this string
244 status = self.get_status()
247 'Cannot stack status for stack with ID - ' + self.__stack.id)
250 if status == STATUS_CREATE_FAILED:
251 raise StackCreationError('Stack had an error during deployment')
252 logger.debug('Stack status is - ' + status)
253 return status == expected_status_code
257 def __init__(self, **kwargs):
260 :param name: the stack's name (required)
261 :param template: the heat template in dict() format (required if
262 template_path attribute is None)
263 :param template_path: the location of the heat template file (required
264 if template attribute is None)
265 :param env_values: k/v pairs of strings for substitution of template
266 default values (optional)
269 self.name = kwargs.get('name')
270 self.template = kwargs.get('template')
271 self.template_path = kwargs.get('template_path')
272 self.env_values = kwargs.get('env_values')
273 if 'stack_create_timeout' in kwargs:
274 self.stack_create_timeout = kwargs['stack_create_timeout']
276 self.stack_create_timeout = STACK_COMPLETE_TIMEOUT
279 raise StackSettingsError('name is required')
281 if not self.template and not self.template_path:
282 raise StackSettingsError('A Heat template is required')
284 def __eq__(self, other):
285 return (self.name == other.name and
286 self.template == other.template and
287 self.template_path == other.template_path and
288 self.env_values == other.env_values and
289 self.stack_create_timeout == other.stack_create_timeout)
292 class StackSettingsError(Exception):
294 Exception to be thrown when an stack settings are incorrect
298 class StackCreationError(Exception):
300 Exception to be thrown when an stack cannot be created