/**
* Copyright (C) 2008 Progress Software, Inc. All rights reserved.
* http://fusesource.com
*
* The software in this package is published under the terms of the AGPL license
* a copy of which has been included with this distribution in the license.txt file.
*/
package org.fusesource.cloudmix.agent.karaf;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.felix.karaf.features.FeaturesService;
import org.apache.felix.karaf.admin.AdminService;
import org.apache.felix.karaf.admin.Instance;
import org.apache.felix.karaf.admin.InstanceSettings;
import org.fusesource.cloudmix.agent.Bundle;
import org.fusesource.cloudmix.agent.Feature;
import org.fusesource.cloudmix.agent.FeatureList;
import org.fusesource.cloudmix.agent.InstallerAgent;
import org.fusesource.cloudmix.common.dto.AgentDetails;
import org.fusesource.cloudmix.common.dto.ConfigurationUpdate;
import org.fusesource.cloudmix.common.dto.ProvisioningAction;
import org.fusesource.cloudmix.common.util.FileUtils;
import static org.fusesource.cloudmix.agent.karaf.ConfigAdminHelper.merge;
public class KarafAgent extends InstallerAgent {
public static final String FEATURES_REPOSITORIES = "featuresRepositories";
public static final String FEATURES_BOOT = "featuresBoot";
protected static final String VM_PROP_SMX_HOME = "karaf.home";
protected static final String AGENT_WORK_DIR = File.separator + "data" + File.separator + "cloudmix";
protected static final String AGENT_PROPS_PATH_SUFFIX = "agent.properties";
private static final Log LOGGER = LogFactory.getLog(KarafAgent.class);
private static final String FEATURE_FILE_KEY = KarafAgent.class.getName() + ".featureFile";
private static final String SMX4_CONTAINER_TYPE = "smx4";
private static final String JBI_TYPE = "jbi";
private static final String[] SMX4_PACKAGE_TYPES = {
"osgi", "jbi"
};
private static final String JBI_URL_PREFIX = "jbi:";
private FeaturesService featuresService;
private AdminService adminService;
public void setFeaturesService(FeaturesService featuresService) {
this.featuresService = featuresService;
}
public void setAdminService(AdminService adminService) {
this.adminService = adminService;
}
@Override
public String getDetailsPropertyFilePath() {
if (propertyFilePath == null) {
propertyFilePath = defaultWorkFile(AGENT_PROPS_PATH_SUFFIX);
}
return propertyFilePath;
}
@Override
protected boolean validateAgent() {
return featuresService != null && getWorkDirectory() != null;
}
@Override
protected void installFeatures(ProvisioningAction action, String credentials, String resource)
throws Exception {
LOGGER.info("Installing CloudMix feature " + resource);
Feature feat = new Feature(action.getFeature());
if (resource.startsWith("karaf:")) {
String name = getInstanceName(feat.getName());
Instance instance = adminService.createInstance(name, new InstanceSettings(0, null, null, null));
configureInstance(instance, resource);
instance.start(null);
} else {
if (resource.startsWith("scan-features:")) {
resource = resource.substring("scan-features:".length());
}
int idx = resource.indexOf("!/");
if (idx > 1) {
String repo = resource.substring(0, idx);
String feature = resource.substring(idx + 2);
URI repoUri = new URI(repo);
LOGGER.info("Adding feature repository " + repoUri);
featuresService.addRepository(repoUri);
LOGGER.info("Adding feature: " + feature);
featuresService.installFeature(feature);
}
}
addAgentFeature(feat);
}
private void configureInstance(Instance instance, String resource) throws IOException {
File config = new File(instance.getLocation(), "etc" + File.separator
+ "org.apache.felix.karaf.features.cfg");
Map<String, String> properties = new HashMap<String, String>();
String[] parts = resource.split(" ");
if (parts.length > 2) {
properties.put(FEATURES_BOOT, parts[2]);
}
if (parts.length > 1) {
properties.put(FEATURES_REPOSITORIES, parts[1]);
}
merge(config, properties);
}
@Override
protected void installFeature(org.fusesource.cloudmix.agent.Feature feature,
List<ConfigurationUpdate> featureCfgOverrides) {
boolean success = false;
URI reposUri = null;
File featuresFile = null;
try {
featuresFile = File.createTempFile("features_", ".xml", getWorkDirectory());
FileWriter writer = new FileWriter(featuresFile);
writer.write(generateSMX4FeatureDoc(feature.getFeatureList()));
writer.close();
LOGGER.info("Wrote features document to " + featuresFile);
reposUri = featuresFile.toURI();
LOGGER.info("Adding features repository " + reposUri);
featuresService.removeRepository(reposUri);
featuresService.addRepository(reposUri);
super.installFeature(feature, featureCfgOverrides);
try {
featuresService.installFeature(feature.getName());
// TODO: check featuresService.listInstalledFeatures()?
} catch (Exception e) {
LOGGER.error("Error installing feature " + feature, e);
LOGGER.debug(e);
}
success = true;
LOGGER.info("installation of feature " + feature.getName() + " successful");
feature.getAgentProperties().put(FEATURE_FILE_KEY, featuresFile);
} catch (Exception e) {
LOGGER.error("Error installing feature " + feature + ", exception " + e);
LOGGER.debug(e);
} finally {
if (!success) {
LOGGER.warn("installFeature failed");
try {
if (reposUri != null) {
featuresService.removeRepository(reposUri);
}
} catch (Throwable t) {
LOGGER.warn("error removing repository " + reposUri, t);
}
if (featuresFile != null) {
featuresFile.delete();
}
}
}
}
@Override
protected void uninstallFeature(Feature feature) {
LOGGER.info("Uninstalling CloudMix feature " + feature);
saveFeatureLog(feature);
Instance instance = adminService == null ? null : adminService.getInstance(getInstanceName(feature
.getName()));
if (instance != null) {
try {
instance.stop();
instance.destroy();
} catch (Exception e) {
LOGGER.warn("Unable to stop/destroy a Karaf instance: " + e.getMessage(), e);
}
} else {
String featureName = feature.getName();
removeFeatureId(featureName);
try {
featuresService.uninstallFeature(featureName);
// TODO: check featuresService.listInstalledFeatures()?
super.uninstallFeature(feature);
File featuresFile = (File)feature.getAgentProperties().get(FEATURE_FILE_KEY);
if (featuresFile == null) {
LOGGER.error("Cannot find features file for feature " + featureName);
} else {
URI reposUri = featuresFile.toURI();
LOGGER.info("Removing features repository " + reposUri);
featuresService.removeRepository(reposUri);
LOGGER.info("Deleting features file " + featuresFile);
featuresFile.delete();
}
feature.getAgentProperties().remove(FEATURE_FILE_KEY);
} catch (Exception e) {
LOGGER.error("Error uninstalling feature " + featureName + ", exception " + e);
e.printStackTrace();
LOGGER.debug(e);
}
}
}
private void saveFeatureLog(Feature feature) {
try {
File workDir = getWorkDirectory();
LOGGER.info("Copying feature log to " + workDir.getCanonicalPath()
+ "/" + feature.getName());
String path = System.getProperty(VM_PROP_SMX_HOME);
if (path == null) {
LOGGER.warn("Container home directory system property is not set,"
+ " feature log can not be copied");
return;
}
String featureName = feature.getName().replace(':', '_');
String featureLogPath = path + "/instances/" + featureName + "/data/log/karaf.log";
InputStream is = new FileInputStream(featureLogPath);
File outDir = new File(workDir.getCanonicalPath(), "/" + featureName);
outDir.mkdirs();
String outLogPath = outDir.getCanonicalPath() + "/karaf.log";
OutputStream os = new FileOutputStream(outLogPath);
// likely to work slower but better than invoking OS copy commands
FileUtils.copy(is, os, 1024 * 16);
} catch (FileNotFoundException e) {
LOGGER.warn("Feature log is not available");
e.printStackTrace();
} catch (IOException e) {
LOGGER.warn("Can not copy the feature log");
e.printStackTrace();
}
}
public String generateSMX4FeatureDoc(FeatureList fl) {
StringBuilder sb = new StringBuilder().append("<features>\n");
for (Feature feature : fl.getAllFeatures()) {
sb.append(" <feature name=\"").append(feature.getName()).append("\">\n");
for (String pn : feature.getPropertyNames()) {
sb.append(" <config name=\"").append(pn).append("\">\n");
Properties props = feature.getProperties(pn);
for (Object o : props.keySet()) {
sb.append(" ").append(o).append(" = ").append(props.get(o)).append("\n");
}
sb.append(" </config>\n");
}
Map<String, Bundle> unorderedBundles = new HashMap<String, Bundle>();
for (Bundle b : feature.getBundles()) {
unorderedBundles.put(b.getUri(), b);
}
List<Bundle> orderedBundles = new ArrayList<Bundle>();
for (Bundle b : feature.getBundles()) {
doOrderBundlesBasedOnTheirInterDependencies(b, orderedBundles, unorderedBundles);
}
for (Bundle b : orderedBundles) {
sb.append(" <bundle>");
String type = b.getType();
if (JBI_TYPE.equals(type)) {
sb.append(JBI_URL_PREFIX);
}
sb.append(b.getUri()).append("</bundle>\n");
}
sb.append(" </feature>\n");
}
sb.append("</features>\n");
return sb.toString();
}
private void doOrderBundlesBasedOnTheirInterDependencies(Bundle b, List<Bundle> orderedBundles,
Map<String, Bundle> unOrderedBundles) {
if (b == null) {
return;
}
for (String depUri : b.getDepUris()) {
doOrderBundlesBasedOnTheirInterDependencies(unOrderedBundles.get(depUri), orderedBundles,
unOrderedBundles);
}
String uri = b.getUri();
if (unOrderedBundles.get(uri) != null) {
orderedBundles.add(b);
unOrderedBundles.remove(uri);
}
}
@Override
protected boolean installBundle(org.fusesource.cloudmix.agent.Feature feature, Bundle bundle) {
return true;
}
@Override
protected boolean uninstallBundle(Feature feature, Bundle bundle) {
return false;
}
@Override
public AgentDetails updateAgentDetails() {
AgentDetails rc = super.updateAgentDetails();
try {
getClient().updateAgentDetails(getAgentId(), getAgentDetails());
} catch (URISyntaxException e) {
LOGGER.info("Problem updating agent information ", e);
e.printStackTrace();
}
return rc;
}
@Override
public void init() throws Exception {
File dir = getWorkDirectory();
if (dir != null) {
if (!dir.exists()) {
FileUtils.createDirectory(dir);
}
}
if (getContainerType() == null) {
setContainerType(SMX4_CONTAINER_TYPE);
}
if (getSupportPackageTypes() == null || getSupportPackageTypes().length == 0) {
setSupportPackageTypes(SMX4_PACKAGE_TYPES);
}
super.init();
}
private String defaultWorkFile(String fileName) {
// Default work files based off the ServiceMix data directory.
String path = System.getProperty(VM_PROP_SMX_HOME);
if (path == null) {
LOGGER.error("cannot determing ServiceMix home directory");
return null;
}
path += AGENT_WORK_DIR;
if (fileName != null) {
path += File.separator + fileName;
}
LOGGER.info("using work file " + path);
return path;
}
public String getInstanceName(String feature) {
return feature.replaceAll(":", "_");
}
@Override
public String getBaseHref() {
return "**unavailable**";
}
}