nova-powervm/nova_powervm/virt/powervm/event.py

230 lines
8.6 KiB
Python

# Copyright 2014, 2017 IBM Corp.
#
# All Rights Reserved.
#
# 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 eventlet import greenthread
from nova.compute import power_state
from nova.compute import task_states
from nova import context as ctx
from nova import exception
from nova.virt import event
from oslo_concurrency import lockutils
from oslo_log import log as logging
from pypowervm import adapter as pvm_apt
from pypowervm import util as pvm_util
from pypowervm.wrappers import event as pvm_evt
from nova_powervm.virt.powervm import vm
LOG = logging.getLogger(__name__)
_INST_ACTIONS_HANDLED = {'PartitionState', 'NVRAM'}
_NO_EVENT_TASK_STATES = {
task_states.SPAWNING,
task_states.RESIZE_MIGRATING,
task_states.RESIZE_REVERTING,
task_states.REBOOTING,
task_states.REBOOTING_HARD,
task_states.REBOOT_STARTED_HARD,
task_states.PAUSING,
task_states.UNPAUSING,
task_states.SUSPENDING,
task_states.RESUMING,
task_states.POWERING_OFF,
task_states.POWERING_ON,
task_states.RESCUING,
task_states.UNRESCUING,
task_states.REBUILDING,
task_states.REBUILD_SPAWNING,
task_states.MIGRATING,
task_states.DELETING,
task_states.SOFT_DELETING,
task_states.RESTORING,
task_states.SHELVING,
task_states.SHELVING_OFFLOADING,
task_states.UNSHELVING,
}
_LIFECYCLE_EVT_LOCK = 'pvm_lifecycle_event'
_CONTEXT = None
def _get_instance(inst, pvm_uuid):
global _CONTEXT
if inst is not None:
return inst
with lockutils.lock('get_context_once'):
if _CONTEXT is None:
_CONTEXT = ctx.get_admin_context()
LOG.debug('PowerVM Nova Event Handler: Getting inst for id %s', pvm_uuid)
return vm.get_instance(_CONTEXT, pvm_uuid)
class PowerVMNovaEventHandler(pvm_apt.WrapperEventHandler):
"""Used to receive and handle events from PowerVM and convert to Nova."""
def __init__(self, driver):
self._driver = driver
self._lifecycle_handler = PowerVMLifecycleEventHandler(self._driver)
def _handle_inst_event(self, inst, pvm_uuid, details):
"""Handle an instance event.
This method will check if an instance event signals a change in the
state of the instance as known to OpenStack and if so, trigger an
event upward.
:param inst: the instance object.
:param pvm_uuid: the PowerVM uuid of the vm
:param details: Parsed Details from the event
:return inst: The nova instance, which may be None
"""
# If the NVRAM has changed for this instance and a store is configured.
if 'NVRAM' in details and self._driver.nvram_mgr is not None:
# Schedule the NVRAM for the instance to be stored.
inst = _get_instance(inst, pvm_uuid)
if inst is None:
return None
LOG.debug('Handle NVRAM event for PowerVM LPAR %s', pvm_uuid,
instance=inst)
self._driver.nvram_mgr.store(inst)
# If the state of the vm changed see if it should be handled
if 'PartitionState' in details:
self._lifecycle_handler.process(inst, pvm_uuid)
return inst
def process(self, events):
"""Process the event that comes back from PowerVM.
:param events: The pypowervm Event wrapper.
"""
inst_cache = {}
for pvm_event in events:
try:
if pvm_event.etype in (pvm_evt.EventType.NEW_CLIENT,
pvm_evt.EventType.CACHE_CLEARED):
# TODO(efried): Should we pull and check all the LPARs?
continue
# See if this uri (from data) ends with a PowerVM UUID.
pvm_uuid = pvm_util.get_req_path_uuid(
pvm_event.data, preserve_case=True)
if pvm_uuid is None:
continue
# Is it an instance event?
if not pvm_event.data.endswith('LogicalPartition/' + pvm_uuid):
continue
# Pull all the pieces of the event.
details = (pvm_event.detail.split(',') if pvm_event.detail
else [])
# Is it one we care about?
if not _INST_ACTIONS_HANDLED & set(details):
continue
inst_cache[pvm_event.data] = self._handle_inst_event(
inst_cache.get(pvm_event.data), pvm_uuid, details)
except Exception:
# We deliberately keep this exception clause as broad as
# possible - we don't want *any* error to stop us from
# attempting to process the next event.
LOG.exception('Unable to process PowerVM event %s',
str(pvm_event))
class PowerVMLifecycleEventHandler(object):
"""Because lifecycle events are weird, we need our own handler.
Lifecycle events that come back from the hypervisor are very 'surface
value'. They tell you that it started, stopped, migrated, etc... However,
multiple events may come in quickly that represent a bigger action. For
instance a restart will generate a stop and then a start rapidly.
Nova being asynchronous can flip those events around. Where the start
would flow through before the stop. That is bad.
We need to make sure that these events that can be linked to bigger
lifecycle events can be wiped out if the converse action is run against
it. Ex. Don't send a stop event up to nova if you received a start event
shortly after it.
"""
def __init__(self, driver):
self._driver = driver
self._delayed_event_threads = {}
@lockutils.synchronized(_LIFECYCLE_EVT_LOCK)
def _emit_event(self, pvm_uuid, inst):
# Get the current state
try:
pvm_state = vm.get_vm_qp(self._driver.adapter, pvm_uuid,
'PartitionState')
except exception.InstanceNotFound:
LOG.debug("LPAR %s was deleted while event was delayed.", pvm_uuid,
instance=inst)
return
LOG.debug('New state %s for partition %s', pvm_state, pvm_uuid,
instance=inst)
inst = _get_instance(inst, pvm_uuid)
if inst is None:
LOG.debug("Not emitting LifecycleEvent: no instance for LPAR %s",
pvm_uuid)
return
# If we're in the middle of a nova-driven operation, no event necessary
if inst.task_state in _NO_EVENT_TASK_STATES:
LOG.debug("Not emitting LifecycleEvent: instance task_state is %s",
inst.task_state, instance=inst)
return
# See if it's really a change of state from what OpenStack knows
transition = vm.translate_event(pvm_state, inst.power_state)
if transition is None:
LOG.debug("No LifecycleEvent necessary for pvm_state(%s) and "
"power_state(%s).", pvm_state,
power_state.STATE_MAP[inst.power_state], instance=inst)
return
# Log as if normal event
lce = event.LifecycleEvent(inst.uuid, transition)
LOG.info('Sending LifecycleEvent for instance state change to: %s',
pvm_state, instance=inst)
self._driver.emit_event(lce)
# Delete out the queue
del self._delayed_event_threads[pvm_uuid]
@lockutils.synchronized(_LIFECYCLE_EVT_LOCK)
def process(self, inst, pvm_uuid):
"""Emits the event, or adds it to the queue for delayed emission.
:param inst: The nova instance. May be None.
:param pvm_uuid: The PowerVM LPAR UUID.
"""
# Cancel out the current delay event. Can happen as it goes
# from SHUTTING_DOWN to NOT_ACTIVATED, multiple delayed events
# can come in at once. Only want the last.
if pvm_uuid in self._delayed_event_threads:
self._delayed_event_threads[pvm_uuid].cancel()
# Spawn in the background
elem = greenthread.spawn_after(15, self._emit_event, pvm_uuid, inst)
self._delayed_event_threads[pvm_uuid] = elem