blob: 066b7f1feea58515e1e66844b6af4528733ea3bf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.test.karaf;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.net.URL;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesService;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.ProbeBuilder;
import org.ops4j.pax.exam.TestProbeBuilder;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
public class CamelKarafTestSupport extends CamelTestSupport {
static final Long COMMAND_TIMEOUT = 30000L;
static final Long SERVICE_TIMEOUT = 30000L;
protected ExecutorService executor = Executors.newCachedThreadPool();
@Inject
protected BundleContext bundleContext;
@Inject
protected FeaturesService featuresService;
@ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*,org.apache.felix.service.*;status=provisional");
return probe;
}
public File getConfigFile(String path) {
URL res = this.getClass().getResource(path);
if (res == null) {
throw new RuntimeException("Config resource " + path + " not found");
}
return new File(res.getFile());
}
public static Option[] configure(String... extra) {
return AbstractFeatureTest.configure(extra);
}
/**
* Executes a shell command and returns output as a String.
* Commands have a default timeout of 10 seconds.
*
* @param command The command to execute
* @param principals The principals (e.g. RolePrincipal objects) to run the command under
*/
protected String executeCommand(final String command, Principal... principals) {
return executeCommand(command, COMMAND_TIMEOUT, false, principals);
}
/**
* Executes a shell command and returns output as a String.
* Commands have a default timeout of 10 seconds.
*
* @param command The command to execute.
* @param timeout The amount of time in millis to wait for the command to execute.
* @param silent Specifies if the command should be displayed in the screen.
* @param principals The principals (e.g. RolePrincipal objects) to run the command under
*/
protected String executeCommand(final String command, final Long timeout, final Boolean silent, final Principal... principals) {
waitForCommandService(command);
String response;
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final PrintStream printStream = new PrintStream(byteArrayOutputStream);
final Callable<String> commandCallable = new Callable<String>() {
@Override
public String call() throws Exception {
try {
if (!silent) {
System.err.println(command);
}
final CommandProcessor commandProcessor = getOsgiService(CommandProcessor.class);
final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
commandSession.execute(command);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
printStream.flush();
return byteArrayOutputStream.toString();
}
};
FutureTask<String> commandFuture;
if (principals.length == 0) {
commandFuture = new FutureTask<>(commandCallable);
} else {
// If principals are defined, run the command callable via Subject.doAs()
commandFuture = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
Subject subject = new Subject();
subject.getPrincipals().addAll(Arrays.asList(principals));
return Subject.doAs(subject, new PrivilegedExceptionAction<String>() {
@Override
public String run() throws Exception {
return commandCallable.call();
}
});
}
});
}
try {
executor.submit(commandFuture);
response = commandFuture.get(timeout, TimeUnit.MILLISECONDS);
} catch (Exception e) {
e.printStackTrace(System.err);
response = "SHELL COMMAND TIMED OUT: ";
}
return response;
}
private void waitForCommandService(String command) {
// the commands are represented by services. Due to the asynchronous nature of services they may not be
// immediately available. This code waits the services to be available, in their secured form. It
// means that the code waits for the command service to appear with the roles defined.
if (command == null || command.length() == 0) {
return;
}
int spaceIdx = command.indexOf(' ');
if (spaceIdx > 0) {
command = command.substring(0, spaceIdx);
}
int colonIndx = command.indexOf(':');
try {
if (colonIndx > 0) {
String scope = command.substring(0, colonIndx);
String function = command.substring(colonIndx + 1);
waitForService("(&(osgi.command.scope=" + scope + ")(osgi.command.function=" + function + "))", SERVICE_TIMEOUT);
} else {
waitForService("(osgi.command.function=" + command + ")", SERVICE_TIMEOUT);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
private void waitForService(String filter, long timeout) throws InvalidSyntaxException,
InterruptedException {
ServiceTracker st = new ServiceTracker(bundleContext,
bundleContext.createFilter(filter),
null);
try {
st.open();
st.waitForService(timeout);
} finally {
st.close();
}
}
protected <T> T getOsgiService(Class<T> type, long timeout) {
return getOsgiService(type, null, timeout);
}
protected <T> T getOsgiService(Class<T> type) {
return getOsgiService(type, null, SERVICE_TIMEOUT);
}
@SuppressWarnings("unchecked")
protected <T> T getOsgiService(Class<T> type, String filter, long timeout) {
ServiceTracker tracker = null;
try {
String flt;
if (filter != null) {
if (filter.startsWith("(")) {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
} else {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
}
} else {
flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
}
Filter osgiFilter = FrameworkUtil.createFilter(flt);
tracker = new ServiceTracker(bundleContext, osgiFilter, null);
tracker.open(true);
// Note that the tracker is not closed to keep the reference
// This is buggy, as the service reference may change i think
Object svc = type.cast(tracker.waitForService(timeout));
if (svc == null) {
Dictionary dic = bundleContext.getBundle().getHeaders();
System.err.println("Test bundle headers: " + explode(dic));
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
System.err.println("ServiceReference: " + ref);
}
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
System.err.println("Filtered ServiceReference: " + ref);
}
throw new RuntimeException("Gave up waiting for service " + flt);
}
return type.cast(svc);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/*
* Explode the dictionary into a ,-delimited list of key=value pairs
*/
private static String explode(Dictionary dictionary) {
Enumeration keys = dictionary.keys();
StringBuilder sb = new StringBuilder();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
sb.append(String.format("%s=%s", key, dictionary.get(key)));
if (keys.hasMoreElements()) {
sb.append(", ");
}
}
return sb.toString();
}
/**
* Provides an iterable collection of references, even if the original array is null
*/
private static Collection<ServiceReference> asCollection(ServiceReference[] references) {
return references != null ? Arrays.asList(references) : Collections.<ServiceReference>emptyList();
}
public JMXConnector getJMXConnector() throws Exception {
return getJMXConnector("karaf", "karaf");
}
public JMXConnector getJMXConnector(String userName, String passWord) throws Exception {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root");
Hashtable<String, Object> env = new Hashtable<>();
String[] credentials = new String[]{userName, passWord};
env.put("jmx.remote.credentials", credentials);
JMXConnector connector = JMXConnectorFactory.connect(url, env);
return connector;
}
public void assertFeatureInstalled(String featureName) {
try {
Feature[] features = featuresService.listInstalledFeatures();
for (Feature feature : features) {
if (featureName.equals(feature.getName())) {
return;
}
}
fail("Feature " + featureName + " should be installed but is not");
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}
}
public void assertFeatureInstalled(String featureName, String featureVersion) {
try {
Feature[] features = featuresService.listInstalledFeatures();
for (Feature feature : features) {
if (featureName.equals(feature.getName()) && featureVersion.equals(feature.getVersion())) {
return;
}
}
fail("Feature " + featureName + "/" + featureVersion + " should be installed but is not");
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}
}
protected void installAndAssertFeature(String feature) throws Exception {
featuresService.installFeature(feature);
assertFeatureInstalled(feature);
}
protected void installAndAssertFeature(String feature, String version) throws Exception {
featuresService.installFeature(feature, version);
assertFeatureInstalled(feature, version);
}
protected void installAssertAndUninstallFeature(String feature) throws Exception {
Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
try {
featuresService.installFeature(feature);
assertFeatureInstalled(feature);
} finally {
uninstallNewFeatures(featuresBefore);
}
}
protected void installAssertAndUninstallFeature(String feature, String version) throws Exception {
Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
try {
featuresService.installFeature(feature, version);
assertFeatureInstalled(feature, version);
} finally {
uninstallNewFeatures(featuresBefore);
}
}
protected void installAssertAndUninstallFeatures(String... feature) throws Exception {
Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
try {
for (String curFeature : feature) {
featuresService.installFeature(curFeature);
assertFeatureInstalled(curFeature);
}
} finally {
uninstallNewFeatures(featuresBefore);
}
}
/**
* The feature service does not uninstall feature dependencies when uninstalling a single feature.
* So we need to make sure we uninstall all features that were newly installed.
*/
protected void uninstallNewFeatures(Set<Feature> featuresBefore) {
try {
Feature[] features = featuresService.listInstalledFeatures();
for (Feature curFeature : features) {
if (!featuresBefore.contains(curFeature)) {
try {
System.out.println("Uninstalling " + curFeature.getName());
featuresService.uninstallFeature(curFeature.getName(), curFeature.getVersion());
} catch (Exception e) {
// ignore
}
}
}
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}
}
}