/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation: version 3 of * the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package org.ow2.proactive.resourcemanager.nodesource.infrastructure; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import org.objectweb.proactive.core.config.CentralPAPropertyRepository; import org.objectweb.proactive.core.node.Node; import org.ow2.proactive.resourcemanager.authentication.Client; import org.ow2.proactive.resourcemanager.common.NodeState; import org.ow2.proactive.resourcemanager.common.event.RMEventType; import org.ow2.proactive.resourcemanager.common.event.RMNodeEvent; import org.ow2.proactive.resourcemanager.exception.RMException; import org.ow2.proactive.resourcemanager.nodesource.NodeSource; import org.ow2.proactive.resourcemanager.rmnode.RMDeployingNode; import org.ow2.proactive.resourcemanager.rmnode.RMNode; import org.ow2.proactive.resourcemanager.utils.CommandLineBuilder; import org.ow2.proactive.resourcemanager.utils.OperatingSystem; /** * * Represents underlying infrastructure and defines ways to acquire / release * nodes.<br> * * Acquisition requests are supposed to be asynchronous. When new new node is * available it should register itself into the resource manager by calling * {@link InfrastructureManager#internalRegisterAcquiredNode(Node)}<br> * * To define a new infrastructure manager - define a way to add information * about further node acquisition implementing * {@link InfrastructureManager#configure(Object...)} - define a way to acquire * a single node from underlying infrastructure in * {@link InfrastructureManager#acquireNode()} - define a way to acquire all * available nodes from the infrastructure in the method * {@link InfrastructureManager#acquireAllNodes()} - register available nodes in * the resource manager using * {@link InfrastructureManager#internalRegisterAcquiredNode(Node)}, so they * till be taken into account. - add the name of new class to the resource * manager configuration file (config/rm/nodesource/infrastructures). * */ public abstract class InfrastructureManager implements Serializable { /** class' logger */ protected static final Logger logger = Logger.getLogger(InfrastructureManager.class); /** manager's node source */ protected NodeSource nodeSource; /** deploying nodes list */ private final Map<String, RMDeployingNode> deployingNodes = new ConcurrentHashMap<>(); private final Map<String, RMDeployingNode> lostNodes = new ConcurrentHashMap<>(); /** * node list, miror of nodesource.getAliveNodes(), to implement random * access */ private Hashtable<String, Node> acquiredNodes = new Hashtable<>(); private final ReentrantLock nodeAcquisitionLock = new ReentrantLock(); // shared fields, needs to be volatile not to cache it private volatile boolean usingDeployingNodes = false; // shutdown marker private volatile boolean shutdown = false; // used to timeout the nodes private transient Timer timeouter = null; protected String rmUrl; public InfrastructureManager() { } /** * Sets an infrastructure node source. Also sets the field rmUrl if it is * not provided by users. * * @param nodeSource * policy node source */ public void setNodeSource(NodeSource nodeSource) { this.nodeSource = nodeSource; this.rmUrl = nodeSource.getRegistrationURL(); } /** * To retrieve nodes whose registration status is deploying or lost. * * @return nodes whose registration status is deploying or lost. */ public ArrayList<RMDeployingNode> getDeployingNodes() { ArrayList<RMDeployingNode> result; synchronized (deployingNodes) { Collection<RMDeployingNode> rmDeployingNodes = this.deployingNodes.values(); Collection<RMDeployingNode> rmLostNodes = this.lostNodes.values(); result = new ArrayList<>(rmDeployingNodes.size() + rmLostNodes.size()); result.addAll(rmDeployingNodes); result.addAll(rmLostNodes); } return result; } public RMDeployingNode getDeployingNode(String nodeUrl) { RMDeployingNode deployingNode; synchronized (deployingNodes) { deployingNode = deployingNodes.get(nodeUrl); } if (deployingNode == null) { return lostNodes.get(nodeUrl); } return deployingNode; } /** * To remove a deploying node given its url * * @param pnUrl * the url of the deploying node to remove. * @return true if successful, false otherwise */ public final boolean internalRemoveDeployingNode(String pnUrl) { RMDeployingNode pn = null; boolean isLost = false; synchronized (deployingNodes) { pn = this.deployingNodes.remove(pnUrl); if (pn == null) { pn = this.lostNodes.remove(pnUrl); isLost = true; } } // if such a deploying or lost node exists if (pn != null) { String url = pn.getNodeURL(); RMNodeEvent event = pn.createNodeEvent(RMEventType.NODE_REMOVED, pn.getState(), pn.getProvider().getName()); emitEvent(event); logger.trace("DeployingNode " + url + " removed from IM"); // one notifies listeners about the deploying node removal // if the node is not lost if (!isLost) { this.notifyDeployingNodeLost(pn.getNodeURL()); } return true; } else { logger.trace("DeployingNode: " + pnUrl + " no more managed by IM, cannot remove it"); return false; } } /** * Performs some cleanup ( essentially removal of the cached node ) and call * removeNodeImpl * * @param node * the node to be removed * @throws RMException */ public final void internalRemoveNode(Node node) throws RMException { try { this.acquiredNodes.remove(node.getNodeInformation().getName()); } catch (Exception e) { logger.warn("Exception occurred while removing node " + node); } this.removeNode(node); } /** * This method is called by the system when a Node is detected as DOWN. * * @param proactiveProgrammingNode the ProActive Programming Node that is down. * * @throws RMException if any problems occurred. */ public void internalNotifyDownNode(Node proactiveProgrammingNode) throws RMException { notifyDownNode(proactiveProgrammingNode); } /** * The default behavior is to remove the node from the infrastructure manager. * * @param proactiveProgrammingNode the ProActive Programming Node that is down. * * @throws RMException if any problems occurred. */ public void notifyDownNode(Node proactiveProgrammingNode) throws RMException { internalRemoveNode(proactiveProgrammingNode); } /** * This method is called by the RMCore to notify the InfrastructureManager * that a new node was added to the core. If the IM throws an exception, the * node is not added. Note that this method is in mutual exclusion with * {@link #checkNodeIsAcquiredAndDo(String, Runnable, Runnable)} At this * point, if a previous call to * {@link InfrastructureManager#addDeployingNode(String, String, String, long)} * was made (means that implementor uses deploying nodes features), this * method ensures that the implementation method (see * {@link InfrastructureManager#notifyAcquiredNode(Node)} is only called if * no timeout has occurred for the associated deploying node. * * @param node * the newly added node * @throws RMException */ public final RMDeployingNode internalRegisterAcquiredNode(Node node) throws RMException { // if implementation doesn't use deploying nodes, we just execute // factory method and return if (!usingDeployingNodes) { this.notifyAcquiredNode(node); return null; } // here we use deploying nodes and timeout RMDeployingNode pn; // we build the url of the associated deploying node String deployingNodeURL = this.buildDeployingNodeURL(node.getNodeInformation().getName()); synchronized (nodeAcquisitionLock) { pn = this.deployingNodes.remove(deployingNodeURL); // if a deploying node with this name exists, one runs the // implementation callback if (pn != null) { RMNodeEvent event = pn.createNodeEvent(RMEventType.NODE_REMOVED, pn.getState(), pn.getProvider().getName()); emitEvent(event); this.notifyAcquiredNode(node); // if everything went well with the new node, caching it try { this.acquiredNodes.put(node.getNodeInformation().getName(), node); } catch (Exception e) { // if an exception occurred, we don't want to discard the // node registration logger.warn("Cannot cache the node in the InfrastructureManager after registration: " + node, e); } } else { String url = node.getNodeInformation().getURL(); logger.warn("Not expected node registered, discarding it: " + url); throw new RMException("Not expected node registered, discarding it: " + url); } } return pn; } /** * Add multiple deploying nodes * @param nodeNames node names which will be created * @param obfuscatedCmd obfuscated command used to start the node * @param message user message * @param nodeTimeout node timeout used * @return a list of deploying nodes urls */ protected List<String> addMultipleDeployingNodes(List<String> nodeNames, String obfuscatedCmd, String message, int nodeTimeout) { List<String> answer = new ArrayList<>(nodeNames.size()); for (String nodeName : nodeNames) { String depNodeURL = this.addDeployingNode(nodeName, obfuscatedCmd, message, nodeTimeout); answer.add(depNodeURL); } return answer; } /** * Declare multiple nodes lost * * @param deployingNodes list of urls of deploying nodes * @param message user message */ protected void multipleDeclareDeployingNodeLost(List<String> deployingNodes, String message) { for (String node : deployingNodes) { this.declareDeployingNodeLost(node, message); } } /** * called by the node source at configuration time. Shifts the parameter * array once done to let implementation only care about their own * configurable parameters. * * @param parameters * the parameters of the infrastructure manager * @throws IllegalArgumentException * if the parameters are invalid */ public final void internalConfigure(Object... parameters) { this.configure(parameters); } /** * Called by the node source at shutdown time. First removes every * registered Deploying and Lost nodes and delegates the call to the * implementation thanks to the method {@link #shutDown()} */ public final void internalShutDown() { this.shutdown = true; // first removing deploying nodes for (String dnUrl : this.deployingNodes.keySet()) { this.internalRemoveDeployingNode(dnUrl); } // no need to remove lost nodes, implementation is notified // at the timeout of the deploying node this.deployingNodes.clear(); // delegating the call to the implementation this.shutDown(); if (timeouter != null) { timeouter.cancel(); } } // **********************************************************************************************\\ // **************************************** SPI methods // *****************************************\\ // **********************************************************************************************\\ /** * * @return The Description of the implemented Infrastructure */ public abstract String getDescription(); /** * Adds information required to deploy nodes in the future. Do not initiate * a real nodes deployment/acquisition as it's up to the policy. * * @param parameters * of the infrastructure manager * @throws IllegalArgumentException * if the parameters are invalid */ protected abstract void configure(Object... parameters); /** * Asynchronous node acquisition request. Proactive node should be * registered by calling * {@link InfrastructureManager#internalRegisterAcquiredNode(Node)} */ public abstract void acquireNode(); public void acquireNodes(int n, Map<String, ?> nodeConfiguration) { throw new UnsupportedOperationException("Node configuration is not implemented for this infrastructure manager."); } /** * Asynchronous request of all nodes acquisition. Proactive nodes should be * registered by calling * {@link InfrastructureManager#internalRegisterAcquiredNode(Node)} */ public abstract void acquireAllNodes(); public void acquireAllNodes(Map<String, ?> nodeConfiguration) { throw new UnsupportedOperationException("Node configuration is not implemented for this infrastructure manager."); } /** * Removes the node from the resource manager. * * @param node * the node to release. * @throws RMException * if any problems occurred. */ public abstract void removeNode(Node node) throws RMException; /** * Notifies the user that the deploying node was lost or removed (because of * a timeout, user interaction...) Default empty implementation is provided * because implementors don't necessary use this feature. Anyway, if they * decide to do so, they can override this method, for instance, to change a * flag that would get a control loop to exit... * * @param pnURL * the deploying node's URL for which one the timeout occurred. */ protected void notifyDeployingNodeLost(String pnURL) { } /** * Notifies the implementation of the infrastructure manager that a new node * has been registered. If this method throws an exception, the node * registration will be discarded. This method is always called if * implementor doesn't use deploying nodes (no call to * {@link InfrastructureManager#addDeployingNode(String, String, String, long)} * was made), and is called only for deploying nodes for which one no * timeout occurred. * * @param node * the newly registered node * @throws RMException * if the implementation does not approve the node acquisition * request */ protected abstract void notifyAcquiredNode(Node node) throws RMException; /** * Notify this infrastructure it is going to be shut down along with its * nodesource. All necessary cleanup should be done here. */ protected void shutDown() { } // **********************************************************************************************\\ // **************************************** API methods // *****************************************\\ // **********************************************************************************************\\ /** * This method returns a * {@link org.ow2.proactive.resourcemanager.utils.CommandLineBuilder} filled * in with "default" settings. That means that the returned * CommandLineBuilder is useable as such. * <ul> * <li>It tries to set the Java Path to use, either JAVA_HOME retrieved from * your environment or java.home set by Java itself.</li> * <li>The target operating system is set to {@link OperatingSystem#UNIX} * </li> * <li>If a ProActive configuration file is provided, it is used as such. * </li> * <li>Finally, it tries to set the nodesource's name, the rm's URL and the * node's name.</li> * </ul> * * @param targetOS * the operating system on which one the node will be deployed */ protected final CommandLineBuilder getDefaultCommandLineBuilder(OperatingSystem targetOS) { CommandLineBuilder result = new CommandLineBuilder(); String javaPath = System.getProperty("java.home") + targetOS.fs + "bin" + targetOS.fs + "java"; result.setJavaPath(javaPath); result.setTargetOS(targetOS); if (CentralPAPropertyRepository.PA_CONFIGURATION_FILE.isSet()) { try { result.setPaProperties(new File(CentralPAPropertyRepository.PA_CONFIGURATION_FILE.getValue())); } catch (IOException e) { logger.debug("Cannot set default pa configuration file for " + CommandLineBuilder.class.getSimpleName(), e); } } result.setRmURL(this.rmUrl); if (this.nodeSource != null) { String nsName = this.nodeSource.getName(); result.setSourceName(nsName); result.setNodeName(nsName + "_DefaultNodeName"); } return result; } /** * Returns an empty * {@link org.ow2.proactive.resourcemanager.utils.CommandLineBuilder} * * @return Returns an empty * {@link org.ow2.proactive.resourcemanager.utils.CommandLineBuilder} */ protected final CommandLineBuilder getEmptyCommandLineBuilder() { return new CommandLineBuilder(); } /** * Creates a new RMDeployingNode's, stores it in a local ArrayList and * notify the owning NodeSource of the RMDeployingNode creation * * @param name * The RMDeployingNode's name. * @param description * The RMDeployingNode's description * @param timeout * after which one the deploying node will be declared lost. ( * node acquisition after this timeout is discarded ) * @return The newly created RMDeployingNode's URL. * @throws UnsupportedOperationException * if the infrastructure manager is shuting down */ protected final String addDeployingNode(String name, String command, String description, final long timeout) { checkName(name); checkTimeout(timeout); if (shutdown) { throw new UnsupportedOperationException("The infrastructure manager is shuting down."); } // if the user calls this method, we use the require nodes/timeout // mecanism usingDeployingNodes = true; NodeSource nsStub = this.nodeSource.getStub(); RMDeployingNode deployingNode = RMDeployingNodeAccessor.getDefault().newRMDeployingNode(name, nsStub, command, nsStub.getAdministrator(), description); final String deployingNodeUrl = deployingNode.getNodeURL(); this.deployingNodes.put(deployingNodeUrl, deployingNode); nodeSource.setDeploying(deployingNode); // The value for 'deployingNode' is retrieved before calling 'nodeSource.setDeploying' // However, 'nodeSource.setDeploying' may lock the node that is currently handled // (e.g. node lock restoration on RM startup) and thus update 'deployingNodes'. // In such a case, the 'deployingNodes' collection is updated with a new deploying node instance which has the // same URL as 'deployingNode' but different state information (e.g. lock status). // This is due to deep copies made by ProActive Programming with method invocation on Active Objects. // As a consequence, the 'deployingNode' variable must be updated with the last value available // in the 'deployingNodes' collection deployingNode = getDeployingNode(deployingNodeUrl); if (logger.isTraceEnabled()) { logger.trace("New DeployingNode " + name + " instantiated in IM"); } RMNodeEvent event = deployingNode.createNodeEvent(RMEventType.NODE_ADDED, null, deployingNode.getProvider().getName()); emitEvent(event); this.sched(new TimerTask() { @Override public void run() { InfrastructureManager.this.timeout(deployingNodeUrl, timeout); } }, timeout); return deployingNode.getNodeURL(); } /** * To update the description of a deploying node. If a timeout has occurred * for this node, the update is discarded. * * @param toUpdateURL * The RMDeployingNode's URL whose description will be updated. * @param newDescription * The new description * @return true in case of success, false if the deploying node is not * managed by the IM anymore. */ protected final boolean updateDeployingNodeDescription(String toUpdateURL, String newDescription) { RMDeployingNode pn = this.deployingNodes.get(toUpdateURL); if (pn != null) { NodeState previousState = pn.getState(); RMDeployingNodeAccessor.getDefault().setDescription(pn, newDescription); RMNodeEvent event = pn.createNodeEvent(RMEventType.NODE_STATE_CHANGED, previousState, pn.getProvider().getName()); emitEvent(event); logger.trace("DeployingNode " + toUpdateURL + " updated in IM"); return true; } else { logger.trace("DeployingNode " + toUpdateURL + " no more managed by the IM, cannot update it"); return false; } } /** * Declares a deploying node lost. Future attempts to modify the deploying * node will be ignored. * * @param toUpdateURL * The RMDeployingNode's URL which is to be declared as lost * @param description * the new rmdeployingnode's description, can be null. * @return true if the method ran successfully, false otherwise. */ protected final boolean declareDeployingNodeLost(String toUpdateURL, String description) { RMDeployingNode deployingNode; // we need to atomically move the node from the deploying collection to // the lost one. synchronized (deployingNodes) { deployingNode = this.deployingNodes.remove(toUpdateURL); if (deployingNode != null) { this.lostNodes.put(toUpdateURL, deployingNode); } } if (deployingNode != null) { logger.warn("Declaring node as lost: " + toUpdateURL + ", " + description); NodeState previousState = deployingNode.getState(); RMDeployingNodeAccessor.getDefault().setLost(deployingNode); if (description != null) { RMDeployingNodeAccessor.getDefault().setDescription(deployingNode, description); } RMNodeEvent event = deployingNode.createNodeEvent(RMEventType.NODE_STATE_CHANGED, previousState, deployingNode.getProvider().getName()); emitEvent(event); if (logger.isTraceEnabled()) { logger.trace(RMDeployingNode.class.getSimpleName() + " " + toUpdateURL + " declared lost in IM"); } return true; } else { if (logger.isTraceEnabled()) { logger.trace(RMDeployingNode.class.getSimpleName() + " " + toUpdateURL + " no more managed by IM, cannot declare it as lost"); } return false; } } /** * Check if the Node with the given name is already acquired ( usefull when * the launcher of the process which is supposed to launch a RMNode waits * for its registration ). At the same time, this methode executes the first * runnable is the node has been found amoung acquired nodes and executes * the second runnable if the node has not been found. Note that this method * is in mutual exclusion with * {@link InfrastructureManager#internalRegisterAcquiredNode(Node)} to avoid * races. * * @param nodeName * the node's name * @param toRunWhenOK * the Runnable that will be launched if the node is already * acquired (can be null) * @param toRunWhenKO * the Runnable that will be launched if the node has not been * found among nodesource.getAliveNodes(). (can be null) * @return true if the node with such name is already acquired false * otherwise. */ protected final boolean checkNodeIsAcquiredAndDo(String nodeName, Runnable toRunWhenOK, Runnable toRunWhenKO) { synchronized (nodeAcquisitionLock) { if (this.acquiredNodes.containsKey(nodeName)) { checkNodePostAction(toRunWhenOK, "An exception occurred while running implementation's code whereas the node " + nodeName + " was found."); return true; } else { checkNodePostAction(toRunWhenKO, "An exception occurred while running implementation's code whereas the node " + nodeName + " was not found."); return false; } } } private void checkNodePostAction(Runnable toRunWhenOK, String message) { if (toRunWhenOK != null) { try { toRunWhenOK.run(); } catch (Exception e) { logger.warn(message, e); } } } protected final boolean checkAllNodesAreAcquiredAndDo(List<String> nodeNames, Runnable toRunWhenOK, Runnable toRunWhenKO) { boolean answer = true; for (String nodeName : nodeNames) { answer = answer && checkNodeIsAcquiredAndDo(nodeName, toRunWhenOK, toRunWhenKO); } return answer; } // **********************************************************************************************// // *********************** Package private accessors & Helpers // **********************************// // **********************************************************************************************// /** * To emit an event and register it in the database */ private void emitEvent(final RMNodeEvent event) { NodeSource nsStub = this.nodeSource.getStub(); nsStub.internalEmitDeployingNodeEvent(event); } private void timeout(String pnURL, long timeout) { if (this.declareDeployingNodeLost(pnURL, "Timeout occurred after " + timeout + " ms.")) { this.notifyDeployingNodeLost(pnURL); } } private synchronized void sched(TimerTask task, long delay) { if (this.timeouter == null) { this.timeouter = new Timer("InfrastructureManager Timer"); } this.timeouter.schedule(task, delay); } private void checkTimeout(long timeout) { if (timeout <= 0) { throw new IllegalArgumentException("Timeout cannot be negative"); } } private void checkName(String name) { if (name.contains(" ")) { throw new IllegalArgumentException("Deploying node name cannot contain white spaces: \"" + name + "\" is forbidden"); } String pnURL = this.buildDeployingNodeURL(name); if (this.deployingNodes.containsKey(pnURL) || this.lostNodes.containsKey(pnURL)) { throw new IllegalArgumentException(RMDeployingNode.class.getSimpleName() + " with the same name (\"" + name + "\") has already been created"); } } /** * Builds the name of the deploying node given its name * * @param pnName * The name of the deploying node * @return the URL of the deploying node */ private String buildDeployingNodeURL(String pnName) { return RMDeployingNode.PROTOCOL_ID + "://" + this.nodeSource.getName() + "/" + pnName; } /** * Called by the system every time a node that is DOWN reconnects * and changes its status to FREE or BUSY. * * @param node the node that has reconnected. */ public void onDownNodeReconnection(Node node) { // to be overridden by children } /** * Updates a deploying node. * <p> * The update is performed on deploying nodes first. * If no node if found, lost nodes are considered. * * @param rmNode the new deploying node instance to use. * @return the previous value or {@code null}. */ public RMDeployingNode update(RMDeployingNode rmNode) { String nodeUrl = rmNode.getNodeURL(); if (rmNode.isLost() && lostNodes.containsKey(nodeUrl)) { return lostNodes.put(nodeUrl, rmNode); } else if (deployingNodes.containsKey(nodeUrl)) { return deployingNodes.put(nodeUrl, rmNode); } else { return null; } } void addDeployingNode(RMDeployingNode node) { deployingNodes.put(node.getNodeURL(), node); } void addLostNode(RMDeployingNode node) { lostNodes.put(node.getNodeURL(), node); } Map<String, RMDeployingNode> getDeployingNodesDeployingState() { return deployingNodes; } Map<String, RMDeployingNode> getDeployingNodesLostState() { return lostNodes; } /** * Helper nested class. Used not to expose methods that should be package * private of the {@link RMDeployingNode} object. */ public static abstract class RMDeployingNodeAccessor implements Serializable { private static volatile RMDeployingNodeAccessor DEFAULT; public static void setDefault(RMDeployingNodeAccessor d) { RMDeployingNodeAccessor.DEFAULT = d; } private static RMDeployingNodeAccessor getDefault() { if (RMDeployingNodeAccessor.DEFAULT != null) { return RMDeployingNodeAccessor.DEFAULT; } try { Class.forName(RMDeployingNode.class.getName(), true, RMDeployingNode.class.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } return RMDeployingNodeAccessor.DEFAULT; } /** * Instantiate a new {@link RMDeployingNode} with the given parameters * * @param name * The deploying node's name * @param ns * The node source owning the deploying node * @param command * The command that has been used to launch the * {@link RMNode} * @param provider * The node's provider * @param description * A first description of the node * @return The newly created deploying node */ protected abstract RMDeployingNode newRMDeployingNode(String name, NodeSource ns, String command, Client provider, String description); /** * To set the Description of the {@link RMDeployingNode} * * @param pn * the deploying node to update * @param newDescription * the new description */ protected abstract void setDescription(RMDeployingNode pn, String newDescription); /** * To update the lost field of the deploying node * * @param pn * The deploying node to update */ protected abstract void setLost(RMDeployingNode pn); } }