Java tutorial
/* * 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.globus.workspace.Counter; import org.globus.workspace.Lager; import org.globus.workspace.WorkspaceConstants; import org.globus.workspace.WorkspaceException; import org.globus.workspace.accounting.AccountingEventAdapter; import org.globus.workspace.persistence.DataConvert; import org.globus.workspace.persistence.PersistenceAdapter; import org.globus.workspace.service.InstanceResource; import org.globus.workspace.service.binding.BindNetwork; import org.globus.workspace.service.binding.BindingAdapter; import org.globus.workspace.service.binding.GlobalPolicies; import org.globus.workspace.service.binding.authorization.CreationAuthorizationCallout; import org.globus.workspace.service.binding.authorization.Decision; import org.globus.workspace.service.binding.authorization.PostTaskAuthorization; import org.globus.workspace.service.binding.vm.FileCopyNeed; import org.globus.workspace.service.binding.vm.VirtualMachine; import org.nimbustools.api.repr.ShutdownTasks; import org.nimbustools.api.services.rm.AuthorizationException; import org.nimbustools.api.services.rm.DestructionCallback; import org.nimbustools.api.services.rm.DoesNotExistException; import org.nimbustools.api.services.rm.ManageException; import org.nimbustools.api.services.rm.OperationDisabledException; import org.nimbustools.api.services.rm.StateChangeCallback; import org.nimbustools.api.services.rm.CreationException; import java.net.URI; import java.net.URISyntaxException; import java.text.DateFormat; import java.util.Calendar; import java.util.LinkedList; import java.util.List; /** * More organization (and docs) coming. This class is too tightly coupled. */ public abstract class InstanceResourceImpl implements InstanceResource { // ------------------------------------------------------------------------- // STATIC VARIABLES // ------------------------------------------------------------------------- private static final Log logger = LogFactory.getLog(InstanceResourceImpl.class.getName()); private static DateFormat localFormat = DateFormat.getDateTimeInstance(); // ------------------------------------------------------------------------- // INSTANCE VARIABLES // ------------------------------------------------------------------------- protected final PersistenceAdapter persistence; protected final BindingAdapter binding; protected final BindNetwork bindNetwork; protected final GlobalPolicies globals; protected final DataConvert dataConvert; protected final Lager lager; protected final Counter pendingNotifications = new Counter(0, null); protected final List destructionListeners = new LinkedList(); protected final List stateListeners = new LinkedList(); // optionally set protected AccountingEventAdapter accounting; protected CreationAuthorizationCallout authzCallout; protected int id = -1; protected String name; protected VirtualMachine vm; protected String ensembleId; protected String groupId; protected boolean lastInGroup; protected int groupSize = 1; protected boolean partOfGroupRequest; protected int launchIndex; protected Calendar terminationTime; protected Calendar startTime; protected volatile int state = WorkspaceConstants.STATE_INVALID; protected volatile int targetState = WorkspaceConstants.STATE_INVALID; protected boolean opsEnabled; protected boolean vmmAccessOK = true; protected volatile Throwable throwableForState; // currently, mgmt policy limited to one entity, the create() caller protected String creatorID; protected String clientToken; protected double chargeRatio = 1.0; // ------------------------------------------------------------------------- // CONSTRUCTOR // ------------------------------------------------------------------------- protected InstanceResourceImpl(PersistenceAdapter persistenceImpl, BindingAdapter bindingImpl, GlobalPolicies globalsImpl, DataConvert dataConvertImpl, Lager lagerImpl, BindNetwork bindNetworkImpl) { if (lagerImpl == null) { throw new IllegalArgumentException("lagerImpl may not be null"); } this.lager = lagerImpl; if (persistenceImpl == null) { throw new IllegalArgumentException("persistenceImpl may not be null"); } this.persistence = persistenceImpl; if (bindingImpl == null) { throw new IllegalArgumentException("bindingImpl may not be null"); } this.binding = bindingImpl; if (globalsImpl == null) { throw new IllegalArgumentException("globalsImpl may not be null"); } this.globals = globalsImpl; if (dataConvertImpl == null) { throw new IllegalArgumentException("da may not be null"); } this.dataConvert = dataConvertImpl; if (bindNetworkImpl == null) { throw new IllegalArgumentException("bindNetworkImpl may not be null"); } this.bindNetwork = bindNetworkImpl; } // ------------------------------------------------------------------------- // OPTIONAL MODULES SETTERS // ------------------------------------------------------------------------- public void setAuthzCallout(CreationAuthorizationCallout callout) { this.authzCallout = callout; } public void setAccountingEventAdapter(AccountingEventAdapter events) { this.accounting = events; } // ------------------------------------------------------------------------- // CREATION // ------------------------------------------------------------------------- public Calendar getStartTime() { return this.startTime; } public void setStartTime(Calendar start) { this.startTime = start; } /** * Only called when this resource is created. * * @param id id# * @param vm instantiation details * @param startTime start, can be null * @param termTime resource destruction time, can be null which mean either * "never" or "no setting" (while using best-effort sched) * @param node assigned node, can be null * @param chargeRatio ratio to compute the real minutes charge, typically <= 1.0 and > 0 * @throws CreationException problem */ public void populate(int id, VirtualMachine vm, Calendar startTime, Calendar termTime, String node, double chargeRatio) throws CreationException { if (vm == null) { throw new CreationException("null vm"); } if (vm.getDeployment() == null) { throw new CreationException("null deployment information"); } this.id = id; this.vm = vm; this.chargeRatio = chargeRatio; // could be null if best effort, infinite for timebeing this.terminationTime = termTime; // could be null if best effort this.startTime = startTime; this.name = vm.getName(); this.setInitialState(WorkspaceConstants.STATE_FIRST_LEGAL, null); this.setInitialTargetState(this.vm.getDeployment().getRequestedState()); this.vm.setNode(node); if (lager.traceLog) { String msg = "populating " + Lager.id(this.id) + ", resource " + "termination time = "; if (this.terminationTime != null) { msg += localFormat.format(this.terminationTime.getTime()); } else { msg += "not set"; } logger.trace(msg); } } // ------------------------------------------------------------------------- // GENERAL INFORMATION // ------------------------------------------------------------------------- public int getID() { return this.id; } public void setID(int idnum) { this.id = idnum; } public String getName() { return this.name; } public void setName(String resourceName) { this.name = resourceName; } public String getEnsembleId() { return this.ensembleId; } public void setEnsembleId(String ensembleId) { this.ensembleId = ensembleId; } public String getGroupId() { return this.groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public boolean isLastInGroup() { return this.lastInGroup; } public void setLastInGroup(boolean lastInGroup) { this.lastInGroup = lastInGroup; } public int getGroupSize() { return this.groupSize; } public void setGroupSize(int groupSize) { this.groupSize = groupSize; } public String getCreatorID() { return this.creatorID; } public void setCreatorID(String ID) { this.creatorID = ID; } public String getClientToken() { return clientToken; } public void setClientToken(String clientToken) { this.clientToken = clientToken; } public void setChargeRatio(double chargeRatio) { if (chargeRatio < 0) { throw new IllegalArgumentException("charge ratio can not be < 0"); } this.chargeRatio = chargeRatio; } public double getChargeRatio() { return this.chargeRatio; } public VirtualMachine getVM() { return this.vm; } public boolean isPropagateRequired() { return this.vm.isPropagateRequired(); } public boolean isUnPropagateRequired() { return this.vm.isUnPropagateRequired(); } public boolean isPropagateStartOK() { return this.vm.isPropagateStartOK(); } public boolean isVMMaccessOK() { return this.vmmAccessOK; } public void setVMMaccessOK(boolean accessOK) { this.vmmAccessOK = accessOK; try { this.persistence.setVMMaccessOK(this.id, accessOK); } catch (ManageException e) { logger.error("", e); } } public void setInitialVMMaccessOK(boolean accessOK) { this.vmmAccessOK = accessOK; } public synchronized void newFileCopyNeed(FileCopyNeed need) { if (this.vm == null) { throw new IllegalStateException("vm is null"); } this.vm.addFileCopyNeed(need); try { this.persistence.addCustomizationNeed(this.id, need); } catch (ManageException e) { logger.error("", e); // TODO } } public synchronized void newHostname(String hostname) { if (this.vm == null) { logger.fatal(Lager.id(this.id) + ": could not set hostname, no vm"); return; } this.vm.setNode(hostname); try { this.persistence.setHostname(this.id, hostname); } catch (ManageException e) { logger.error("", e); } } public synchronized void newStartTime(Calendar startTime) { this.setStartTime(startTime); try { this.persistence.setStartTime(this.id, startTime); } catch (ManageException e) { logger.error("", e); } } public synchronized void newStopTime(Calendar stopTime) { // this only in fact sets term time, scheduler is the only thing // that currently needs stop time, but todo: stop should be stored // in workspace resource for future querying from client (since // with best effort scheduler it is not always known at create time // and therefore returned to client) try { stopTime.add(Calendar.SECOND, this.globals.getTerminationOffsetSeconds()); } catch (Exception e) { logger.error(e.getMessage(), e); return; } this.terminationTime = stopTime; try { this.persistence.setTerminationTime(this.id, stopTime); } catch (ManageException e) { logger.error("", e); } } public synchronized void newNetwork(String network) { if (this.vm == null) { logger.fatal(Lager.id(this.id) + ": could not set network, no vm"); return; } this.vm.setNetwork(network); try { this.persistence.setNetwork(this.id, network); } catch (ManageException e) { logger.error("", e); } } public boolean isPartOfGroupRequest() { return this.partOfGroupRequest; } public void setPartOfGroupRequest(boolean isInGroupRequest) { this.partOfGroupRequest = isInGroupRequest; } public int getLaunchIndex() { return this.launchIndex; } public void setLaunchIndex(int launchIndex) { this.launchIndex = launchIndex; } // ------------------------------------------------------------------------- // REMOTE OPERATION POLICY // ------------------------------------------------------------------------- /** * @return true if client is permitted to invoke WS operations * other than destroy */ public boolean isOpsEnabled() { return this.opsEnabled; } /** * For creating the object in the first place; setOpsEnabled calls * persistenceAdapter.setOpsEnabled() - but entry for id does not * exist yet when setInitialOpsEnabled() is needed. * * @see PersistenceAdapter#load(int, org.globus.workspace.service.InstanceResource) ) * @param enabled true if enabled */ public void setInitialOpsEnabled(boolean enabled) { this.opsEnabled = enabled; } /** * For regular changes during the life of the workspace. * * @param enabled true if enabled */ public synchronized void setOpsEnabled(boolean enabled) { if (enabled != this.opsEnabled) { this.opsEnabled = enabled; try { this.persistence.setOpsEnabled(this.id, enabled); } catch (ManageException e) { logger.error("", e); } if (lager.eventLog) { String msg = "disabled"; if (enabled) { msg = "enabled"; } logger.info(Lager.ev(this.id) + " WS-operations " + msg); } } } // ------------------------------------------------------------------------- // REMOTE CLIENT MANIPULATION // ------------------------------------------------------------------------- public void start() throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { start(WorkspaceConstants.STATE_STARTED); } else { throw new OperationDisabledException("start is currently disabled"); } } private void start(int target) throws ManageException { if (lager.traceLog) { logger.trace( "_start(): " + Lager.id(this.id) + "current state is " + this.dataConvert.stateName(this.state) + ", current target is " + this.dataConvert.stateName(this.targetState) + ", target is " + this.dataConvert.stateName(target)); } this.setTargetState(target); } public void shutdown(ShutdownTasks tasks) throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { this.handlePostTasks(tasks, false); this._shutdown(WorkspaceConstants.STATE_PROPAGATED); } else { throw new OperationDisabledException("Shutdown is currently disabled"); } } public void shutdownSave(ShutdownTasks tasks) throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { this.handlePostTasks(tasks, true); this._shutdown(WorkspaceConstants.STATE_READY_FOR_TRANSPORT); } else { throw new OperationDisabledException("ReadyForTransport (shutdown-save) is currently disabled"); } } public void pause(ShutdownTasks tasks) throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { this.handlePostTasks(tasks, false); this._shutdown(WorkspaceConstants.STATE_PAUSED); } else { throw new OperationDisabledException("Pause is currently disabled"); } } public void serialize(ShutdownTasks tasks) throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { this.handlePostTasks(tasks, false); this._shutdown(WorkspaceConstants.STATE_SERIALIZED); } else { throw new OperationDisabledException("Serialize is currently disabled"); } } public void reboot(ShutdownTasks tasks) throws ManageException, OperationDisabledException { if (this.isOpsEnabled()) { this.handlePostTasks(tasks, false); this._shutdown(WorkspaceConstants.STATE_REBOOT); } else { throw new OperationDisabledException("Reboot is currently disabled"); } } private void handlePostTasks(ShutdownTasks tasks, boolean isReadyForTransport) throws ManageException { if (tasks == null) { return; // *** EARLY RETURN *** } URI target = tasks.getBaseFileUnpropagationTarget(); if (target == null) { return; // *** EARLY RETURN *** } // technically possible but only someone else's client implementation // would allow this to happen if (!isReadyForTransport) { final String err = "Post-shutdown tasks are only compatible " + "with a ReadyForTransport request."; throw new ManageException(err); } if (this.authzCallout != null && this.authzCallout instanceof PostTaskAuthorization) { // shortcut: currently we know owner is the caller at this point final String dnToAuthorize = this.creatorID; final Integer decision; String newTargetName; try { decision = ((PostTaskAuthorization) this.authzCallout).isRootPartitionUnpropTargetPermitted(target, dnToAuthorize); } catch (AuthorizationException e) { throw new ManageException(e.getMessage(), e); } if (!Decision.PERMIT.equals(decision)) { throw new ManageException("request denied, no message for client"); } } final String trueTarget; if (tasks.isAppendID()) { trueTarget = target.toASCIIString() + "-" + this.id; // check new uri syntax: try { new URI(trueTarget); } catch (URISyntaxException e) { throw new ManageException(e.getMessage(), e); } } else { trueTarget = target.toASCIIString(); } this.vm.overrideRootUnpropTarget(trueTarget, logger); if (!this.isUnPropagateRequired()) { this.vm.setUnPropagateRequired(true); } this.persistence.setRootUnpropTarget(this.id, trueTarget); } private void _shutdown(int target) throws ManageException { if (lager.traceLog) { logger.trace("shutdown(): " + Lager.id(this.id) + ", " + "target state = " + target); } try { this.setTargetState(target); } catch (Exception e) { final String err = "problem shutting down " + Lager.id(this.id) + ": " + e.getMessage(); logger.error(err, e); // setting to corrupted and/or deciding to destroy depends on the // situation and has already happened via the state machine throw new ManageException(err, e); } } // ------------------------------------------------------------------------- // LISTENERS // ------------------------------------------------------------------------- public void registerStateChangeListener(StateChangeCallback listener) { if (listener == null) { logger.warn("Something sent null state change listener, ignoring"); return; // *** EARLY RETURN *** } // re-registrations expected after a container crash synchronized (this.stateListeners) { this.stateListeners.add(listener); } } protected void expungeStateListeners() { this.stateListeners.clear(); } public void registerDestructionListener(DestructionCallback listener) { if (listener == null) { logger.warn("Something sent null destruction listener, ignoring"); return; // *** EARLY RETURN *** } // re-registrations expected after a container crash synchronized (this.destructionListeners) { this.destructionListeners.add(listener); } } protected void resourceDestroyed() { this.expungeStateListeners(); // todo: assuming listeners will quickly return from the notification, // should not assume that about all future messaging layers, so in the // future launch a separate thread to then make the callbacks (there // are many articles about this to consult) synchronized (this.destructionListeners) { for (int i = 0; i < this.destructionListeners.size(); i++) { try { final DestructionCallback dc = (DestructionCallback) this.destructionListeners.get(i); if (dc != null) { dc.destroyed(); } } catch (Throwable t) { final String err = "Problem with asynchronous " + "destruction notification: " + t.getMessage(); logger.error(err, t); } } // never happens again this.destructionListeners.clear(); } } // ------------------------------------------------------------------------- // REMOVAL // ------------------------------------------------------------------------- /** * Called when termination time passes or if client invoked destroy * operation. * * This invokes all it can to remove all traces of the workspace, * including cancelling any current transfers or calling shutdown + * trash on the backend for example. * * It currently blocks until finished. * * Don't call unless you are managing the instance cache (or not using * one, perhaps). * * @return true if remove succeeded * @throws ManageException problem with removal */ public synchronized boolean remove() throws ManageException { if (this.lager.eventLog || this.lager.traceLog) { if (this.lager.eventLog) { logger.info(Lager.ev(this.id) + "destroy begins"); } else if (this.lager.traceLog) { logger.trace(Lager.id(this.id) + "destroy begins"); } } this.setTargetStateUnderLockEvaluate(WorkspaceConstants.STATE_DESTROYING); if (this.getState() != WorkspaceConstants.STATE_DESTROY_SUCCEEDED) { logger.debug(Lager.id(this.id) + " destroy failed (state " + this.getState() + ")"); return false; } if (this.lager.eventLog) { logger.info(Lager.ev(this.id) + "destroyed ('" + this.name + "')"); } else if (this.lager.traceLog) { logger.trace(Lager.id(this.id) + " destroyed ('" + this.name + "')"); } // inform any destruction listeners: this.resourceDestroyed(); return true; } public synchronized void cleanup() { do_remove(); } protected void do_remove() { // scheduler already notified by doStateChange() logger.debug("removing " + Lager.id(this.id)); // notify accounting if (this.accounting == null) { if (lager.accounting) { logger.debug("accounting not available, " + "no stop event generated"); } } else { final String network; final String resource; if (this.vm != null) { network = this.vm.getNetwork(); resource = this.vm.getNode(); } else { network = null; resource = null; } if (this.startTime == null) { if (lager.accounting) { logger.debug("no start time available (will happen with " + "best effort scheduler with destruction coming " + "before any resource assignment"); } this.accounting.destroy(this.id, this.getCreatorID(), 0L, this.chargeRatio); } else { final long runningTimeMS = Calendar.getInstance().getTimeInMillis() - this.startTime.getTimeInMillis(); // convert milliseconds to minutes, take ceiling long runningTime = runningTimeMS / 60000L; if (runningTimeMS % 60000L > 0L) { runningTime += 1L; } this.accounting.destroy(this.id, this.getCreatorID(), runningTime, this.chargeRatio); } } try { logger.debug("backing out allocations for " + Lager.id(this.id)); this.bindNetwork.backOutIPAllocations(vm); //this.binding.backOutAllocations(this.vm); } catch (WorkspaceException e) { // candidate for admin log/trigger of severe issues final String err = "error retiring allocations, " + Lager.id(this.id); logger.error(err, e); } try { this.persistence.remove(this.id, this); } catch (Throwable t) { // nothing more we can really do about this // candidate for admin log/trigger of severe issues logger.fatal("error removing " + Lager.id(this.id) + " from persistence layer: " + t.getMessage(), t); } // After remove is done, home notifies subscribers of termination } public void load(String key) throws ManageException, DoesNotExistException { final int keyInt; try { keyInt = Integer.parseInt(key); } catch (Throwable t) { final String err = "invalid key: " + key; throw new ManageException(err, t); } this.id = keyInt; if (lager.traceLog) { logger.trace("load(): " + Lager.id(this.id)); } this.persistence.load(keyInt, this); } public Calendar getTerminationTime() { return this.terminationTime; } public void setTerminationTime(Calendar time) { this.terminationTime = time; } }