blazar/blazar/status.py

334 lines
11 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.
from oslo_log import log as logging
from blazar.db import api as db_api
from blazar import exceptions
LOG = logging.getLogger(__name__)
class BaseStatus(object):
"""Base class of status."""
# All statuses
ALL = ()
# Valid status transitions
NEXT_STATUSES = {}
@classmethod
def is_valid_transition(cls, current_status, next_status, **kwargs):
"""Check validity of a status transition.
:param current_status: Current status
:param next_status: Next status
:return: True if the transition is valid
"""
if next_status not in cls.NEXT_STATUSES[current_status]:
LOG.warn('Invalid transition from %s to %s.',
current_status, next_status)
return False
return True
class EventStatus(BaseStatus):
"""Event status class."""
# Statuses of an event
UNDONE = 'UNDONE'
IN_PROGRESS = 'IN_PROGRESS'
DONE = 'DONE'
ERROR = 'ERROR'
ALL = (UNDONE, IN_PROGRESS, DONE, ERROR)
# Valid status transitions
NEXT_STATUSES = {
UNDONE: (IN_PROGRESS,),
IN_PROGRESS: (DONE, ERROR),
DONE: (),
ERROR: ()
}
class ReservationStatus(BaseStatus):
"""Reservation status class."""
# Statuses of a reservation
PENDING = 'pending'
ACTIVE = 'active'
DELETED = 'deleted'
ERROR = 'error'
ALL = (PENDING, ACTIVE, DELETED, ERROR)
# Valid status transitions
NEXT_STATUSES = {
PENDING: (ACTIVE, DELETED, ERROR),
ACTIVE: (DELETED, ERROR),
DELETED: (),
ERROR: (DELETED,)
}
class LeaseStatus(BaseStatus):
"""Lease status class."""
# Stable statuses of a lease
PENDING = 'PENDING'
ACTIVE = 'ACTIVE'
TERMINATED = 'TERMINATED'
ERROR = 'ERROR'
STABLE = (PENDING, ACTIVE, TERMINATED, ERROR)
# Transitional statuses of a lease
CREATING = 'CREATING'
STARTING = 'STARTING'
UPDATING = 'UPDATING'
TERMINATING = 'TERMINATING'
DELETING = 'DELETING'
TRANSITIONAL = (CREATING, STARTING, UPDATING, TERMINATING, DELETING)
# All statuses
ALL = STABLE + TRANSITIONAL
# Valid status transitions
NEXT_STATUSES = {
PENDING: (STARTING, UPDATING, DELETING),
ACTIVE: (TERMINATING, UPDATING, DELETING),
TERMINATED: (UPDATING, DELETING),
ERROR: (TERMINATING, UPDATING, DELETING),
CREATING: (PENDING, DELETING),
STARTING: (ACTIVE, ERROR, DELETING),
UPDATING: STABLE + (DELETING,),
TERMINATING: (TERMINATED, ERROR, DELETING),
DELETING: (ERROR,)
}
@classmethod
def is_valid_transition(cls, current, next, **kwargs):
"""Check validity of a status transition.
:param current: Current status
:param next: Next status
:return: True if the transition is valid
"""
if super(LeaseStatus, cls).is_valid_transition(current,
next, **kwargs):
if cls.is_valid_combination(kwargs['lease_id'], next):
return True
else:
LOG.warn('Invalid combination of statuses.')
return False
@classmethod
def is_valid_combination(cls, lease_id, status):
"""Validator for the combination of statuses.
Check if the combination of statuses of lease, reservations and events
is valid
:param lease_id: Lease ID
:param status: Lease status
:return: True if the combination is valid
"""
# Validate reservation statuses
reservations = db_api.reservation_get_all_by_lease_id(lease_id)
if any([r['status'] not in COMBINATIONS[status]['reservation']
for r in reservations]):
return False
# Validate event statuses
for event_type in ('start_lease', 'end_lease'):
event = db_api.event_get_first_sorted_by_filters(
'lease_id', 'asc',
{'lease_id': lease_id, 'event_type': event_type}
)
if event['status'] not in COMBINATIONS[status][event_type]:
return False
return True
@classmethod
def lease_status(cls, transition, result_in):
"""Decorator for managing a lease status.
This checks and updates a lease status before and after executing a
decorated function.
:param transition: A status which is set while executing the
decorated function.
:param result_in: A tuple of statuses to which a lease transits after
executing the decorated function.
"""
def decorator(func):
def wrapper(*args, **kwargs):
# Update a lease status
lease_id = kwargs['lease_id']
l = db_api.lease_get(lease_id)
if cls.is_valid_transition(l['status'],
transition,
lease_id=lease_id):
db_api.lease_update(lease_id,
{'status': transition})
LOG.debug('Status of lease %s changed from %s to %s.',
lease_id, l['status'], transition)
else:
LOG.warn('Aborting %s. '
'Invalid lease status transition from %s to %s.',
func.__name__, l['status'],
transition)
raise exceptions.InvalidStatus
# Executing the wrapped function
try:
result = func(*args, **kwargs)
except Exception as e:
LOG.exception('Lease %s went into ERROR status. %s',
lease_id, str(e))
db_api.lease_update(lease_id,
{'status': cls.ERROR})
raise e
# Update a lease status if it exists
if db_api.lease_get(lease_id):
next_status = cls.derive_stable_status(lease_id)
if (next_status in result_in
and cls.is_valid_transition(transition,
next_status,
lease_id=lease_id)):
db_api.lease_update(lease_id,
{'status': next_status})
LOG.debug('Status of lease %s changed from %s to %s.',
lease_id, transition, next_status)
else:
LOG.error('Lease %s went into ERROR status.',
lease_id)
db_api.lease_update(lease_id, {'status': cls.ERROR})
raise exceptions.InvalidStatus
return result
return wrapper
return decorator
@classmethod
def derive_stable_status(cls, lease_id):
"""Derive stable lease status.
This derives a lease status from statuses of reservations and events.
:param lease_id: Lease ID
:return: Derived lease status
"""
# Possible lease statuses. Key is a tuple of (lease_start event
# status, lease_end event status)
possible_statuses = {
(EventStatus.UNDONE, EventStatus.UNDONE): cls.PENDING,
(EventStatus.DONE, EventStatus.UNDONE): cls.ACTIVE,
(EventStatus.DONE, EventStatus.DONE): cls.TERMINATED
}
# Derive a lease status from event statuses
event_statuses = {}
for event_type in ('start_lease', 'end_lease'):
event = db_api.event_get_first_sorted_by_filters(
'lease_id', 'asc',
{'lease_id': lease_id, 'event_type': event_type}
)
event_statuses[event_type] = event['status']
try:
status = possible_statuses[(event_statuses['start_lease'],
event_statuses['end_lease'])]
except KeyError:
status = cls.ERROR
# Check the combination of statuses.
if cls.is_valid_combination(lease_id, status):
return status
else:
return cls.ERROR
COMBINATIONS = {
LeaseStatus.CREATING: {
'reservation': (ReservationStatus.PENDING,),
'start_lease': (EventStatus.UNDONE,),
'end_lease': (EventStatus.UNDONE,)
},
LeaseStatus.PENDING: {
'reservation': (ReservationStatus.PENDING,),
'start_lease': (EventStatus.UNDONE,),
'end_lease': (EventStatus.UNDONE,)
},
LeaseStatus.STARTING: {
'reservation': (ReservationStatus.PENDING,
ReservationStatus.ACTIVE,
ReservationStatus.ERROR),
'start_lease': (EventStatus.IN_PROGRESS,),
'end_lease': (EventStatus.UNDONE,)
},
LeaseStatus.ACTIVE: {
'reservation': (ReservationStatus.ACTIVE,),
'start_lease': (EventStatus.DONE,),
'end_lease': (EventStatus.UNDONE,)
},
LeaseStatus.TERMINATING: {
'reservation': (ReservationStatus.ACTIVE,
ReservationStatus.DELETED,
ReservationStatus.ERROR),
'start_lease': (EventStatus.DONE,
EventStatus.ERROR),
'end_lease': (EventStatus.IN_PROGRESS,)
},
LeaseStatus.TERMINATED: {
'reservation': (ReservationStatus.DELETED,),
'start_lease': (EventStatus.DONE,),
'end_lease': (EventStatus.DONE,)
},
LeaseStatus.DELETING: {
'reservation': ReservationStatus.ALL,
'start_lease': EventStatus.ALL,
'end_lease': EventStatus.ALL
},
LeaseStatus.UPDATING: {
'reservation': ReservationStatus.ALL,
'start_lease': (EventStatus.UNDONE,
EventStatus.DONE,
EventStatus.ERROR),
'end_lease': (EventStatus.UNDONE,
EventStatus.DONE,
EventStatus.ERROR)
},
LeaseStatus.ERROR: {
'reservation': ReservationStatus.ERROR,
'start_lease': (EventStatus.DONE,
EventStatus.ERROR),
'end_lease': (EventStatus.UNDONE,
EventStatus.ERROR)
}
}
event = EventStatus
reservation = ReservationStatus
lease = LeaseStatus