def convergence_remove_server_from_group(log, transaction_id, server_id, replace, purge, group, state): """ Remove a specific server from the group, optionally decrementing the desired capacity. The server may just be scheduled for deletion, or it may be evicted from the group by removing otter-specific metdata from the server. :param log: A bound logger :param bytes trans_id: The transaction id for this operation. :param bytes server_id: The id of the server to be removed. :param bool replace: Should the server be replaced? :param bool purge: Should the server be deleted from Nova? :param group: The scaling group to remove a server from. :type group: :class:`~otter.models.interface.IScalingGroup` :param state: The current state of the group. :type state: :class:`~otter.models.interface.GroupState` :return: The updated state. :rtype: Effect of :class:`~otter.models.interface.GroupState` :raise: :class:`CannotDeleteServerBelowMinError` if the server cannot be deleted without replacement, and :class:`ServerNotFoundError` if there is no such server to be deleted. """ effects = [_is_server_in_group(group, server_id)] if not replace: effects.append(_can_scale_down(group, server_id)) # the (possibly) two checks can happen in parallel, but we want # ServerNotFoundError to take precedence over # CannotDeleteServerBelowMinError both_checks = yield parallel_all_errors(effects) for is_error, result in both_checks: if is_error: reraise(*result) # Remove the server if purge: eff = set_nova_metadata_item(server_id, *DRAINING_METADATA) else: eff = Effect( EvictServerFromScalingGroup(log=log, transaction_id=transaction_id, scaling_group=group, server_id=server_id)) yield Effect( TenantScope( retry_effect(eff, retry_times(3), exponential_backoff_interval(2)), group.tenant_id)) if not replace: yield do_return(assoc_obj(state, desired=state.desired - 1)) else: yield do_return(state)
def convergence_remove_server_from_group( log, transaction_id, server_id, replace, purge, group, state): """ Remove a specific server from the group, optionally decrementing the desired capacity. The server may just be scheduled for deletion, or it may be evicted from the group by removing otter-specific metdata from the server. :param log: A bound logger :param bytes trans_id: The transaction id for this operation. :param bytes server_id: The id of the server to be removed. :param bool replace: Should the server be replaced? :param bool purge: Should the server be deleted from Nova? :param group: The scaling group to remove a server from. :type group: :class:`~otter.models.interface.IScalingGroup` :param state: The current state of the group. :type state: :class:`~otter.models.interface.GroupState` :return: The updated state. :rtype: Effect of :class:`~otter.models.interface.GroupState` :raise: :class:`CannotDeleteServerBelowMinError` if the server cannot be deleted without replacement, and :class:`ServerNotFoundError` if there is no such server to be deleted. """ effects = [_is_server_in_group(group, server_id)] if not replace: effects.append(_can_scale_down(group, server_id)) # the (possibly) two checks can happen in parallel, but we want # ServerNotFoundError to take precedence over # CannotDeleteServerBelowMinError both_checks = yield parallel_all_errors(effects) for is_error, result in both_checks: if is_error: reraise(*result) # Remove the server if purge: eff = set_nova_metadata_item(server_id, *DRAINING_METADATA) else: eff = Effect( EvictServerFromScalingGroup(log=log, transaction_id=transaction_id, scaling_group=group, server_id=server_id)) yield Effect(TenantScope( retry_effect(eff, retry_times(3), exponential_backoff_interval(2)), group.tenant_id)) if not replace: yield do_return(assoc_obj(state, desired=state.desired - 1)) else: yield do_return(state)
def add_event(event, admin_tenant_id, region, log): """ Add event to cloud feeds """ event, error, timestamp, event_tenant_id, event_id = sanitize_event(event) req = prepare_request(request_format, event, error, timestamp, region, event_tenant_id, event_id) eff = retry_effect( publish_autoscale_event(req, log=log), compose_retries(lambda f: (not f.check(APIError) or f.value.code < 400 or f.value.code >= 500), retry_times(5)), exponential_backoff_interval(2), ) return Effect(TenantScope(tenant_id=admin_tenant_id, effect=eff))
def as_effect(self): """Produce a :obj:`Effect` to delete a server.""" eff = retry_effect( delete_and_verify(self.server_id), can_retry=retry_times(3), next_interval=exponential_backoff_interval(2)) def report_success(result): return StepResult.RETRY, [ ErrorReason.String( 'must re-gather after deletion in order to update the ' 'active cache')] return eff.on(success=report_success)
def test_retry_effect(self): """ :func:`retry_effect` takes an effect and returns an :obj:`Effect` of :obj:`Retry`, with a :obj:`ShouldDelayAndRetry` as the should_retry callable. """ can_retry = lambda f: True next_interval = lambda f: 1 eff = retry_effect(STUB, can_retry, next_interval) self.assertEqual( eff, Effect( Retry(effect=STUB, should_retry=ShouldDelayAndRetry( can_retry=can_retry, next_interval=next_interval))))
def test_retry_effect(self): """ :func:`retry_effect` takes an effect and returns an :obj:`Effect` of :obj:`Retry`, with a :obj:`ShouldDelayAndRetry` as the should_retry callable. """ can_retry = lambda f: True next_interval = lambda f: 1 eff = retry_effect(STUB, can_retry, next_interval) self.assertEqual( eff, Effect(Retry( effect=STUB, should_retry=ShouldDelayAndRetry(can_retry=can_retry, next_interval=next_interval))))
def add_event(event, admin_tenant_id, region, log): """ Add event to cloud feeds """ event, error, timestamp, event_tenant_id, event_id = sanitize_event(event) req = prepare_request(request_format, event, error, timestamp, region, event_tenant_id, event_id) eff = retry_effect( publish_autoscale_event(req, log=log), compose_retries( lambda f: (not f.check(APIError) or f.value.code < 400 or f.value.code >= 500), retry_times(5)), exponential_backoff_interval(2)) return Effect(TenantScope(tenant_id=admin_tenant_id, effect=eff))
def _is_server_in_group(group, server_id): """ Given a group and server ID, determines if the server is a member of the group. If it isn't, it raises a :class:`ServerNotFoundError`. """ try: response, server_info = yield Effect( TenantScope( retry_effect(get_server_details(server_id), retry_times(3), exponential_backoff_interval(2)), group.tenant_id, ) ) except NoSuchServerError: raise ServerNotFoundError(group.tenant_id, group.uuid, server_id) group_id = group_id_from_metadata(get_in(("server", "metadata"), server_info, {})) if group_id != group.uuid: raise ServerNotFoundError(group.tenant_id, group.uuid, server_id)
def _is_server_in_group(group, server_id): """ Given a group and server ID, determines if the server is a member of the group. If it isn't, it raises a :class:`ServerNotFoundError`. """ try: response, server_info = yield Effect( TenantScope( retry_effect(get_server_details(server_id), retry_times(3), exponential_backoff_interval(2)), group.tenant_id)) except NoSuchServerError: raise ServerNotFoundError(group.tenant_id, group.uuid, server_id) group_id = group_id_from_metadata( get_in(('server', 'metadata'), server_info, {})) if group_id != group.uuid: raise ServerNotFoundError(group.tenant_id, group.uuid, server_id)
def _retry(eff): """Retry an effect with a common policy.""" return retry_effect( eff, retry_times(5), exponential_backoff_interval(2))
def _retry(eff): """Retry an effect with a common policy.""" return retry_effect(eff, retry_times(5), exponential_backoff_interval(2))