/* * Copyright 2008 the original author or authors. * Copyright 2005 Sun Microsystems, Inc. * * 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.rioproject.impl.system; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.EmptyConfiguration; import org.rioproject.config.Constants; import org.rioproject.deploy.DownloadRecord; import org.rioproject.deploy.StagedSoftware; import org.rioproject.net.HostUtil; import org.rioproject.system.ComputeResourceUtilization; import org.rioproject.system.MeasuredResource; import org.rioproject.system.ResourceCapability; import org.rioproject.impl.util.DownloadManager; import org.rioproject.impl.util.FileUtils; import org.rioproject.system.capability.PlatformCapability; import org.rioproject.impl.system.capability.PlatformCapabilityWriter; import org.rioproject.system.capability.platform.StorageCapability; import org.rioproject.impl.system.measurable.MeasurableCapability; import org.rioproject.impl.system.measurable.disk.DiskSpace; import org.rioproject.impl.system.measurable.memory.Memory; import org.rioproject.impl.system.measurable.memory.SystemMemory; import org.rioproject.impl.util.StringUtil; import org.rioproject.util.TimeUtil; import org.rioproject.impl.watch.ThresholdListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * The <code>ComputeResource</code> represents an abstract notion of a compute * resource that offers computational resources. These computational resources correlate * to qualitative and quantitative capabilities such as CPU, Memory, and others. * * @author Dennis Reedy */ public class ComputeResource { /** Name to use when getting Configuration values and to get the Logger */ static final String COMPONENT = "org.rioproject.system"; static Logger logger = LoggerFactory.getLogger(ComputeResource.class); /** * A description of the <code>ComputeResource</code> */ private String description; /** * The <code>InetAddress</code> of the compute resource the * <code>ComputeResource</code> object was constructed on */ private final InetAddress address; /** * The local host, used for host name */ private final InetAddress localHost; /** * The <code>platformCapabilities</code> defines a <code>Collection</code> of * <code>PlatformCapabilities</code> which are specific types of mechanism(s) * associated with this <code>ComputeResource</code>, and is used to define * base platform capabilities and resources. */ private final List<PlatformCapability> platformCapabilities = new ArrayList<PlatformCapability>(); /** * A collection of <tt>PlatformCapability</tt> instances being * provisioned/installed */ private final List<PlatformCapability> platformCapabilityPending = new ArrayList<PlatformCapability>(); /** * The <code>measurables</code> defines a <code>Collection</code> of * <code>MeasurableCapabilities</code> which are specific types of measurable * mechanism(s) associated with this <code>ComputeResource</code> */ private final List<MeasurableCapability> measurables = new ArrayList<MeasurableCapability>(); /** * Flag determines whether persistent provisioning is supported */ private boolean persistentProvisioning=true; /** * The directory to provision PlatformCapability software. This is the root * directory where PlatformCapability software may be installed */ private String provisionRoot; /** * Configuration object */ private Configuration config; /** * Flag indicated we are initializing */ private boolean initializing = false; /** * PlatformCapability name table */ private final Map<String, String> platformCapabilityNameTable = new HashMap<String, String>(); private final List<PlatformCapability> removals = new ArrayList<PlatformCapability>(); private SystemCapabilitiesLoader systemCapabilitiesLoader; public static final long DEFAULT_REPORT_INTERVAL=1000*60; private long reportInterval; private final List<ResourceCapabilityChangeListener> listeners = new ArrayList<ResourceCapabilityChangeListener>(); private final ScheduledExecutorService resourceCapabilityChangeService = Executors.newScheduledThreadPool(1); private ScheduledFuture resourceCapabilityChangeNotifierFuture; /** * Create a ComputeResource with a default (empty) configuration * * @throws ConfigurationException If there are problems accessing the * ComputeResource's configuration object * @throws UnknownHostException If the local host cannot be obtained */ public ComputeResource() throws ConfigurationException, UnknownHostException { this(EmptyConfiguration.INSTANCE); } /** * Create a ComputeResource * * @param config The Configuration used to initialize the ComputeResource * * @throws ConfigurationException If there are problems accessing the * ComputeResource's configuration object * @throws UnknownHostException If the local host cannot be obtained * @throws IllegalArgumentException If the config is null */ public ComputeResource(Configuration config) throws ConfigurationException, UnknownHostException { if(config==null) throw new IllegalArgumentException("config is null"); String system = System.getProperty("os.name")+", "+ System.getProperty("os.arch")+", "+ System.getProperty("os.version"); this.config = config; address = HostUtil.getInetAddressFromProperty(Constants.RMI_HOST_ADDRESS); localHost = InetAddress.getLocalHost(); String defaultDescription = address.getHostName()+" "+system; description = (String)config.getEntry(COMPONENT, "description", String.class, defaultDescription); reportInterval = (Long)config.getEntry(COMPONENT, "reportInterval", Long.class, DEFAULT_REPORT_INTERVAL); if(reportInterval<1000) throw new ConfigurationException("The reportInterval must not be less then 1000 milliseconds."); scheduleResourceCapabilityReporting(); } /** * Set the reportInterval property which controls how often the {@code ComputeResource} * will inform registered {@link ResourceCapabilityChangeListener}s with an update to * the {@link org.rioproject.system.ResourceCapability}. * * @param reportInterval The interval controlling when the ComputeResource * reports change of state to registered Observers */ public void setReportInterval(long reportInterval) { if(this.reportInterval!=reportInterval) { this.reportInterval = reportInterval; if(!resourceCapabilityChangeNotifierFuture.isDone()) resourceCapabilityChangeNotifierFuture.cancel(true); logger.debug("Set ResourceCapability reportInterval to {}", TimeUtil.format(reportInterval)); scheduleResourceCapabilityReporting(); } } private void scheduleResourceCapabilityReporting() { resourceCapabilityChangeNotifierFuture = resourceCapabilityChangeService.scheduleAtFixedRate(new ResourceCapabilityChangeNotifier(), reportInterval, reportInterval, TimeUnit.MILLISECONDS); } /** * Get the reportInterval property which controls how often the ComputeResource * will inform registered Observers of a state change. A state change is * determined if any of the MeasurableCapability components contained within * this ComputeResource provide an update in the interval specified by the * reportInterval property * * @return The interval controlling when the ComputeResource reports change * of state to registered Observers */ public long getReportInterval() { return reportInterval; } /** * Get the description * * @return The description */ private String getDescription() { return(description); } /** * Get the InetAddress ComputeResource object has been configured to represent * * @return The InetAddress ComputeResource object has been configured to represent */ public InetAddress getAddress() { return(address); } /** * Get the host name. * * @return The host name. */ public String getHostName() { return localHost.getHostName(); } /** * Add a <code>PlatformCapability</code> object. The addition of a * <code>PlatformCapability</code> component causes the state of this object * to change, triggering the notification of all registered * <code>Observer</code> instances. * * <p>If there is a <code>PlatformCapability</code> component that is equal to * the provided <code>PlatformCapability</code> (according to the * <code>PlatformCapability.equals()</code> contract), the existing * <code>PlatformCapability</code> object will be remain unchanged and * the provided <code>PlatformCapability</code> ignored * * @param pCap The PlatformCapability to add */ public void addPlatformCapability(PlatformCapability pCap) { if(!hasPlatformCapability(pCap)) { logger.trace("Have PlatformCapability : {} load any system resources", pCap.getClass().getName()); boolean stateChange; synchronized(platformCapabilities) { stateChange = platformCapabilities.add(pCap); } if(stateChange) stateChange(); } } /** * Provision a {@link org.rioproject.deploy.StagedSoftware} for a * {@link org.rioproject.system.capability.PlatformCapability} object. The * provisioning of {@link org.rioproject.deploy.StagedSoftware} * object for a{@link org.rioproject.system.capability.PlatformCapability} * component causes the state of this object to change, triggering the * notification of all registered {@link java.util.Observer} instances. * * <p>If there is a <tt>PlatformCapability</tt> component that is equal to * the provided <tt>PlatformCapability</tt> (according to the * <tt>PlatformCapability.equals()</tt> contract), the existing * <tt>PlatformCapability</tt> object will be changed. If the * <tt>PlatformCapability</tt> object does not exist, it will be added * * @param pCap The PlatformCapability * @param stagedSoftware StagedSoftware to provision for the * PlatformCapability * * @return Array of DownloadRecord instances which * represent any software downloaded to add the PlatformCapability. If no * software was downloaded to add the PlatformCapability, return an empty array */ public DownloadRecord[] provision(PlatformCapability pCap, StagedSoftware stagedSoftware) { boolean pendingInstallation; synchronized(platformCapabilityPending) { pendingInstallation = platformCapabilityPending.contains(pCap); } if(!pendingInstallation) { synchronized(platformCapabilityPending) { platformCapabilityPending.add(pCap); } } else { do { synchronized(platformCapabilityPending) { pendingInstallation = platformCapabilityPending.contains(pCap); } try { Thread.sleep(500); } catch (InterruptedException e) { break; } } while(pendingInstallation); } if(hasPlatformCapability(pCap)) { return new DownloadRecord[0]; } synchronized(platformCapabilities) { platformCapabilities.add(pCap); } try { if(stagedSoftware !=null) { DownloadManager downloadMgr = new DownloadManager(provisionRoot, stagedSoftware); DownloadRecord record = null; try { logger.trace("Provisioning StagedSoftware for PlatformCapability : {}", pCap.getClass().getName()); record = downloadMgr.download(); if(record!=null) { logger.trace(record.toString()); pCap.addDownloadRecord(record); pCap.setPath(record.unarchived()? record.getExtractedPath():record.getPath()); } pCap.addStagedSoftware(stagedSoftware); DownloadRecord postInstallRecord = downloadMgr.postInstall(); if(postInstallRecord!=null) pCap.addDownloadRecord(postInstallRecord); if(stagedSoftware.getUseAsClasspathResource()) { String[] classpath; if(pCap.getPath().endsWith(".jar") || pCap.getPath().endsWith(".zip")) { classpath = StringUtil.toArray(pCap.getPath()); } else { String cp = pCap.getPath(); File f = new File(cp); if(!f.isDirectory()) cp = FileUtils.getFilePath(f.getParentFile()); classpath = new String[]{cp}; } pCap.setClassPath(classpath); } if(!stagedSoftware.removeOnDestroy()) { String configFileLocation = systemCapabilitiesLoader.getPlatformConfigurationDirectory(config); if(configFileLocation==null) { logger.warn("Unable to write PlatformConfiguration [{}] configuration, " + "unknown platform configuration directory. The RIO_HOME environment " + "variable must be set", pCap.getName()); } else { PlatformCapabilityWriter pCapWriter = new PlatformCapabilityWriter(); String fileName = pCapWriter.write(pCap, configFileLocation); pCap.setConfigurationFile(fileName); logger.info("Wrote PlatformCapability [{}] configuration to {}", pCap.getName(), fileName); } } logger.trace("Have PlatformCapability : {} load any system resources", pCap.getClass().getName()); stateChange(); } catch(IOException e) { if(record!=null) downloadMgr.remove(); logger.warn("Provisioning StagedSoftware for PlatformCapability : {}", pCap.getClass().getName(), e); } } } finally { synchronized(platformCapabilityPending) { platformCapabilityPending.remove(pCap); } } return(pCap.getDownloadRecords()); } /** * Determine if the PlatformCapability exists in this ComputeResource * * @param capability The PlatformCapability to check * * @return True if exists, false otherwise */ public boolean hasPlatformCapability(PlatformCapability capability) { boolean contains; synchronized(platformCapabilities) { contains = platformCapabilities.contains(capability); } return(contains); } /** * Remove a <code>PlatformCapability</code> object. The removal of a * <code>PlatformCapability</code> component causes the state of this object to * change, triggering the notification of all registered <code>Observer</code> * instances * * @param pCap The PlatformCapability to remove * @param clean If this value is true and if the PlatformCapability has a * DownloadRecord defined (was provisioned during the time this Cybernode * was running) then remove the PlatformCapability from the system. * * @return True if removed, false otherwise */ public boolean removePlatformCapability(PlatformCapability pCap, boolean clean) { boolean removed = false; synchronized(removals) { removals.add(pCap); } try { if(clean) { DownloadRecord[] downloadRecords = pCap.getDownloadRecords(); StringBuilder buff = new StringBuilder(); buff.append("Removing StagedSoftware for PlatformCapability: [") .append(pCap.getName()) .append("]"); for (DownloadRecord downloadRecord : downloadRecords) { buff.append("\n\tRemoved ").append(DownloadManager.remove(downloadRecord)); } logger.info(buff.toString()); if(pCap.getConfigurationFile()!=null) { File configFile = new File(pCap.getConfigurationFile()); if(configFile.exists()) { if(configFile.delete()) logger.info("Removed PlatformCapability [{}] configuration file: {}", pCap.getName(), FileUtils.getFilePath(configFile)); } } } synchronized(platformCapabilities) { removed = platformCapabilities.remove(pCap); } if(removed) { stateChange(); } } finally { removals.remove(pCap); } return(removed); } /** * Determine if a {@link org.rioproject.system.capability.PlatformCapability} * is being removed from the system. This will occur for provisioned * <tt>PlatformCapability</tt> instances that are removed as a result of * service termination. * * @param pCap The <tt>PlatformCapability</tt> to check * * @return True if the <tt>PlatformCapability</tt> is currently in the process of being removed */ public boolean removalInProcess(PlatformCapability pCap) { boolean beingRemoved; synchronized(removals) { beingRemoved = removals.contains(pCap); } return beingRemoved; } /** * Add a <code>MeasurableCapability</code> object. The addition of a * <code>MeasurableCapability</code> component causes the state of this object * to change, triggering the notification of all registered <code>Observer</code> * instances. * * <p>If there is a <code>MeasurableCapability</code> component that is equal * to the provided <code>MeasurableCapability</code> (according to the * <code>MeasurableCapability.equals()</code> contract), the existing * <code>MeasurableCapability</code> object will be remain unchanged and * the provided <code>MeasurableCapability</code> ignored * * @param capability The MeasurableCapability to add * * @return True if added, false otherwise */ public boolean addMeasurableCapability(MeasurableCapability capability) { boolean added = false; if(measurables.contains(capability)) return(false); if(measurables.add(capability)) { added = true; stateChange(); } return(added); } public void addListener(ResourceCapabilityChangeListener listener) { if(listener==null) return; synchronized (listeners) { if(!listeners.contains(listener)) { listeners.add(listener); } } } public void removeListener(ResourceCapabilityChangeListener listener) { if(listener==null) return; synchronized (listeners) { listeners.remove(listener); } } /** * Notify all registered Observers the ComputeResource has changed. The argument * provided on the Observable.notifyObservers() invocation will be the * ResourceCapability component, used to communicate the resource utilization, * platform and measurable capabilities attributes of the ComputeResource */ private void stateChange() { if(initializing) return; logger.trace("Notify listeners of a state change"); notifyResourceCapabilityChangeListeners(); } private void notifyResourceCapabilityChangeListeners() { ResourceCapabilityChangeListener[] changeListeners; synchronized (listeners) { changeListeners = listeners.toArray(new ResourceCapabilityChangeListener[listeners.size()]); } ResourceCapability resourceCapability = getResourceCapability(); for(ResourceCapabilityChangeListener l : changeListeners) { l.update(resourceCapability); } } /** * A helper method which will add a <code>ThresholdListener</code> to all * contained <code>MeasurableCapability</code> components. An alternate * mechanism is to obtain all <code>MeasurableCapability</code> components * contained by the <code>ComputeResource</code> and add the listener to each * component * * @param listener The ThresholdListener to add */ public void addThresholdListener(ThresholdListener listener) { MeasurableCapability[] mCaps = getMeasurableCapabilities(); for (MeasurableCapability mCap : mCaps) mCap.addThresholdListener(listener); } /** * Return an array of <code>PlatformCapability</code> objects. This method will * create a new array of <code>PlatformCapability</code> objects each time it * is invoked. If there are no <code>PlatformCapability</code> objects contained * within the <code>platformCapabilities</code> Collection, a zero-length array * will be returned. * * @return Array of PlatformCapability objects */ public PlatformCapability[] getPlatformCapabilities() { PlatformCapability[] pCaps; synchronized(platformCapabilities) { pCaps = platformCapabilities.toArray(new PlatformCapability[platformCapabilities.size()]); } return pCaps; } /** * Get a PlatformCapability * * @param name The name of the PlatformCapability * * @return The first PlatformCapability that matches the name. If no * PlatformCapability matches the name, return null * * @see org.rioproject.system.capability.PlatformCapability#NAME */ public PlatformCapability getPlatformCapability(String name) { PlatformCapability[] pCaps = getPlatformCapabilities(); for (PlatformCapability pCap : pCaps) { if (pCap.getName().equals(name)) return (pCap); } return(null); } /** * Get the PlatformCapability name table * * @return A Map of PlatformCapability names to PlatformCapability * classnames. A new Map is created each time */ public Map<String, String> getPlatformCapabilityNameTable() { Map<String, String> map = new HashMap<String, String>(); map.putAll(platformCapabilityNameTable); return map; } /** * Get a MeasurableCapability * * @param description The description of the MeasurableCapability * * @return The first MeasurableCapability that matches the description. If no * MeasurableCapability matches the description, return null */ public MeasurableCapability getMeasurableCapability(String description) { MeasurableCapability[] mCaps = getMeasurableCapabilities(); for (MeasurableCapability mCap : mCaps) { if (mCap.getId().equals(description)) return (mCap); } return(null); } /** * Return an array of <code>MeasurableCapability</code> objects. This method * will create a new array of <code>MeasurableCapability</code> objects each * time it is invoked. If there are no <code>MeasurableCapability</code> * objects contained within the <code>measurables</code> Collection, a * zero-length array will be returned. * * @return Array of MeasurableCapability objects */ public MeasurableCapability[] getMeasurableCapabilities() { return(measurables.toArray(new MeasurableCapability[measurables.size()])); } /** * Get the MeasuredResource components for the ComputeResource * * @return Array of MeasuredResource instances that correspond to * MeasurableCapability components */ public MeasuredResource[] getMeasuredResources() { MeasurableCapability[] mCaps; synchronized(measurables) { mCaps = getMeasurableCapabilities(); } MeasuredResource[] measured = new MeasuredResource[mCaps.length]; for(int i=0; i<mCaps.length; i++) measured[i] = mCaps[i].getMeasuredResource(); return(measured); } /** * Get the resource utilization as computed by a summation of the * MeasurableCapability components * * @return The aggregate utilization of the ComputeResource */ public double getUtilization() { return getUtilization(getMeasuredResources()); } /* * Get the resource utilization as computed by a summation of the * MeasurableCapability components * * @return The aggregate utilization of the ComputeResource */ private double getUtilization(MeasuredResource[] mResources) { double utilization=0; for (MeasuredResource mRes : mResources) { utilization = utilization + mRes.getValue(); } if(mResources.length>0) utilization = utilization/mResources.length; return(utilization); } /** * Set the persistentProvisioning property * * @param persistentProvisioning True if the ComputeResource supports * persistent provisioning, false otherwise */ public void setPersistentProvisioning(boolean persistentProvisioning) { boolean changed=false; if(this.persistentProvisioning!=persistentProvisioning) changed=true; this.persistentProvisioning = persistentProvisioning; if(changed) stateChange(); } /** * Get the persistentProvisioning property * * @return True if the ComputeResource supports persistent provisioning, * false otherwise */ public boolean getPersistentProvisioning() { return(persistentProvisioning); } /** * Get the location for the persistent provisioning of PlatformCapability * instances * * @return The root directory for the persistent provisioning of * PlatformCapability instances */ public String getPersistentProvisioningRoot() { return(provisionRoot); } /** * Set the location for the persistent provisioning of PlatformCapability * instances * * @param provisionRoot The root directory for the persistent provisioning of * PlatformCapability instances */ public void setPersistentProvisioningRoot(String provisionRoot) { if(provisionRoot!=null) this.provisionRoot = provisionRoot; } /** * Return the <code>ResourceCapability</code>object that will be used to * communicate the platform and measurable capabilities attributes of the * ComputeResource * * @return The ResourceCapability object for the ComputeResource */ public ResourceCapability getResourceCapability() { return(new ResourceCapability(address.getHostAddress(), address.getHostName(), getPersistentProvisioning(), getPlatformCapabilities(), getComputeResourceUtilization())); } /** * Get the ComputeResourceUtilization * * @return A ComputeResourceUtilization object representing the utilization * of the ComputeResource */ public ComputeResourceUtilization getComputeResourceUtilization() { MeasuredResource[] mrs = getMeasuredResources(); ComputeResourceUtilization computeResourceUtilization = new ComputeResourceUtilization(getDescription(), getAddress().getHostName(), getAddress().getHostAddress(), getUtilization(mrs), Arrays.asList(mrs)); return(computeResourceUtilization); } /** * Boot the ComputeResource, loading all PlatformCapability and * MeasurableCapability instances */ public void boot() { if(config==null) throw new IllegalArgumentException("config is null, cannot boot"); initializing = true; try { systemCapabilitiesLoader = (SystemCapabilitiesLoader)config.getEntry(COMPONENT, "systemCapabilitiesLoader", SystemCapabilitiesLoader.class, new SystemCapabilities()); /* Get the PlatformCapability instances */ PlatformCapability[] pCaps = systemCapabilitiesLoader.getPlatformCapabilities(config); StorageCapability storage = null; org.rioproject.system.capability.platform.Memory memory = null; org.rioproject.system.capability.platform.SystemMemory systemMemory = null; for (PlatformCapability pCap : pCaps) { if (pCap instanceof StorageCapability) { storage = (StorageCapability) pCap; } if (pCap instanceof org.rioproject.system.capability.platform.Memory) { memory = (org.rioproject.system.capability.platform.Memory) pCap; } if (pCap instanceof org.rioproject.system.capability.platform.SystemMemory) { systemMemory = (org.rioproject.system.capability.platform.SystemMemory) pCap; } addPlatformCapability(pCap); } platformCapabilityNameTable.putAll(systemCapabilitiesLoader.getPlatformCapabilityNameTable()); /* Get the MeasurableCapability instances */ MeasurableCapability[] mCaps = systemCapabilitiesLoader.getMeasurableCapabilities(config); for (MeasurableCapability mCap : mCaps) { if (mCap instanceof DiskSpace) { if (storage != null) { mCap.addWatchDataReplicator(storage); } } if (mCap instanceof Memory) { if(mCap instanceof SystemMemory) { if(systemMemory!=null) { mCap.addWatchDataReplicator(systemMemory); } } else { if (memory != null) { mCap.addWatchDataReplicator(memory); } } } addMeasurableCapability(mCap); mCap.start(); } } catch (Exception e) { logger.warn("Booting ComputeResource", e); } finally { initializing = false; } } /** * Inform all <code>MeasurableCapability</code> instances to stop measuring */ public void shutdown() { for (MeasurableCapability measurable : measurables) { measurable.stop(); } resourceCapabilityChangeService.shutdown(); } class ResourceCapabilityChangeNotifier implements Runnable { public void run() { notifyResourceCapabilityChangeListeners(); } } }