# # 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