You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by jw...@apache.org on 2012/11/28 17:41:30 UTC

svn commit: r1414804 - in /aries/trunk/subsystem: subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/

Author: jwross
Date: Wed Nov 28 16:41:29 2012
New Revision: 1414804

URL: http://svn.apache.org/viewvc?rev=1414804&view=rev
Log:
[ARIES-968] Consider using a "multiple attempts" strategy versus the current locking strategy when updating sharing policies.

The synchronization has	been replaced with a retry strategy. If	the region digraph fails to replace because something else
modified it after the copy was made, up	to 9 additional	attempts will be made, for a total of 10. This has the advantage of
avoiding a bottleneck as well as protecting against modifications from actors other than subsystems. If replacement still
fails after the 10th attempt, a SubsystemException is thrown.

Also created a more robust test for adding requirements. This required some fixes to the copy/replace algorithm in order to
account for bundle ids and child connections.

Modified:
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/AriesSubsystemTest.java
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java?rev=1414804&r1=1414803&r2=1414804&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java Wed Nov 28 16:41:29 2012
@@ -24,11 +24,13 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.aries.subsystem.AriesSubsystem;
 import org.apache.aries.subsystem.core.archive.AriesSubsystemParentsHeader;
@@ -552,47 +554,86 @@ public class BasicSubsystem implements R
 		// Unscoped subsystems import everything.already.
 		if (!isScoped())
 			return;
-		Region currentRegion = getRegion();
-		RegionDigraph currentDigraph = currentRegion.getRegionDigraph();
-		RegionFilterBuilder filterBuilder = currentDigraph.createRegionFilterBuilder();
-		try {
-			// Copy the sharing policy of the current region.
-			for (FilteredRegion filteredRegion : currentRegion.getEdges()) {
-				Map<String, Collection<String>> sharingPolicy = filteredRegion.getFilter().getSharingPolicy();
-				for (Map.Entry<String, Collection<String>> entry : sharingPolicy.entrySet())
-					for (String filter : entry.getValue())
-						filterBuilder.allow(entry.getKey(), filter);
-			}
-			// Add the additional requirements to the sharing policy.
-			for (Requirement requirement : requirements) {
-				String namespace = requirement.getNamespace();
-				// The osgi.service namespace requires translation.
-				if (ServiceNamespace.SERVICE_NAMESPACE.equals(namespace))
-					namespace = RegionFilter.VISIBLE_SERVICE_NAMESPACE;
-				String filter = requirement.getDirectives().get(IdentityNamespace.REQUIREMENT_FILTER_DIRECTIVE);
-				// A null filter means import everything from that namespace.
-				if (filter == null)
-					filterBuilder.allowAll(namespace);
-				else
-					filterBuilder.allow(namespace, filter);
-			}
-			// Update the region digraph. Lock on the class to prevent conflicts
-			// with other subsystems updating their own requirements.
-			// TODO This lock does not prevent conflicts with users outside of
-			// subsystems.
-			synchronized (BasicSubsystem.class) {
+		for (int i = 0; i < 10; i++) {
+			try {
+				Region oldRegion = getRegion();
+				RegionDigraph currentDigraph = oldRegion.getRegionDigraph();
 				RegionDigraph copiedDigraph = currentDigraph.copy();
-				copiedDigraph.removeRegion(currentRegion);
-				Region fromRegion = copiedDigraph.createRegion(currentRegion.getName());
-				Region toRegion = ((BasicSubsystem)getParents().iterator().next()).getRegion();
-				copiedDigraph.connect(fromRegion, filterBuilder.build(), copiedDigraph.getRegion(toRegion.getName()));
-				// TODO Protect against the possibility of an already modified
-				// digraph with multiple attempts, if necessary?
-				currentDigraph.replace(copiedDigraph);
+				Region newRegion = copiedDigraph.getRegion(oldRegion.getName());
+				// Store the bundle ids for future reference.
+				Set<Long> bundleIds = newRegion.getBundleIds();
+				// Store the current connection info with parent for future reference.
+				RegionFilterBuilder parentFilter = copiedDigraph.createRegionFilterBuilder();
+				for (FilteredRegion filteredRegion : newRegion.getEdges()) {
+					Map<String, Collection<String>> sharingPolicy = filteredRegion.getFilter().getSharingPolicy();
+					for (Map.Entry<String, Collection<String>> entry : sharingPolicy.entrySet())
+						for (String filter : entry.getValue())
+							parentFilter.allow(entry.getKey(), filter);
+				}
+				// Add the additional requirements to the connection info with parent.
+				for (Requirement requirement : requirements) {
+					String namespace = requirement.getNamespace();
+					// The osgi.service namespace requires translation.
+					if (ServiceNamespace.SERVICE_NAMESPACE.equals(namespace))
+						namespace = RegionFilter.VISIBLE_SERVICE_NAMESPACE;
+					String filter = requirement.getDirectives().get(IdentityNamespace.REQUIREMENT_FILTER_DIRECTIVE);
+					// A null filter means import everything from that namespace.
+					if (filter == null)
+						parentFilter.allowAll(namespace);
+					else
+						parentFilter.allow(namespace, filter);
+				}
+				// Store the connection info with children for future reference.
+				Map<String, RegionFilterBuilder> childFilters = new HashMap<String, RegionFilterBuilder>();
+				for (Subsystem child : getChildren()) {
+					if (!((BasicSubsystem)child).isScoped())
+						continue;
+					Region childRegion = ((BasicSubsystem)child).getRegion();
+					RegionFilterBuilder childBuilder = copiedDigraph.createRegionFilterBuilder();
+					for (FilteredRegion filteredRegion : childRegion.getEdges()) {
+						Map<String, Collection<String>> sharingPolicy = filteredRegion.getFilter().getSharingPolicy();
+						for (Map.Entry<String, Collection<String>> entry : sharingPolicy.entrySet())
+							for (String filter : entry.getValue())
+								childBuilder.allow(entry.getKey(), filter);
+					}
+					childFilters.put(childRegion.getName(), childBuilder);
+				}
+				// Remove the region so the parent connection can be updated.
+				copiedDigraph.removeRegion(newRegion);
+				// Recreate the region.
+				newRegion = copiedDigraph.createRegion(newRegion.getName());
+				// Copy the bundle ids.
+				for (Long bundleId : bundleIds)
+					newRegion.addBundle(bundleId);
+				// Reconnect to the parent.
+				copiedDigraph.connect(newRegion, parentFilter.build(), copiedDigraph.getRegion(((BasicSubsystem)getParents().iterator().next()).getRegion().getName()));
+				// Reconnect the children.
+				for (Map.Entry<String, RegionFilterBuilder> entry : childFilters.entrySet())
+					copiedDigraph.connect(copiedDigraph.getRegion(entry.getKey()), entry.getValue().build(), newRegion);
+				// Replace the current digraph.
+				try {
+					currentDigraph.replace(copiedDigraph);
+				}
+				catch (BundleException e) {
+					// Something modified digraph since the copy was made.
+					if (i < 10)
+						// There are more attempts to make.
+						continue;
+					// Number of attempts has been exhausted.
+					throw e;
+				}
+				// Success! No need to continue looping.
+				break;
+			}
+			// If an exception occurs for any reason other than a replacement
+			// failure, or replacement failed with no more attempts left, break 
+			// out of the loop and throw it.
+			catch (SubsystemException e) {
+				throw e;
+			}
+			catch (Exception e) {
+				throw new SubsystemException(e);
 			}
-		}
-		catch (Exception e) {
-			throw new SubsystemException(e);
 		}
 	}
 

Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/AriesSubsystemTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/AriesSubsystemTest.java?rev=1414804&r1=1414803&r2=1414804&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/AriesSubsystemTest.java (original)
+++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/AriesSubsystemTest.java Wed Nov 28 16:41:29 2012
@@ -1,5 +1,6 @@
 package org.apache.aries.subsystem.itests;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
@@ -18,6 +19,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.MavenConfiguredJUnit4TestRunner;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
@@ -25,8 +29,6 @@ import org.osgi.service.subsystem.Subsys
 import org.osgi.service.subsystem.SubsystemConstants;
 import org.osgi.service.subsystem.SubsystemException;
 
-import aQute.lib.osgi.Constants;
-
 @RunWith(MavenConfiguredJUnit4TestRunner.class)
 public class AriesSubsystemTest extends SubsystemTest {
 	/*
@@ -35,11 +37,21 @@ public class AriesSubsystemTest extends 
 	 */
 	private static final String APPLICATION_A = "application.a.esa";
 	/*
+	 * Subsystem-SymbolicName: application.b.esa
+	 * Subsystem-Content: bundle.b.jar
+	 */
+	private static final String APPLICATION_B = "application.b.esa";
+	/*
 	 * Bundle-SymbolicName: bundle.a.jar
 	 * Import-Package: org.osgi.framework,org.osgi.resource
 	 */
 	private static final String BUNDLE_A = "bundle.a.jar";
 	/*
+	 * Bundle-SymbolicName: bundle.b.jar
+	 * Import-Package: org.osgi.resource
+	 */
+	private static final String BUNDLE_B = "bundle.b.jar";
+	/*
 	 * Subsystem-SymbolicName: composite.a.esa
 	 * Subsystem-Type: osgi.subsystem.composite
 	 */
@@ -50,27 +62,47 @@ public class AriesSubsystemTest extends 
 		createSubsystem(APPLICATION_A, BUNDLE_A);
 	}
 	
+	private static void createApplicationB() throws IOException {
+		createApplicationBManifest();
+		createSubsystem(APPLICATION_B, BUNDLE_B);
+	}
+	
 	private static void createApplicationAManifest() throws IOException {
 		Map<String, String> attributes = new HashMap<String, String>();
 		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_A);
 		createManifest(APPLICATION_A + ".mf", attributes);
 	}
 	
+	private static void createApplicationBManifest() throws IOException {
+		Map<String, String> attributes = new HashMap<String, String>();
+		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_B);
+		createManifest(APPLICATION_B + ".mf", attributes);
+	}
+	
 	private static void createBundleA() throws IOException {
 		Map<String, String> headers = new HashMap<String, String>();
 		headers.put(Constants.IMPORT_PACKAGE, "org.osgi.framework,org.osgi.resource");
 		createBundle(BUNDLE_A, headers);
 	}
 	
+	private static void createBundleB() throws IOException {
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put(Constants.IMPORT_PACKAGE, "org.osgi.resource");
+		createBundle(BUNDLE_B, headers);
+	}
+	
 	private static void createCompositeA() throws IOException {
 		createCompositeAManifest();
-		createSubsystem(COMPOSITE_A);
+		createSubsystem(COMPOSITE_A, BUNDLE_B, APPLICATION_B);
 	}
 	
 	private static void createCompositeAManifest() throws IOException {
 		Map<String, String> attributes = new HashMap<String, String>();
 		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_A);
 		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
+		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, 
+				BUNDLE_B + ';' + IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=\"[0,0]\","
+				+ APPLICATION_B + ';' + IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=\"[0,0]\";" + IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE + '=' + SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION);
 		attributes.put(Constants.IMPORT_PACKAGE, "org.osgi.resource");
 		createManifest(COMPOSITE_A + ".mf", attributes);
 	}
@@ -81,7 +113,9 @@ public class AriesSubsystemTest extends 
 		if (createdTestFiles)
 			return;
 		createBundleA();
+		createBundleB();
 		createApplicationA();
+		createApplicationB();
 		createCompositeA();
 		createdTestFiles = true;
 	}
@@ -90,31 +124,62 @@ public class AriesSubsystemTest extends 
 		super.setUp();
 	}
 	
+	/*
+	 * Test the AriesSubsystem.addRequirements(Collection<Requirement>) method.
+	 * 
+	 * There are several things to consider for this test.
+	 * 
+	 * (1) Installing a child subsystem before the requirement has been added
+	 *     should fail.
+	 * (2) Installing a child subsystem after the requirement has been added
+	 *     should succeed.
+	 * (3) The newly created region should contain all of the bundles from the
+	 *     old one.
+	 * (4) The connections between the subsystem with the added requirement and
+	 *     its parents should be reestablished.
+	 * (5) The connections between the subsystem with the added requirement and
+	 *     its children should be reestablished.
+	 */
 	@Test
 	public void testAddRequirements() throws Exception {
-		Subsystem compositeA = installSubsystemFromFile(COMPOSITE_A);
+		AriesSubsystem compositeA = (AriesSubsystem)installSubsystemFromFile(COMPOSITE_A);
 		try {
+			startSubsystem(compositeA);
+			assertCompositeABefore(compositeA);
+			// Test that the installation of applicationA fails.
 			try {
 				installSubsystemFromFile(compositeA, APPLICATION_A);
-				fail("Subsystem should not have installed");
+				fail("Subsystem should not have installed due to unresolved org.osgi.framework package requirement");
 			} catch (SubsystemException e) {
 				// Okay.
 			}
+			// Add the org.osgi.framework package requirement.
 			Requirement requirement = new BasicRequirement.Builder()
-					.namespace(PackageNamespace.PACKAGE_NAMESPACE)
-					.directive(
-							PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, 
-							"(osgi.wiring.package=org.osgi.framework)")
+				.namespace(PackageNamespace.PACKAGE_NAMESPACE)
+				.directive(
+					PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, 
+					"(osgi.wiring.package=org.osgi.framework)")
 					.resource(EasyMock.createMock(Resource.class))
 					.build();
-			((AriesSubsystem)compositeA).addRequirements(Collections.singleton(requirement));
+			compositeA.addRequirements(Collections.singleton(requirement));
+			// Test that the bundles were copied over to the newly created region.
+			assertCompositeABefore(compositeA);
+			// Test that the parent connections were reestablished.
+			assertRefreshAndResolve(Collections.singletonList(getConstituentAsBundle(compositeA, BUNDLE_B, null, null)));
+			// Test that the child connections were reestablished.
+			assertRefreshAndResolve(Collections.singletonList(getConstituentAsBundle(getConstituentAsSubsystem(compositeA, APPLICATION_B, null, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION), BUNDLE_B, null, null)));
+			// Test that the installation of applicationA succeeds.
+			AriesSubsystem applicationA;
 			try {
-				installSubsystemFromFile(compositeA, APPLICATION_A);
+				applicationA = (AriesSubsystem)installSubsystemFromFile(compositeA, APPLICATION_A);
+				startSubsystem(applicationA);
 			} catch (SubsystemException e) {
-				fail("Subsystem should have installed");
+				fail("Subsystem should have installed and started");
 			}
-		} finally {
-			uninstallSubsystemSilently(compositeA);
+			assertCompositeAAfter(compositeA);
+		}
+		finally {
+			stopAndUninstallSubsystemSilently(compositeA);
 		}
 	}
 	
@@ -149,4 +214,47 @@ public class AriesSubsystemTest extends 
 		}
 		assertSame("Services should be the same instance", root1, root2);
 	}
+	
+	private void assertCompositeAAfter(Subsystem compositeA) {
+		// applicationA, applicationB, bundleB, region context bundle
+		assertConstituents(4, compositeA);
+		assertConstituent(compositeA, APPLICATION_A, null, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION);
+		assertConstituent(compositeA, APPLICATION_B, null, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION);
+		assertConstituent(compositeA, BUNDLE_B);
+		assertNotNull("Bundle not in region", getRegion(compositeA).getBundle(BUNDLE_B, Version.emptyVersion));
+		assertConstituent(compositeA, "org.osgi.service.subsystem.region.context.1", Version.parseVersion("1"));
+		// applicationA, applicationB
+		assertChildren(2, compositeA);
+		assertApplicationA(assertChild(compositeA, APPLICATION_A));
+		assertApplicationB(assertChild(compositeA, APPLICATION_B));
+	}
+	
+	private void assertCompositeABefore(Subsystem compositeA) {
+		// applicationB, bundleB, region context bundle
+		assertConstituents(3, compositeA);
+		assertConstituent(compositeA, APPLICATION_B, null, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION);
+		assertConstituent(compositeA, BUNDLE_B);
+		assertNotNull("Bundle not in region", getRegion(compositeA).getBundle(BUNDLE_B, Version.emptyVersion));
+		assertConstituent(compositeA, "org.osgi.service.subsystem.region.context.1", Version.parseVersion("1"));
+		// applicationB
+		assertChildren(1, compositeA);
+		assertApplicationB(assertChild(compositeA, APPLICATION_B));
+	}
+	
+	private void assertApplicationA(Subsystem applicationA) {
+		// bundleA, region context bundle
+		assertConstituents(2, applicationA);
+		assertConstituent(applicationA, BUNDLE_A);
+		// The subsystem id is 4 instead of 3 due to the first installation that failed.
+		assertConstituent(applicationA, "org.osgi.service.subsystem.region.context.4", Version.parseVersion("1"));
+		assertChildren(0, applicationA);
+	}
+	
+	private void assertApplicationB(Subsystem applicationB) {
+		// bundleB, region context bundle
+		assertConstituents(2, applicationB);
+		assertConstituent(applicationB, BUNDLE_B);
+		assertConstituent(applicationB, "org.osgi.service.subsystem.region.context.2", Version.parseVersion("1"));
+		assertChildren(0, applicationB);
+	}
 }

Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java?rev=1414804&r1=1414803&r2=1414804&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java (original)
+++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java Wed Nov 28 16:41:29 2012
@@ -38,6 +38,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.aries.subsystem.AriesSubsystem;
 import org.apache.aries.subsystem.core.archive.ProvisionPolicyDirective;
@@ -60,6 +61,8 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
@@ -69,6 +72,7 @@ import org.osgi.framework.namespace.Iden
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
 import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.FrameworkWiring;
 import org.osgi.resource.Resource;
 import org.osgi.service.repository.Repository;
 import org.osgi.service.repository.RepositoryContent;
@@ -250,6 +254,20 @@ public abstract class SubsystemTest exte
 		assertTrue("Wrong state: " + bundle + " [expected " + state + " but was " + bundle.getState() + "]", (bundle.getState() & state) != 0);
 	}
 	
+	protected Subsystem assertChild(Subsystem parent, String symbolicName) {
+		return assertChild(parent, symbolicName, null, null);
+	}
+	
+	protected Subsystem assertChild(Subsystem parent, String symbolicName, Version version) {
+		return assertChild(parent, symbolicName, version, null);
+	}
+	
+	protected Subsystem assertChild(Subsystem parent, String symbolicName, Version version, String type) {
+		Subsystem result = getChild(parent, symbolicName, version, type);
+		assertNotNull("Child does not exist: " + symbolicName, result);
+		return result;
+	}
+	
 	protected void assertChild(Subsystem parent, Subsystem child) {
 		Collection<Subsystem> children = new ArrayList<Subsystem>(1);
 		children.add(child);
@@ -397,6 +415,31 @@ public abstract class SubsystemTest exte
 			assertTrue("Subsystem accepts dependencies", directive.isRejectDependencies());
 	}
 	
+	protected void assertRefresh(Collection<Bundle> bundles) throws InterruptedException {
+		FrameworkWiring wiring = getSystemBundleAsFrameworkWiring();
+		final AtomicBoolean refreshed = new AtomicBoolean(false);
+		wiring.refreshBundles(bundles, new FrameworkListener() {
+			@Override
+			public void frameworkEvent(FrameworkEvent event) {
+				if (FrameworkEvent.PACKAGES_REFRESHED == event.getType()) {
+					synchronized (refreshed) {
+						refreshed.set(true);
+						refreshed.notify();
+					}
+				}
+			}
+		});
+		synchronized (refreshed) {
+			refreshed.wait(5000);
+		}
+		assertTrue("Bundles not refreshed", refreshed.get());
+	}
+	
+	protected void assertRefreshAndResolve(Collection<Bundle> bundles) throws InterruptedException {
+		assertRefresh(bundles);
+		assertResolve(bundles);
+	}
+	
 	protected void assertRegionContextBundle(Subsystem s) {
 		Bundle b = getRegionContextBundle(s);
 		assertEquals("Not active", Bundle.ACTIVE, b.getState());
@@ -406,6 +449,11 @@ public abstract class SubsystemTest exte
 		assertConstituent(s, "org.osgi.service.subsystem.region.context." + s.getSubsystemId(), Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
 	}
 	
+	protected void assertResolve(Collection<Bundle> bundles) {
+		FrameworkWiring wiring = getSystemBundleAsFrameworkWiring();
+		assertTrue("Bundles not resolved", wiring.resolveBundles(bundles));
+	}
+	
 	protected void assertServiceEventsInstall(Subsystem subsystem) throws InterruptedException {
 		assertEvent(subsystem, Subsystem.State.INSTALLING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
 		assertEvent(subsystem, Subsystem.State.INSTALLED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
@@ -584,6 +632,31 @@ public abstract class SubsystemTest exte
 		return null;
 	}
 	
+	protected Subsystem getChild(Subsystem parent, String symbolicName) {
+		return getChild(parent, symbolicName, null, null);
+	}
+	
+	protected Subsystem getChild(Subsystem parent, String symbolicName, Version version) {
+		return getChild(parent, symbolicName, version, null);
+	}
+	
+	protected Subsystem getChild(Subsystem parent, String symbolicName, Version version, String type) {
+		for (Subsystem child : parent.getChildren()) {
+			if (symbolicName.equals(child.getSymbolicName())) {
+				if (version == null)
+					version = Version.emptyVersion;
+				if (version.equals(child.getVersion())) {
+					if (type == null)
+						type = SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION;
+					if (type.equals(child.getType())) {
+						return child;
+					}
+				}
+			}
+		}
+		return null;
+	}
+	
 	protected Resource getConstituent(Subsystem subsystem, String symbolicName, Version version, String type) {
 		for (Resource resource : subsystem.getConstituents()) {
 			if (symbolicName.equals(ResourceHelper.getSymbolicNameAttribute(resource))) {
@@ -601,6 +674,11 @@ public abstract class SubsystemTest exte
 		return null;
 	}
 	
+	protected AriesSubsystem getConstituentAsAriesSubsystem(Subsystem subsystem, String symbolicName, Version version, String type) {
+		Resource resource = getConstituent(subsystem, symbolicName, version, type);
+		return (AriesSubsystem)resource;
+	}
+	
 	protected Bundle getConstituentAsBundle(Subsystem subsystem, String symbolicName, Version version, String type) {
 		return getConstituentAsBundleRevision(subsystem, symbolicName, version, type).getBundle();
 	}
@@ -610,6 +688,19 @@ public abstract class SubsystemTest exte
 		return (BundleRevision)resource;
 	}
 	
+	protected Subsystem getConstituentAsSubsystem(Subsystem subsystem, String symbolicName, Version version, String type) {
+		Resource resource = getConstituent(subsystem, symbolicName, version, type);
+		return (Subsystem)resource;
+	}
+	
+	protected Region getRegion(Subsystem subsystem) {
+		RegionDigraph digraph = getOsgiService(RegionDigraph.class);
+		String name = subsystem.getSymbolicName() + ';' + subsystem.getVersion() + ';' + subsystem.getType() + ';' + subsystem.getSubsystemId();
+		Region region = digraph.getRegion(name);
+		assertNotNull("Region not found: " + name, region);
+		return region;
+	}
+	
 	protected Bundle getRegionContextBundle(Subsystem subsystem) {
 		BundleContext bc = subsystem.getBundleContext();
 		assertNotNull("No region context bundle", bc);
@@ -632,6 +723,10 @@ public abstract class SubsystemTest exte
 		return getSystemBundle().adapt(FrameworkStartLevel.class);
 	}
 	
+	protected FrameworkWiring getSystemBundleAsFrameworkWiring() {
+		return getSystemBundle().adapt(FrameworkWiring.class);
+	}
+	
 	protected Bundle getSubsystemCoreBundle() {
 		return findBundleBySymbolicName("org.apache.aries.subsystem.core");
 	}