
Iec6b3c56d464d26e4f1fc143e6a7804add67a35d I3f2d3a12fcb53759a906fcbae6fae768833d325e I566811521da16055a73c73052ffcd497aaa8e475 I2ee04b6d5aaa26d49243cf7e0b6045026f052625 I329620f3c8aa7e7f1bdd658cbaa8ea20d9aa4ba5 I5ff3d9146b4fbec74d8d65de84d7ab61d869725c Ib38fd52811812170bdd9bf9df90a66f1a2e6c8d9 I64ce3efaec6df2e402ca2acf6a3cf1a6f2bb1909 I66c3659ab0f33772d7a51c8961a37e32c65354c2 I29ce4a6ef165daa0fe60003301a0d807fd1cce42 Ibd2a4f55e2a64d9a992833200a791dbb20c41eca I16133a213ef25a1b374f10fa80cd5a03d1f40753 Ie09f32fcacfe70f436cad71e5749edf94be038ed Iaba6a6bf07ff223e41f705f0f1db5688a5290f5c I64a0474ecfe5ea38393fe681d520a7b6ce00d959 I270b3ce5ef776522a62d9622b36f0d6b50b9cc57 Ic5f6849ea166bb0295f84685b0a2b5c4701f972a I51190cb02255254a888f66404ecdc3dfc5be0386 I0c2180c603cd09e29d4e6c5e592b987e2b447972 Iae1cdbeb7fa3e49c2cb5cac7c92eceffef477e7e I6c643f58aada0a8525711bc452d0c581625f3d26 I9c4f999b1b3006b8ae5f18a030d5b30c7e85e03b I32eaad36edcb889b448c45ba36f4e97f7c87d1e5 I8c91c40a922690b475aac1c0a3b2c0c28274b130 I574fd1dbeea58dbf41f77d295dc03c23d2feaf96 Change-Id: I0ffb3c38c0c1b3aafa8617364e22036c47aaef76
238 lines
8.6 KiB
Python
238 lines
8.6 KiB
Python
# Copyright (c) 2013 Mirantis Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import anyjson
|
|
import eventlet
|
|
import types
|
|
|
|
from muranoconductor.openstack.common import log as logging
|
|
import muranoconductor.helpers
|
|
from command import CommandBase
|
|
import muranoconductor.config
|
|
from heatclient.client import Client
|
|
import heatclient.exc
|
|
from keystoneclient.v2_0 import client as ksclient
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class HeatExecutor(CommandBase):
|
|
def __init__(self, stack, token, tenant_id, reporter):
|
|
self._update_pending_list = []
|
|
self._delete_pending_list = []
|
|
self._stack = stack
|
|
self._reporter = reporter
|
|
|
|
keystone_settings = muranoconductor.config.CONF.keystone
|
|
heat_settings = muranoconductor.config.CONF.heat
|
|
|
|
client = ksclient.Client(
|
|
endpoint=keystone_settings.auth_url,
|
|
cacert=keystone_settings.ca_file or None,
|
|
cert=keystone_settings.cert_file or None,
|
|
key=keystone_settings.key_file or None,
|
|
insecure=keystone_settings.insecure)
|
|
|
|
if not client.authenticate(
|
|
auth_url=keystone_settings.auth_url,
|
|
tenant_id=tenant_id,
|
|
token=token):
|
|
raise heatclient.exc.HTTPUnauthorized()
|
|
|
|
heat_url = client.service_catalog.url_for(
|
|
service_type='orchestration',
|
|
endpoint_type=heat_settings.endpoint_type)
|
|
|
|
self._heat_client = Client(
|
|
'1',
|
|
heat_url,
|
|
token_only=True,
|
|
token=client.auth_token,
|
|
ca_file=heat_settings.ca_file or None,
|
|
cert_file=heat_settings.cert_file or None,
|
|
key_file=heat_settings.key_file or None,
|
|
insecure=heat_settings.insecure)
|
|
|
|
def execute(self, command, callback, **kwargs):
|
|
log.debug('Got command {0} on stack {1}'.format(command, self._stack))
|
|
|
|
if command == 'CreateOrUpdate':
|
|
return self._execute_create_update(
|
|
kwargs['template'],
|
|
muranoconductor.helpers.str2unicode(
|
|
kwargs.get('mappings') or {}),
|
|
muranoconductor.helpers.str2unicode(
|
|
kwargs.get('arguments') or {}),
|
|
callback)
|
|
elif command == 'Delete':
|
|
return self._execute_delete(callback)
|
|
|
|
def _execute_create_update(self, template, mappings, arguments, callback):
|
|
with open('data/templates/cf/%s.template' % template) as template_file:
|
|
template_data = template_file.read()
|
|
|
|
template_data = muranoconductor.helpers.transform_json(
|
|
anyjson.loads(template_data), mappings)
|
|
|
|
self._update_pending_list.append({
|
|
'template': template_data,
|
|
'arguments': arguments,
|
|
'callback': callback
|
|
})
|
|
|
|
def _execute_delete(self, callback):
|
|
self._delete_pending_list.append({
|
|
'callback': callback
|
|
})
|
|
|
|
def has_pending_commands(self):
|
|
return len(self._update_pending_list) + len(
|
|
self._delete_pending_list) > 0
|
|
|
|
def execute_pending(self):
|
|
# wait for the stack not to be IN_PROGRESS
|
|
self._wait_state(lambda status: True)
|
|
r1 = self._execute_pending_updates()
|
|
r2 = self._execute_pending_deletes()
|
|
return r1 or r2
|
|
|
|
def _execute_pending_updates(self):
|
|
if not len(self._update_pending_list):
|
|
return False
|
|
|
|
try:
|
|
template, arguments = self._get_current_template()
|
|
stack_exists = (template != {})
|
|
# do not need to merge with current stack cause we rebuilding it
|
|
# from scratch on every deployment
|
|
template, arguments = ({}, {})
|
|
|
|
for t in self._update_pending_list:
|
|
template = muranoconductor.helpers.merge_dicts(template,
|
|
t['template'])
|
|
arguments = muranoconductor.helpers.merge_dicts(arguments,
|
|
t['arguments'],
|
|
max_levels=1)
|
|
log.info(
|
|
'Executing heat template {0} with arguments {1} on stack {2}'
|
|
.format(anyjson.dumps(template), arguments, self._stack))
|
|
|
|
if stack_exists:
|
|
self._heat_client.stacks.update(
|
|
stack_id=self._stack,
|
|
parameters=arguments,
|
|
template=template)
|
|
log.debug(
|
|
'Waiting for the stack {0} to be update'.format(
|
|
self._stack))
|
|
outs = self._wait_state(
|
|
lambda status: status == 'UPDATE_COMPLETE')
|
|
log.info('Stack {0} updated'.format(self._stack))
|
|
else:
|
|
self._heat_client.stacks.create(
|
|
stack_name=self._stack,
|
|
parameters=arguments,
|
|
template=template,
|
|
disable_rollback=False)
|
|
|
|
log.debug('Waiting for the stack {0} to be create'.format(
|
|
self._stack))
|
|
outs = self._wait_state(
|
|
lambda status: status == 'CREATE_COMPLETE')
|
|
log.info('Stack {0} created'.format(self._stack))
|
|
|
|
pending_list = self._update_pending_list
|
|
self._update_pending_list = []
|
|
|
|
for item in pending_list:
|
|
item['callback'](outs)
|
|
return True
|
|
except Exception as ex:
|
|
pending_list = self._update_pending_list
|
|
self._update_pending_list = []
|
|
for item in pending_list:
|
|
item['callback'](None, ex)
|
|
return True
|
|
|
|
def _execute_pending_deletes(self):
|
|
if not len(self._delete_pending_list):
|
|
return False
|
|
|
|
log.debug('Deleting stack {0}'.format(self._stack))
|
|
try:
|
|
self._heat_client.stacks.delete(
|
|
stack_id=self._stack)
|
|
log.debug(
|
|
'Waiting for the stack {0} to be deleted'.format(self._stack))
|
|
self._wait_state(
|
|
lambda status: status in ('DELETE_COMPLETE', 'NOT_FOUND'))
|
|
log.info('Stack {0} deleted'.format(self._stack))
|
|
except Exception as ex:
|
|
log.exception(ex)
|
|
|
|
pending_list = self._delete_pending_list
|
|
self._delete_pending_list = []
|
|
|
|
for item in pending_list:
|
|
item['callback'](True)
|
|
return True
|
|
|
|
def _get_current_template(self):
|
|
try:
|
|
stack_info = self._heat_client.stacks.get(stack_id=self._stack)
|
|
template = self._heat_client.stacks.template(
|
|
stack_id='{0}/{1}'.format(
|
|
stack_info.stack_name,
|
|
stack_info.id))
|
|
return template, stack_info.parameters
|
|
except heatclient.exc.HTTPNotFound:
|
|
return {}, {}
|
|
|
|
def _wait_state(self, status_func):
|
|
tries = 4
|
|
delay = 1
|
|
while tries > 0:
|
|
while True:
|
|
try:
|
|
stack_info = self._heat_client.stacks.get(
|
|
stack_id=self._stack)
|
|
status = stack_info.stack_status
|
|
tries = 4
|
|
delay = 1
|
|
except heatclient.exc.HTTPNotFound:
|
|
stack_info = None
|
|
status = 'NOT_FOUND'
|
|
except Exception:
|
|
tries -= 1
|
|
delay *= 2
|
|
if not tries:
|
|
raise
|
|
eventlet.sleep(delay)
|
|
break
|
|
|
|
if 'IN_PROGRESS' in status:
|
|
eventlet.sleep(2)
|
|
continue
|
|
if not status_func(status):
|
|
raise EnvironmentError(
|
|
"Unexpected stack state {0}".format(status))
|
|
|
|
try:
|
|
return dict([(t['output_key'], t['output_value'])
|
|
for t in stack_info.outputs])
|
|
except Exception:
|
|
return {}
|
|
return {}
|