/*************************************************************************** * 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.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.CmsWorker; import com.vmware.aurora.vc.VcObjectImpl.UpdateType; import com.vmware.vim.binding.vmodl.ManagedObject; import com.vmware.vim.binding.vmodl.ManagedObjectReference; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * {@link VcCache} maintains an in-memory cache for vc objects. * Each VC object is indexed by its unique moref in the cache. * * When a thread requires a reference to a VC object and misses the cache, * it creates a unique {@link VcObjectRequest} for the VC object and * queue it in {@link CmsWorker.WorkQueue.VC_QUERY_NO_DELAY}, which will * be processed in order by a CmsWorker thread. In processing the request, * a copy of the VC object is created by fetching data from VC. * The requesting threads wait on the request until the VC object has been * created and inserted into the cache. * * A VC object in the cache may have a long life and can be accessed concurrently * by multiple requesting threads. Some properties of the object are fetched * and calculated at construction time. Other properties are set to null initially * and are fetched dynamically on demand. The goal is to have all of VC operations * performed by the VC daemon, thus no VC context sessions will be needed by * requesting threads. * * Cached objects can be purged to guarantee no stale values of the VC object * can be fetched from the cache. * * Cached objects can be refreshed to update values from VC. Similar to getting * a missing VC Object, a request would be created and the update would be fulfilled * by a CmsWorker thread. * */ public class VcCache { private static Logger logger = Logger.getLogger(VcCache.class); private static VcCache instance = new VcCache(); /** * Objects mapped in VC cache. */ public interface IVcCacheObject { /** * @return the unique MoRef identifier */ ManagedObjectReference getMoRef(); } // VcObject cache private ConcurrentMap<ManagedObjectReference, IVcCacheObject> objCache; /** * Reverse map from VM to RP * * VC's VM event doesn't contain a reference to related RP, thus * it's not possible to find which RP creates or deletes a VM. We keep * a (VM->RP) reverse map to lookup an RP from VM moRef. */ private ConcurrentMap<ManagedObjectReference, ManagedObjectReference> vmRpMap; public static VcCache getInstance() { return instance; } private VcCache() { objCache = new ConcurrentHashMap<ManagedObjectReference, IVcCacheObject>(); vmRpMap = new ConcurrentHashMap<ManagedObjectReference, ManagedObjectReference>(); } /** * Reinitialize VcCache. * Should only be called by JUnit tests. */ public synchronized void restart() { // Start over. objCache = new ConcurrentHashMap<ManagedObjectReference, IVcCacheObject>(); vmRpMap = new ConcurrentHashMap<ManagedObjectReference, ManagedObjectReference>(); } public final static void putVmRpPair(ManagedObjectReference vmRef, ManagedObjectReference rpRef) { getInstance().vmRpMap.put(vmRef, rpRef); } public final static ManagedObjectReference removeVmRpPair(ManagedObjectReference vmRef) { return getInstance().vmRpMap.remove(vmRef); } private VcObject lookupVcObject(ManagedObjectReference moRef) { IVcCacheObject obj = objCache.get(moRef); if (obj instanceof VcObject) { return (VcObject)obj; } return null; } /** * Get a cached VcObject. If not cached, request a fresh copy. * @param moRef * @return the VcObject */ private VcObject getObject(ManagedObjectReference moRef) { // Should not call get object from the VcObject cache worker thread itself. VcObject obj = lookupVcObject(moRef); if (obj != null) { Profiler.inc(StatsType.VC_GET_HIT, obj); return obj; } Profiler.inc(StatsType.VC_GET_MISS, moRef); return requestObject(moRef, true, null, true); } /** * Get a cached VcObject with updates from VC. * If the object doesn't exist in the cache, creates a fresh VcObject. * @param moRef * @parem updates update requests * @return the VcObject */ protected VcObject getUpdate(ManagedObjectReference moRef, EnumSet<UpdateType> updates) { // Should not call get object from the VcObject cache worker thread itself. return requestObject(moRef, true, updates, true); } private VcObject getUpdateAsync(ManagedObjectReference moRef) { return requestObject(moRef, false, EnumSet.of(UpdateType.CONFIG), true); } /** * Sync for completing existing request on an VcObject. * If the object or a request on the object doesn't exist in VcCache, * the function returns immediately. * If the request generates an exception, it is ignored and return. * @param moRef * @return cached VcObject if a result is available, null otherwise */ private VcObject syncRequest(ManagedObjectReference moRef) { try { return requestObject(moRef, true, null, false); } catch (VcException e) { if (!e.isINVALID_MOREF()) { logger.info("got exception " + e + " while sync the object on " + moRef); } return null; } } /** * Asynchronously update a VcObject if cached. * @param moRef * @return the cached VcObject if it exists */ private VcObject refreshAsync(ManagedObjectReference moRef, EnumSet<UpdateType> updates) { return requestObject(moRef, false, updates, false); } /** * Send request to load or update a VcObject with synchronization. * * @param moRef * @param waitForRequest true if the caller blocks until the request finishes. * @param forcedUpdates the set of forced updates on the object * @param forceLoad true if force fetching the object when missing the cache. * @return the VcObject if available, null is possible if {@code waitForRequest} * is set to false. */ private VcObject requestObject(ManagedObjectReference moRef, boolean waitForRequest, final EnumSet<UpdateType> forcedUpdates, boolean forceLoad) { VcObjectRequest req = null; boolean isNewRequest = false; // Use objCache lock to atomically insert/lookup an entry. synchronized (objCache) { IVcCacheObject obj = objCache.get(moRef); // request already posted if (obj instanceof VcObjectRequest) { req = (VcObjectRequest)obj; AuAssert.check(req.getMoRef().equals(moRef)); if (forcedUpdates != null) { // Try to renew an existing request with new updates request. VcObjectImpl renewResult = req.addUpdates(forcedUpdates); if (renewResult != null) { // The existing request is done. obj = renewResult; } } } if (obj == null) { if (forceLoad) { req = new VcObjectRequest(objCache, moRef); isNewRequest = true; } else { return null; } } else if (obj instanceof VcObject) { if (forcedUpdates != null) { req = new VcObjectRequest(objCache, (VcObjectImpl)obj, forcedUpdates); isNewRequest = true; } else { return (VcObject)obj; } } } if (isNewRequest) { // post request without holding objCache lock as we may block here CmsWorker.addRequest(CmsWorker.WorkQueue.VC_QUERY_NO_DELAY, req); } if (waitForRequest && req != null) { // block to get result return req.getResult(); } else { return null; } } public static void put(ManagedObjectReference moRef, VcObject vcObject) { getInstance().objCache.put(moRef, vcObject); } /** * Remove an old VcObject mapped by moRef in the cache. * @param moRef * @return old VcObject, null if none existed */ protected VcObject remove(ManagedObjectReference moRef) { while (true) { IVcCacheObject obj; // Use objCache lock to atomically remove an entry. synchronized(objCache) { obj = objCache.get(moRef); if (obj instanceof VcObject) { objCache.remove(moRef); return (VcObject)obj; } } if (obj instanceof VcObjectRequest) { /* If renew request fails, it would return the new VC object * and we would try again. */ if (((VcObjectRequest)obj).renew() == null) { return null; } } else { AuAssert.check(obj == null); return null; } } } /** * Gets VC object from VC cache. If not present in the cache, wait for * VcCacheThread to fetch value from VC and create the object. * @param <T> * @param moRef * @return */ @SuppressWarnings("unchecked") public static <T extends VcObject> T get(ManagedObjectReference moRef) { return (T)instance.getObject(moRef); } @SuppressWarnings("unchecked") public static <T extends VcObject> T get(ManagedObject mo) { return (T)instance.getObject(mo._getRef()); } /** * Get a list of VC Objects from cache. Throws an exception if any of * the passed morefs is missing. * @param <T> * @param moRefs * @return */ public static <T extends VcObject> List<T> getList(Iterable<ManagedObjectReference> moRefs) { return getPartialList(moRefs, null); } /** * Get a list of vc objects from cache. If supplied refreshMoRef parameter * is not null, proceeds to retrieve a partial list in the face of missing * objects and initiates the update of refreshMoRef which might contain stale * pointers. * @param <T> * @param moRefs to get * @param refreshMoRef to update on any missing objects * @return */ public static <T extends VcObject> List<T> getPartialList( Iterable<ManagedObjectReference> moRefs, ManagedObjectReference refreshMoRef) { List<T> list = new ArrayList<T>(); boolean refresh = false; for (ManagedObjectReference moRef : moRefs) { try { T vcObj = VcCache.<T>get(moRef); if (vcObj != null) { list.add(vcObj); } } catch (VcException e) { if (e.isINVALID_MOREF()) { /* Ok to skip missing vc objects. */ refresh = true; } else if (e.isMOREF_NOTREADY()) { logger.info("skipping child " + moRef + ": not ready"); } else { throw e; } } } if (refresh && refreshMoRef != null) { VcCache.refresh(refreshMoRef); } return list; } /** * Looks up a VC object identified by moRef. * @param <T> * @param moRef * @return the object or null if not cached */ @SuppressWarnings("unchecked") public static <T extends VcObject> T lookup(ManagedObjectReference moRef) { return (T)instance.lookupVcObject(moRef); } /** * Loads a new copy of the VC object, with value fetched * from VC by a worker thread. * @param moRef * @return VC object */ @SuppressWarnings("unchecked") static public <T extends VcObject> T load(ManagedObjectReference moRef) { return (T)instance.getUpdate(moRef, EnumSet.of(UpdateType.CONFIG)); } /** * Synchronously update the runtime info of an object. * @param <T> * @param moRef * @return the object */ @SuppressWarnings("unchecked") static public <T extends VcObject> T loadRuntime(ManagedObjectReference moRef) { return (T)instance.getUpdate(moRef, EnumSet.of(UpdateType.RUNTIME)); } /** * Same as {@code load()} except that the caller don't block on the request. * @param <T> * @param moRef * @return */ @SuppressWarnings("unchecked") static protected <T extends VcObject> T loadAsync(ManagedObjectReference moRef) { return (T)instance.getUpdateAsync(moRef); } /** * Sync for existing request on an VcObject. * Same as {@code syncRequest()}. */ static protected VcObject sync(ManagedObjectReference moRef) { return instance.syncRequest(moRef); } static public VcObject purge(ManagedObjectReference moRef) { return instance.remove(moRef); } /** * Asynchronously refresh the values of an object if it is cached. * @param moRef * @param updates */ static private void refresh(ManagedObjectReference moRef, EnumSet<UpdateType> updates) { Profiler.inc(StatsType.VC_REFRESH); instance.refreshAsync(moRef, updates); } static public void refresh(ManagedObjectReference moRef) { refresh(moRef, EnumSet.of(UpdateType.CONFIG)); } static public void refreshRuntime(ManagedObjectReference moRef) { refresh(moRef, EnumSet.of(UpdateType.RUNTIME)); } static public void refreshAll(ManagedObjectReference moRef) { refresh(moRef, EnumSet.allOf(UpdateType.class)); } /* * Get a VC task callback to refresh a VcObject asynchronously * and wait for refresh in the VC task caller. */ static private IVcTaskCallback getRefreshVcTaskCB(final VcObject obj, final EnumSet<UpdateType> updates) { final ManagedObjectReference moref = obj.getMoRef(); return new IVcTaskCallback() { @Override public final void completeCB(VcTask task) { refresh(moref, updates); } @Override public final void syncCB() { sync(moref); /* * XXX We should be able to assert that obj == sync(moref), * as CMS always references the same copy of the VcObject. * Currently we're not enabling this as we haven't * converted all sagas to use objects. */ } }; } /** * Get a callback to refresh a VcObject asynchronously * and wait for refresh in the VC task caller. * @param obj * @return the callback */ static protected IVcTaskCallback getRefreshVcTaskCB(final VcObject obj) { return getRefreshVcTaskCB(obj, EnumSet.of(UpdateType.CONFIG)); } static protected IVcTaskCallback getRefreshRuntimeVcTaskCB(final VcObject obj) { return getRefreshVcTaskCB(obj, EnumSet.of(UpdateType.RUNTIME)); } static protected IVcTaskCallback getRefreshAllVcTaskCB(final VcObject obj) { return getRefreshVcTaskCB(obj, EnumSet.allOf(UpdateType.class)); } static private ManagedObjectReference getMoRef(String id) { ManagedObjectReference moRef = MoUtil.stringToMoref(id); if (moRef == null) { throw VcException.INVALID_MOREF(id); } return moRef; } /** * Gets a VcObject identified by id. * @param unique id of the VcObject * @return the VcObject if id is valid * @throws VcException.INVALID_MOREF if object not found */ static public <T extends VcObject> T get(String id) { return VcCache.<T>get(getMoRef(id)); } /** * Similar to get(), but return null if the object is missing. */ static public <T extends VcObject> T getIgnoreMissing(String id) { try { return VcCache.<T>get(id); } catch (VcException e) { if (e.isINVALID_MOREF()) { return null; } throw e; } } static public <T extends VcObject> T getIgnoreMissing(ManagedObjectReference moRef) { try { return VcCache.<T>get(moRef); } catch (VcException e) { if (e.isINVALID_MOREF()) { return null; } throw e; } } /** * Similar to (), but return null if the object is missing. */ static public <T extends VcObject> T loadIgnoreMissing(String id) { try { return VcCache.<T>load(getMoRef(id)); } catch (VcException e) { if (e.isINVALID_MOREF()) { return null; } throw e; } } /** * Force reload values of a VcObject. * @param id * @return * @throws Exception */ static public VcObject load(String id) { return load(getMoRef(id)); } /** * Synchronously load the runtime info of a VcObject. * @param <T> * @param id * @return */ static public <T extends VcObject> T loadRuntime(String id) { return VcCache.<T>loadRuntime(getMoRef(id)); } }