/***************************************************************************
* Copyright (c) 2012-2015 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.
***************************************************************************/
package com.vmware.aurora.vc;
import com.vmware.aurora.exception.AuroraException;
import com.vmware.aurora.exception.VcException;
import com.vmware.aurora.stats.Profiler;
import com.vmware.aurora.stats.StatsType;
import com.vmware.aurora.util.AuAssert;
import com.vmware.aurora.util.worker.Request;
import com.vmware.aurora.vc.VcCache.IVcCacheObject;
import com.vmware.aurora.vc.VcObjectImpl.UpdateType;
import com.vmware.aurora.vc.vcservice.VcContext;
import com.vmware.aurora.vc.vcservice.VcSession;
import com.vmware.vim.binding.vmodl.ManagedObjectReference;
import com.vmware.vim.binding.vmodl.fault.ManagedObjectNotFound;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentMap;
/**
* Request for a VC object.
*
* A request is added to VC cache in place of the VcObject itself.
* All requesting threads would block on the request until the request
* has been processed and the request object being replaced by the
* resulting VcObject.
*/
public class VcObjectRequest extends Request implements IVcCacheObject {
private ConcurrentMap<ManagedObjectReference, IVcCacheObject> map;
private ManagedObjectReference moRef;
private VcObjectImpl updateObj = null;
private EnumSet<UpdateType> updates = EnumSet.noneOf(UpdateType.class);
// set exception if failed to get the result
private AuroraException exception = null;
// result of the request
private VcObjectImpl result = null;
// set the flag to request fetch data from VC again
private boolean renewRequested = false;
/**
* Request to create a new VcObject.
* @param map
* @param moRef
*/
protected VcObjectRequest(ConcurrentMap<ManagedObjectReference, IVcCacheObject> map,
ManagedObjectReference moRef) {
super(Profiler.getStatsEntry(StatsType.VC_LOAD_REQ, moRef));
this.map = map;
this.moRef = moRef;
AuAssert.check(Thread.holdsLock(map));
map.put(moRef, this);
}
/**
* Request to update an existing VcObject.
* @param map
* @param obj
*/
protected VcObjectRequest(ConcurrentMap<ManagedObjectReference, IVcCacheObject> map,
VcObjectImpl obj, EnumSet<UpdateType> updates) {
super(Profiler.getStatsEntry(StatsType.VC_UPDATE_REQ, obj));
this.map = map;
this.moRef = obj.getMoRef();
this.updateObj = obj;
this.updates = updates;
AuAssert.check(Thread.holdsLock(map));
map.put(moRef, this);
}
@Override
public ManagedObjectReference getMoRef() {
return moRef;
}
/*
* Block for result, or exception on error.
*/
protected synchronized VcObjectImpl getResult() {
while (true) {
if (exception != null) {
throw AuroraException.stitchStackTraces(exception);
}
if (result != null) {
return result;
}
try {
wait();
} catch (InterruptedException e) {
throw AuroraException.INTERNAL(e);
}
}
}
/*
* Set exception object and unblock all waiters.
*/
private synchronized void setException(AuroraException e) {
AuAssert.check(exception == null && result == null);
// remove the objCache entry
map.remove(moRef);
exception = e;
notifyAll();
}
private synchronized void clearRenewRequest() {
renewRequested = false;
}
/*
* Set result object and unblock all waiters.
*/
private synchronized boolean setResult(VcObjectImpl obj) {
AuAssert.check(exception == null && result == null);
AuAssert.check(obj.getMoRef().equals(moRef));
if (renewRequested) {
return false;
}
// replace the objCache entry with real object
map.put(moRef, obj);
result = obj;
notifyAll();
return true;
}
/*
* Execute this request.
* Note: should only be called by VcCacheThread
*/
@Override
protected boolean execute() {
try {
/*
* Repeat until we successfully set the result.
*/
while (true) {
clearRenewRequest();
VcObjectImpl obj;
try {
obj = VcContext.inVcSessionDo(new VcSession<VcObjectImpl>() {
public VcObjectImpl body() throws Exception {
if (updateObj != null) {
updateObj.updateInternal(updates);
return updateObj;
} else {
return VcObjectImpl.loadFromMoRef(moRef);
}
}
});
} catch (AuroraException e) {
if (e.getCause() instanceof ManagedObjectNotFound) {
// Set INVALID_MOREF if the managed object doesn't exist.
setException(VcException.INVALID_MOREF(MoUtil.morefToString(moRef)));
} else {
setException(e);
}
return true;
}
if (setResult(obj)) {
return true;
}
}
} catch (Throwable e) {
setException(VcException.GENERAL_ERROR(e));
}
return true;
}
/*
* Abort this request by sending exception to all requesters.
* Note: should only be called by VcCacheThread
*/
protected void abort() {
setException(AuroraException.INTERNAL());
}
/**
* Renew this request so that we'll fetch new data.
* @return null on success and the object if the request was too late.
*/
protected synchronized VcObjectImpl renew() {
if (result != null) {
/* The renew request came in too late.
* Return the existing object and let the caller handle it.
*/
return result;
}
if (exception != null) {
// no need to do anything
return null;
}
renewRequested = true;
return null;
}
/**
* Add update requirements and renew request if it's already finished.
* @param updates
* @return
*/
protected synchronized VcObjectImpl addUpdates(EnumSet<UpdateType> updates) {
this.updates.addAll(updates);
return renew();
}
@Override
protected void cleanup() {
AuAssert.unreachable();
}
}