/** * 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.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<String>(commandCallable); } else { // If principals are defined, run the command callable via Subject.doAs() commandFuture = new FutureTask<String>(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) { Feature[] features = featuresService.listInstalledFeatures(); for (Feature feature : features) { if (featureName.equals(feature.getName())) { return; } } fail("Feature " + featureName + " should be installed but is not"); } public void assertFeatureInstalled(String featureName, String featureVersion) { 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"); } 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<Feature>(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<Feature>(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<Feature>(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) { 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 } } } } }