nfv/nfv/nfv-vim/nfv_vim/objects/_host.py

880 lines
28 KiB
Python
Executable File

#
# Copyright (c) 2015-2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import os
import six
from nfv_common import debug
from nfv_common import state_machine
from nfv_common import timers
from nfv_common.helpers import Constant
from nfv_common.helpers import Singleton
from nfv_vim.objects._object import ObjectData
from nfv_vim import alarm
from nfv_vim import event_log
from nfv_vim import host_fsm
from nfv_vim import nfvi
DLOG = debug.debug_get_logger('nfv_vim.objects.host')
@six.add_metaclass(Singleton)
class HostPersonality(object):
"""
Host Personality Constants
"""
UNKNOWN = Constant('unknown')
CONTROLLER = Constant('controller')
STORAGE = Constant('storage')
SWIFT = Constant('swift')
WORKER = Constant('worker')
@six.add_metaclass(Singleton)
class HostNames(object):
"""
Host Name Constants
"""
CONTROLLER_0 = Constant('controller-0')
CONTROLLER_1 = Constant('controller-1')
STORAGE_0 = Constant('storage-0')
@six.add_metaclass(Singleton)
class HostServicesState(object):
"""
Host-Services State Constants
"""
ENABLED = Constant('enabled')
DISABLED = Constant('disabled')
FAILED = Constant('failed')
@six.add_metaclass(Singleton)
class HostServices(object):
"""
Host-Services Constants
"""
GUEST = Constant('guest')
NETWORK = Constant('network')
COMPUTE = Constant('compute')
CONTAINER = Constant('container')
# Host-Services Constant Instantiation
HOST_SERVICE_STATE = HostServicesState()
HOST_PERSONALITY = HostPersonality()
HOST_NAME = HostNames()
HOST_SERVICES = HostServices()
class Host(ObjectData):
"""
Host Object
"""
_ACTION_NONE = Constant('')
_ACTION_LOCKING = Constant('locking')
_ACTION_LOCKING_FORCE = Constant('locking (force)')
_ACTION_UNLOCKING = Constant('unlocking')
def __init__(self, nfvi_host, initial_state=None, action=None,
elapsed_time_in_state=0, upgrade_inprogress=False,
recover_instances=True, host_services_locked=False):
super(Host, self).__init__('1.0.0')
if initial_state is None:
initial_state = host_fsm.HOST_STATE.INITIAL
if action is None:
action = self._ACTION_NONE
self._elapsed_time_in_state = int(elapsed_time_in_state)
self._task = state_machine.StateTask('EmptyTask', list())
self._action = action
self._reason = ''
self._upgrade_inprogress = upgrade_inprogress
self._recover_instances = recover_instances
self._host_services_locked = host_services_locked
self._nfvi_host = nfvi_host
self._fsm = host_fsm.HostStateMachine(self, initial_state)
self._fsm.register_state_change_callback(self._state_change_callback)
self._last_state_timestamp = timers.get_monotonic_timestamp_in_ms()
self._fail_notification_required = False
self._fsm_start_time = None
self._host_service_state = dict()
if self.host_service_configured(HOST_SERVICES.COMPUTE):
self._host_service_state[HOST_SERVICES.COMPUTE] = \
HOST_SERVICE_STATE.ENABLED if self.is_enabled() else \
HOST_SERVICE_STATE.DISABLED
if self.host_service_configured(HOST_SERVICES.NETWORK):
self._host_service_state[HOST_SERVICES.NETWORK] = \
HOST_SERVICE_STATE.ENABLED if self.is_enabled() else \
HOST_SERVICE_STATE.DISABLED
if self.host_service_configured(HOST_SERVICES.GUEST):
self._host_service_state[HOST_SERVICES.GUEST] = \
HOST_SERVICE_STATE.ENABLED if self.is_enabled() else \
HOST_SERVICE_STATE.DISABLED
if self.host_service_configured(HOST_SERVICES.CONTAINER):
self._host_service_state[HOST_SERVICES.CONTAINER] = \
HOST_SERVICE_STATE.ENABLED if self.is_enabled() else \
HOST_SERVICE_STATE.DISABLED
self._alarms = list()
self._events = list()
@property
def uuid(self):
"""
Returns the uuid of the host
"""
return self._nfvi_host.uuid
@property
def name(self):
"""
Returns the name of the host
"""
return self._nfvi_host.name
@property
def personality(self):
"""
Returns the personality of the host
"""
return self._nfvi_host.personality
@property
def state(self):
"""
Returns the current state of the host
"""
return self._fsm.current_state.name
@property
def kubernetes_configured(self):
"""
Returns whether kubernetes is configured. This will disappear once
we cut over to kubernetes.
"""
if not os.path.isfile('/etc/kubernetes/admin.conf'):
return False
return True
def host_service_configured(self, service):
"""
Returns whether a host service is configured or not
"""
kubernetes_config = True
if not os.path.isfile('/etc/kubernetes/admin.conf'):
kubernetes_config = False
configured = True
if kubernetes_config:
if service == HOST_SERVICES.COMPUTE:
configured = (not nfvi.nfvi_compute_plugin_disabled() and
self._nfvi_host.openstack_compute)
elif service == HOST_SERVICES.NETWORK:
configured = (not nfvi.nfvi_network_plugin_disabled() and
self._nfvi_host.openstack_compute)
elif service == HOST_SERVICES.GUEST:
configured = (not nfvi.nfvi_guest_plugin_disabled() and
self._nfvi_host.openstack_compute)
elif service != HOST_SERVICES.CONTAINER:
DLOG.error("unknown service %s" % service)
configured = False
else:
if service == HOST_SERVICES.CONTAINER:
configured = False
DLOG.verbose("Host configure check for service %s, result %s" %
(service, configured))
return configured
def host_service_state(self, service):
"""
Returns the state for a host service
"""
return self._host_service_state[service]
def host_service_state_aggregate(self):
"""
Returns the overall state of the host services
"""
all_enabled = True
at_least_one_failed = False
for service, service_state in self._host_service_state.items():
# Ignore state of kubernetes, plugin as
# there is no query function for that sevice.
if service == HOST_SERVICES.CONTAINER:
continue
all_enabled = all_enabled and \
(service_state == HOST_SERVICE_STATE.ENABLED)
at_least_one_failed = at_least_one_failed or \
(service_state == HOST_SERVICE_STATE.FAILED)
DLOG.verbose("service %s service_state: %s, all_enabled: %s" %
(service, service_state, all_enabled))
if all_enabled:
return HOST_SERVICE_STATE.ENABLED
elif at_least_one_failed:
return HOST_SERVICE_STATE.FAILED
else:
return HOST_SERVICE_STATE.DISABLED
@property
def host_services_locked(self):
"""
Returns the whether the host services have been locked
"""
return self._host_services_locked
@host_services_locked.setter
def host_services_locked(self, value):
"""
Allows setting the host services locked
"""
self._host_services_locked = value
self._persist()
@property
def action(self):
"""
Returns the current action the host is performing
"""
return self._action
@property
def reason(self):
"""
Returns the current reason for the host
"""
return self._reason
@property
def uptime(self):
"""
Returns the approximate uptime of the host
"""
return self._nfvi_host.uptime
@property
def elapsed_time_in_state(self):
"""
Returns the elapsed time this host has been in the current state
"""
elapsed_time_in_state = self._elapsed_time_in_state
if 0 != self._last_state_timestamp:
now_ms = timers.get_monotonic_timestamp_in_ms()
secs_expired = (now_ms - self._last_state_timestamp) / 1000
elapsed_time_in_state += int(secs_expired)
return elapsed_time_in_state
@property
def upgrade_inprogress(self):
"""
Returns true if this host is being upgraded. Note that this is abused
and really just means that we are in the early stages of an upgrade.
It has nothing to do with a specific host.
"""
return self._upgrade_inprogress
@property
def software_load(self):
"""
Returns software_load running on this host
"""
return self._nfvi_host.software_load
@property
def target_load(self):
"""
Returns target_load for this host
"""
return self._nfvi_host.target_load
@property
def openstack_compute(self):
"""
Returns openstack_compute for this host
"""
return self._nfvi_host.openstack_compute
@property
def openstack_control(self):
"""
Returns openstack_control for this host
"""
return self._nfvi_host.openstack_control
@property
def remote_storage(self):
"""
Returns remote_storage for this host
"""
return self._nfvi_host.remote_storage
@property
def recover_instances(self):
"""
Returns true if the instances on this host are allowed to be recovered
"""
return self._recover_instances
@property
def nfvi_host(self):
"""
Returns the nfvi host data
"""
return self._nfvi_host
@property
def fsm(self):
"""
Returns access to the fsm
"""
return self._fsm
@property
def task(self):
"""
Returns access to the current task
"""
return self._task
@task.setter
def task(self, task):
"""
Allows setting the current task
"""
if self._task is not None:
del self._task
self._task = task
@property
def fail_notification_required(self):
"""
Returns true if notification that the host has failed is required
"""
return self._fail_notification_required
@fail_notification_required.setter
def fail_notification_required(self, value):
"""
Allows setting the host has failed notification required
"""
self._fail_notification_required = value
@property
def fsm_start_time(self):
"""
Returns access to the current action fsm
"""
return self._fsm_start_time
@fsm_start_time.setter
def fsm_start_time(self, fsm_start_time):
"""
Allows setting the current fsm_start_time
"""
if self._fsm_start_time is not None:
del self._fsm_start_time
self._fsm_start_time = fsm_start_time
def has_reason(self):
"""
Returns True if host has reason
"""
return self._reason != '' and self._reason is not None
def update_failure_reason(self, reason):
"""
Update reason for this host
"""
if self._reason is None:
self._reason = ''
self._reason += reason
self._persist()
def clear_reason(self):
"""
Update reason for this host
"""
self._reason = ''
self._persist()
def is_locked(self):
"""
Returns true if the host is locked
"""
return (nfvi.objects.v1.HOST_ADMIN_STATE.LOCKED ==
self._nfvi_host.admin_state)
def is_unlocked(self):
"""
Returns true if the host is unlocked
"""
return (nfvi.objects.v1.HOST_ADMIN_STATE.UNLOCKED ==
self._nfvi_host.admin_state)
def is_enabled(self):
"""
Returns true if the host is enabled
"""
return self._fsm.current_state.name == host_fsm.HOST_STATE.ENABLED
def is_disabled(self):
"""
Returns true if the host is disabled
"""
return self._fsm.current_state.name == host_fsm.HOST_STATE.DISABLED
def is_locking(self):
"""
Returns true if the host is locking or not
"""
return ((nfvi.objects.v1.HOST_ACTION.LOCK ==
self._nfvi_host.action) or
(nfvi.objects.v1.HOST_ACTION.LOCK_FORCE ==
self._nfvi_host.action) or
(self._action == self._ACTION_LOCKING) or
(self._action == self._ACTION_LOCKING_FORCE))
def is_force_lock(self):
"""
Returns true if the host is force locking or not
"""
return (self._action == self._ACTION_LOCKING_FORCE or
(nfvi.objects.v1.HOST_ACTION.LOCK_FORCE ==
self._nfvi_host.action))
def is_unlocking(self):
"""
Returns if the host is unlocking or not
"""
return self._action == self._ACTION_UNLOCKING
def is_available(self):
"""
Returns true if the host is available or not
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.AVAILABLE \
== self._nfvi_host.avail_status:
return True
return False
def is_online(self):
"""
Returns true if the host is online or not
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.ONLINE \
== self._nfvi_host.avail_status:
return True
return False
def is_offline(self):
"""
Returns true if the host is offline or not
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.OFFLINE \
== self._nfvi_host.avail_status:
return True
return False
def is_power_off(self):
"""
Returns true if the host is powered off
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.POWER_OFF \
== self._nfvi_host.avail_status:
return True
return False
def is_failed(self):
"""
Returns true if the host is failed or not
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.FAILED \
== self._nfvi_host.avail_status:
return True
if nfvi.objects.v1.HOST_AVAIL_STATUS.FAILED_COMPONENT \
== self._nfvi_host.avail_status:
return True
return False
def is_component_failure(self):
"""
Returns true if the host is failed because of a component or not
"""
if nfvi.objects.v1.HOST_AVAIL_STATUS.FAILED_COMPONENT \
== self._nfvi_host.avail_status:
return True
return False
def is_deleted(self):
"""
Returns true if this host has been deleted
"""
return self._fsm.current_state.name == host_fsm.HOST_STATE.DELETED
def lock(self, force=False):
"""
Lock this host
"""
if force:
self._action = self._ACTION_LOCKING_FORCE
else:
self._action = self._ACTION_LOCKING
self._fsm.handle_event(host_fsm.HOST_EVENT.LOCK)
self._persist()
def cancel_lock(self):
"""
Cancel the lock on this host
"""
if self.is_locking():
self._action = self._ACTION_NONE
self._persist()
def unlock(self):
"""
Unlock this host
"""
self._action = self._ACTION_UNLOCKING
self._fsm.handle_event(host_fsm.HOST_EVENT.UNLOCK)
self._persist()
def notify_instance_moved(self):
"""
Notify that an instance has moved
"""
self._fsm.handle_event(host_fsm.HOST_EVENT.INSTANCE_MOVED)
def notify_instances_moved(self, operation):
"""
Notify that the instances have moved
"""
event_data = dict()
event_data['host-operation'] = operation
self._fsm.handle_event(host_fsm.HOST_EVENT.INSTANCES_MOVED, event_data)
def notify_instance_stopped(self):
"""
Notify that an instance has stopped
"""
self._fsm.handle_event(host_fsm.HOST_EVENT.INSTANCE_STOPPED)
def notify_instances_stopped(self, operation):
"""
Notify that the instances have stopped
"""
event_data = dict()
event_data['host-operation'] = operation
self._fsm.handle_event(host_fsm.HOST_EVENT.INSTANCES_STOPPED, event_data)
def nfvi_host_is_enabled(self):
"""
Returns true if the nfvi host is enabled
"""
if not self.is_locking():
if nfvi.objects.v1.HOST_OPER_STATE.ENABLED \
== self._nfvi_host.oper_state:
return True
return False
def _nfvi_host_handle_state_change(self):
"""
NFVI Host Handle State Change
"""
if self.is_unlocking():
if nfvi.objects.v1.HOST_ADMIN_STATE.UNLOCKED \
== self._nfvi_host.admin_state:
self._action = self._ACTION_NONE
self._persist()
if self.is_locking() and host_fsm.HOST_STATE.DISABLED == self.state:
if nfvi.objects.v1.HOST_ADMIN_STATE.LOCKED \
== self._nfvi_host.admin_state:
self._action = self._ACTION_NONE
self._persist()
if self.is_locking():
self._fsm.handle_event(host_fsm.HOST_EVENT.LOCK)
elif nfvi.objects.v1.HOST_ADMIN_STATE.LOCKED \
== self._nfvi_host.admin_state:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
elif nfvi.objects.v1.HOST_OPER_STATE.ENABLED \
== self._nfvi_host.oper_state:
self._fsm.handle_event(host_fsm.HOST_EVENT.ENABLE)
elif nfvi.objects.v1.HOST_OPER_STATE.DISABLED \
== self._nfvi_host.oper_state:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
elif nfvi.objects.v1.HOST_AVAIL_STATUS.FAILED \
== self._nfvi_host.avail_status:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
elif nfvi.objects.v1.HOST_AVAIL_STATUS.FAILED_COMPONENT \
== self._nfvi_host.avail_status:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
elif nfvi.objects.v1.HOST_AVAIL_STATUS.OFFLINE \
== self._nfvi_host.avail_status:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
def nfvi_host_state_change(self, nfvi_admin_state, nfvi_oper_state,
nfvi_avail_status, nfvi_data=None):
"""
NFVI Host State Change
"""
if nfvi_data is not None:
self._nfvi_host.nfvi_data = nfvi_data
self._persist()
if nfvi.objects.v1.HOST_ADMIN_STATE.UNKNOWN == nfvi_admin_state:
DLOG.info("Ignoring unknown administrative state change for %s."
% self._nfvi_host.name)
return
if nfvi.objects.v1.HOST_OPER_STATE.UNKNOWN == nfvi_oper_state:
DLOG.info("Ignoring unknown operation state change for %s."
% self._nfvi_host.name)
return
if nfvi_admin_state != self._nfvi_host.admin_state \
or nfvi_oper_state != self._nfvi_host.oper_state \
or nfvi_avail_status != self._nfvi_host.avail_status:
DLOG.debug("Host State-Change detected: nfvi_admin_state=%s "
"host_admin_state=%s, nfvi_oper_state=%s "
"host_oper_state=%s, nfvi_avail_state=%s "
"host_avail_status=%s, locking=%s unlocking=%s "
"fsm current_state=%s for %s." %
(nfvi_admin_state, self._nfvi_host.admin_state,
nfvi_oper_state, self._nfvi_host.oper_state,
nfvi_avail_status, self._nfvi_host.avail_status,
self.is_locking(), self.is_unlocking(),
self._fsm.current_state.name,
self._nfvi_host.name))
notify_offline = False
if nfvi.objects.v1.HOST_AVAIL_STATUS.OFFLINE == nfvi_avail_status:
if nfvi.objects.v1.HOST_AVAIL_STATUS.OFFLINE \
!= self._nfvi_host.avail_status:
notify_offline = True
self._nfvi_host.admin_state = nfvi_admin_state
self._nfvi_host.oper_state = nfvi_oper_state
self._nfvi_host.avail_status = nfvi_avail_status
self._persist()
self._nfvi_host_handle_state_change()
if notify_offline:
from nfv_vim import directors
host_director = directors.get_host_director()
host_director.host_offline(self)
elif host_fsm.HOST_STATE.INITIAL == self._fsm.current_state.name:
self._fsm.handle_event(host_fsm.HOST_EVENT.ADD)
return
elif host_fsm.HOST_STATE.CONFIGURE == self._fsm.current_state.name:
self._fsm.handle_event(host_fsm.HOST_EVENT.ADD)
return
elif host_fsm.HOST_STATE.ENABLED == self._fsm.current_state.name \
and nfvi.objects.v1.HOST_OPER_STATE.DISABLED == nfvi_oper_state:
self._fsm.handle_event(host_fsm.HOST_EVENT.DISABLE)
return
elif host_fsm.HOST_STATE.DISABLED == self._fsm.current_state.name \
and nfvi.objects.v1.HOST_OPER_STATE.ENABLED == nfvi_oper_state:
self._fsm.handle_event(host_fsm.HOST_EVENT.ENABLE)
return
else:
now_ms = timers.get_monotonic_timestamp_in_ms()
secs_expired = (now_ms - self._last_state_timestamp) / 1000
if 30 <= secs_expired:
if 0 != self._last_state_timestamp:
self._elapsed_time_in_state += int(secs_expired)
self._last_state_timestamp = now_ms
self._persist()
self._fsm.handle_event(host_fsm.HOST_EVENT.AUDIT)
def nfvi_host_update(self, nfvi_host):
"""
NFVI Host Update
"""
self.nfvi_host_state_change(nfvi_host.admin_state, nfvi_host.oper_state,
nfvi_host.avail_status)
self._nfvi_host = nfvi_host
self._persist()
def nfvi_host_add(self):
"""
NFVI Host Add
"""
self._fsm.handle_event(host_fsm.HOST_EVENT.ADD)
def nfvi_host_delete(self):
"""
NFVI Host Delete
"""
alarm.host_clear_alarm(self._alarms)
self._fsm.handle_event(host_fsm.HOST_EVENT.DELETE)
def periodic_timer(self):
"""
Periodic Timer
"""
self._fsm.handle_event(host_fsm.HOST_EVENT.PERIODIC_TIMER)
def host_services_update_all(self, host_service_state, reason=None):
"""
Host services update all
"""
at_least_one_change = False
for service, state in self._host_service_state.items():
if state != host_service_state:
at_least_one_change = True
self._host_service_state[service] = host_service_state
if at_least_one_change:
self.host_services_update(None, host_service_state, reason)
def host_services_update(self, service,
host_service_state, reason=None):
"""
Host services update. None input service parameter indicates
that the _host_service_state has already been updated through
host_services_update_all.
"""
if service is not None:
if host_service_state == self._host_service_state[service]:
return
self._host_service_state[service] = host_service_state
# Host services logs and alarms only apply to worker hosts
if 'worker' in self.personality:
host_service_state_overall = \
self.host_service_state_aggregate()
if (HOST_SERVICE_STATE.ENABLED ==
host_service_state_overall):
self._events = event_log.host_issue_log(
self, event_log.EVENT_ID.HOST_SERVICES_ENABLED)
alarm.host_clear_alarm(self._alarms)
self._alarms[:] = list()
elif (HOST_SERVICE_STATE.DISABLED ==
host_service_state_overall):
self._events = event_log.host_issue_log(
self, event_log.EVENT_ID.HOST_SERVICES_DISABLED)
alarm.host_clear_alarm(self._alarms)
self._alarms[:] = list()
elif (HOST_SERVICE_STATE.FAILED ==
host_service_state_overall):
if reason is None:
additional_text = ''
else:
additional_text = ", %s" % reason
self._events = event_log.host_issue_log(
self, event_log.EVENT_ID.HOST_SERVICES_FAILED,
additional_text=additional_text)
self._alarms = alarm.host_raise_alarm(
self, alarm.ALARM_TYPE.HOST_SERVICES_FAILED,
additional_text=additional_text)
def nfvi_host_upgrade_status(self, upgrade_inprogress, recover_instances):
"""
NFVI Host Upgrade
"""
if upgrade_inprogress != self._upgrade_inprogress:
if upgrade_inprogress:
DLOG.info("Host %s upgrade inprogress, recover_instances=%s."
% (self.name, recover_instances))
else:
DLOG.info("Host %s upgrade no longer inprogress, "
"recover_instances=%s." % (self.name, recover_instances))
self._upgrade_inprogress = upgrade_inprogress
self._recover_instances = recover_instances
self._persist()
def _state_change_callback(self, prev_state, state, event):
"""
Host state change callback
"""
from nfv_vim import directors
DLOG.info("Host %s FSM State-Change: prev_state=%s, state=%s, event=%s."
% (self.name, prev_state, state, event))
self._elapsed_time_in_state = 0
self._last_state_timestamp = timers.get_monotonic_timestamp_in_ms()
if self.is_locking() and host_fsm.HOST_STATE.DISABLED == self.state:
if nfvi.objects.v1.HOST_ADMIN_STATE.LOCKED \
== self.nfvi_host.admin_state:
self._action = self._ACTION_NONE
if self.is_unlocking():
if nfvi.objects.v1.HOST_ADMIN_STATE.UNLOCKED \
== self.nfvi_host.admin_state:
self._action = self._ACTION_NONE
self._persist()
host_director = directors.get_host_director()
host_director.host_state_change_notify(self)
def _persist(self):
"""
Persist changes to host object
"""
from nfv_vim import database
database.database_host_add(self)
def as_dict(self):
"""
Represent host object as dictionary
"""
data = dict()
data['uuid'] = self.uuid
data['name'] = self.name
data['personality'] = self.personality
data['state'] = self.state
data['action'] = self.action
data['reason'] = self.reason
data['elapsed_time_in_state'] = self.elapsed_time_in_state
data['nfvi_host'] = self.nfvi_host.as_dict()
return data