blob: 051e0328fa3f15719e54eb758af3206d712abb22 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) Intel Corporation
* Copyright (c) 2017
*
* Licensed 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.felix.fileinstall.plugins.installer.test;
import static org.junit.Assert.*;
import static org.ops4j.pax.exam.CoreOptions.*;
import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.felix.fileinstall.plugins.installer.FrameworkInstaller;
import org.apache.felix.fileinstall.plugins.installer.InstallableListener;
import org.apache.felix.fileinstall.plugins.installer.InstallableUnit;
import org.apache.felix.fileinstall.plugins.installer.InstallableUnitEvent;
import org.apache.felix.fileinstall.plugins.installer.State;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerMethod;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerMethod.class)
public class InstallerIntegrationTest {
@Configuration
public Option[] config() {
return options(
// vmOptions("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"),
systemProperty("org.apache.felix.fileinstall.plugins.installer.debug").value("true"),
mavenBundle(FELIX_GROUPID, "org.apache.felix.configadmin").versionAsInProject(),
mavenBundle(FELIX_GROUPID, "org.apache.felix.scr").versionAsInProject(),
mavenBundle(FELIX_GROUPID, "org.apache.felix.log").versionAsInProject(),
mavenBundle(FELIX_GROUPID, "org.apache.felix.resolver").versionAsInProject(),
// NB FileInstall included for its API, but NOT started so the directory polling doesn't happen
mavenBundle(FELIX_GROUPID, "org.apache.felix.fileinstall").versionAsInProject().start(false),
mavenBundle("org.osgi", "org.osgi.service.repository").versionAsInProject(),
mavenBundle("biz.aQute.bnd", "biz.aQute.bndlib").versionAsInProject(),
mavenBundle("biz.aQute.bnd", "biz.aQute.repository").versionAsInProject(),
mavenBundle(FELIX_GROUPID, "org.apache.felix.fileinstall.plugins.resolver").versionAsInProject(),
mavenBundle(FELIX_GROUPID, "org.apache.felix.fileinstall.plugins.installer").versionAsInProject(),
junitBundles(),
// javax.xml exports required as version=1.0 for bnd repository
systemPackage("javax.xml.namespace;version=1.0.0"),
systemPackage("javax.xml.stream;version=1.0.0")
);
}
private static final String FELIX_GROUPID = "org.apache.felix";
@Inject
private BundleContext bundleContext;
private final File dataDir = new File("./target/test-classes/");
private ServiceTracker<ArtifactInstaller, ArtifactInstaller> artifactInstallerTracker;
private ServiceTracker<FrameworkInstaller, FrameworkInstaller> fwkInstallerTracker;
@Before
public void before() throws Exception {
assertAllBundlesResolved();
// Track ArtifactInstaller service ONLY from bundle org.apache.felix.fileinstall.plugins.installer, to avoid potential interference
Bundle installerBundle = findBundle("org.apache.felix.fileinstall.plugins.installer");
Filter artifactInstallerTrackerFilter = FrameworkUtil.createFilter(String.format("(&(objectClass=%s)(service.bundleid=%d))", ArtifactInstaller.class.getName(), installerBundle.getBundleId()));
this.artifactInstallerTracker = new ServiceTracker<>(this.bundleContext, artifactInstallerTrackerFilter, null);
this.artifactInstallerTracker.open();
// Wait up to 5 seconds for ArtifactInstaller to appear
ArtifactInstaller artifactInstaller = this.artifactInstallerTracker.waitForService(5000);
if (artifactInstaller == null) {
fail("ArtifactInstaller service not available within 5 seconds");
}
this.fwkInstallerTracker = new ServiceTracker<>(this.bundleContext, FrameworkInstaller.class, null);
this.fwkInstallerTracker.open();
}
@After
public void after() {
this.fwkInstallerTracker.close();
this.artifactInstallerTracker.close();
}
@Test
public void testErrorFromInvalidArchive() throws Exception {
Object[] artifactInstallers = this.artifactInstallerTracker.getServices();
assertNotNull("Should be exactly one ArtifactInstaller service", artifactInstallers);
assertEquals("Should be exactly one ArtifactInstaller service", 1, artifactInstallers.length);
// Register mock InstallableListener
List<String> installEvents = new LinkedList<>();
InstallableListener mockInstallListener = new InstallableListener() {
@Override
public void installableUnitsChanged(Collection<InstallableUnitEvent> events) {
for (InstallableUnitEvent event : events) {
String message = String.format("%s %s", event.getNewState(), event.getUnit().getSymbolicName());
installEvents.add(message);
}
}
};
ServiceRegistration<InstallableListener> mockListenerReg = this.bundleContext.registerService(InstallableListener.class, mockInstallListener, null);
try {
assertEquals("Shouldn't be any install events yet", 0, installEvents.size());
// Provide the sample archive
File sampleArchive = new File(this.dataDir, "org.example.invalid-missing-rb.bar");
ArtifactInstaller installer = this.artifactInstallerTracker.getService();
assertTrue("installer should handle sample archive", installer.canHandle(sampleArchive));
installer.install(sampleArchive);
// Wait for resolve to occur
Thread.sleep(2000);
assertEquals(1, installEvents.size());
assertEquals("ERROR org.example.invalid-missing-rb", installEvents.get(0));
} finally {
mockListenerReg.unregister();
}
}
private class EventQueue implements InstallableListener {
private final Queue<InstallableUnitEvent> q = new LinkedList<>();
@Override
public synchronized void installableUnitsChanged(Collection<InstallableUnitEvent> events) {
for (InstallableUnitEvent event : events) {
this.q.add(event);
System.out.printf("!!! added event %s (%s), queue depth is now %d%n ", event.getNewState(), event.getUnit().getSymbolicName(), this.q.size());
}
}
private synchronized InstallableUnitEvent pop() {
if (this.q.isEmpty()) {
throw new IllegalStateException("Trying to take from empty queue");
}
InstallableUnitEvent event = this.q.remove();
System.out.printf("!!! removed event %s (%s), queue depth is now %d%n ", event.getNewState(), event.getUnit().getSymbolicName(), this.q.size());
return event;
}
private synchronized int depth() {
return this.q.size();
}
}
/*
* This is a complex case that tests the interplay of multiple archives with
* overlapping content.
*
* - Archive 1 contains bundles A and B.
*
* - Archive 2 contains bundles A and C.
*
* The process is as follows:
*
* i. Make installer aware of both archives. Both now present as an
* installable unit with 2 artifacts (A+B and A+C).
*
* ii. Install archive 1.
* Archive 2 gets invalidated and re-resolved. It now presents as an
* installable unit with 1 artifact (C) because A is already in the
* framework.
*
* iii. Remove archive 1. Archive 2 gets invalidated and
* re-resolved. It now presents as an installable unit with 2 artifacts
* (A+C) because A is no longer in the framework.
*
* Note that step (iii) is equivalent to deleting the archive from
* FileInstall's load directory. Therefore it doesn't reappear as a RESOLVED
* installable unit. To reinstall this archive, the user would have to copy
* it back into the load directory.
*
*/
@Test
public void testInstallArchives() throws Exception {
// Register InstallableListener
EventQueue eventQueue = new EventQueue();
ServiceRegistration<InstallableListener> mockListenerReg = this.bundleContext.registerService(InstallableListener.class, eventQueue, null);
try {
assertEquals("Shouldn't be any install events yet", 0, eventQueue.depth());
InstallableUnitEvent event;
// Provide the sample archive
File sampleArchive1 = new File(this.dataDir, "valid1.bar");
ArtifactInstaller installer = this.artifactInstallerTracker.getService();
assertNotNull("ArtifactInstaller service should exist", installer);
assertTrue("installer should handle sample archive", installer.canHandle(sampleArchive1));
installer.install(sampleArchive1);
// Wait for resolve to occur
Thread.sleep(2000);
assertEquals(1, eventQueue.depth());
event = eventQueue.pop();
InstallableUnit unit1 = event.getUnit();
assertEquals(State.RESOLVED, event.getNewState());
assertEquals("samples.valid1", event.getUnit().getSymbolicName());
assertEquals("samples.valid1 requires 2 bundles to be installed", 2, event.getUnit().getArtifacts().size());
// Add a second archive
File sampleArchive2 = new File(this.dataDir, "valid2.bar");
assertTrue("installer should handle sample archive", installer.canHandle(sampleArchive2));
installer.install(sampleArchive2);
// Wait for resolve to occur
Thread.sleep(2000);
assertEquals(1, eventQueue.depth());
event = eventQueue.pop();
assertEquals(State.RESOLVED, event.getNewState());
assertEquals("samples.valid2", event.getUnit().getSymbolicName());
assertEquals("samples.valid2 requires 2 bundles to be installed", 2, event.getUnit().getArtifacts().size());
// Install first archive.
unit1.install().getValue();
// Check bundles were actually installed!
assertNotNull(findBundle("org.example.a"));
assertNotNull(findBundle("org.example.b"));
// The installation of first archive should cause invalidation of second archive resolution result, followed by re-resolve.
// First, installing #1
event = eventQueue.pop();
assertEquals(State.INSTALLING, event.getNewState());
assertEquals("samples.valid1", event.getUnit().getSymbolicName());
// Next installed #1
event = eventQueue.pop();
assertEquals(State.INSTALLED, event.getNewState());
assertEquals("samples.valid1", event.getUnit().getSymbolicName());
// Next removed #2 (due to invalidation)
// Short sleep necessary because the invalidate can happen on another thread (eg framework refresh)
Thread.sleep(500);
event = eventQueue.pop();
assertEquals(State.REMOVED, event.getNewState());
assertEquals("samples.valid2", event.getUnit().getSymbolicName());
// Next resolved #2 (re-resolve after invalidation)
// Another sleep because the re-resolve happens after at least 1 sec delay
Thread.sleep(2000);
event = eventQueue.pop();
assertEquals(State.RESOLVED, event.getNewState());
assertEquals("samples.valid2", event.getUnit().getSymbolicName());
assertEquals("samples.valid2 now requires 1 bundle to be installed", 1, event.getUnit().getArtifacts().size());
assertEquals(0, eventQueue.depth());
// Now uninstall first archive
installer.uninstall(sampleArchive1);
Thread.sleep(500);
// Check bundles were actually uninstalled!
assertNull(findBundle("org.example.a"));
assertNull(findBundle("org.example.b"));
// Removed event for #1
Thread.sleep(500);
event = eventQueue.pop();
assertEquals(State.REMOVED, event.getNewState());
assertEquals("samples.valid1", event.getUnit().getSymbolicName());
// Removed event for #2 due to invalidation
event = eventQueue.pop();
assertEquals(State.REMOVED, event.getNewState());
assertEquals("samples.valid2", event.getUnit().getSymbolicName());
// Re-resolution of #2 after a little sleep
Thread.sleep(2000);
event = eventQueue.pop();
assertEquals(State.RESOLVED, event.getNewState());
assertEquals("samples.valid2", event.getUnit().getSymbolicName());
assertEquals("samples.valid2 now requires 2 bundle to be installed", 2, event.getUnit().getArtifacts().size());
assertEquals(0, eventQueue.depth());
} finally {
mockListenerReg.unregister();
}
}
@Test
public void testAddRemoveBundles() throws Exception {
// Precondition: no example bundles
assertNull("example bundle already present - broken prior test?", findBundle("org.example.a"));
assertNull("example bundle already present - broken prior test?", findBundle("org.example.b"));
FrameworkInstaller installer = this.fwkInstallerTracker.getService();
assertNotNull("installer service not found", installer);
// Install bundles and check they were installed
File fileA = new File(this.dataDir, "org.example.a.jar");
File fileB = new File(this.dataDir, "org.example.b.jar");
List<String> locations = Stream.of(fileA, fileB)
.map(File::toURI)
.map(URI::toString)
.collect(Collectors.toList());
Object sponsor = new Object();
List<Bundle> installed = installer.addLocations(sponsor, locations);
assertEquals(2, installed.size());
assertEquals("org.example.a", installed.get(0).getSymbolicName());
assertEquals("org.example.b", installed.get(1).getSymbolicName());
assertNotNull("example bundle not present after install", findBundle("org.example.a"));
assertNotNull("example bundle not present after install", findBundle("org.example.b"));
// Uninstall bundles and check they are gone
Set<String> removed = installer.removeSponsor(sponsor).stream().map(Bundle::getSymbolicName).collect(Collectors.toSet());
assertEquals(2, removed.size());
assertTrue(removed.contains("org.example.a"));
assertTrue(removed.contains("org.example.b"));
assertNull("example bundle still present after uninstall", findBundle("org.example.a"));
assertNull("example bundle still present after uninstall", findBundle("org.example.b"));
}
@Test
public void testAddRemoveOverlappingBundles() throws Exception {
// Precondition: no example bundles
assertNull("example bundle already present - broken prior test?", findBundle("org.example.a"));
assertNull("example bundle already present - broken prior test?", findBundle("org.example.b"));
assertNull("example bundle already present - broken prior test?", findBundle("org.example.c"));
FrameworkInstaller installer = this.fwkInstallerTracker.getService();
assertNotNull("installer service not found", installer);
File fileA = new File(this.dataDir, "org.example.a.jar");
File fileB = new File(this.dataDir, "org.example.b.jar");
File fileC = new File(this.dataDir, "org.example.c.jar");
// Install first set including A and B
Object sponsor1 = new Object();
List<String> locations1 = Stream.of(fileA, fileB)
.map(File::toURI)
.map(URI::toString)
.collect(Collectors.toList());
List<Bundle> installed1 = installer.addLocations(sponsor1, locations1);
assertEquals(2, installed1.size());
assertEquals("org.example.a", installed1.get(0).getSymbolicName());
assertEquals("org.example.b", installed1.get(1).getSymbolicName());
assertNotNull("example bundle not present after install", findBundle("org.example.a"));
assertNotNull("example bundle not present after install", findBundle("org.example.b"));
assertNull("incorrect bundle installed", findBundle("org.example.c"));
// Install second set including A and C
Object sponsor2 = new Object();
List<String> locations2 = Stream.of(fileA, fileC)
.map(File::toURI)
.map(URI::toString)
.collect(Collectors.toList());
List<Bundle> installed2 = installer.addLocations(sponsor2, locations2);
assertEquals(1, installed2.size());
assertEquals("org.example.c", installed2.get(0).getSymbolicName());
assertNotNull("example bundle not present after install", findBundle("org.example.a"));
assertNotNull("example bundle not present after install", findBundle("org.example.b"));
assertNotNull("example bundle not present after install", findBundle("org.example.c"));
// Uninstall first set - bundle B should go away but not A
Set<String> removed1 = installer.removeSponsor(sponsor1).stream().map(Bundle::getSymbolicName).collect(Collectors.toSet());
assertEquals(1, removed1.size());
assertTrue(removed1.contains("org.example.b"));
assertNotNull("example bundle should still be present", findBundle("org.example.a"));
assertNull("example bundle still present after uninstall", findBundle("org.example.b"));
assertNotNull("example bundle should still be present", findBundle("org.example.c"));
// Uninstall second set - all bundles should go away
Set<String> removed2 = installer.removeSponsor(sponsor2).stream().map(Bundle::getSymbolicName).collect(Collectors.toSet());
assertEquals(2, removed2.size());
assertTrue(removed2.contains("org.example.a"));
assertTrue(removed2.contains("org.example.c"));
assertNull("example bundle still present after uninstall", findBundle("org.example.a"));
assertNull("example bundle still present after uninstall", findBundle("org.example.b"));
assertNull("example bundle still present after uninstall", findBundle("org.example.c"));
}
@Test
public void testDontRemoveExistingBundle() throws Exception {
// Precondition: no example bundles
assertNull("example bundle already present - broken prior test?", findBundle("org.example.a"));
assertNull("example bundle already present - broken prior test?", findBundle("org.example.b"));
FrameworkInstaller installer = this.fwkInstallerTracker.getService();
assertNotNull("installer service not found", installer);
Bundle existingBundle = findBundle("org.apache.felix.scr");
assertNotNull("SCR bundle has been uninstalled -- broken prior test?", existingBundle);
String locationA = new File(this.dataDir, "org.example.a.jar").toURI().toString();
String locationB = new File(this.dataDir, "org.example.b.jar").toURI().toString();
String existingLocation = existingBundle.getLocation();
// Install set including A, B and SCR
Object sponsor = new Object();
List<String> locations = Arrays.asList(locationA, locationB, existingLocation);
installer.addLocations(sponsor, locations);
assertNotNull("example bundle not present after install", findBundle("org.example.a"));
assertNotNull("example bundle not present after install", findBundle("org.example.b"));
assertNotNull("preexisting bundle not present after install", findBundle("org.apache.felix.scr"));
// Uninstall set - bundles A and B should go away but not SCR
installer.removeSponsor(sponsor);
assertNull("example bundle still present after uninstall", findBundle("org.example.a"));
assertNull("example bundle still present after uninstall", findBundle("org.example.b"));
assertNotNull("preexisting bundle uninstalled", findBundle("org.apache.felix.scr"));
}
private Bundle findBundle(String bsn) {
Bundle found = null;
for (Bundle bundle : this.bundleContext.getBundles()) {
if (bsn.equals(bundle.getSymbolicName()) && bundle.getState() != Bundle.UNINSTALLED) {
if (found != null && bundle.getBundleId() != found.getBundleId()) {
throw new IllegalArgumentException("Ambiguous bundle symbolic name " + bsn);
}
found = bundle;
}
}
return found;
}
private void assertAllBundlesResolved() {
int mask = Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE;
for (Bundle bundle : this.bundleContext.getBundles()) {
assertTrue("Unresolved bundle " + bundle.getSymbolicName(), (bundle.getState() & mask) > 0);
}
}
}