/***************************************************************************
* Copyright (c) 2012-2013 VMware, Inc. 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.
***************************************************************************/
/**
* <code>VcEventListener</code> is a thread associated with VC session
* that is responsible for listening to "interesting" VC events rooted
* at an inventory subtree. Clients will eventually use VcEventListener
* to get callbacks when watched objects change in VC.
*
* @since 0.6
* @version 0.6
* @author Boris Weissman
*/
package com.vmware.aurora.vc.vcevent;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.concurrent.Semaphore;
import javax.net.ssl.SSLException;
import org.apache.log4j.Logger;
import com.vmware.aurora.exception.AuroraException;
import com.vmware.aurora.util.AuAssert;
import com.vmware.aurora.vc.MoUtil;
import com.vmware.aurora.vc.VcCluster;
import com.vmware.aurora.vc.VcTask;
import com.vmware.aurora.vc.vcevent.VcEventHandlers.IVcEventHandler;
import com.vmware.aurora.vc.vcevent.VcEventHandlers.TaskFinishedEvent;
import com.vmware.aurora.vc.vcevent.VcEventHandlers.TaskUpdateProgressEvent;
import com.vmware.aurora.vc.vcevent.VcEventHandlers.VcEventType;
import com.vmware.aurora.vc.vcservice.VcContext;
import com.vmware.aurora.vc.vcservice.VcService;
import com.vmware.vim.binding.impl.vim.event.ComputeResourceEventArgumentImpl;
import com.vmware.vim.binding.impl.vim.event.EventFilterSpecImpl;
import com.vmware.vim.binding.impl.vim.event.GeneralUserEventImpl;
import com.vmware.vim.binding.impl.vim.event.ManagedEntityEventArgumentImpl;
import com.vmware.vim.binding.impl.vmodl.TypeNameImpl;
import com.vmware.vim.binding.vim.TaskInfo;
import com.vmware.vim.binding.vim.event.Event;
import com.vmware.vim.binding.vim.event.EventFilterSpec;
import com.vmware.vim.binding.vim.event.EventHistoryCollector;
import com.vmware.vim.binding.vim.event.EventManager;
import com.vmware.vim.binding.vim.fault.InvalidLogin;
import com.vmware.vim.binding.vmodl.ManagedObjectReference;
import com.vmware.vim.binding.vmodl.fault.ManagedObjectNotFound;
import com.vmware.vim.binding.vmodl.query.InvalidProperty;
import com.vmware.vim.binding.vmodl.query.PropertyCollector;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.Change;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.Change.Op;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.FilterSpec;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.FilterUpdate;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.ObjectSpec;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.ObjectUpdate;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.ObjectUpdate.Kind;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.PropertySpec;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.SelectionSpec;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.TraversalSpec;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.UpdateSet;
import com.vmware.vim.binding.vmodl.query.PropertyCollector.WaitOptions;
import com.vmware.vim.vmomi.core.exception.InternalException;
public class VcEventListener extends Thread {
/* VcEventListener DFA states. */
public enum VcEventListenerState {
CREATED, // Just created, no VC server state.
RESET, // VC server state established.
DRAINED, // "Old" events drained.
LISTENING, // Listening in inner loop.
STOPPED, // Stopped on shutdown.
STOPPED_UNCLEAN, // Unclean stop on shutdown.
CONNECTING, // Trying to reconnect.
INVALID // Fatal configuration problems.
}
private static Logger logger = Logger.getLogger(VcEventListener.class);
private Semaphore initSema; // Signals when init completes.
// Exponential back-off parameters for vc connection problems.
private static final int MIN_RETRY_DELAY_SEC = 1;
private static final int MAX_RETRY_DELAY_SEC = 60;
private int currentRetryDelaySec = MIN_RETRY_DELAY_SEC;
/* Read by other threads. */
private volatile VcEventListenerState state; // Current state.
private volatile int totalEventCount; // Total number of events seen so far.
private volatile int dispatchedEventCount; // Interesting events seen so far.
private volatile int totalTaskCount; // All task state transitions seen.
private volatile int dispatchedTaskCount; // Those that caused callback dispatch.
private volatile Exception initException; // Exception encountered during init.
/* Written by other threads */
private volatile boolean stopRequested; // Stop requested?
private ManagedObjectReference targetMoRef; // Root object to monitor.
private String currentVersion; // From last UpdateSet.
private WaitOptions waitForUpdateOptions; // Property Collector parameters.
private EventFilterSpec eventFilterSpec; // Events of interest.
private FilterSpec eventPFS; // PC "latestPage" prop filter spec.
private FilterSpec taskPFS; // PC task state prop filter spec.
private ManagedObjectReference eventManagerMoRef;
private ManagedObjectReference eventHistoryCollectorMoRef;
private EventManager eventManager;
private EventHistoryCollector eventHistoryCollector;
private ManagedObjectReference taskManagerMoRef;
final private int eventPageSize = 100; // Max # of events for single read.
final private int maxWaitSeconds = 60; // WaitForUpdatesEx timeout.
private ManagedObjectReference eventFilterMoRef; // PC filter reference.
private ManagedObjectReference taskFilterMoRef; // PC filter reference.
/**
* Returns the total number of events trapped by our event collector. Ok to
* be called by other threads concurrently. Not synchronized, might return
* stale values.
* @return total events seen so far
*/
public int getTotalEventCount() {
return totalEventCount;
}
/**
* A count of dispatched events - events with registered handlers. Might
* return stale values.
* @return dispatched events
*/
public int getDispatchedEventCount() {
return dispatchedEventCount;
}
/**
* Return exception thrown at VcEventListener initialization time or null
* if everything went well.
* @return Exception or null
*/
public Exception getInitException() {
return initException;
}
/**
* If the server side event listener state has been established, returns a
* reference to Event Manager proxy object. Otherwise returns null.
* @return EventManager
*/
private EventManager getEventManager() {
AuAssert.check(eventManager != null);
return eventManager;
}
/**
* DFA transitions clearing house.
* @param newState
*/
private void setState(VcEventListenerState newState) {
if (newState == VcEventListenerState.LISTENING) {
AuAssert.check(state == VcEventListenerState.DRAINED ||
state == VcEventListenerState.CONNECTING);
} else if (newState == VcEventListenerState.DRAINED) {
AuAssert.check(state == VcEventListenerState.CREATED ||
state == VcEventListenerState.RESET);
} else if (newState == VcEventListenerState.INVALID) {
AuAssert.check(state == VcEventListenerState.LISTENING);
}
state = newState;
}
/**
* Listening to events in the inner event loop?
* @return true if all is well
*/
public boolean isListening() {
return state == VcEventListenerState.LISTENING;
}
/**
* Create a new Event Listener for a specified target. The target
* is a moRef of either cluster or datacenter. Currently all events
* for objects rooted under the target are monitored. While desirable,
* watching for events rooted at Resource Pool does not quite work:
* - VC is unwilling to report sub-RP related events for parent RP
* - we want all host and network events that are per cluster
* This function just creates a java object. reset() needs to be called
* to establish VC server side event collector state.
*
* @param targetMoRef either cluster or datacenter
* @param taskMgr internal task events manager
* @throws Exception
*/
public VcEventListener(ManagedObjectReference targetMoRef)
throws Exception {
this.targetMoRef = targetMoRef;
waitForUpdateOptions = new WaitOptions();
waitForUpdateOptions.maxWaitSeconds = maxWaitSeconds;
initSema = new Semaphore(0);
setName("VcEventListener");
setState(VcEventListenerState.CREATED);
initException = null;
}
/**
* VcEventListener creator thread can wait here until the listener is
* fully initialized.
* @throws InterruptedException
*/
public void waitUntilStarted() throws InterruptedException {
AuAssert.check(Thread.currentThread() != this);
initSema.acquire();
}
/**
* Re-initialize EventListener state either on start-up or following
* a lost VC connections. Sets up all the necessary VC server side state:
* property collector filters for "lastPage" as well as the event collector
* itself and its associated filters. Must be called each time a new VC
* connection is established.
*
* Note that reset() does not remove any previously installed event handlers.
* We don't want the handlers to disappear on re-establishing VC connection.
*
* @throws Exception
*/
public void reset() throws Exception {
VcService vcService = VcContext.getService();
AuAssert.check(vcService != null && vcService.isConnected());
AuAssert.check(Thread.currentThread() == this); // Other threads can't do this.
logger.info("VcEventListener reset");
currentVersion = null;
eventManagerMoRef = vcService.getServiceInstanceContent().getEventManager();
eventManager = MoUtil.getManagedObject(eventManagerMoRef);
taskManagerMoRef = vcService.getServiceInstanceContent().getTaskManager();
/* Listen to all events under targetMoRef. */
eventFilterSpec = createEventFilterSpec();
eventHistoryCollectorMoRef = eventManager.createCollector(eventFilterSpec);
eventHistoryCollector = MoUtil.getManagedObject(eventHistoryCollectorMoRef);
/*
* Set up property collector to watch for changes of either:
* - EventColletor.latestPage property
* - Task.info.state property in recent tasks
*/
eventPFS = createEventPFS();
eventFilterMoRef = vcService.getPropertyCollector().createFilter(
eventPFS, false);
taskPFS = createTaskPFS();
taskFilterMoRef = vcService.getPropertyCollector().createFilter(
taskPFS, false);
/*
* Reset retry delay only if the last generation did any real work in order
* to avoid login()/logout() in a tight loop when VC is persistently down.
*/
if (totalEventCount > 0) {
resetRetryDelay();
}
totalEventCount = 0;
dispatchedEventCount = 0;
stopRequested = false;
setState(VcEventListenerState.RESET);
drainEvents();
}
/**
* We don't care about the differences between truncated and non-truncated
* UpdatSet versions. We use a property collector to watch for *any* changes
* to kick off a round of real event reads. Truncated UpdateSet might cause
* occasional spurious event reads which is ok.
*
* @param version new version
* @param UpdateSet latest changes
*/
private void setCurrentVersion(String version, UpdateSet updates) {
if(logger.isInfoEnabled()) {
if (version.equals("")) {
logger.debug("Listener: version reset");
} else {
logger.debug("Listener: version <- " + version +
" truncated " + updates.truncated);
}
}
currentVersion = version;
}
/**
* Create an event filter to listen to events of interest under targetMoRef
* recursively. We subscribe only to events that are of interest to cms.
* These events are explicitly listed in VcEventType.
* @return EventFilterSpec
*/
private EventFilterSpec createEventFilterSpec() {
AuAssert.check(targetMoRef != null);
EventFilterSpec efs = new EventFilterSpecImpl();
String[] eventTypeIds = VcEventType.getEventTypeIds();
logger.info("Subscribing to vc events:");
for (String eventId : eventTypeIds) {
logger.info("\t" + eventId);
}
efs.setEntity(new EventFilterSpecImpl.ByEntityImpl(
targetMoRef,
EventFilterSpec.RecursionOption.all));
efs.setEventTypeId(eventTypeIds);
return efs;
}
/**
* Create a property filter spec for PropertyCollector with these parameters:
* PropertySpec: "latestPage" property of EventHistoryCollector
* ObjectSpec: eventHistoryCollector
* SelectionSpec: none
* The idea is to wait for any changes to "latestPage" in the eventCollector
* which designates an arrival of a new event.
*
* @return FilterSpec
*/
private FilterSpec createEventPFS() {
PropertySpec propSpec = new PropertySpec();
propSpec.setAll(false);
propSpec.setPathSet(new String[] { "latestPage" });
propSpec.setType(MoUtil.getTypeName(eventHistoryCollectorMoRef));
ObjectSpec objSpec = new ObjectSpec();
objSpec.setObj(eventHistoryCollectorMoRef);
objSpec.setSkip(false);
objSpec.setSelectSet(new SelectionSpec[] { });
FilterSpec filterSpec = new FilterSpec();
filterSpec.setPropSet(new PropertySpec[] { propSpec });
filterSpec.setObjectSet(new ObjectSpec[] { objSpec });
return filterSpec;
}
/**
* Create a property filter spec to get Task state updates with these
* parameters:
* PropertySpec: "info.state" & "info.progress" properties of Task
* ObjectSpec: taskManager
* SelectionSpec: "recentTask"
*
* TODO This filter triggers a bunch of "enter" updates on startup as
* all recentTask[] objects come into view. Could this be addressed as
* we should not care about initial recentTasks?
*
* @return FilterSpec
*/
private FilterSpec createTaskPFS() {
PropertySpec propSpec = new PropertySpec();
propSpec.setAll(false);
propSpec.setPathSet(new String[] { "info.state", "info.progress" });
propSpec.setType(new TypeNameImpl("Task"));
ObjectSpec objSpec = new ObjectSpec();
objSpec.setObj(taskManagerMoRef);
objSpec.setSkip(false);
TraversalSpec tSpec = new TraversalSpec();
tSpec.setType(new TypeNameImpl("TaskManager"));
tSpec.setPath("recentTask");
tSpec.setSkip(false);
objSpec.setSelectSet(new SelectionSpec[] {tSpec});
FilterSpec filterSpec = new FilterSpec();
filterSpec.setPropSet(new PropertySpec[] { propSpec });
filterSpec.setObjectSet(new ObjectSpec[] { objSpec });
return filterSpec;
}
/**
* Block on waitForUpdatesEx() until a new event arrives.
* @return UpdateSet
* @throws Exception
*/
private UpdateSet waitForUpdates() throws Exception {
UpdateSet updateSet;
PropertyCollector pc = VcContext.getService().getPropertyCollector();
logger.debug("waitForUpdates");
updateSet = pc.waitForUpdatesEx(currentVersion, waitForUpdateOptions);
return updateSet;
}
/**
* Returns true if the exception received in innerWaitForEventsLoop() could
* be recovered from via retry *without* re-initialization of vc session:
* transient network failures, etc. List borrowed from redwood.
*
* Hierarchy:
* Exception
* -- IOException
* ---- SocketException
* ------ ConnectException
* -------- HttpHostConnectException
* ---- InterruptedIOException
* ------ SocketTimeoutException
*
* Socket exception involves many recoverable cases, but also includes vc
* shutdown, unfortunately. Upon vc restart, we'll get NotAuthenticated
* breaking us out of the inner loop. Until then, it is ok to retry in
* the inner loop.
*
* @param exception to check
* @return true if retry could help
*/
private boolean isRecoverableInnerException(Throwable e) {
return MoUtil.isNetworkException(e) || /* Transient network problem. */
e instanceof InterruptedException; /* Will recheck stopRequested. */
}
/**
* Inner wait for events loop. Waits for "lastPage" property updates from VC
* via property collector (new event received). Then reads all new events from
* EventCollector. Runs continuously until shutDown() or a fatal VC error
* that invalidates the current VC session.
*
* Tries hard not to break out of the loop on recoverable errors. Returning
* on a fatal VC error will likely mean VcEventListener reset *and* full
* inventory reload/verification.
*
* Returns when Event Listener was shut down. Throws an exception upon
* encountering an irrecoverable VC session problems.
*
* @throws Exception when a VC session cannot be recovered
*/
private void innerWaitForEventsLoop() throws Exception {
logger.debug("innerWaitForEventsLoop");
setState(VcEventListenerState.LISTENING);
while (!stopRequested) {
UpdateSet updateSet = null;
try {
updateSet = waitForUpdates();
dispatchUpdates(updateSet);
resetRetryDelay();
} catch (ManagedObjectNotFound e) {
/*
* Managed object is not found, do not need to break session.
*/
continue;
} catch (InternalException e) {
/*
* VLSI fun: deal with unchecked internal exceptions. Here we try
* to extract potentially recoverable exceptions to preserve the
* current session with its established Event Collector state.
*/
Throwable cause = e.getCause();
if (isRecoverableInnerException(cause)) {
onInnerLoopContinue(e);
continue;
} else {
/* For now, terminate on all unknown internal exceptions. */
onInnerLoopTerminate(e);
throw e;
}
} catch (AuroraException e) {
/*
* Something went wrong during event handler execution (not related to this
* VC session). Also might land here if CMS DB is down - breaks some event
* handlers (Bug 733665). No need to break out of inner loop - logging out
* of VC will not fix this problem.
*/
onInnerLoopContinue(e);
continue;
} catch (Exception e) {
/* For now, terminate on all unknown exceptions. */
onInnerLoopTerminate(e);
throw e;
}
}
onInnerLoopTerminate(null);
}
/**
* Clean up on recoverable exception in waitForEventLoop().
* @param recoverable exception
*/
private void onInnerLoopContinue(Throwable e) {
if(!stopRequested) {
logger.warn("Recovering from inner exception: " + e.getMessage());
}
/* Don't delay for client timeout or stop request. */
if (!(e instanceof SocketTimeoutException || stopRequested)) {
retryDelay();
}
}
/**
* Executed before breaking out of the inner event loop. Since the
* exception will be re-thrown to the outer loop that will perform
* additional parsing, we log the stack trace at the outer level.
* @param irrecoverable exception
*/
private void onInnerLoopTerminate(Throwable e) {
if (stopRequested) {
logger.debug("Inner stopRequested");
} else {
logger.warn("Inner irrecoverable exception: " +
((e == null) ? "null" : e.getMessage()));
}
}
/**
* Sleep between successive attempts to contact vc that fail because of
* recoverable errors. Uses an exponential back-off algorithm.
*/
private void retryDelay() {
logger.debug("retryDelay secs: " + currentRetryDelaySec);
try {
Thread.sleep(currentRetryDelaySec * 1000L);
} catch (Exception e) {
}
updateRetryDelay();
}
/**
* Apply exponential back-off to the retry interval.
*/
private void updateRetryDelay() {
currentRetryDelaySec *= 2;
if (currentRetryDelaySec > MAX_RETRY_DELAY_SEC) {
currentRetryDelaySec = MAX_RETRY_DELAY_SEC;
}
logger.debug("currentRetryDelaySec <- " + currentRetryDelaySec);
}
/**
* Reset vc retry delay following a successful round of vc communication.
*/
private void resetRetryDelay() {
currentRetryDelaySec = MIN_RETRY_DELAY_SEC;
logger.debug("Resetting currenRetryDelaySec <- " + currentRetryDelaySec);
}
/**
* "Drain" events on VC server. In effect, moves the current event
* pointer past all events received so far. Our inventory snapshot is
* not longer trustworthy after this operation.
* @throws InterruptedException
*/
private void drainEvents() throws InterruptedException {
int drained = 0;
Event[] events;
/* Move the cursor to the last "viewable page" */
eventHistoryCollector.reset();
/* Read and discard everything in the last page. */
while ((events = readEvents(false)) != null) {
drained += events.length;
}
setState(VcEventListenerState.DRAINED);
logger.debug("EventListener drained: " + drained);
}
/**
* A placeholder for event callback dispatch. Currently just logs
* event info.
* @param events
* @throws Exception
*/
private void dispatchEvents(Event[] events) throws Exception {
if (logger.isDebugEnabled()) {
VcEventUtil.dumpEvents(events);
}
for (Event e : events) {
VcEventHandlers.getInstance().fire(e);
}
}
/**
* Returns true for exceptions triggered by invalid configuration
* parameters: username/password, bad URL, etc. We cannot do anything
* to recover from exceptions of this kind.
* @param e
* @return true for 'invalid' exceptions
*/
boolean isInvalidConfigOuterException(Throwable e) {
return
e instanceof InvalidLogin ||
e instanceof URISyntaxException ||
e instanceof InvalidProperty ||
e instanceof SSLException;
}
/**
* The outer event processing loop. The inner loop breaks out on shutdown
* request or when vc session is no longer available. The outer loop deals
* with vc session re-initialization. Full inventory re-validation will
* also belong here.
*
* Returns in two cases:
* - shut down request
* - invalid configuration: bad authentication, url, etc.
*
* On all other failures, tries to reconnect to vc.
*/
private void outerWaitForEventsLoop() {
while (true) {
logger.debug("outerWaitForEventsLoop");
try {
if (stopRequested) {
onOuterLoopTerminate(VcEventListenerState.STOPPED, null);
break;
}
innerWaitForEventsLoop();
} catch (Exception e) {
if (isInvalidConfigOuterException(e)) {
onOuterLoopTerminate(VcEventListenerState.INVALID, e);
break;
} else {
onOuterLoopContinue(VcEventListenerState.CONNECTING, e);
}
}
/*
* VC session went bad. Logout to force automatic relogin that will
* trigger the establishment of the new VC session. On stop, logout
* happens along with other cleanup.
*/
if (!stopRequested) {
VcService vcService = VcContext.getService();
if (vcService == null) {
/*
* Tomcat likes to clean out ThreadLocal on shutdown. We might land here
* if something went wrong with the thread executing WebServiceContextListener
* callbacks before it had a chance to cleanly shut down VcEventListener.
* Terminate the thread - Bug 733665.
*/
onOuterLoopTerminate(VcEventListenerState.STOPPED_UNCLEAN, null);
break;
} else {
vcService.logout();
}
}
}
}
private void onOuterLoopContinue(VcEventListenerState newState, Throwable e) {
if (!stopRequested) {
logger.info("onOuterLoopContinue ", e);
retryDelay();
}
setState(newState);
}
private void onOuterLoopTerminate(VcEventListenerState newState, Throwable e) {
StringBuilder msg = new StringBuilder("onOuterLoopTerminate ").append(newState);
if (stopRequested) {
logger.info(msg); // No stack trace ok.
} else if (newState == VcEventListenerState.INVALID){
logger.error(msg.toString(), e);
} else if (newState == VcEventListenerState.STOPPED_UNCLEAN){
logger.warn(msg);
} else {
logger.warn(msg.toString(), e);
}
setState(newState);
}
/**
* Activate Event Listener thread.
*/
public void run() {
VcContext.startEventSession();
try {
try {
reset();
} catch (Exception e) {
logger.error("EventListener reset failure", e);
initException = e; // Runs before initSema.release().
return;
} finally {
initSema.release(); // Wake up the creator.
}
AuAssert.check(state == VcEventListenerState.DRAINED);
outerWaitForEventsLoop();
} finally {
VcContext.endSession();
}
}
/**
* Request Event Listener shutdown and wait until it completes.
* @throws InterruptedException
*/
public void shutDown() {
if (Thread.currentThread() == this) {
return;
}
/* Wait until the Event Listener breaks out of event loops. */
stopRequested = true;
interrupt();
while (true) {
try {
join();
logger.info("VcEventListener joined");
break;
} catch (InterruptedException e) {
logger.debug("Interrupted while waiting for VcEventListener shutdown");
}
}
}
/**
* Read at most eventPageSize events from VC and advance the cursor.
* Hint says if we should expect to find some events. Every now and
* then we get a VC hiccup when "lastPage" indicates a new event
* arrival, but readNext() still returns null for a while. So retry.
*
* @param hint do we expect to find new events?
* @return events new events, if any
* @throws InterruptedException
*/
private Event[] readEvents(boolean hint) throws InterruptedException {
final int retryCount = 5;
int iter = 0;
Event[] events = null;
do {
events = eventHistoryCollector.readNext(eventPageSize);
if (hint && events == null) {
Thread.sleep(100);
}
} while (hint && events == null && iter++ < retryCount);
if (iter >= retryCount) {
logger.debug("lastPage & readNext() mismatch");
}
return events;
}
private void fireTaskFinishedEventHandlers(ManagedObjectReference taskMoRef,
TaskInfo.State state)
throws Exception {
VcEventHandlers handlers = VcEventHandlers.getInstance();
/* Create and fire TaskFinished pseudo-event. */
TaskFinishedEvent taskEvent = handlers.new TaskFinishedEvent(taskMoRef, state);
handlers.fire(taskEvent);
dispatchedTaskCount++; // stats only, unlocked ok.
}
/**
* Dispatch qualified task state transitions to registered handlers.
*
* @param taskMoRef affected task
* @param taskState new state
* @throws Exception
*/
private void taskUpdateStateHandler(ManagedObjectReference taskMoRef,
TaskInfo.State taskState) throws Exception {
logger.debug("\ttaskMoRef: " + taskMoRef.getValue());
logger.debug("\tstate: " + taskState);
if (taskState == TaskInfo.State.success ||
taskState == TaskInfo.State.error) {
fireTaskFinishedEventHandlers(taskMoRef, taskState);
}
}
/**
* Dispatch task progress updates to registered handlers.
*
* @param taskMoRef affected task
* @param progress current progress
* @throws Exception
*/
private void taskUpdateProgressHandler(ManagedObjectReference taskMoRef,
Integer progress) throws Exception {
VcEventHandlers handlers = VcEventHandlers.getInstance();
logger.debug("\ttaskMoRef: " + taskMoRef.getValue());
logger.debug("\tprogress: " + progress);
if (progress != null) {
/* Create and fire TaskUpdateProgress pseudo-event. */
TaskUpdateProgressEvent taskEvent =
handlers.new TaskUpdateProgressEvent(taskMoRef, progress);
handlers.fire(taskEvent);
}
}
/**
* Called to trigger fake TaskFinishedEvent for "bad" tasks that don't naturally
* update VC recentTask[] objects on their own...
* @param task
*/
public void fireBadTaskFinishedEventHandlers(VcTask task) throws Exception {
AuAssert.check(task.isBadTask());
fireTaskFinishedEventHandlers(task.getMoRef(), task.getState());
}
/**
* Dispatch all newly found events to the handler.
* @throws Exception
*/
private void eventUpdateHandler() throws Exception {
Event[] events;
while ((events = readEvents(true)) != null) {
totalEventCount += events.length;
dispatchEvents(events);
}
}
/**
* Parse an update set and dispatch individual changes to their
* respective high level handlers:
* - if at least one real VcEvent detected, invoke generic vc Event handler
* - for each individual task completion, invoke a task completion handler
*
* @param updates
* @throws Exception
*/
private void dispatchUpdates(final UpdateSet updates) throws Exception {
boolean invokeEventHandler = false;
logger.debug("dispatchUpdates: curVersion: " + currentVersion);
if (updates == null) {
logger.debug("UpdateSet is null, curVersion: " + currentVersion);
return;
}
FilterUpdate[] filterUpdates = updates.getFilterSet();
if (filterUpdates == null) {
logger.debug("PropertyFilterUpdate is null, curversion: " + currentVersion);
return;
}
VcEventUtil.dumpUpdateSet(updates, currentVersion);
for (FilterUpdate pfu : filterUpdates) {
ManagedObjectReference filterMoRef = pfu.getFilter();
if (filterMoRef.equals(eventFilterMoRef)) {
/* EventCollector.latestPage update. */
invokeEventHandler = true;
} else {
/* Task.info.state updates: extract individual moRefs & changes. */
AuAssert.check(filterMoRef.equals(taskFilterMoRef));
if (pfu.getObjectSet() != null) {
for (ObjectUpdate oUpdate : pfu.getObjectSet()) {
AuAssert.check(oUpdate.getObj().getType().equalsIgnoreCase("Task"));
totalTaskCount++;
if ((oUpdate.getKind() == Kind.modify ||
oUpdate.getKind() == Kind.enter) &&
oUpdate.getChangeSet() != null) {
for (Change c : oUpdate.getChangeSet()) {
if (c.getName().equals("info.state")) {
if (c.getOp() == Op.assign || c.getOp() == Op.add) {
TaskInfo.State taskState = (TaskInfo.State) c.getVal();
taskUpdateStateHandler(oUpdate.getObj(), taskState);
}
} else {
AuAssert.check(c.getName().equals("info.progress"));
if (c.getOp() == Op.assign || c.getOp() == Op.add) {
Integer progress = (Integer) c.getVal();
taskUpdateProgressHandler(oUpdate.getObj(), progress);
}
}
}
}
}
}
}
}
if (invokeEventHandler) {
eventUpdateHandler();
}
/* Get ready for next PC cycle. */
String newVersion = updates.getVersion();
setCurrentVersion(newVersion, updates);
}
/**
* Post GeneralUserEvent to a target cluster. Event Collector must have
* been initialized. Used by junit.
*
* @param cluster target cluster
* @param msg short event message
* @param fullMsg fully formatted event message
* @param userName user to post event on behalf of
* @throws Exception
*/
public void postGeneralUserEvent(VcCluster cluster, String msg, String fullMsg,
String userName)
throws Exception {
GeneralUserEventImpl event = new GeneralUserEventImpl();
event.setMessage(msg);
event.setFullFormattedMessage(fullMsg);
event.setEntity(new ManagedEntityEventArgumentImpl(cluster.getName(), cluster.getMoRef()));
event.setComputeResource(new ComputeResourceEventArgumentImpl(cluster.getName(), cluster.getMoRef()));
event.setCreatedTime(new GregorianCalendar());
event.setUserName(userName);
getEventManager().postEvent(event, null);
}
/**
* Install an event handler for a cms event of a specified event type. An event
* is classified as cms event if it is triggered as a result of any vc task
* performed by cms. Otherwise it is an external event. Multiple handlers may be
* installed for the same type. The firing order is unspecified. Installed
* handlers survive VC connection re-initialization.
*
* @param eventType target event kind
* @param handler supplied event handler
*/
public static void installEventHandler(VcEventType eventType,
IVcEventHandler handler) {
VcEventHandlers.getInstance().installEventHandler(eventType, handler, false);
}
/**
* Installs a given handler for a set of events specified via EnumSet.
* @param eventTypes target event kinds
* @param handler supplied event handler
*/
public static void installEventHandler(EnumSet<VcEventType> eventTypes,
IVcEventHandler handler) {
for (VcEventType type : eventTypes) {
installEventHandler(type, handler);
}
}
/**
* Similar to installEventHandler(), but is fired for events that are triggered
* by actions that originate outside CMS: hardware failure, VC admin actions, etc.
* @param eventType target event kind
* @param handler supplied event handler
*/
public static void installExtEventHandler(VcEventType eventType,
IVcEventHandler handler) {
VcEventHandlers.getInstance().installEventHandler(eventType, handler, true);
}
/**
* Installs a given handler for a set of external events specified via EnumSet.
* @param eventTypes target event kinds
* @param handler supplied event handler
*/
public static void installExtEventHandler(EnumSet<VcEventType> eventTypes,
IVcEventHandler handler) {
for (VcEventType type : eventTypes) {
installExtEventHandler(type, handler);
}
}
/**
* Remove a handler for a specified event type. Returns true if the handler was
* previously installed and was, in fact, removed. False otherwise. If multiple
* handlers are installed, this removes only a single handler instance.
*
* @param eventType target event kind
* @param handler handler to be removed
* @return true if hanlder removed
*/
public static boolean removeEventHandler(VcEventType eventType,
IVcEventHandler handler) {
return VcEventHandlers.getInstance().removeEventHandler(eventType, handler, false);
}
public static boolean removeEventHandler(EnumSet<VcEventType> eventTypes,
IVcEventHandler handler) {
boolean res = true;
for (VcEventType type : eventTypes) {
res = res && removeEventHandler(type, handler);
}
return res;
}
/**
* Removes an external event handler. Returns true if the handler was removed,
* false if no such handler was found.
*
* @param eventType target event kind
* @param handler handler to be removed
*/
public static boolean removeExtEventHandler(VcEventType eventType,
IVcEventHandler handler) {
return VcEventHandlers.getInstance().removeEventHandler(eventType, handler, true);
}
public static boolean removeExtEventHandler(EnumSet<VcEventType> eventTypes,
IVcEventHandler handler) {
boolean res = true;
for (VcEventType type : eventTypes) {
res = res && removeExtEventHandler(type, handler);
}
return res;
}
}