aboutsummaryrefslogtreecommitdiffstats
path: root/tools/os_deploy_tgen/osclients/heat.py
blob: 8681731b721503757b120c642681dff66770812c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Copyright 2020 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.

"""
Heat Client
"""

#import sys
import time

from heatclient import exc
from oslo_log import log as logging
from timeout_decorator import timeout

LOG = logging.getLogger(__name__)


def create_stack(heat_client, stack_name, template, parameters,
                 environment=None):
    """
    Create Stack
    """
    stack_params = {
        'stack_name': stack_name,
        'template': template,
        'parameters': parameters,
        'environment': environment,
    }

    stack = heat_client.stacks.create(**stack_params)['stack']
    LOG.info('New stack: %s', stack)

    wait_stack_completion(heat_client, stack['id'])

    return stack['id']


def get_stack_status(heat_client, stack_id):
    """
    Get Stack Status
    """
    # stack.get operation may take long time and run out of time. The reason
    # is that it resolves all outputs which is done serially. On the other hand
    # stack status can be retrieved from the list operation. Internally listing
    # supports paging and every request should not take too long.
    for stack in heat_client.stacks.list():
        if stack.id == stack_id:
            return stack.status, stack.stack_status_reason
        else:
            raise exc.HTTPNotFound(message='Stack %s is not found' % stack_id)
    return None

def get_id_with_name(heat_client, stack_name):
    """
    Get Stack ID by name
    """
    # This method isn't really necessary since the Heat client accepts
    # stack_id and stack_name interchangeably. This is provided more as a
    # safety net to use ids which are guaranteed to be unique and provides
    # the benefit of keeping the Shaker code consistent and more easily
    # traceable.
    stack = heat_client.stacks.get(stack_name)
    return stack.id


def wait_stack_completion(heat_client, stack_id):
    """
    Wait for Stack completion
    """
    reason = None
    status = None

    while True:
        status, reason = get_stack_status(heat_client, stack_id)
        LOG.debug('Stack status: %s', status)
        if status not in ['IN_PROGRESS', '']:
            break

        time.sleep(5)

    if status != 'COMPLETE':
        resources = heat_client.resources.list(stack_id)
        for res in resources:
            if (res.resource_status != 'CREATE_COMPLETE' and
                    res.resource_status_reason):
                LOG.error('Heat stack resource %(res)s of type %(type)s '
                          'failed with %(reason)s',
                          dict(res=res.logical_resource_id,
                               type=res.resource_type,
                               reason=res.resource_status_reason))

        raise exc.StackFailure(stack_id, status, reason)


# set the timeout for this method so we don't get stuck polling indefinitely
# waiting for a delete
@timeout(600)
def wait_stack_deletion(heat_client, stack_id):
    """
    Wait for stack deletion
    """
    try:
        heat_client.stacks.delete(stack_id)
        while True:
            status, reason = get_stack_status(heat_client, stack_id)
            LOG.debug('Stack status: %s Stack reason: %s', status, reason)
            if status == 'FAILED':
                raise exc.StackFailure('Failed to delete stack %s' % stack_id)

            time.sleep(5)

    except TimeoutError:
        LOG.error('Timed out waiting for deletion of stack %s' % stack_id)

    except exc.HTTPNotFound:
        # once the stack is gone we can assume it was successfully deleted
        # clear the exception so it doesn't confuse the logs
        #if sys.version_info < (3, 0):
        #    sys.exc_clear()
        LOG.info('Stack %s was successfully deleted', stack_id)


def get_stack_outputs(heat_client, stack_id):
    """
    Get Stack Output
    """
    # try to use optimized way to retrieve outputs, fallback otherwise
    if hasattr(heat_client.stacks, 'output_list'):
        try:
            output_list = heat_client.stacks.output_list(stack_id)['outputs']

            result = {}
            for output in output_list:
                output_key = output['output_key']
                value = heat_client.stacks.output_show(stack_id, output_key)
                result[output_key] = value['output']['output_value']

            return result
        except BaseException as err:
            LOG.info('Cannot get output list, fallback to old way: %s', err)

    outputs_list = heat_client.stacks.get(stack_id).to_dict()['outputs']
    return dict((item['output_key'], item['output_value'])
                for item in outputs_list)