summaryrefslogtreecommitdiff
path: root/heat/engine/resources/wait_condition.py
blob: 4fd25b899b3e41fd3fc3f7941f92346198a224bc (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
#
#    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 collections

from oslo_log import log as logging
import six

from heat.common import exception
from heat.common.i18n import _
from heat.engine.resources import signal_responder

LOG = logging.getLogger(__name__)


class BaseWaitConditionHandle(signal_responder.SignalResponder):
    """Base WaitConditionHandle resource.

    The main point of this class is to :
    - have no dependencies (so the instance can reference it)
    - create credentials to allow for signalling from the instance.
    - handle signals from the instance, validate and store result
    """
    properties_schema = {}

    WAIT_STATUSES = (
        STATUS_FAILURE,
        STATUS_SUCCESS,
    ) = (
        'FAILURE',
        'SUCCESS',
    )

    def _get_ec2_signed_url(self, signal_type=signal_responder.WAITCONDITION):
        stored = self.data().get('ec2_signed_url')
        if stored is not None:
            return stored
        url = super(BaseWaitConditionHandle,
                    self)._get_ec2_signed_url(signal_type)
        self.data_set('ec2_signed_url', url)
        return url

    def handle_create(self):
        super(BaseWaitConditionHandle, self).handle_create()
        self.resource_id_set(self._get_user_id())

    def _status_ok(self, status):
        return status in self.WAIT_STATUSES

    def _metadata_format_ok(self, metadata):
        if not isinstance(metadata, collections.Mapping):
            return False
        if set(metadata) != set(self.METADATA_KEYS):
            return False
        return self._status_ok(metadata[self.STATUS])

    def normalise_signal_data(self, signal_data, latest_metadata):
        return signal_data

    def handle_signal(self, details=None):
        write_attempts = []

        def merge_signal_metadata(signal_data, latest_rsrc_metadata):
            signal_data = self.normalise_signal_data(signal_data,
                                                     latest_rsrc_metadata)

            if not self._metadata_format_ok(signal_data):
                LOG.info("Metadata failed validation for %s", self.name)
                raise ValueError(_("Metadata format invalid"))

            new_entry = signal_data.copy()
            unique_id = six.text_type(new_entry.pop(self.UNIQUE_ID))

            new_rsrc_metadata = latest_rsrc_metadata.copy()
            if unique_id in new_rsrc_metadata:
                LOG.info("Overwriting Metadata item for id %s!",
                         unique_id)
            new_rsrc_metadata.update({unique_id: new_entry})

            write_attempts.append(signal_data)
            return new_rsrc_metadata

        self.metadata_set(details, merge_metadata=merge_signal_metadata)

        data_written = write_attempts[-1]
        signal_reason = ('status:%s reason:%s' %
                         (data_written[self.STATUS],
                          data_written[self.REASON]))
        return signal_reason

    def get_status(self):
        """Return a list of the Status values for the handle signals."""
        return [v[self.STATUS]
                for v in six.itervalues(self.metadata_get(refresh=True))]

    def get_status_reason(self, status):
        """Return a list of reasons associated with a particular status."""
        return [v[self.REASON]
                for v in six.itervalues(self.metadata_get(refresh=True))
                if v[self.STATUS] == status]


class WaitConditionFailure(exception.Error):
    def __init__(self, wait_condition, handle):
        reasons = handle.get_status_reason(handle.STATUS_FAILURE)
        super(WaitConditionFailure, self).__init__(';'.join(reasons))


class WaitConditionTimeout(exception.Error):
    def __init__(self, wait_condition, handle):
        reasons = handle.get_status_reason(handle.STATUS_SUCCESS)
        vals = {'len': len(reasons),
                'count': wait_condition.properties[wait_condition.COUNT]}
        if reasons:
            vals['reasons'] = ';'.join(reasons)
            message = (_('%(len)d of %(count)d received - %(reasons)s') % vals)
        else:
            message = (_('%(len)d of %(count)d received') % vals)
        super(WaitConditionTimeout, self).__init__(message)