/* * RHQ Management Platform * Copyright (C) 2005-2014 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.core.pc.bundle; import static java.util.concurrent.TimeUnit.HOURS; import static org.rhq.core.pluginapi.bundle.BundleHandoverResponse.FailureType; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.StringReader; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.bundle.BundleAgentService; import org.rhq.core.clientapi.agent.bundle.BundlePurgeRequest; import org.rhq.core.clientapi.agent.bundle.BundlePurgeResponse; import org.rhq.core.clientapi.agent.bundle.BundleScheduleRequest; import org.rhq.core.clientapi.agent.bundle.BundleScheduleResponse; import org.rhq.core.clientapi.server.bundle.BundleServerService; import org.rhq.core.domain.bundle.BundleDeployment; import org.rhq.core.domain.bundle.BundleDeploymentStatus; import org.rhq.core.domain.bundle.BundleDestination; import org.rhq.core.domain.bundle.BundleResourceDeployment; import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory; import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory.Status; import org.rhq.core.domain.bundle.BundleType; import org.rhq.core.domain.bundle.BundleVersion; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration.BundleDestinationBaseDirectory; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertyList; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.content.PackageVersion; import org.rhq.core.domain.measurement.DataType; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pc.ContainerService; import org.rhq.core.pc.PluginContainer; import org.rhq.core.pc.PluginContainerConfiguration; import org.rhq.core.pc.agent.AgentService; import org.rhq.core.pc.agent.AgentServiceStreamRemoter; import org.rhq.core.pc.inventory.InventoryManager; import org.rhq.core.pc.inventory.ResourceContainer; import org.rhq.core.pc.measurement.MeasurementManager; import org.rhq.core.pc.util.ComponentUtil; import org.rhq.core.pc.util.FacetLockType; import org.rhq.core.pc.util.LoggingThreadFactory; import org.rhq.core.pluginapi.bundle.BundleDeployRequest; import org.rhq.core.pluginapi.bundle.BundleDeployResult; import org.rhq.core.pluginapi.bundle.BundleFacet; import org.rhq.core.pluginapi.bundle.BundleHandoverFacet; import org.rhq.core.pluginapi.bundle.BundleHandoverRequest; import org.rhq.core.pluginapi.bundle.BundleHandoverResponse; import org.rhq.core.pluginapi.bundle.BundleManagerProvider; import org.rhq.core.pluginapi.bundle.BundlePurgeResult; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.TokenReplacingReader; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.core.util.file.FileUtil; import org.rhq.core.util.stream.StreamUtil; /** * Manages the bundle subsystem, which allows bundles of content to be installed. * <p/> * <p>This is an agent service; its interface is made remotely accessible if this is deployed within the agent.</p> * * @author John Mazzitelli */ public class BundleManager extends AgentService implements BundleAgentService, BundleManagerProvider, ContainerService { private static final Log LOG = LogFactory.getLog(BundleManager.class); private static final String AUDIT_DEPLOYMENT_ENDED = "Deployment Ended"; private static final String AUDIT_DEPLOYMENT_STARTED = "Deployment Started"; private static final String AUDIT_DEPLOYMENT_SCHEDULED = "Deployment Scheduled"; private static final String AUDIT_FILE_DOWNLOAD_ENDED = "File Download Started"; private static final String AUDIT_FILE_DOWNLOAD_STARTED = "File Download Started"; private static final String AUDIT_PURGE_STARTED = "Purge Started"; private static final String AUDIT_PURGE_ENDED = "Purge Ended"; private final PluginContainerConfiguration configuration; private final ExecutorService deployerThreadPool; private final InventoryManager inventoryManager; private final MeasurementManager measurementManager; public BundleManager(PluginContainerConfiguration configuration, AgentServiceStreamRemoter streamRemoter, InventoryManager inventoryManager, MeasurementManager measurementManager) { super(BundleAgentService.class, streamRemoter); this.configuration = configuration; LoggingThreadFactory threadFactory = new LoggingThreadFactory("BundleDeployment", true); this.deployerThreadPool = Executors.newSingleThreadExecutor(threadFactory); // single-threaded so only one deployment at a time this.inventoryManager = inventoryManager; this.measurementManager = measurementManager; } @Override public void shutdown() { // pass false, so we don't interrupt a plugin in the middle of a bundle deployment PluginContainer.shutdownExecutorService(this.deployerThreadPool, false); } @Override public List<PackageVersion> getAllBundleVersionPackageVersions(BundleVersion bundleVersion) throws Exception { int bvId = bundleVersion.getId(); List<PackageVersion> pvs = getBundleServerService().getAllBundleVersionPackageVersions(bvId); return pvs; } @Override public long getFileContent(PackageVersion packageVersion, OutputStream outputStream) throws Exception { outputStream = remoteOutputStream(outputStream); long size = getBundleServerService().downloadPackageBits(packageVersion, outputStream); return size; } @Override public BundleHandoverResponse handoverContent(Resource bundleTarget, BundleHandoverRequest handoverRequest) { try { BundleHandoverFacet component = getBundleHandoverFacet(bundleTarget.getId(), HOURS.toMillis(1)); BundleHandoverResponse report = component.handleContent(handoverRequest); if (report == null) { return BundleHandoverResponse.failure(FailureType.EXECUTION, "Plugin component returned null report"); } return report; } catch (PluginContainerException e) { return BundleHandoverResponse.failure(FailureType.PLUGIN_CONTAINER, "Caught a plugin container exception", e); } } private BundleHandoverFacet getBundleHandoverFacet(int bundleTargetId, long timeout) throws PluginContainerException { return ComponentUtil.getComponent(bundleTargetId, BundleHandoverFacet.class, FacetLockType.WRITE, timeout, false, true, false); } @Override public BundleScheduleResponse schedule(final BundleScheduleRequest request) { final BundleScheduleResponse response = new BundleScheduleResponse(); try { final BundleResourceDeployment resourceDeployment = request.getBundleResourceDeployment(); final BundleDeployment bundleDeployment = resourceDeployment.getBundleDeployment(); // find the resource that will handle the bundle processing BundleType bundleType = bundleDeployment.getBundleVersion().getBundle().getBundleType(); ResourceType resourceType = bundleType.getResourceType(); Set<Resource> resources = inventoryManager.getResourcesWithType(resourceType); if (resources.isEmpty()) { throw new Exception("No bundle plugin supports bundle type [" + bundleType + "]"); } final int bundleHandlerResourceId = resources.iterator().next().getId(); final ResourceContainer resourceContainer = inventoryManager.getResourceContainer(bundleHandlerResourceId); if (null == resourceContainer.getResourceContext()) { throw new Exception("No bundle plugin resource available to handle deployment for bundle type [" + bundleType + "]. Ensure the bundle plugin is deployed and its resource is imported into inventory."); } auditDeployment(resourceDeployment, AUDIT_DEPLOYMENT_SCHEDULED, bundleDeployment.getName(), "Scheduled deployment time: " + request.getRequestedDeployTimeAsString()); Runnable deployerRunnable = new Runnable() { @Override public void run() { try { // pull down the bundle files that the plugin will need in order to process the bundle File pluginTmpDir = resourceContainer.getResourceContext().getTemporaryDirectory(); File bundleFilesDir = new File(pluginTmpDir, "bundle-versions/" + bundleDeployment.getBundleVersion().getId()); bundleFilesDir.mkdirs(); // clean up any old downloads we may have retrieved before. This helps clean out // our temp directory so we don't unnecessarily fill up our file system with obsolete files removeOldDownloadedBundleFiles(bundleFilesDir); // now download the bundle files we need for the current deployment Map<PackageVersion, File> downloadedFiles = downloadBundleFiles(resourceDeployment, bundleFilesDir); // deploy the bundle utilizing the bundle facet object String deploymentMessage = "Deployment [" + bundleDeployment + "] to [" + resourceDeployment.getResource() + "]"; auditDeployment(resourceDeployment, AUDIT_DEPLOYMENT_STARTED, bundleDeployment.getName(), deploymentMessage); BundleDeployRequest deployRequest = new BundleDeployRequest(); deployRequest.setBundleManagerProvider(BundleManager.this); deployRequest.setResourceDeployment(resourceDeployment); deployRequest.setBundleFilesLocation(bundleFilesDir); deployRequest.setPackageVersionFiles(downloadedFiles); deployRequest.setCleanDeployment(request.isCleanDeployment()); deployRequest.setRevert(request.isRevert()); File absoluteDestDir = getAbsoluteDestinationDir(request.getBundleResourceDeployment()); if (absoluteDestDir != null) { deployRequest.setDestinationTarget(absoluteDestDir.toURI()); } else { String connectionString = getConnectionString(resourceDeployment); if (connectionString != null) { deployRequest.setDestinationTarget(URI.create(connectionString)); } deployRequest.setReferencedConfiguration( createReferencedConfigurationFromResource(resourceDeployment)); } int facetMethodTimeout = 4 * 60 * 60 * 1000; // 4 hours is given to the bundle plugin to do its thing as default if(bundleDeployment.getConfiguration() != null) { PropertySimple deploymentTimeout = bundleDeployment.getConfiguration().getSimple("org.rhq.deploymentTimeout"); if(deploymentTimeout != null) { facetMethodTimeout = deploymentTimeout.getIntegerValue().intValue() * 1000; } } // get the bundle facet object that will process the bundle and call it to start the deployment BundleFacet bundlePluginComponent = getBundleFacet(bundleHandlerResourceId, facetMethodTimeout); BundleDeployResult result = bundlePluginComponent.deployBundle(deployRequest); if (result.isSuccess()) { completeDeployment(resourceDeployment, BundleDeploymentStatus.SUCCESS, deploymentMessage); } else { completeDeployment(resourceDeployment, BundleDeploymentStatus.FAILURE, result.getErrorMessage()); } } catch (InterruptedException ie) { LOG.error("Failed to complete bundle deployment due to interrupt", ie); completeDeployment(resourceDeployment, BundleDeploymentStatus.FAILURE, "Deployment interrupted"); } catch (Throwable t) { LOG.error("Failed to complete bundle deployment", t); completeDeployment(resourceDeployment, BundleDeploymentStatus.FAILURE, "Deployment failed: " + ThrowableUtil.getAllMessages(t)); } } }; this.deployerThreadPool.execute(deployerRunnable); } catch (Throwable t) { LOG.error("Failed to schedule bundle request: " + request, t); response.setErrorMessage(t); } return response; } private Configuration createReferencedConfigurationFromResource(BundleResourceDeployment resourceDeployment) { ResourceContainer rc = inventoryManager.getResourceContainer(resourceDeployment.getResource()); Set<ResourceTypeBundleConfiguration.BundleDestinationSpecification> specs = rc.getResource().getResourceType() .getResourceTypeBundleConfiguration().getBundleDestinationSpecifications(); String specName = resourceDeployment.getBundleDeployment().getDestination() .getDestinationSpecificationName(); for (ResourceTypeBundleConfiguration.BundleDestinationSpecification spec : specs) { if (specName.equals(spec.getName())) { ResourceTypeBundleConfiguration.BundleDestinationDefinition def = (ResourceTypeBundleConfiguration.BundleDestinationDefinition) spec; Resource resource = rc.getResource(); Configuration transferred = new Configuration(); Configuration pluginConfiguration = resource.getPluginConfiguration(); Configuration resourceConfiguration = InventoryManager.getResourceConfiguration(resource); for (ResourceTypeBundleConfiguration.BundleDestinationDefinition.ConfigRef refProp : def.getReferencedConfiguration()) { switch (refProp.getContext()) { case PLUGIN_CONFIGURATION: switch (refProp.getType()) { case LIST: PropertyList list = pluginConfiguration.getList(refProp.getName()).deepCopy(false); list.setName(refProp.getTargetName()); transferred.put(list); break; case MAP: PropertyMap map = pluginConfiguration.getMap(refProp.getName()).deepCopy(false); map.setName(refProp.getTargetName()); transferred.put(map); break; case SIMPLE: PropertySimple simple = pluginConfiguration.getSimple(refProp.getName()).deepCopy(false); simple.setName(refProp.getTargetName()); transferred.put(simple); break; case FULL: for (Property p : pluginConfiguration.getProperties()) { Property copy = p.deepCopy(false); if (refProp.getTargetName() != null) { copy.setName(refProp.getTargetName() + copy.getName()); } transferred.put(copy); } break; } break; case RESOURCE_CONFIGURATION: switch (refProp.getType()) { case LIST: PropertyList list = resourceConfiguration.getList(refProp.getName()).deepCopy(false); list.setName(refProp.getTargetName()); transferred.put(list); break; case MAP: PropertyMap map = resourceConfiguration.getMap(refProp.getName()).deepCopy(false); map.setName(refProp.getTargetName()); transferred.put(map); break; case SIMPLE: PropertySimple simple = resourceConfiguration.getSimple(refProp.getName()).deepCopy(false); simple.setName(refProp.getTargetName()); transferred.put(simple); break; case FULL: for (Property p : resourceConfiguration.getProperties()) { Property copy = p.deepCopy(false); if (refProp.getTargetName() != null) { copy.setName(refProp.getTargetName() + copy.getName()); } transferred.put(copy); } break; } break; case MEASUREMENT_TRAIT: if (refProp.getType() == ResourceTypeBundleConfiguration.BundleDestinationDefinition.ConfigRef.Type.FULL) { Set<MeasurementScheduleRequest> schedules = rc.getMeasurementSchedule(); for (MeasurementScheduleRequest schedule : schedules) { if (schedule.getDataType() != DataType.TRAIT) { String value = measurementManager.getTraitValue(rc, schedule.getName()); String name = schedule.getName(); if (refProp.getTargetName() != null) { name = refProp.getTargetName() + name; } PropertySimple prop = new PropertySimple(name, value); transferred.put(prop); } } } else { String value = measurementManager.getTraitValue(rc, refProp.getName()); PropertySimple simple = new PropertySimple(refProp.getTargetName(), value); transferred.put(simple); } break; } } return transferred; } } return null; } private String getConnectionString(BundleResourceDeployment resourceDeployment) { ResourceContainer rc = inventoryManager.getResourceContainer(resourceDeployment.getResource()); BundleDestination dest = resourceDeployment.getBundleDeployment().getDestination(); ResourceType type = rc.getResource().getResourceType(); String specName = dest.getDestinationSpecificationName(); String relativeDeployDir = dest.getDeployDir(); Configuration config = new Configuration(); config.put(new PropertySimple("deployDir", relativeDeployDir)); ConnectionStringAvailableProperties props = new ConnectionStringAvailableProperties(rc, measurementManager, config); for (ResourceTypeBundleConfiguration.BundleDestinationSpecification spec : type .getResourceTypeBundleConfiguration().getBundleDestinationSpecifications()) { if (specName.equals(spec.getName())) { ResourceTypeBundleConfiguration.BundleDestinationDefinition def = (ResourceTypeBundleConfiguration.BundleDestinationDefinition) spec; String rawConnectionString = def.getConnectionString(); if (rawConnectionString == null) { return null; } TokenReplacingReader trr = new TokenReplacingReader(new StringReader(rawConnectionString), props); try { return StreamUtil.slurp(trr); } finally { StreamUtil.safeClose(trr); } } } return null; } @Override public BundlePurgeResponse purge(BundlePurgeRequest request) { final BundlePurgeResponse response = new BundlePurgeResponse(); try { final BundleResourceDeployment resourceDeployment = request.getLiveBundleResourceDeployment(); final BundleDeployment bundleDeployment = resourceDeployment.getBundleDeployment(); // find the resource that will purge the bundle BundleType bundleType = bundleDeployment.getBundleVersion().getBundle().getBundleType(); ResourceType resourceType = bundleType.getResourceType(); Set<Resource> resources = inventoryManager.getResourcesWithType(resourceType); if (resources.isEmpty()) { throw new Exception("No bundle plugin supports bundle type [" + bundleType + "]"); } final int bundleHandlerResourceId = resources.iterator().next().getId(); final ResourceContainer resourceContainer = inventoryManager.getResourceContainer(bundleHandlerResourceId); if (null == resourceContainer.getResourceContext()) { throw new Exception("No bundle plugin resource available to handle purge for bundle type [" + bundleType + "]. Ensure the bundle plugin is deployed and its resource is imported into inventory."); } // purge the bundle utilizing the bundle facet object String deploymentMessage = "Deployment [" + bundleDeployment + "] to be purged via [" + resourceDeployment.getResource() + "]"; auditDeployment(resourceDeployment, AUDIT_PURGE_STARTED, bundleDeployment.getName(), deploymentMessage); org.rhq.core.pluginapi.bundle.BundlePurgeRequest purgeRequest = new org.rhq.core.pluginapi.bundle.BundlePurgeRequest(); purgeRequest.setBundleManagerProvider(this); purgeRequest.setLiveResourceDeployment(resourceDeployment); File absoluteDestDir = getAbsoluteDestinationDir(request.getLiveBundleResourceDeployment()); if (absoluteDestDir != null) { purgeRequest.setDestinationTarget(absoluteDestDir.toURI()); } else { String connectionString = getConnectionString(request.getLiveBundleResourceDeployment()); if (connectionString != null) { purgeRequest.setDestinationTarget(URI.create(connectionString)); } purgeRequest.setReferencedConfiguration( createReferencedConfigurationFromResource(request.getLiveBundleResourceDeployment())); } // get the bundle facet object that will process the bundle and call it to start the purge int facetMethodTimeout = 30 * 60 * 1000; // 30 minutes should be enough time for the bundle plugin to purge everything BundleFacet bundlePluginComponent = getBundleFacet(bundleHandlerResourceId, facetMethodTimeout); BundlePurgeResult result = bundlePluginComponent.purgeBundle(purgeRequest); if (result.isSuccess()) { auditDeployment(resourceDeployment, AUDIT_PURGE_ENDED, bundleDeployment.getName(), deploymentMessage); } else { response.setErrorMessage(result.getErrorMessage()); auditDeployment(resourceDeployment, AUDIT_PURGE_ENDED, bundleDeployment.getName(), null, Status.FAILURE, "Failed: " + deploymentMessage, result.getErrorMessage()); } } catch (Throwable t) { LOG.error("Failed to purge bundle: " + request, t); response.setErrorMessage(t); } return response; } /** * convenience method:<br/> * category defaults to null<br/> * status defaults to SUCCESS<br/> * attachment defaults null <br/> * * @param bundleResourceDeployment not null * @param action not null * @param info not null * @param message optional */ public void auditDeployment(BundleResourceDeployment bundleResourceDeployment, String action, String info, String message) { auditDeployment(bundleResourceDeployment, action, info, null, BundleResourceDeploymentHistory.Status.SUCCESS, message, null); } @Override public void auditDeployment(BundleResourceDeployment bundleResourceDeployment, String action, String info, BundleResourceDeploymentHistory.Category category, BundleResourceDeploymentHistory.Status status, String message, String attachment) { if (null == action || null == info) { throw new IllegalArgumentException("action or info is null"); } if (null == status) { status = BundleResourceDeploymentHistory.Status.SUCCESS; } BundleResourceDeploymentHistory history = new BundleResourceDeploymentHistory("Bundle Plugin", action, info, category, status, message, attachment); if (LOG.isDebugEnabled()) { LOG.debug("Reporting deployment step [" + history + "] to Server..."); } getBundleServerService().addDeploymentHistory(bundleResourceDeployment.getId(), history); } /** * Given a directory where the current deployment's bundle files will reside, this method will clear out * any other files located in other peer directories. In other words, the given directory and all its * subdirectories and child files are left untouched, but all files and directories found in peer * directories are wiped. This helps clean out our temp directory so we don't fill up the file system * with old downloaded files that we don't need anymore. See BZ 752550. */ private void removeOldDownloadedBundleFiles(final File currentBundleVersionFilesDir) { File parent = null; try { parent = currentBundleVersionFilesDir.getParentFile(); File[] doomedFiles = parent.listFiles(new FileFilter() { @Override public boolean accept(File child) { return !currentBundleVersionFilesDir.equals(child); } }); for (File doomedFile : doomedFiles) { FileUtil.purge(doomedFile, true); } } catch (Exception e) { LOG.warn("Failed to clean up old downloaded bundle files in [" + parent + "]. You can ignore this but if the agent is asked to deploy a lot of bundles, the file system may fill up." + " Cause: " + e); } } /** * Downloads the bundle's files into the bundle plugin's tmp directory and returns that tmp directory. * * @param resourceDeployment access to deployment information, including what bundle files need to be downloaded * @param downloadDir location where the bundle files should be downloaded * * @return map of the package versions to their files that were downloaded * * @throws Exception */ private Map<PackageVersion, File> downloadBundleFiles(BundleResourceDeployment resourceDeployment, File downloadDir) throws Exception { BundleDeployment bundleDeployment = resourceDeployment.getBundleDeployment(); BundleVersion bundleVersion = bundleDeployment.getBundleVersion(); Map<PackageVersion, File> packageVersionFiles = new HashMap<PackageVersion, File>(); List<PackageVersion> packageVersions = getAllBundleVersionPackageVersions(bundleVersion); for (PackageVersion packageVersion : packageVersions) { File packageFile = new File(downloadDir, packageVersion.getFileName()); try { verifyHash(packageVersion, packageFile); } catch (Exception e) { // file either doesn't exist or it hash doesn't match, download a new copy packageFile.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(packageFile); try { auditDeployment(resourceDeployment, AUDIT_FILE_DOWNLOAD_STARTED, packageVersion.getDisplayName(), "Downloading [" + packageVersion + "]"); long size = getFileContent(packageVersion, fos); if (packageVersion.getFileSize() != null && size != packageVersion.getFileSize()) { String message = "Downloaded bundle file [" + packageVersion + "] but its size was [" + size + "] when it was expected to be [" + packageVersion.getFileSize() + "]."; LOG.warn(message); auditDeployment(resourceDeployment, AUDIT_FILE_DOWNLOAD_ENDED, packageVersion.getDisplayName(), null, BundleResourceDeploymentHistory.Status.WARN, message, null); } else { auditDeployment(resourceDeployment, AUDIT_FILE_DOWNLOAD_ENDED, packageVersion.getDisplayName(), "Download complete for [" + packageVersion + "]"); } } catch (Exception e2) { String message = "Failed to downloaded bundle file [" + packageVersion + "] " + e2; LOG.warn(message); auditDeployment(resourceDeployment, AUDIT_FILE_DOWNLOAD_ENDED, packageVersion.getDisplayName(), null, BundleResourceDeploymentHistory.Status.FAILURE, message, null); } finally { fos.close(); } // now try to verify it again, if this throws an exception, that is very bad and we need to abort verifyHash(packageVersion, packageFile); } packageVersionFiles.put(packageVersion, packageFile); } return packageVersionFiles; } private void completeDeployment(final BundleResourceDeployment resourceDeployment, BundleDeploymentStatus status, String message) { getBundleServerService().setBundleDeploymentStatus(resourceDeployment.getId(), status); BundleResourceDeploymentHistory.Status auditStatus = null; if(BundleDeploymentStatus.SUCCESS == status) { auditStatus = BundleResourceDeploymentHistory.Status.SUCCESS; Integer discoveryDelay = resourceDeployment.getBundleDeployment().getDiscoveryDelay(); if(discoveryDelay == null) { discoveryDelay = Integer.valueOf(0); // Fallback } if(!(discoveryDelay.intValue() < 0)) { inventoryManager.executeServiceScanDeferred(resourceDeployment.getResource().getId(), discoveryDelay * 1000); } } else { auditStatus = BundleResourceDeploymentHistory.Status.FAILURE; } auditDeployment(resourceDeployment, AUDIT_DEPLOYMENT_ENDED, resourceDeployment.getBundleDeployment().getName(), null, auditStatus, message, null); } /** * Checks to see if the package file's hash matches that of the given package version. * If the file doesn't exist or the hash doesn't match, an exception is thrown. * This method returns normally if the hash matches the file. * If there is no known hash in the package version, this method returns normally. * * @param packageVersion contains the hash that is expected * @param packageFile the local file whose hash is to be checked * * @throws Exception if the file does not match the hash or the file doesn't exist */ private void verifyHash(PackageVersion packageVersion, File packageFile) throws Exception { if (!packageFile.exists()) { throw new Exception("Package version [" + packageVersion + "] does not exist, cannot check hash"); } String realHash; if (packageVersion.getMD5() != null) { realHash = new MessageDigestGenerator(MessageDigestGenerator.MD5).calcDigestString(packageFile); if (!packageVersion.getMD5().equals(realHash)) { throw new Exception("Package version [" + packageVersion + "] failed MD5 check. expected=[" + packageVersion.getMD5() + "], actual=[" + realHash + "]"); } } else if (packageVersion.getSHA256() != null) { realHash = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(packageFile); if (!packageVersion.getSHA256().equals(realHash)) { throw new Exception("Package version [" + packageVersion + "] failed SHA256 check. expected=[" + packageVersion.getSHA256() + "], actual=[" + realHash + "]"); } } else { if (LOG.isDebugEnabled()) { LOG.debug("Package version [" + packageVersion + "] has no MD5/SHA256 hash - not verifying it"); } } } /** * Given a deployment, this examines the destination and the resource to determine where exactly * the bundle distribution should be written. * * @param bundleResourceDeployment describes where the bundle should be or is deployed * * @return absolute directory location where the bundle should be deployed */ private File getAbsoluteDestinationDir(BundleResourceDeployment bundleResourceDeployment) { BundleDestination dest = bundleResourceDeployment.getBundleDeployment().getDestination(); String destBaseDirName = dest.getDestinationBaseDirectoryName(); String relativeDeployDir = dest.getDeployDir(); // paranoia, if no deploy dir is given, as assume it will be directly under the base location if (relativeDeployDir == null || relativeDeployDir.trim().length() == 0) { relativeDeployDir = File.separator; } // get the resource entity stored in our local inventory Resource resource = bundleResourceDeployment.getResource(); ResourceContainer container = inventoryManager.getResourceContainer(resource); resource = container.getResource(); // find out the type of base location that is specified by the bundle destination BundleDestinationBaseDirectory bundleDestBaseDir = null; ResourceTypeBundleConfiguration rtbc = resource.getResourceType().getResourceTypeBundleConfiguration(); if (rtbc == null) { throw new IllegalArgumentException("The resource type doesn't support bundle deployments: " + resource); } for (BundleDestinationBaseDirectory bdbd : rtbc.getBundleDestinationBaseDirectories()) { if (bdbd.getName().equals(destBaseDirName)) { bundleDestBaseDir = bdbd; break; } } if (bundleDestBaseDir == null) { return null; } // based on the type of destination base location, determine the root base directory String destBaseDirValueName = bundleDestBaseDir.getValueName(); // the name we look up in the given context String baseLocation; switch (bundleDestBaseDir.getValueContext()) { case fileSystem: { if (!new File(relativeDeployDir).isAbsolute()) { // the deploy dir is not absolute; since we need to pin it to something, we assume the top root directory // unless the descriptor told us to go somewhere else differently baseLocation = destBaseDirValueName; // ultimately this came from the plugin descriptor if (baseLocation == null || baseLocation.trim().length() == 0) { baseLocation = File.separator; // paranoia, if the plugin descriptor didn't specify, assume the top root directory } } else { baseLocation = null; // so the relativeDeployDir is processed as an absolute dir } break; } case pluginConfiguration: { baseLocation = resource.getPluginConfiguration().getSimpleValue(destBaseDirValueName, null); if (baseLocation == null) { throw new IllegalArgumentException("Cannot determine the bundle base deployment location - " + "there is no plugin configuration setting for [" + destBaseDirValueName + "]"); } break; } case resourceConfiguration: { baseLocation = InventoryManager.getResourceConfiguration(resource).getSimpleValue(destBaseDirValueName, null); if (baseLocation == null) { throw new IllegalArgumentException("Cannot determine the bundle base deployment location - " + "there is no resource configuration setting for [" + destBaseDirValueName + "]"); } break; } case measurementTrait: { baseLocation = measurementManager.getTraitValue(container, destBaseDirValueName); if (baseLocation == null) { throw new IllegalArgumentException("Cannot obtain trait [" + destBaseDirName + "] for resource [" + resource.getName() + "]"); } break; } default: { throw new IllegalArgumentException("Unknown bundle destination location context: " + bundleDestBaseDir); } } File destDir = new File(baseLocation, relativeDeployDir); if (!destDir.isAbsolute()) { throw new IllegalArgumentException("The base location path specified by [" + destBaseDirValueName + "] in the context [" + bundleDestBaseDir.getValueContext() + "] along with the destination directory of [" + relativeDeployDir + "] did not resolve to an absolute path [" + destDir.getPath() + "] so there is no way to know where to put the bundle."); } return destDir; } /** * If this manager can talk to a server-side {@link BundleServerService}, a proxy to that service is returned. * * @return the server-side proxy; <code>null</code> if this manager doesn't have a server to talk to */ private BundleServerService getBundleServerService() { if (configuration.getServerServices() != null) { return configuration.getServerServices().getBundleServerService(); } throw new IllegalStateException("There is no bundle server service available to obtain bundle files"); } /** * Given a resource, this obtains that resource's {@link BundleFacet} interface. * If the resource does not support that facet, an exception is thrown. * The resource must be in the STARTED (i.e. connected) state. * * @param resourceId identifies the resource that is to perform the bundle activities * @param timeout if any facet method invocation thread has not completed after this many milliseconds, * interrupt * it; value must be positive * * @return the resource's bundle facet interface * * @throws PluginContainerException on error */ protected BundleFacet getBundleFacet(int resourceId, long timeout) throws PluginContainerException { return ComponentUtil.getComponent(resourceId, BundleFacet.class, FacetLockType.READ, timeout, false, true, false); } }