529 lines
17 KiB
Python
Executable File
529 lines
17 KiB
Python
Executable File
#
|
|
# Copyright (c) 2015-2016 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
import json
|
|
|
|
from nfv_common import debug
|
|
|
|
from nfv_common.strategy._strategy_defs import STRATEGY_PHASE
|
|
from nfv_common.strategy._strategy_defs import STRATEGY_STATE
|
|
from nfv_common.strategy._strategy_phase import StrategyPhase
|
|
from nfv_common.strategy._strategy_result import STRATEGY_RESULT
|
|
from nfv_common.strategy._strategy_result import strategy_result_update
|
|
|
|
DLOG = debug.debug_get_logger('nfv_common.strategy')
|
|
|
|
|
|
class Strategy(object):
|
|
"""
|
|
Strategy
|
|
"""
|
|
def __init__(self, uuid, name, state=None, current_phase=None, build_phase=None,
|
|
apply_phase=None, abort_phase=None):
|
|
self._uuid = uuid
|
|
self._name = name
|
|
|
|
if state is None:
|
|
self._state = STRATEGY_STATE.INITIAL
|
|
else:
|
|
self._state = state
|
|
|
|
if current_phase is None:
|
|
self._current_phase = STRATEGY_PHASE.INITIAL
|
|
else:
|
|
self._current_phase = current_phase
|
|
|
|
if build_phase is None:
|
|
build_phase = StrategyPhase(STRATEGY_PHASE.BUILD)
|
|
|
|
if apply_phase is None:
|
|
apply_phase = StrategyPhase(STRATEGY_PHASE.APPLY)
|
|
|
|
if abort_phase is None:
|
|
abort_phase = StrategyPhase(STRATEGY_PHASE.ABORT)
|
|
|
|
build_phase.strategy = self
|
|
apply_phase.strategy = self
|
|
abort_phase.strategy = self
|
|
|
|
self._phase = dict()
|
|
self._phase[STRATEGY_PHASE.BUILD] = build_phase
|
|
self._phase[STRATEGY_PHASE.APPLY] = apply_phase
|
|
self._phase[STRATEGY_PHASE.ABORT] = abort_phase
|
|
|
|
def __del__(self):
|
|
del self._phase[STRATEGY_PHASE.BUILD]
|
|
del self._phase[STRATEGY_PHASE.APPLY]
|
|
del self._phase[STRATEGY_PHASE.ABORT]
|
|
|
|
@property
|
|
def uuid(self):
|
|
"""
|
|
Returns the uuid of the strategy
|
|
"""
|
|
return self._uuid
|
|
|
|
@property
|
|
def name(self):
|
|
"""
|
|
Returns the name of the strategy
|
|
"""
|
|
return self._name
|
|
|
|
@property
|
|
def state(self):
|
|
"""
|
|
Returns the state of the strategy
|
|
"""
|
|
return self._state
|
|
|
|
@property
|
|
def current_phase(self):
|
|
"""
|
|
Returns the current phase being executed for the strategy
|
|
"""
|
|
return self._current_phase
|
|
|
|
@property
|
|
def build_phase(self):
|
|
"""
|
|
Returns the build phase of the strategy
|
|
"""
|
|
return self._phase[STRATEGY_PHASE.BUILD]
|
|
|
|
@property
|
|
def apply_phase(self):
|
|
"""
|
|
Returns the apply phase of the strategy
|
|
"""
|
|
return self._phase[STRATEGY_PHASE.APPLY]
|
|
|
|
@property
|
|
def abort_phase(self):
|
|
"""
|
|
Returns the abort phase of the strategy
|
|
"""
|
|
return self._phase[STRATEGY_PHASE.ABORT]
|
|
|
|
def is_building(self):
|
|
"""
|
|
Returns true if the strategy is building
|
|
"""
|
|
return STRATEGY_STATE.BUILDING == self._state
|
|
|
|
def is_build_failed(self):
|
|
"""
|
|
Returns true if the strategy build failed
|
|
"""
|
|
return (STRATEGY_STATE.BUILD_FAILED == self._state or
|
|
self.build_phase.is_failed())
|
|
|
|
def is_build_timed_out(self):
|
|
"""
|
|
Returns true if the strategy build timed out
|
|
"""
|
|
return (STRATEGY_STATE.BUILD_TIMEOUT == self._state or
|
|
self.build_phase.is_timed_out())
|
|
|
|
def is_ready_to_apply(self):
|
|
"""
|
|
Returns true if the strategy is ready to apply
|
|
"""
|
|
return STRATEGY_STATE.READY_TO_APPLY == self._state
|
|
|
|
def is_applying(self):
|
|
"""
|
|
Returns true if the strategy is applying
|
|
"""
|
|
return STRATEGY_STATE.APPLYING == self._state
|
|
|
|
def is_apply_failed(self):
|
|
"""
|
|
Returns true if the strategy apply failed
|
|
"""
|
|
return (STRATEGY_STATE.APPLY_FAILED == self._state or
|
|
self.apply_phase.is_failed())
|
|
|
|
def is_apply_timed_out(self):
|
|
"""
|
|
Returns true if the strategy apply timed out
|
|
"""
|
|
return (STRATEGY_STATE.APPLY_TIMEOUT == self._state or
|
|
self.apply_phase.is_timed_out())
|
|
|
|
def is_applied(self):
|
|
"""
|
|
Returns true if the strategy is applied
|
|
"""
|
|
return STRATEGY_STATE.APPLIED == self._state
|
|
|
|
def is_aborting(self):
|
|
"""
|
|
Returns true if the strategy is aborting
|
|
"""
|
|
return STRATEGY_STATE.ABORTING == self._state
|
|
|
|
def is_abort_failed(self):
|
|
"""
|
|
Returns true if the strategy abort failed
|
|
"""
|
|
return (STRATEGY_STATE.ABORT_FAILED == self._state or
|
|
self.abort_phase.is_failed())
|
|
|
|
def is_abort_timed_out(self):
|
|
"""
|
|
Returns true if the strategy abort timed out
|
|
"""
|
|
return (STRATEGY_STATE.ABORT_TIMEOUT == self._state or
|
|
self.abort_phase.is_timed_out())
|
|
|
|
def is_aborted(self):
|
|
"""
|
|
Returns true if the strategy is aborted
|
|
"""
|
|
return STRATEGY_STATE.ABORTED == self._state
|
|
|
|
def _build(self):
|
|
"""
|
|
Strategy Build
|
|
"""
|
|
if STRATEGY_PHASE.INITIAL == self._current_phase:
|
|
if STRATEGY_STATE.INITIAL == self._state:
|
|
self._state = STRATEGY_STATE.BUILDING
|
|
self._current_phase = STRATEGY_PHASE.BUILD
|
|
self.build_phase.apply()
|
|
|
|
def _apply(self, stage_id=None):
|
|
"""
|
|
Strategy Apply
|
|
"""
|
|
success = True
|
|
reason = ''
|
|
|
|
if STRATEGY_PHASE.BUILD == self._current_phase:
|
|
if STRATEGY_STATE.READY_TO_APPLY == self._state:
|
|
if stage_id is None:
|
|
self._state = STRATEGY_STATE.APPLYING
|
|
self._current_phase = STRATEGY_PHASE.APPLY
|
|
self.apply_phase.apply()
|
|
|
|
elif 0 == stage_id and stage_id < self.apply_phase.total_stages:
|
|
self._state = STRATEGY_STATE.APPLYING
|
|
self._current_phase = STRATEGY_PHASE.APPLY
|
|
self.apply_phase.apply(stage_id + 1)
|
|
|
|
else:
|
|
success = False
|
|
reason = ("invalid stage id %s for the apply, total-stages "
|
|
"are %s" % (stage_id, self.apply_phase.total_stages))
|
|
|
|
else:
|
|
if stage_id is None:
|
|
success = False
|
|
reason = self.build_phase.result_reason
|
|
else:
|
|
success = False
|
|
reason = ("apply of stage id %s failed: %s "
|
|
% (stage_id, self.build_phase.result_reason))
|
|
|
|
elif STRATEGY_PHASE.APPLY == self._current_phase:
|
|
if self._state in [STRATEGY_STATE.APPLIED, STRATEGY_STATE.APPLY_FAILED,
|
|
STRATEGY_STATE.APPLY_TIMEOUT,
|
|
STRATEGY_STATE.ABORTED, STRATEGY_STATE.ABORT_FAILED,
|
|
STRATEGY_STATE.ABORT_TIMEOUT]:
|
|
success = False
|
|
reason = "apply already completed"
|
|
|
|
# Allow an apply after a single stage apply has completed
|
|
elif stage_id is None and self.apply_phase.current_stage == \
|
|
self.apply_phase.stop_at_stage:
|
|
self.apply_phase.apply()
|
|
|
|
elif stage_id is None or self.apply_phase.is_inprogress():
|
|
success = False
|
|
reason = "apply already inprogress"
|
|
|
|
elif stage_id < self.apply_phase.current_stage:
|
|
success = False
|
|
reason = "apply already complete for stage id %s" % stage_id
|
|
|
|
elif stage_id >= self.apply_phase.total_stages:
|
|
success = False
|
|
reason = ("invalid stage id %s for the apply, total-stages are %s"
|
|
% (stage_id, self.apply_phase.total_stages))
|
|
|
|
elif self.apply_phase.current_stage != stage_id:
|
|
success = False
|
|
reason = ("stage id %s is not the next stage to be applied, "
|
|
"next-stage = %s"
|
|
% (stage_id, self.apply_phase.current_stage))
|
|
|
|
else:
|
|
self.apply_phase.apply(stage_id + 1)
|
|
|
|
else:
|
|
if stage_id is None:
|
|
success = False
|
|
reason = "apply not supported during an abort"
|
|
else:
|
|
success = False
|
|
reason = ("apply of stage id %s not supported during an abort"
|
|
% stage_id)
|
|
|
|
return success, reason
|
|
|
|
def _abort(self, stage_id=None):
|
|
"""
|
|
Strategy Abort
|
|
"""
|
|
if STRATEGY_PHASE.APPLY == self._current_phase:
|
|
if stage_id is not None:
|
|
if not self.apply_phase.is_inprogress() or \
|
|
stage_id != self.apply_phase.current_stage:
|
|
reason = "apply not inprogress for stage id %s" % stage_id
|
|
return False, reason
|
|
|
|
if self._state in [STRATEGY_STATE.APPLYING, STRATEGY_STATE.APPLY_FAILED,
|
|
STRATEGY_STATE.APPLY_TIMEOUT]:
|
|
self._state = STRATEGY_STATE.ABORTING
|
|
abort_phase = self.apply_phase.abort()
|
|
if not abort_phase:
|
|
abort_phase = StrategyPhase(STRATEGY_PHASE.ABORT)
|
|
abort_phase.strategy = self
|
|
self._phase[STRATEGY_PHASE.ABORT] = abort_phase
|
|
|
|
# In the case of a single stage apply, if we are not currently
|
|
# applying anything, we need to go to the aborted state now.
|
|
if self.apply_phase.current_stage == self.apply_phase.stop_at_stage:
|
|
self._state = STRATEGY_STATE.ABORTED
|
|
self.abort_complete(STRATEGY_RESULT.ABORTED, "")
|
|
|
|
elif STRATEGY_STATE.APPLIED != self._state:
|
|
self._state = STRATEGY_STATE.ABORTED
|
|
|
|
else:
|
|
reason = "apply not inprogress"
|
|
return False, reason
|
|
else:
|
|
reason = "apply not inprogress"
|
|
return False, reason
|
|
|
|
return True, ''
|
|
|
|
def _handle_event(self, event, event_data=None):
|
|
"""
|
|
Strategy Handle Event
|
|
"""
|
|
handled = False
|
|
|
|
if STRATEGY_STATE.BUILDING == self._state:
|
|
if STRATEGY_PHASE.BUILD == self._current_phase:
|
|
handled = self.build_phase.handle_event(event, event_data)
|
|
|
|
elif STRATEGY_STATE.APPLYING == self._state:
|
|
if STRATEGY_PHASE.APPLY == self._current_phase:
|
|
handled = self.apply_phase.handle_event(event, event_data)
|
|
|
|
elif STRATEGY_STATE.ABORTING == self._state:
|
|
if STRATEGY_PHASE.APPLY == self._current_phase:
|
|
handled = self.apply_phase.handle_event(event, event_data)
|
|
|
|
elif STRATEGY_PHASE.ABORT == self._current_phase:
|
|
handled = self.abort_phase.handle_event(event, event_data)
|
|
|
|
return handled
|
|
|
|
def phase_save(self):
|
|
"""
|
|
Strategy Phase Save
|
|
"""
|
|
self.save()
|
|
|
|
def phase_extend_timeout(self, phase):
|
|
"""
|
|
Strategy Phase Extend Timeout
|
|
"""
|
|
phase.refresh_timeouts()
|
|
|
|
def phase_complete(self, phase, phase_result, phase_result_reason=None):
|
|
"""
|
|
Strategy Phase Complete
|
|
"""
|
|
self.save()
|
|
|
|
result, result_reason = \
|
|
strategy_result_update(STRATEGY_RESULT.INITIAL, '',
|
|
phase_result, phase_result_reason)
|
|
|
|
if STRATEGY_STATE.BUILDING == self._state:
|
|
if self._phase[STRATEGY_PHASE.BUILD] == phase:
|
|
if phase.is_success() or phase.is_degraded():
|
|
self._state = STRATEGY_STATE.READY_TO_APPLY
|
|
self.build_complete(result, result_reason)
|
|
|
|
elif phase.is_failed():
|
|
self._state = STRATEGY_STATE.BUILD_FAILED
|
|
self.build_complete(result, result_reason)
|
|
|
|
elif phase.is_timed_out():
|
|
self._state = STRATEGY_STATE.BUILD_TIMEOUT
|
|
self.build_complete(result, result_reason)
|
|
|
|
elif STRATEGY_STATE.APPLYING == self._state:
|
|
if self._phase[STRATEGY_PHASE.APPLY] == phase:
|
|
if phase.is_success() or phase.is_degraded():
|
|
self._state = STRATEGY_STATE.APPLIED
|
|
self.apply_complete(result, result_reason)
|
|
|
|
elif phase.is_failed():
|
|
self._state = STRATEGY_STATE.APPLY_FAILED
|
|
self.apply_complete(result, result_reason)
|
|
self._abort()
|
|
self._current_phase = STRATEGY_PHASE.ABORT
|
|
self.abort_phase.apply()
|
|
|
|
elif phase.is_timed_out():
|
|
self._state = STRATEGY_STATE.APPLY_TIMEOUT
|
|
self.apply_complete(result, result_reason)
|
|
self._abort()
|
|
self._current_phase = STRATEGY_PHASE.ABORT
|
|
self.abort_phase.apply()
|
|
|
|
elif STRATEGY_STATE.ABORTING == self._state:
|
|
if self._phase[STRATEGY_PHASE.APPLY] == phase:
|
|
if phase.is_success() or phase.is_degraded():
|
|
self._state = STRATEGY_STATE.APPLIED
|
|
self.apply_complete(result, result_reason)
|
|
|
|
elif phase.is_failed():
|
|
self._state = STRATEGY_STATE.APPLY_FAILED
|
|
self.apply_complete(result, result_reason)
|
|
|
|
elif phase.is_timed_out():
|
|
self._state = STRATEGY_STATE.APPLY_TIMEOUT
|
|
self.apply_complete(result, result_reason)
|
|
|
|
self._current_phase = STRATEGY_PHASE.ABORT
|
|
self.abort_phase.apply()
|
|
|
|
elif self._phase[STRATEGY_PHASE.ABORT] == phase:
|
|
if phase.is_success() or phase.is_degraded():
|
|
self._state = STRATEGY_STATE.ABORTED
|
|
self.abort_complete(result, result_reason)
|
|
|
|
elif phase.is_failed():
|
|
self._state = STRATEGY_STATE.ABORT_FAILED
|
|
self.abort_complete(result, result_reason)
|
|
|
|
elif phase.is_timed_out():
|
|
self._state = STRATEGY_STATE.ABORT_TIMEOUT
|
|
self.abort_complete(result, result_reason)
|
|
|
|
self.save()
|
|
|
|
def refresh_timeouts(self):
|
|
"""
|
|
Strategy Refresh Timeouts
|
|
"""
|
|
self.build_phase.refresh_timeouts()
|
|
self.apply_phase.refresh_timeouts()
|
|
self.abort_phase.refresh_timeouts()
|
|
|
|
def save(self):
|
|
"""
|
|
Strategy Save (can be overridden by child class)
|
|
"""
|
|
pass
|
|
|
|
def build(self):
|
|
"""
|
|
Strategy Build (can be overridden by child class)
|
|
"""
|
|
self._build()
|
|
self.save()
|
|
|
|
def build_complete(self, result, result_reason):
|
|
"""
|
|
Strategy Build Complete (can be overridden by child class)
|
|
"""
|
|
self.save()
|
|
return result, result_reason
|
|
|
|
def apply(self, stage_id):
|
|
"""
|
|
Strategy Apply (can be overridden by child class)
|
|
"""
|
|
success, reason = self._apply(stage_id)
|
|
self.save()
|
|
return success, reason
|
|
|
|
def apply_complete(self, result, result_reason):
|
|
"""
|
|
Strategy Apply Complete (can be overridden by child class)
|
|
"""
|
|
self.save()
|
|
return result, result_reason
|
|
|
|
def abort(self, stage_id):
|
|
"""
|
|
Strategy Abort (can be overridden by child class)
|
|
"""
|
|
success, reason = self._abort(stage_id)
|
|
self.save()
|
|
return success, reason
|
|
|
|
def abort_complete(self, result, result_reason):
|
|
"""
|
|
Strategy Abort Complete (can be overridden by child class)
|
|
"""
|
|
self.save()
|
|
return result, result_reason
|
|
|
|
def handle_event(self, event, event_data=None):
|
|
"""
|
|
Strategy Handle Event (can be overridden by child class)
|
|
"""
|
|
return self._handle_event(event, event_data)
|
|
|
|
def from_dict(self, data, build_phase=None, apply_phase=None, abort_phase=None):
|
|
"""
|
|
Initializes a strategy object using the given dictionary
|
|
"""
|
|
Strategy.__init__(self, data['uuid'], data['name'], data['state'],
|
|
data['current_phase'], build_phase, apply_phase,
|
|
abort_phase)
|
|
return self
|
|
|
|
def as_dict(self):
|
|
"""
|
|
Represent the strategy as a dictionary
|
|
"""
|
|
data = dict()
|
|
data['uuid'] = self.uuid
|
|
data['name'] = self.name
|
|
data['state'] = self.state
|
|
data['current_phase'] = self._current_phase
|
|
if self.build_phase.name == self._current_phase:
|
|
data['current_phase_completion_percentage'] \
|
|
= self.build_phase.completion_percentage
|
|
elif self.apply_phase.name == self._current_phase:
|
|
data['current_phase_completion_percentage'] \
|
|
= self.apply_phase.completion_percentage
|
|
elif self.abort_phase.name == self._current_phase:
|
|
data['current_phase_completion_percentage'] \
|
|
= self.abort_phase.completion_percentage
|
|
else:
|
|
data['current_phase_completion_percentage'] = 0
|
|
data['build_phase'] = self.build_phase.as_dict()
|
|
data['apply_phase'] = self.apply_phase.as_dict()
|
|
data['abort_phase'] = self.abort_phase.as_dict()
|
|
return data
|
|
|
|
def as_json(self):
|
|
"""
|
|
Represent the strategy as json
|
|
"""
|
|
return json.dumps(self.as_dict())
|