| /* |
| * 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.sling.installer.it; |
| |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Random; |
| |
| import org.apache.sling.installer.api.InstallableResource; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.ops4j.pax.exam.Option; |
| import org.ops4j.pax.exam.junit.PaxExam; |
| import org.osgi.service.log.LogService; |
| |
| /** Repeatedly install/remove/reinstall semi-random sets |
| * of bundles, to stress-test the installer and framework. |
| * |
| * Randomly selects bundles to remove and reinstall in a folder |
| * containing from 4 to N bundles - by supplying a folder with many |
| * bundles, and increasing the number of cycles executed (via |
| * system properties, see pom.xml) the test can be turned into a |
| * long-running stress test. |
| */ |
| @RunWith(PaxExam.class) |
| public class BundleInstallStressTest extends OsgiInstallerTestBase { |
| |
| public static final String PROP_BUNDLES_FOLDER = "osgi.installer.BundleInstallStressTest.bundles.folder"; |
| public static final String PROP_CYCLE_COUNT = "osgi.installer.BundleInstallStressTest.cycle.count"; |
| public static final String PROP_EXPECT_TIMEOUT_SECONDS = "osgi.installer.BundleInstallStressTest.expect.timeout.seconds"; |
| public static final int MIN_TEST_BUNDLES = 4; |
| |
| /** Folder where test bundles are found */ |
| private File bundlesFolder; |
| |
| /** How many cycles to run */ |
| private int cycleCount; |
| |
| /** List of available test bundles */ |
| private List<File> testBundles; |
| |
| /** Always use the same random sequence */ |
| private Random random; |
| |
| /** Timeout for expectBundles() */ |
| private long expectBundlesTimeoutMsec; |
| |
| /** Synchronize (somewhat) with OSGi operations, to be fair */ |
| private EventsDetector eventsDetector; |
| public static final long MSEC_WITHOUT_EVENTS = 1000L; |
| |
| @org.ops4j.pax.exam.Configuration |
| public Option[] config() { |
| return defaultConfiguration(); |
| } |
| |
| @Before |
| public void setUp() { |
| setupInstaller(); |
| |
| final String bf = System.getProperty(PROP_BUNDLES_FOLDER); |
| if(bf == null) { |
| fail("Missing system property: " + PROP_BUNDLES_FOLDER); |
| } |
| bundlesFolder = new File(bf); |
| if(!bundlesFolder.isDirectory()) { |
| fail("Bundles folder '" + bundlesFolder.getAbsolutePath() + "' not found"); |
| } |
| |
| final String cc = System.getProperty(PROP_CYCLE_COUNT); |
| if(cc == null) { |
| fail("Missing system property:" + PROP_CYCLE_COUNT); |
| } |
| cycleCount = Integer.parseInt(cc); |
| |
| final String et = System.getProperty(PROP_EXPECT_TIMEOUT_SECONDS); |
| if(et == null) { |
| fail("Missing system property:" + PROP_EXPECT_TIMEOUT_SECONDS); |
| } |
| expectBundlesTimeoutMsec = Integer.parseInt(et) * 1000L; |
| |
| log(LogService.LOG_INFO, getClass().getSimpleName() |
| + ": cycle count=" + cycleCount |
| + ", expect timeout (msec)=" + expectBundlesTimeoutMsec |
| + ", test bundles folder=" + bundlesFolder.getAbsolutePath()); |
| |
| testBundles = new LinkedList<File>(); |
| final String [] files = bundlesFolder.list(); |
| for(String filename : files) { |
| if(filename.endsWith(".jar")) { |
| testBundles.add(new File(bundlesFolder, filename)); |
| } |
| } |
| |
| if(testBundles.size() < MIN_TEST_BUNDLES) { |
| fail("Found only " + testBundles.size() |
| + " bundles in test folder, expected at least " + MIN_TEST_BUNDLES |
| + " (test folder=" + bundlesFolder.getAbsolutePath() + ")" |
| ); |
| } |
| |
| random = new Random(42 + cycleCount); |
| eventsDetector = new EventsDetector(bundleContext); |
| } |
| |
| @Override |
| @After |
| public void tearDown() { |
| super.tearDown(); |
| eventsDetector.close(); |
| } |
| |
| @Test |
| public void testSemiRandomInstall() throws Exception { |
| if (cycleCount < 1) { |
| fail("Cycle count (" + cycleCount + ") should be >= 1"); |
| } |
| |
| final int initialBundleCount = bundleContext.getBundles().length; |
| log(LogService.LOG_INFO,"Initial bundle count=" + initialBundleCount); |
| logInstalledBundles(); |
| |
| // Start by installing all bundles |
| Object listener = this.startObservingBundleEvents(); |
| log(LogService.LOG_INFO,"Registering all test bundles, " + testBundles.size() + " resources"); |
| install(testBundles); |
| BundleEvent[] installedEvents = new BundleEvent[testBundles.size()]; |
| for(int i=0; i<installedEvents.length; i++) { |
| installedEvents[i] = new BundleEvent(null, null, org.osgi.framework.BundleEvent.INSTALLED); |
| } |
| this.waitForBundleEvents("All bundles should be installed", listener, expectBundlesTimeoutMsec, installedEvents); |
| expectBundleCount("After installing all test bundles", initialBundleCount + testBundles.size()); |
| |
| // And run a number of cycles where randomly selected bundles are removed and reinstalled |
| final List<File> currentInstallation = new ArrayList<File>(testBundles); |
| for(int i=0; i < cycleCount; i++) { |
| final long start = System.currentTimeMillis(); |
| log(LogService.LOG_INFO, "Test cycle " + i + ", semi-randomly selecting a subset of our test bundles"); |
| |
| for(final File f : currentInstallation) { |
| log(LogService.LOG_DEBUG, "Installed bundle: " + f); |
| } |
| |
| final List<File> toInstall = selectRandomBundles(); |
| log(LogService.LOG_INFO,"Re-registering " + toInstall.size() + " randomly selected resources (other test bundles should be uninstalled)"); |
| for(final File f : toInstall) { |
| log(LogService.LOG_DEBUG, "Re-Registering bundle: " + f); |
| } |
| int updates = 0; |
| int installs = 0; |
| for(final File f : toInstall ) { |
| if ( currentInstallation.contains(f) ) { |
| updates++; |
| } else { |
| installs++; |
| } |
| } |
| final int removes = currentInstallation.size() - updates; |
| installedEvents = new BundleEvent[removes + installs]; |
| for(int m=0; m<installedEvents.length; m++) { |
| if ( m < removes ) { |
| installedEvents[m] = new BundleEvent(null, null, org.osgi.framework.BundleEvent.UNINSTALLED); |
| } else { |
| installedEvents[m] = new BundleEvent(null, null, org.osgi.framework.BundleEvent.INSTALLED); |
| } |
| } |
| log(LogService.LOG_DEBUG, "Cycle results in " + removes + " removed bundles, " + updates + " updated bundles, " + installs + " installed bundles"); |
| |
| listener = this.startObservingBundleEvents(); |
| install(toInstall); |
| this.waitForBundleEvents("All bundles should be installed in cycle " + i, listener, expectBundlesTimeoutMsec, installedEvents); |
| |
| eventsDetector.waitForNoEvents(MSEC_WITHOUT_EVENTS, expectBundlesTimeoutMsec); |
| expectBundleCount("At cycle " + i, initialBundleCount + toInstall.size()); |
| log(LogService.LOG_INFO,"Test cycle " + i + " successful, " |
| + toInstall.size() + " bundles, " |
| + (System.currentTimeMillis() - start) + " msec"); |
| |
| // update for next cycle |
| currentInstallation.clear(); |
| currentInstallation.addAll(toInstall); |
| try { |
| Thread.sleep(500); |
| } catch (final InterruptedException ie) { } |
| } |
| } |
| |
| private void install(List<File> bundles) throws IOException { |
| final List<InstallableResource> toInstall = new LinkedList<InstallableResource>(); |
| for(File f : bundles) { |
| toInstall.add(getInstallableResource(f, f.getAbsolutePath() + f.lastModified())[0]); |
| } |
| installer.registerResources(URL_SCHEME, toInstall.toArray(new InstallableResource[toInstall.size()])); |
| } |
| |
| private void expectBundleCount(String info, final int nBundles) throws Exception { |
| log(LogService.LOG_INFO,"Expecting " + nBundles + " bundles to be installed"); |
| final Condition c = new Condition() { |
| int actualCount = 0; |
| @Override |
| public boolean isTrue() throws Exception { |
| actualCount = bundleContext.getBundles().length; |
| return actualCount == nBundles; |
| } |
| |
| @Override |
| String additionalInfo() { |
| return "Expected " + nBundles + " installed bundles, got " + actualCount; |
| } |
| |
| @Override |
| void onFailure() { |
| log(LogService.LOG_INFO, "Failure: " + additionalInfo()); |
| logInstalledBundles(); |
| } |
| |
| @Override |
| long getMsecBetweenEvaluations() { |
| return 1000L; |
| } |
| }; |
| waitForCondition(info, c, expectBundlesTimeoutMsec); |
| } |
| |
| private List<File> selectRandomBundles() { |
| final List<File> result = new LinkedList<File>(); |
| for(File f : testBundles) { |
| if(random.nextBoolean()) { |
| log(LogService.LOG_DEBUG, "Test bundle selected: " + f.getName()); |
| result.add(f); |
| } |
| } |
| |
| if(result.size() == 0) { |
| result.add(testBundles.get(0)); |
| } |
| |
| return result; |
| } |
| } |