org.globus.workspace.service.impls.WorkspaceHomeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.workspace.service.impls.WorkspaceHomeImpl.java

Source

/*
 * Copyright 1999-2008 University of Chicago
 *
 * 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 org.globus.workspace.service.impls;

import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Executors;
import edu.emory.mathcs.backport.java.util.concurrent.FutureTask;
import edu.emory.mathcs.backport.java.util.concurrent.ScheduledThreadPoolExecutor;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import edu.emory.mathcs.backport.java.util.concurrent.locks.Lock;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.globus.workspace.Lager;
import org.globus.workspace.LockManager;
import org.globus.workspace.RepoFileSystemAdaptor;
import org.globus.workspace.persistence.PersistenceAdapter;
import org.globus.workspace.persistence.WorkspaceDatabaseException;
import org.globus.workspace.persistence.DataConvert;
import org.globus.workspace.scheduler.Scheduler;
import org.globus.workspace.service.InstanceResource;
import org.globus.workspace.service.Sweepable;
import org.globus.workspace.service.WorkspaceHome;
import org.globus.workspace.service.CurrentVMs;
import org.globus.workspace.xen.XenUtil;
import org.nimbustools.api.services.rm.CreationException;
import org.nimbustools.api.services.rm.DoesNotExistException;
import org.nimbustools.api.services.rm.ManageException;
import org.nimbustools.api.repr.vm.NIC;
import org.nimbustools.api.repr.CannotTranslateException;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public abstract class WorkspaceHomeImpl implements WorkspaceHome, CurrentVMs {

    // -------------------------------------------------------------------------
    // STATIC VARIABLES
    // -------------------------------------------------------------------------

    private static final Log logger = LogFactory.getLog(WorkspaceHomeImpl.class.getName());

    private static final InstanceResource[] EMPTY_RESOURCE_ARRAY = new InstanceResource[0];

    // -------------------------------------------------------------------------
    // INSTANCE VARIABLES
    // -------------------------------------------------------------------------

    protected final PersistenceAdapter persistence;
    protected final LockManager lockManager;
    protected final ExecutorService executor;
    protected final Cache cache;
    protected final Lager lager;
    protected final DataConvert dataConvert;
    protected Scheduler scheduler;
    protected RepoFileSystemAdaptor repoAdaptor;

    // perhaps quartz in the future
    protected ScheduledThreadPoolExecutor scheduledExecutor;

    // see comment in initialize()
    private boolean initialized;

    // from configs
    private String backendPath;
    private String sshPath;
    private String scpPath;
    private String sshAccount;
    private String sshIdentityFile;
    private String threadPoolInitialSize;
    private String threadPoolMaxSize;
    private long sweeperDelay = 60000;

    // -------------------------------------------------------------------------
    // CONSTRUCTOR
    // -------------------------------------------------------------------------

    /**
     * see class level comments, a little overly coupled in this release
     */
    public WorkspaceHomeImpl(PersistenceAdapter db, LockManager lockManagerImpl, CacheManager cacheManager,
            DataConvert dataConvert, Lager lagerImpl) {

        if (db == null) {
            throw new IllegalArgumentException("db may not be null");
        }
        this.persistence = db;

        if (lockManagerImpl == null) {
            throw new IllegalArgumentException("lockManager may not be null");
        }
        this.lockManager = lockManagerImpl;

        if (cacheManager == null) {
            throw new IllegalArgumentException("cacheManager may not be null");
        }

        this.cache = cacheManager.getCache("instanceCache");
        if (this.cache == null) {
            throw new IllegalArgumentException("cacheManager does not provide 'instanceCache'");
        }

        if (dataConvert == null) {
            throw new IllegalArgumentException("dataConvert may not be null");
        }
        this.dataConvert = dataConvert;

        if (lagerImpl == null) {
            throw new IllegalArgumentException("lager may not be null");
        }
        this.lager = lagerImpl;

        // todo: options etc. by wrapping as IoC bean
        this.executor = Executors.newCachedThreadPool();
    }

    // -------------------------------------------------------------------------
    // MODULE SET (avoids circular dependency problem)
    // -------------------------------------------------------------------------

    public void setScheduler(Scheduler schedulerImpl) {
        if (schedulerImpl == null) {
            throw new IllegalArgumentException("schedulerImpl may not be null");
        }
        this.scheduler = schedulerImpl;
    }

    public void setRepoAdaptor(RepoFileSystemAdaptor ra) {
        this.repoAdaptor = ra;
        XenUtil.setRepoAdaptor(ra);
    }

    // -------------------------------------------------------------------------
    // SETTERS (from outside config)
    // -------------------------------------------------------------------------

    public void setBackendPath(String path) {
        this.backendPath = path;
    }

    public void setSshPath(String ssh) {
        this.sshPath = ssh;
    }

    public void setScpPath(String scp) {
        this.scpPath = scp;
    }

    public void setSshAccount(String account) {
        this.sshAccount = account;
    }

    public void setSshIdentityFile(String idfile) {
        this.sshIdentityFile = idfile;
    }

    public void setThreadPoolInitialSize(String initialSize) {
        this.threadPoolInitialSize = initialSize;
    }

    public void setThreadPoolMaxSize(String maxSize) {
        this.threadPoolMaxSize = maxSize;
    }

    public void setSweeperDelay(long delay) {
        this.sweeperDelay = delay;
    }

    // -------------------------------------------------------------------------
    // IoC INIT METHOD
    // -------------------------------------------------------------------------

    public synchronized void validate() throws Exception {

        logger.debug("validating/initializing");

        if (this.initialized) {
            throw new Exception("already initialized, illegal to initialize " + "more than once");
        }

        // partial init counts
        this.initialized = true;

        // todo: all this static stuff will go away in favor of these things
        //       being IoC beans etc.
        WorkspaceHomeInit.initializeRequestDispatch(this.threadPoolInitialSize, this.threadPoolMaxSize);
        WorkspaceHomeInit.initializeSSH(this.sshPath, this.scpPath, this.sshAccount, this.sshIdentityFile);

        // todo: temporary hack, RequiredVMM implementation and configurations
        //       will be more encapsulated in the future
        final InstanceResource vw = this.newEmptyResource();
        if (vw instanceof Xen) {
            if (this.backendPath == null) {
                throw new Exception("workspace control executable path is not configured");
            }
            XenUtil.setWorksp(this.backendPath);
        }

        logger.debug("validated/initialized");
    }

    // -------------------------------------------------------------------------
    // ID TYPE CONVERSIONS, ID VALUE RESTRICTION CHECKS
    // -------------------------------------------------------------------------

    public int convertID(String id) throws ManageException {
        final int idInt;
        try {
            idInt = Integer.parseInt(id);
            if (idInt < 1) {
                throw new ManageException("ID may not be less than one");
            }
        } catch (NumberFormatException e) {
            throw new ManageException("Requiring instance IDs be " + "integers, for now: " + e.getMessage(), e);
        }
        return idInt;
    }

    public String convertID(int id) throws ManageException {
        if (id < 1) {
            throw new ManageException("ID may not be less than one");
        }
        return String.valueOf(id);
    }

    // -------------------------------------------------------------------------
    // NEW WORKSPACE RESOURCES
    // -------------------------------------------------------------------------

    // default configuration has this provided on the fly via IoC
    protected abstract InstanceResource newEmptyResource();

    public InstanceResource newInstance(int id) throws CreationException {
        final String idStr;
        try {
            idStr = this.convertID(id);
            return this.newInstance(idStr);
        } catch (ManageException e) {
            throw new CreationException(e.getMessage(), e);
        }
    }

    protected InstanceResource newInstance(String idStr) throws CreationException, ManageException {

        if (idStr == null) {
            throw new IllegalArgumentException("idStr may not be null");
        }

        final InstanceResource resource;

        final Lock lock = this.lockManager.getLock(idStr);
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new CreationException(e.getMessage(), e);
        }

        try {

            final Element el = this.cache.get(idStr);
            if (el == null) {
                resource = this.newEmptyResource();
                this.cache.put(new Element(idStr, resource));
            } else {
                throw new CreationException("ID collision, ID '" + idStr + "' already in cache");
            }

        } finally {
            lock.unlock();
        }

        return resource;
    }

    // -------------------------------------------------------------------------
    // FIND
    // -------------------------------------------------------------------------

    public InstanceResource find(String id)

            throws ManageException, DoesNotExistException {

        if (id == null) {
            throw new ManageException("id may not be null");
        }

        final InstanceResource resource;

        final Lock lock = this.lockManager.getLock(id);
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new ManageException(e.getMessage(), e);
        }

        try {

            final Element el = this.cache.get(id);
            if (el == null) {
                resource = this.newInstance(id);
                resource.load(id); // throws DoesNotExistException if not in db

                final Calendar currTime = Calendar.getInstance();
                final Calendar termTime = resource.getTerminationTime();
                if (termTime != null && termTime.before(currTime)) {
                    boolean destroyed = this.destroy(id);
                    if (destroyed) {
                        throw new DoesNotExistException(Lager.id(id) + " expired");
                    }
                }

            } else {
                resource = (InstanceResource) el.getObjectValue();
            }

        } catch (DoesNotExistException e) {
            this.cache.remove(id);
            throw e;
        } catch (CreationException e) {
            throw new ManageException(e.getMessage(), e); // ...
        } finally {
            lock.unlock();
        }

        return resource;
    }

    public InstanceResource find(int id) throws ManageException, DoesNotExistException {

        return this.find(this.convertID(id));
    }

    // TODO: make this termination process less expensive and memory consuming
    // TODO: in particular, push this responsibility to Scheduler, right now
    //       this is just quickly mimicking the old, inefficient setup from GT.
    public Sweepable[] currentSweeps() {
        final int[] keys;
        try {
            keys = this.persistence.findActiveWorkspacesIDs();
        } catch (WorkspaceDatabaseException e) {
            logger.fatal(e.getMessage(), e);
            return null; // *** EARLY RETURN ***
        }

        if (keys == null || keys.length == 0) {
            return null; // *** EARLY RETURN ***
        }

        final Sweepable[] sweeps = new Sweepable[keys.length];
        for (int i = 0; i < keys.length; i++) {
            final int key = keys[i];
            try {
                sweeps[i] = this.find(key);
            } catch (ManageException e) {
                final String err = Lager.id(keys[i]) + " " + e.getMessage();
                if (logger.isDebugEnabled()) {
                    logger.error(err, e);
                } else {
                    logger.error(err);
                }
            } catch (DoesNotExistException e) {
                logger.debug(Lager.id(keys[i]) + " " + e.getMessage());
            }
        }

        return sweeps;
    }

    // -------------------------------------------------------------------------
    // CLEANUP
    // -------------------------------------------------------------------------

    public void cleanup(String id) throws ManageException, DoesNotExistException {

        if (id == null) {
            throw new IllegalArgumentException("id may not be null");
        }

        this._cleanup(this.convertID(id));
        this.cache.remove(id);
    }

    public void _cleanup(int id) throws ManageException, DoesNotExistException {
        final Lock destroy_lock = this.lockManager.getLock("destroy_" + id);
        final Lock lock = this.lockManager.getLock(id);
        try {
            destroy_lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new ManageException(e.getMessage(), e);
        }

        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            destroy_lock.unlock();
            throw new ManageException(e.getMessage(), e);
        }

        try {
            final InstanceResource resource = this.find(id);
            this.scheduler.cleanup(id);
            resource.cleanup();
        } finally {
            lock.unlock();
            destroy_lock.unlock();
        }
    }

    // -------------------------------------------------------------------------
    // DESTROY
    // -------------------------------------------------------------------------

    public boolean destroy(int id) throws ManageException, DoesNotExistException {

        return this.destroy(this.convertID(id));
    }

    /**
     * @param id key
     * @throws ManageException error
     * @throws DoesNotExistException already gone
     */
    public boolean destroy(String id)

            throws ManageException, DoesNotExistException {

        boolean destroyed;

        if (id == null) {
            throw new IllegalArgumentException("id may not be null");
        }

        final Lock destroy_lock = this.lockManager.getLock("destroy_" + id);
        final Lock lock = this.lockManager.getLock(id);
        try {
            destroy_lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new ManageException(e.getMessage(), e);
        }

        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            destroy_lock.unlock();
            throw new ManageException(e.getMessage(), e);
        }

        try {
            final InstanceResource resource = this.find(id);
            destroyed = resource.remove();
            if (destroyed) {
                this.cache.remove(id);
            }

        } finally {
            lock.unlock();
            destroy_lock.unlock();
        }

        return destroyed;
    }

    public String destroyMultiple(int[] workspaces, String sourceStr) {
        return this.destroyMultiple(workspaces, sourceStr, false);
    }

    public String destroyMultiple(int[] workspaces, String sourceStr, boolean block) {

        final FutureTask[] tasks = new FutureTask[workspaces.length];
        for (int i = 0; i < workspaces.length; i++) {
            tasks[i] = new FutureTask(new DestroyFutureTask(workspaces[i], this, block));
        }

        for (int i = 0; i < tasks.length; i++) {
            this.executor.submit(tasks[i]);
        }

        final StringBuilder buf = new StringBuilder(tasks.length * 256);

        // Log any unexpected errors.  Wait twenty seconds (normal destroy time
        // should be a matter of seconds even if there is high congestion).
        // todo: make timeout configurable
        for (int i = 0; i < tasks.length; i++) {
            try {
                final String msg = (String) tasks[i].get(20L, TimeUnit.SECONDS);
                if (msg != null) {
                    buf.append(msg);
                }
            } catch (Exception e) {
                final String msg = "Error removing workspace #" + workspaces[i] + " " + sourceStr + ": "
                        + e.getMessage();
                logger.error(msg);
                buf.append(msg);
                if (lager.traceLog) {
                    logger.error("[TRACE] " + msg, e);
                }
            }
        }

        final String ret = buf.toString();
        if (ret.trim().length() == 0) {
            return null;
        } else {
            return ret;
        }
    }

    // -------------------------------------------------------------------------
    // LIFECYCLE
    // -------------------------------------------------------------------------

    public void shutdownImmediately() {
        if (this.scheduledExecutor != null) {
            this.scheduledExecutor.shutdownNow();
        }
        if (this.executor != null) {
            this.executor.shutdownNow();
        }
        if (this.cache != null) {
            this.cache.removeAll();
        }
    }

    /**
     * @see org.nimbustools.api.services.rm.Manager#recover_initialize
     * @throws Exception problem
     */
    public void recover_initialize() throws Exception {

        if (!this.initialized) {
            throw new Exception("loading problem, not initialized yet");
        }

        if (this.scheduledExecutor != null) {
            // todo
            // should have a latch mechanism for handling many different layers
            // initializing this service instance; would need to coordinate
            // somehow.  currently we know that only one layer is interested
            // in registering listeners which is the problem...
            logger.debug("already recovered (multiple actors above)");
            return; // *** EARLY RETURN ***
        }

        this.recover_find_active_workspaces();

        // todo: options etc. by wrapping as IoC bean
        this.scheduledExecutor = new ScheduledThreadPoolExecutor(2);
        this.scheduledExecutor.setRemoveOnCancelPolicy(true);
        this.scheduledExecutor.setMaximumPoolSize(5);

        // Pass in the general executor, not the scheduled one.  Sweeper uses that to
        // execute any tasks it creates.
        final ResourceSweeper sweeper = new ResourceSweeper(this.executor, this, this.lager);

        // Can't use FutureTask because of the way scheduleWithFixedDelay
        // will wrap the object.  Results in just one call instead of
        // repeating (thread state gets put into "RAN" in the inner
        // callable.  Instead, made ResourceSweeper implement
        // Runnable interface and so now is native parameter to
        // scheduleWithFixedDelay method instead of wrapped in FutureTask.
        //NOPE: final FutureTask task = new FutureTask(action);

        logger.debug("Launching sweeper with " + this.sweeperDelay + "ms delay");
        this.scheduledExecutor.scheduleWithFixedDelay(sweeper, this.sweeperDelay, this.sweeperDelay,
                TimeUnit.MILLISECONDS);
    }

    /*
     * Some may need to be expired after a container crash/restart.
     */
    private void recover_find_active_workspaces() throws ManageException {

        if (lager.traceLog) {
            logger.trace("find_active_workspaces()");
        }

        final int[] keys;
        try {
            keys = this.persistence.findActiveWorkspacesIDs();
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }

        if (keys == null || keys.length == 0) {
            final String msg = "No workspaces were persisted when the" + " container last shut down";
            if (this.lager.eventLog) {
                logger.info(msg);
            } else if (logger.isDebugEnabled()) {
                logger.debug(msg);
            }
            this.scheduler.recover(0);
            return;
        }

        // TODO: add bit to set notification pending counter

        int numRecovered = 0;
        for (int i = 0; i < keys.length; i++) {
            if (lager.traceLog) {
                logger.trace("found #" + keys[i] + " in DB");
            }

            // To better support 1000s of resources, we should create a custom
            // query for ones that need to be terminated

            try {
                this.find(keys[i]);
                if (lager.eventLog) {
                    logger.info(Lager.ev(keys[i]) + "recovered");
                }
                numRecovered += 1;
            } catch (DoesNotExistException e) {
                // if the workspace is terminated during the load process,
                // that is OK; but, it will return NoSuchResourceException
                if (lager.eventLog) {
                    logger.info(Lager.ev(keys[i]) + "resource terminated " + "during recovery");
                }
            } catch (ManageException e) {
                throw e;
            }
        }

        // The ones terminated during the load process will generate a
        // notification to the scheduler before this is sent (when state
        // is set to destroying, while the destroy process does block in
        // StateTransition, the notification to scheduler is sent before
        // that happens).
        this.scheduler.recover(numRecovered);
    }

    // -------------------------------------------------------------------------
    // EXTRAS
    // -------------------------------------------------------------------------

    public ExecutorService getSharedExecutor() {
        return this.executor;
    }

    public boolean isActiveWorkspaceID(int id) throws ManageException {
        try {
            return this.persistence.isActiveWorkspaceID(id);
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }
    }

    public boolean isActiveWorkspaceID(String id) throws ManageException {
        try {
            return this.persistence.isActiveWorkspaceID(this.convertID(id));
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }
    }

    public InstanceResource[] findByCaller(String callerID)

            throws ManageException {

        // not an efficient way to do this, waiting on some object model
        // revisions to do anything about it
        final int[] ids = this.findIDsByCaller(callerID);

        if (ids == null || ids.length == 0) {
            return EMPTY_RESOURCE_ARRAY;
        }

        final List resourceList = new ArrayList(ids.length);

        // Not worried about races w/ destruction here.

        for (int i = 0; i < ids.length; i++) {
            try {
                resourceList.add(this.find(ids[i]));
            } catch (DoesNotExistException e) {
                if (lager.traceLog) {
                    logger.trace("findByCaller could not retrieve " + Lager.id(ids[i]));
                }
            }
        }

        if (resourceList.isEmpty()) {
            return EMPTY_RESOURCE_ARRAY;
        } else {
            return (InstanceResource[]) resourceList.toArray(new InstanceResource[resourceList.size()]);
        }
    }

    public InstanceResource[] findByIP(String ip) throws ManageException {

        // Not an efficient way to do this, waiting on some object model
        // revisions to do anything about it.  Also, it is highly likely
        // that all VMs are in instance cache at this point.

        final int[] keys;
        try {
            keys = this.persistence.findActiveWorkspacesIDs();
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }

        if (keys == null || keys.length == 0) {
            return EMPTY_RESOURCE_ARRAY;
        }

        final List resourceList = new ArrayList(keys.length);

        // Not worried about races w/ destruction here.

        for (int key : keys) {
            try {
                InstanceResource resource = this.find(key);
                NIC[] nics = this.dataConvert.getNICs(resource.getVM());
                for (NIC nic : nics) {
                    if (nic.getIpAddress().equals(ip)) {
                        resourceList.add(resource);
                        break;
                    }
                }
            } catch (DoesNotExistException e) {
                if (lager.traceLog) {
                    logger.trace("findByIP could not retrieve " + Lager.id(key));
                }
            } catch (CannotTranslateException e) {
                logger.warn(e.getMessage());
            }
        }

        final InstanceResource[] ret;

        if (resourceList.isEmpty()) {
            ret = EMPTY_RESOURCE_ARRAY;
        } else {
            ret = (InstanceResource[]) resourceList.toArray(new InstanceResource[resourceList.size()]);
        }

        if (lager.traceLog) {
            logger.trace("findByIP found " + ret.length);
        }

        return ret;
    }

    public synchronized InstanceResource[] findAll()

            throws ManageException {

        // not an efficient way to do this, waiting on some object model
        // revisions to do anything about it
        final int[] keys;
        try {
            keys = this.persistence.findActiveWorkspacesIDs();
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }

        if (keys == null || keys.length == 0) {
            return EMPTY_RESOURCE_ARRAY;
        }

        final List resourceList = new ArrayList(keys.length);

        // Not worried about races w/ destruction here.

        for (int i = 0; i < keys.length; i++) {
            try {
                resourceList.add(this.find(keys[i]));
            } catch (DoesNotExistException e) {
                if (lager.traceLog) {
                    logger.trace("findGlobalAll could not retrieve " + Lager.id(keys[i]));
                }
            }
        }

        final InstanceResource[] ret;

        if (resourceList.isEmpty()) {
            ret = EMPTY_RESOURCE_ARRAY;
        } else {
            ret = (InstanceResource[]) resourceList.toArray(new InstanceResource[resourceList.size()]);
        }

        if (lager.traceLog) {
            logger.trace("findAll found " + ret.length);
        }

        return ret;
    }

    /**
     * @param callerID may not be null
     * @return IDs, never null, may be length zero
     * @throws ManageException problem
     */
    public int[] findIDsByCaller(String callerID)

            throws ManageException {

        try {
            return this.persistence.findVMsByOwner(callerID);
        } catch (WorkspaceDatabaseException e) {
            throw new ManageException(e.getMessage(), e);
        }
    }

    public int countIDsByCaller(String callerID)

            throws ManageException {

        return this.findIDsByCaller(callerID).length;
    }

    // -------------------------------------------------------------------------
    // OTHER
    // -------------------------------------------------------------------------

    public String getVMMReport() {
        return this.scheduler.getVMMReport();
    }

    public String[] getResourcePools() {
        try {
            return this.persistence.getResourcePools();
        } catch (WorkspaceDatabaseException e) {
            // Error logged down the call stack
            return new String[0];
        }
    }
}