230 lines
8.6 KiB
Python
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
|