heat/heat/engine/resources/openstack/heat/delay.py

173 lines
6.0 KiB
Python

#
# 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 random
from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
from oslo_utils import timeutils
class Delay(resource.Resource):
"""A resource that pauses for a configurable delay.
By manipulating the dependency relationships between resources in the
template, a delay can be inserted at an arbitrary point during e.g. stack
creation or deletion. They delay will occur after any resource that it
depends on during CREATE or SUSPEND, and before any resource that it
depends on during DELETE or RESUME. Similarly, it will occur before any
resource that depends on it during CREATE or SUSPEND, and after any
resource thet depends on it during DELETE or RESUME.
If a non-zero maximum jitter is specified, a random amount of jitter -
chosen with uniform probability in the range from 0 to the product of the
maximum jitter value and the jitter multiplier (1s by default) - is added
to the minimum delay time. This can be used, for example, in the scaled
unit of a large scaling group to prevent 'thundering herd' issues.
"""
support_status = support.SupportStatus(version='11.0.0')
_ALLOWED_ACTIONS = (
resource.Resource.CREATE,
resource.Resource.DELETE,
resource.Resource.SUSPEND,
resource.Resource.RESUME,
)
PROPERTIES = (
MIN_WAIT_SECS, MAX_JITTER, JITTER_MULTIPLIER_SECS, DELAY_ACTIONS,
) = (
'min_wait', 'max_jitter', 'jitter_multiplier', 'actions',
)
properties_schema = {
MIN_WAIT_SECS: properties.Schema(
properties.Schema.NUMBER,
_('Minimum time in seconds to wait during the specified actions.'),
update_allowed=True,
default=0,
constraints=[
constraints.Range(min=0)
]
),
MAX_JITTER: properties.Schema(
properties.Schema.NUMBER,
_('Maximum jitter to add to the minimum wait time.'),
update_allowed=True,
default=0,
constraints=[
constraints.Range(min=0),
]
),
JITTER_MULTIPLIER_SECS: properties.Schema(
properties.Schema.NUMBER,
_('Number of seconds to multiply the maximum jitter value by.'),
update_allowed=True,
default=1.0,
constraints=[
constraints.Range(min=0),
]
),
DELAY_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('Actions during which the delay will occur.'),
update_allowed=True,
default=[resource.Resource.CREATE],
constraints=[constraints.AllowedValues(_ALLOWED_ACTIONS)]
),
}
attributes_schema = {}
def _delay_parameters(self):
"""Return a tuple of the min delay and max jitter, in seconds."""
min_wait_secs = self.properties[self.MIN_WAIT_SECS]
max_jitter_secs = (self.properties[self.MAX_JITTER] *
self.properties[self.JITTER_MULTIPLIER_SECS])
return min_wait_secs, max_jitter_secs
def validate(self):
result = super(Delay, self).validate()
if not self.stack.strict_validate:
return result
min_wait_secs, max_jitter_secs = self._delay_parameters()
max_wait = min_wait_secs + max_jitter_secs
if max_wait > self.stack.timeout_secs():
raise exception.StackValidationFailed(_('%(res_type)s maximum '
'delay %(max_wait)ss '
'exceeds stack timeout.') %
{'res_type': self.type,
'max_wait': max_wait})
return result
def _wait_secs(self, action):
"""Return a (randomised) wait time for the specified action."""
if action not in self.properties[self.DELAY_ACTIONS]:
return 0
min_wait_secs, max_jitter_secs = self._delay_parameters()
return min_wait_secs + (max_jitter_secs * random.random())
def _handle_action(self):
"""Return a tuple of the start time in UTC and the time to wait."""
return timeutils.utcnow(), self._wait_secs(self.action)
@staticmethod
def _check_complete(started_at, wait_secs):
if not wait_secs:
return True
elapsed_secs = (timeutils.utcnow() - started_at).total_seconds()
if elapsed_secs >= wait_secs:
return True
remaining = wait_secs - elapsed_secs
if remaining >= 4:
raise resource.PollDelay(int(remaining // 2))
return False
def handle_create(self):
return self._handle_action()
def handle_delete(self):
return self._handle_action()
def handle_suspend(self):
return self._handle_action()
def handle_resume(self):
return self._handle_action()
def check_create_complete(self, cookie):
return self._check_complete(*cookie)
def check_delete_complete(self, cookie):
return self._check_complete(*cookie)
def check_suspend_complete(self, cookie):
return self._check_complete(*cookie)
def check_resume_complete(self, cookie):
return self._check_status_complete(*cookie)
def resource_mapping():
return {
'OS::Heat::Delay': Delay,
}