diff options
23 files changed, 368 insertions, 67 deletions
diff --git a/framework/src/onos/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java b/framework/src/onos/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java index 17cf89e2..100f6823 100644 --- a/framework/src/onos/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java +++ b/framework/src/onos/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java @@ -41,7 +41,7 @@ public class ApplicationsListCommand extends AbstractShellCommand { private static final String FMT = "%s id=%d, name=%s, version=%s, origin=%s, description=%s, " + - "features=%s, featuresRepo=%s, permissions=%s"; + "features=%s, featuresRepo=%s, apps=%s, permissions=%s"; private static final String SHORT_FMT = "%s %3d %-32s %-8s %s"; @@ -76,7 +76,7 @@ public class ApplicationsListCommand extends AbstractShellCommand { app.id().id(), app.id().name(), app.version(), app.origin(), app.description(), app.features(), app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "", - app.permissions()); + app.requiredApps(), app.permissions()); } } } diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java index 2561280b..e8ff9ec4 100644 --- a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java +++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java @@ -86,4 +86,11 @@ public interface ApplicationDescription { * @return application features */ List<String> features(); + + /** + * Returns list of required application names. + * + * @return list of application names + */ + List<String> requiredApps(); } diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java index b3cdc43e..0a1f0727 100644 --- a/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java +++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/ApplicationStore.java @@ -76,7 +76,7 @@ public interface ApplicationStore extends Store<ApplicationEvent, ApplicationSto void remove(ApplicationId appId); /** - * Mark the application as actived. + * Mark the application as active. * * @param appId application identifier */ diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java index 710d0f9c..569183a7 100644 --- a/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java +++ b/framework/src/onos/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java @@ -41,6 +41,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { private final Set<Permission> permissions; private final Optional<URI> featuresRepo; private final List<String> features; + private final List<String> requiredApps; /** * Creates a new application descriptor using the supplied data. @@ -53,11 +54,13 @@ public class DefaultApplicationDescription implements ApplicationDescription { * @param permissions requested permissions * @param featuresRepo optional features repo URI * @param features application features + * @param requiredApps list of required application names */ public DefaultApplicationDescription(String name, Version version, String description, String origin, ApplicationRole role, Set<Permission> permissions, - URI featuresRepo, List<String> features) { + URI featuresRepo, List<String> features, + List<String> requiredApps) { this.name = checkNotNull(name, "Name cannot be null"); this.version = checkNotNull(version, "Version cannot be null"); this.description = checkNotNull(description, "Description cannot be null"); @@ -66,6 +69,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { this.permissions = checkNotNull(permissions, "Permissions cannot be null"); this.featuresRepo = Optional.ofNullable(featuresRepo); this.features = checkNotNull(features, "Features cannot be null"); + this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null"); checkArgument(!features.isEmpty(), "There must be at least one feature"); } @@ -110,6 +114,11 @@ public class DefaultApplicationDescription implements ApplicationDescription { } @Override + public List<String> requiredApps() { + return requiredApps; + } + + @Override public String toString() { return toStringHelper(this) .add("name", name) @@ -120,6 +129,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { .add("permissions", permissions) .add("featuresRepo", featuresRepo) .add("features", features) + .add("requiredApps", requiredApps) .toString(); } } diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java index fca53843..ea2eab9b 100644 --- a/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java +++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/Application.java @@ -84,4 +84,11 @@ public interface Application { * @return application features */ List<String> features(); + + /** + * Returns list of required application names. + * + * @return list of application names + */ + List<String> requiredApps(); } diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java index d8062ddf..c3515638 100644 --- a/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java +++ b/framework/src/onos/core/api/src/main/java/org/onosproject/core/DefaultApplication.java @@ -40,6 +40,7 @@ public class DefaultApplication implements Application { private final Set<Permission> permissions; private final Optional<URI> featuresRepo; private final List<String> features; + private final List<String> requiredApps; /** * Creates a new application descriptor using the supplied data. @@ -52,11 +53,13 @@ public class DefaultApplication implements Application { * @param permissions requested permissions * @param featuresRepo optional features repo URI * @param features application features + * @param requiredApps list of required application names */ public DefaultApplication(ApplicationId appId, Version version, String description, String origin, ApplicationRole role, Set<Permission> permissions, - Optional<URI> featuresRepo, List<String> features) { + Optional<URI> featuresRepo, List<String> features, + List<String> requiredApps) { this.appId = checkNotNull(appId, "ID cannot be null"); this.version = checkNotNull(version, "Version cannot be null"); this.description = checkNotNull(description, "Description cannot be null"); @@ -65,6 +68,7 @@ public class DefaultApplication implements Application { this.permissions = checkNotNull(permissions, "Permissions cannot be null"); this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null"); this.features = checkNotNull(features, "Features cannot be null"); + this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null"); checkArgument(!features.isEmpty(), "There must be at least one feature"); } @@ -109,9 +113,14 @@ public class DefaultApplication implements Application { } @Override + public List<String> requiredApps() { + return requiredApps; + } + + @Override public int hashCode() { return Objects.hash(appId, version, description, origin, role, permissions, - featuresRepo, features); + featuresRepo, features, requiredApps); } @Override @@ -130,7 +139,8 @@ public class DefaultApplication implements Application { Objects.equals(this.role, other.role) && Objects.equals(this.permissions, other.permissions) && Objects.equals(this.featuresRepo, other.featuresRepo) && - Objects.equals(this.features, other.features); + Objects.equals(this.features, other.features) && + Objects.equals(this.requiredApps, other.requiredApps); } @Override @@ -144,6 +154,7 @@ public class DefaultApplication implements Application { .add("permissions", permissions) .add("featuresRepo", featuresRepo) .add("features", features) + .add("requiredApps", requiredApps) .toString(); } } diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java index d31cc268..34c593c4 100644 --- a/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java +++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java @@ -33,7 +33,7 @@ public class ApplicationEventTest extends AbstractEventTest { private Application createApp() { return new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, - PERMS, Optional.of(FURL), FEATURES); + PERMS, Optional.of(FURL), FEATURES, APPS); } @Test diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java index d40d3fea..0e93c1fe 100644 --- a/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java +++ b/framework/src/onos/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java @@ -46,12 +46,13 @@ public class DefaultApplicationDescriptionTest { new Permission(AppPermission.class.getName(), "FLOWRULE_READ")); public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features"); public static final List<String> FEATURES = ImmutableList.of("foo", "bar"); + public static final List<String> APPS = ImmutableList.of("fifi"); @Test public void basics() { ApplicationDescription app = new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN, - ROLE, PERMS, FURL, FEATURES); + ROLE, PERMS, FURL, FEATURES, APPS); assertEquals("incorrect id", APP_NAME, app.name()); assertEquals("incorrect version", VER, app.version()); assertEquals("incorrect description", DESC, app.description()); @@ -60,6 +61,7 @@ public class DefaultApplicationDescriptionTest { assertEquals("incorrect permissions", PERMS, app.permissions()); assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); assertEquals("incorrect features", FEATURES, app.features()); + assertEquals("incorrect apps", APPS, app.requiredApps()); assertTrue("incorrect toString", app.toString().contains(APP_NAME)); } diff --git a/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java index cbedb79c..77b3812b 100644 --- a/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java +++ b/framework/src/onos/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java @@ -34,7 +34,7 @@ public class DefaultApplicationTest { @Test public void basics() { Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, - PERMS, Optional.of(FURL), FEATURES); + PERMS, Optional.of(FURL), FEATURES, APPS); assertEquals("incorrect id", APP_ID, app.id()); assertEquals("incorrect version", VER, app.version()); assertEquals("incorrect description", DESC, app.description()); @@ -43,19 +43,20 @@ public class DefaultApplicationTest { assertEquals("incorrect permissions", PERMS, app.permissions()); assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); assertEquals("incorrect features", FEATURES, app.features()); + assertEquals("incorrect apps", APPS, app.requiredApps()); assertTrue("incorrect toString", app.toString().contains(APP_NAME)); } @Test public void testEquality() { Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, - PERMS, Optional.of(FURL), FEATURES); + PERMS, Optional.of(FURL), FEATURES, APPS); Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, - PERMS, Optional.of(FURL), FEATURES); + PERMS, Optional.of(FURL), FEATURES, APPS); Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, - PERMS, Optional.empty(), FEATURES); + PERMS, Optional.empty(), FEATURES, APPS); Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd", ROLE, - PERMS, Optional.of(FURL), FEATURES); + PERMS, Optional.of(FURL), FEATURES, APPS); new EqualsTester().addEqualityGroup(a1, a2) .addEqualityGroup(a3).addEqualityGroup(a4).testEquals(); } diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java index b2cab094..a09c0bdb 100644 --- a/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java @@ -32,18 +32,18 @@ public final class ApplicationCodec extends JsonCodec<Application> { public ObjectNode encode(Application app, CodecContext context) { checkNotNull(app, "Application cannot be null"); ApplicationService service = context.getService(ApplicationService.class); - ObjectNode result = context.mapper().createObjectNode() + return context.mapper().createObjectNode() .put("name", app.id().name()) .put("id", app.id().id()) .put("version", app.version().toString()) .put("description", app.description()) .put("origin", app.origin()) - .put("permissions", app.permissions().toString()) + .put("permissions", app.permissions().toString()) // FIXME: change to an array .put("featuresRepo", app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "") - .put("features", app.features().toString()) + .put("features", app.features().toString()) // FIXME: change to an array + .put("requiredApps", app.requiredApps().toString()) // FIXME: change to an array .put("state", service.getState(app.id()).toString()); - return result; } } diff --git a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java index 54f0fb89..37cdbdfc 100644 --- a/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java +++ b/framework/src/onos/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java @@ -17,6 +17,7 @@ package org.onosproject.common.app; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import org.apache.commons.configuration.ConfigurationException; @@ -33,7 +34,6 @@ import org.onosproject.core.Version; import org.onosproject.security.AppPermission; import org.onosproject.security.Permission; import org.onosproject.store.AbstractStore; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +46,6 @@ import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; import java.nio.file.NoSuchFileException; -import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Set; @@ -79,6 +78,7 @@ public class ApplicationArchive private static final String VERSION = "[@version]"; private static final String FEATURES_REPO = "[@featuresRepo]"; private static final String FEATURES = "[@features]"; + private static final String APPS = "[@apps]"; private static final String DESCRIPTION = "description"; private static final String ROLE = "security.role"; @@ -291,8 +291,13 @@ public class ApplicationArchive URI featuresRepo = featRepo != null ? URI.create(featRepo) : null; List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(",")); + String apps = cfg.getString(APPS, ""); + List<String> requiredApps = apps.isEmpty() ? + ImmutableList.of() : ImmutableList.copyOf(apps.split(",")); + return new DefaultApplicationDescription(name, version, desc, origin, role, - perms, featuresRepo, features); + perms, featuresRepo, features, + requiredApps); } // Expands the specified ZIP stream into app-specific directory. @@ -390,7 +395,7 @@ public class ApplicationArchive // Returns the set of Permissions specified in the app.xml file private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) { - List<Permission> permissionList = new ArrayList(); + List<Permission> permissionList = Lists.newArrayList(); for (Object o : cfg.getList(APP_PERMISSIONS)) { String name = (String) o; diff --git a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java index ea9a773e..d9f5285c 100644 --- a/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java +++ b/framework/src/onos/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java @@ -75,7 +75,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic new DefaultApplication(appId, appDesc.version(), appDesc.description(), appDesc.origin(), appDesc.role(), appDesc.permissions(), - appDesc.featuresRepo(), appDesc.features()); + appDesc.featuresRepo(), appDesc.features(), + appDesc.requiredApps()); apps.put(appId, app); states.put(appId, isActive(name) ? INSTALLED : ACTIVE); // load app permissions @@ -117,7 +118,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic DefaultApplication app = new DefaultApplication(appId, appDesc.version(), appDesc.description(), appDesc.origin(), appDesc.role(), appDesc.permissions(), - appDesc.featuresRepo(), appDesc.features()); + appDesc.featuresRepo(), appDesc.features(), + appDesc.requiredApps()); apps.put(appId, app); states.put(appId, INSTALLED); delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java index a9e928e5..d09eb1f1 100644 --- a/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java @@ -212,6 +212,7 @@ public class ApplicationManager // The following methods are fully synchronized to guard against remote vs. // locally induced feature service interactions. + // Installs all feature repositories required by the specified app. private synchronized boolean installAppArtifacts(Application app) throws Exception { if (app.featuresRepo().isPresent() && featuresService.getRepository(app.featuresRepo().get()) == null) { @@ -221,6 +222,7 @@ public class ApplicationManager return false; } + // Uninstalls all the feature repositories required by the specified app. private synchronized boolean uninstallAppArtifacts(Application app) throws Exception { if (app.featuresRepo().isPresent() && featuresService.getRepository(app.featuresRepo().get()) != null) { @@ -230,6 +232,7 @@ public class ApplicationManager return false; } + // Installs all features that define the specified app. private synchronized boolean installAppFeatures(Application app) throws Exception { boolean changed = false; for (String name : app.features()) { @@ -246,6 +249,7 @@ public class ApplicationManager return changed; } + // Uninstalls all features that define the specified app. private synchronized boolean uninstallAppFeatures(Application app) throws Exception { boolean changed = false; invokeHook(deactivateHooks.get(app.id().name()), app.id()); diff --git a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java index 5461cf01..a99fd216 100644 --- a/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java +++ b/framework/src/onos/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java @@ -15,6 +15,7 @@ */ package org.onosproject.app.impl; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.After; import org.junit.Before; @@ -138,7 +139,7 @@ public class ApplicationManagerTest { @Override public Application create(InputStream appDescStream) { app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS, - Optional.of(FURL), FEATURES); + Optional.of(FURL), FEATURES, ImmutableList.of()); state = INSTALLED; delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); return app; @@ -177,6 +178,11 @@ public class ApplicationManagerTest { state = INSTALLED; delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app)); } + + @Override + public ApplicationId getId(String name) { + return new DefaultApplicationId(0, name); + } } private class TestFeaturesService extends FeaturesServiceAdapter { diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java index 6764c222..dda820ae 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java @@ -17,7 +17,9 @@ package org.onosproject.store.app; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; - +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -37,6 +39,7 @@ import org.onosproject.common.app.ApplicationArchive; import org.onosproject.core.Application; import org.onosproject.core.ApplicationId; import org.onosproject.core.ApplicationIdStore; +import org.onosproject.core.CoreService; import org.onosproject.core.DefaultApplication; import org.onosproject.security.Permission; import org.onosproject.store.cluster.messaging.ClusterCommunicationService; @@ -61,6 +64,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; +import static com.google.common.collect.Multimaps.newSetMultimap; +import static com.google.common.collect.Multimaps.synchronizedSetMultimap; import static com.google.common.io.ByteStreams.toByteArray; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.onlab.util.Tools.groupedThreads; @@ -115,6 +120,14 @@ public class GossipApplicationStore extends ApplicationArchive @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ApplicationIdStore idStore; + // Multimap to track which apps are required by others apps + // app -> { required-by, ... } + // Apps explicitly activated will be required by the CORE app + private final Multimap<ApplicationId, ApplicationId> requiredBy = + synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet)); + + private ApplicationId coreAppId; + @Activate public void activate() { KryoNamespace.Builder serializer = KryoNamespace.newBuilder() @@ -128,16 +141,16 @@ public class GossipApplicationStore extends ApplicationArchive groupedThreads("onos/store/app", "message-handler")); clusterCommunicator.<String, byte[]>addSubscriber(APP_BITS_REQUEST, - bytes -> new String(bytes, Charsets.UTF_8), - name -> { - try { - return toByteArray(getApplicationInputStream(name)); - } catch (IOException e) { - throw new StorageException(e); - } - }, - Function.identity(), - messageHandlingExecutor); + bytes -> new String(bytes, Charsets.UTF_8), + name -> { + try { + return toByteArray(getApplicationInputStream(name)); + } catch (IOException e) { + throw new StorageException(e); + } + }, + Function.identity(), + messageHandlingExecutor); // FIXME: Consider consolidating into a single map. @@ -161,6 +174,7 @@ public class GossipApplicationStore extends ApplicationArchive .withTimestampProvider((k, v) -> clockService.getTimestamp()) .build(); + coreAppId = getId(CoreService.CORE_APP_NAME); log.info("Started"); } @@ -174,6 +188,7 @@ public class GossipApplicationStore extends ApplicationArchive try { Application app = create(getApplicationDescription(name), false); if (app != null && isActive(app.id().name())) { + requiredBy.put(app.id(), coreAppId); activate(app.id(), false); // load app permissions } @@ -200,7 +215,6 @@ public class GossipApplicationStore extends ApplicationArchive public void setDelegate(ApplicationStoreDelegate delegate) { super.setDelegate(delegate); loadFromDisk(); -// executor.schedule(this::pruneUninstalledApps, LOAD_TIMEOUT_MS, MILLISECONDS); } @Override @@ -229,7 +243,15 @@ public class GossipApplicationStore extends ApplicationArchive @Override public Application create(InputStream appDescStream) { ApplicationDescription appDesc = saveApplication(appDescStream); - return create(appDesc, true); + if (hasPrerequisites(appDesc)) { + return create(appDesc, true); + } + throw new ApplicationException("Missing dependencies for app " + appDesc.name()); + } + + private boolean hasPrerequisites(ApplicationDescription app) { + return !app.requiredApps().stream().map(n -> getId(n)) + .anyMatch(id -> id == null || getApplication(id) == null); } private Application create(ApplicationDescription appDesc, boolean updateTime) { @@ -246,36 +268,80 @@ public class GossipApplicationStore extends ApplicationArchive public void remove(ApplicationId appId) { Application app = apps.get(appId); if (app != null) { + uninstallDependentApps(app); apps.remove(appId); states.remove(app); permissions.remove(app); } } + // Uninstalls all apps that depend on the given app. + private void uninstallDependentApps(Application app) { + getApplications().stream() + .filter(a -> a.requiredApps().contains(app.id().name())) + .forEach(a -> remove(a.id())); + } + @Override public void activate(ApplicationId appId) { + activate(appId, coreAppId); + } + + private void activate(ApplicationId appId, ApplicationId forAppId) { + requiredBy.put(appId, forAppId); activate(appId, true); } + private void activate(ApplicationId appId, boolean updateTime) { Application app = apps.get(appId); if (app != null) { if (updateTime) { updateTime(appId.name()); } + activateRequiredApps(app); states.put(app, ACTIVATED); } } + // Activates all apps required by this application. + private void activateRequiredApps(Application app) { + app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id())); + } + @Override public void deactivate(ApplicationId appId) { - Application app = apps.get(appId); - if (app != null) { - updateTime(appId.name()); - states.put(app, DEACTIVATED); + deactivateDependentApps(getApplication(appId)); + deactivate(appId, coreAppId); + } + + private void deactivate(ApplicationId appId, ApplicationId forAppId) { + requiredBy.remove(appId, forAppId); + if (requiredBy.get(appId).isEmpty()) { + Application app = apps.get(appId); + if (app != null) { + updateTime(appId.name()); + states.put(app, DEACTIVATED); + deactivateRequiredApps(app); + } } } + // Deactivates all apps that require this application. + private void deactivateDependentApps(Application app) { + getApplications().stream() + .filter(a -> states.get(a) == ACTIVATED) + .filter(a -> a.requiredApps().contains(app.id().name())) + .forEach(a -> deactivate(a.id())); + } + + // Deactivates all apps required by this application. + private void deactivateRequiredApps(Application app) { + app.requiredApps().stream().map(this::getId).map(this::getApplication) + .filter(a -> states.get(a) == ACTIVATED) + .forEach(a -> deactivate(a.id(), app.id())); + } + @Override public Set<Permission> getPermissions(ApplicationId appId) { Application app = apps.get(appId); @@ -424,6 +490,7 @@ public class GossipApplicationStore extends ApplicationArchive ApplicationId appId = idStore.registerApplication(appDesc.name()); return new DefaultApplication(appId, appDesc.version(), appDesc.description(), appDesc.origin(), appDesc.role(), appDesc.permissions(), - appDesc.featuresRepo(), appDesc.features()); + appDesc.featuresRepo(), appDesc.features(), + appDesc.requiredApps()); } } diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java index bfc6127a..5558b13a 100644 --- a/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java +++ b/framework/src/onos/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java @@ -62,6 +62,7 @@ public class OnosAppMojo extends AbstractMojo { private static final String ONOS_APP_NAME = "onos.app.name"; private static final String ONOS_APP_ORIGIN = "onos.app.origin"; + private static final String ONOS_APP_REQUIRES = "onos.app.requires"; private static final String JAR = "jar"; private static final String XML = "xml"; @@ -80,6 +81,7 @@ public class OnosAppMojo extends AbstractMojo { private String name; private String origin; + private String requiredApps; private String version = DEFAULT_VERSION; private String featuresRepo = DEFAULT_FEATURES_REPO; private List<String> artifacts; @@ -160,6 +162,9 @@ public class OnosAppMojo extends AbstractMojo { origin = (String) project.getProperties().get(ONOS_APP_ORIGIN); origin = origin != null ? origin : DEFAULT_ORIGIN; + requiredApps = (String) project.getProperties().get(ONOS_APP_REQUIRES); + requiredApps = requiredApps == null ? "" : requiredApps; + if (appFile.exists()) { loadAppFile(appFile); } else { @@ -338,6 +343,7 @@ public class OnosAppMojo extends AbstractMojo { return string == null ? null : string.replaceAll("\\$\\{onos.app.name\\}", name) .replaceAll("\\$\\{onos.app.origin\\}", origin) + .replaceAll("\\$\\{onos.app.requires\\}", requiredApps) .replaceAll("\\$\\{project.groupId\\}", projectGroupId) .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId) .replaceAll("\\$\\{project.version\\}", projectVersion) diff --git a/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml index 0f3133d3..84998807 100644 --- a/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml +++ b/framework/src/onos/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml @@ -16,7 +16,7 @@ --> <app name="${onos.app.name}" origin="${onos.app.origin}" version="${project.version}" featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" - features="${project.artifactId}"> + features="${project.artifactId}" apps="${onos.app.requires}"> <description>${project.description}</description> <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact> </app> diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/util/DefaultHashMap.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/DefaultHashMap.java new file mode 100644 index 00000000..f9d878ab --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/util/DefaultHashMap.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.onlab.util; + +import java.util.HashMap; + +/** + * HashMap that returns a default value for unmapped keys. + */ +public class DefaultHashMap<K, V> extends HashMap<K, V> { + + /** Default value to return when no key binding exists. */ + protected V defaultValue; + + /** + * Constructs an empty map with the given default value. + * + * @param defaultValue the default value + */ + public DefaultHashMap(V defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public V get(Object k) { + return containsKey(k) ? super.get(k) : defaultValue; + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/DefaultHashMapTest.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/DefaultHashMapTest.java new file mode 100644 index 00000000..db6b5fb7 --- /dev/null +++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/DefaultHashMapTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.onlab.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link DefaultHashMap}. + */ +public class DefaultHashMapTest { + + private static final String ONE = "one"; + private static final String TWO = "two"; + private static final String THREE = "three"; + private static final String FOUR = "four"; + + private static final String ALPHA = "Alpha"; + private static final String BETA = "Beta"; + private static final String OMEGA = "Omega"; + + private DefaultHashMap<String, Integer> map; + private DefaultHashMap<String, String> chartis; + + private void loadMap() { + map.put(ONE, 1); + map.put(TWO, 2); + } + + private void fortioCharti() { + chartis.put(ONE, ALPHA); + chartis.put(TWO, BETA); + } + + @Test + public void nullDefaultIsAllowed() { + // but makes this class behave no different than HashMap + map = new DefaultHashMap<>(null); + loadMap(); + assertEquals("missing 1", 1, (int) map.get(ONE)); + assertEquals("missing 2", 2, (int) map.get(TWO)); + assertEquals("three?", null, map.get(THREE)); + assertEquals("four?", null, map.get(FOUR)); + } + + @Test + public void defaultToFive() { + map = new DefaultHashMap<>(5); + loadMap(); + assertEquals("missing 1", 1, (int) map.get(ONE)); + assertEquals("missing 2", 2, (int) map.get(TWO)); + assertEquals("three?", 5, (int) map.get(THREE)); + assertEquals("four?", 5, (int) map.get(FOUR)); + } + + @Test + public void defaultToOmega() { + chartis = new DefaultHashMap<>(OMEGA); + fortioCharti(); + assertEquals("missing 1", ALPHA, chartis.get(ONE)); + assertEquals("missing 2", BETA, chartis.get(TWO)); + assertEquals("three?", OMEGA, chartis.get(THREE)); + assertEquals("four?", OMEGA, chartis.get(FOUR)); + } + +} diff --git a/framework/src/onos/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java b/framework/src/onos/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java index 6fee43ed..3e72f18d 100644 --- a/framework/src/onos/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java +++ b/framework/src/onos/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java @@ -85,19 +85,19 @@ public class ApplicationsResourceTest extends ResourceTest { private Application app1 = new DefaultApplication(id1, VER, "app1", "origin1", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), - ImmutableList.of("My Feature")); + ImmutableList.of("My Feature"), ImmutableList.of()); private Application app2 = new DefaultApplication(id2, VER, "app2", "origin2", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), - ImmutableList.of("My Feature")); + ImmutableList.of("My Feature"), ImmutableList.of()); private Application app3 = new DefaultApplication(id3, VER, "app3", "origin3", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), - ImmutableList.of("My Feature")); + ImmutableList.of("My Feature"), ImmutableList.of()); private Application app4 = new DefaultApplication(id4, VER, "app4", "origin4", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), - ImmutableList.of("My Feature")); + ImmutableList.of("My Feature"), ImmutableList.of()); /** * Hamcrest matcher to check that an application representation in JSON matches diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java index 840e89f3..8da81016 100644 --- a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java +++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.onlab.osgi.ServiceDirectory; import org.onlab.packet.IpAddress; +import org.onlab.util.DefaultHashMap; import org.onosproject.cluster.ClusterEvent; import org.onosproject.cluster.ClusterService; import org.onosproject.cluster.ControllerNode; @@ -80,17 +81,9 @@ import java.util.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; -import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED; -import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED; import static org.onosproject.cluster.ControllerNode.State.ACTIVE; import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; import static org.onosproject.net.PortNumber.portNumber; -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; -import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED; -import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED; -import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED; -import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; -import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; import static org.onosproject.ui.topo.TopoConstants.CoreButtons; import static org.onosproject.ui.topo.TopoConstants.Properties; import static org.onosproject.ui.topo.TopoUtils.compactLinkString; @@ -100,6 +93,33 @@ import static org.onosproject.ui.topo.TopoUtils.compactLinkString; */ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { + // default to an "add" event... + private static final DefaultHashMap<ClusterEvent.Type, String> CLUSTER_EVENT = + new DefaultHashMap<>("addInstance"); + + // default to an "update" event... + private static final DefaultHashMap<DeviceEvent.Type, String> DEVICE_EVENT = + new DefaultHashMap<>("updateDevice"); + private static final DefaultHashMap<LinkEvent.Type, String> LINK_EVENT = + new DefaultHashMap<>("updateLink"); + private static final DefaultHashMap<HostEvent.Type, String> HOST_EVENT = + new DefaultHashMap<>("updateHost"); + + // but call out specific events that we care to differentiate... + static { + CLUSTER_EVENT.put(ClusterEvent.Type.INSTANCE_REMOVED, "removeInstance"); + + DEVICE_EVENT.put(DeviceEvent.Type.DEVICE_ADDED, "addDevice"); + DEVICE_EVENT.put(DeviceEvent.Type.DEVICE_REMOVED, "removeDevice"); + + LINK_EVENT.put(LinkEvent.Type.LINK_ADDED, "addLink"); + LINK_EVENT.put(LinkEvent.Type.LINK_REMOVED, "removeLink"); + + HOST_EVENT.put(HostEvent.Type.HOST_ADDED, "addHost"); + HOST_EVENT.put(HostEvent.Type.HOST_REMOVED, "removeHost"); + HOST_EVENT.put(HostEvent.Type.HOST_MOVED, "moveHost"); + } + protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class); @@ -204,7 +224,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { } // Produces a cluster instance message to the client. - protected ObjectNode instanceMessage(ClusterEvent event, String messageType) { + protected ObjectNode instanceMessage(ClusterEvent event, String msgType) { ControllerNode node = event.subject(); int switchCount = mastershipService.getDevicesOf(node.id()).size(); ObjectNode payload = objectNode() @@ -222,10 +242,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { payload.set("labels", labels); addMetaUi(node.id().toString(), payload); - String type = messageType != null ? messageType : - ((event.type() == INSTANCE_ADDED) ? "addInstance" : - ((event.type() == INSTANCE_REMOVED ? "removeInstance" : - "addInstance"))); + String type = msgType != null ? msgType : CLUSTER_EVENT.get(event.type()); return JsonUtils.envelope(type, 0, payload); } @@ -251,8 +268,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { addGeoLocation(device, payload); addMetaUi(device.id().toString(), payload); - String type = (event.type() == DEVICE_ADDED) ? "addDevice" : - ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice"); + String type = DEVICE_EVENT.get(event.type()); return JsonUtils.envelope(type, 0, payload); } @@ -268,8 +284,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { .put("srcPort", link.src().port().toString()) .put("dst", link.dst().deviceId().toString()) .put("dstPort", link.dst().port().toString()); - String type = (event.type() == LINK_ADDED) ? "addLink" : - ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink"); + String type = LINK_EVENT.get(event.type()); return JsonUtils.envelope(type, 0, payload); } @@ -277,20 +292,24 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { protected ObjectNode hostMessage(HostEvent event) { Host host = event.subject(); String hostType = host.annotations().value(AnnotationKeys.TYPE); + HostLocation prevLoc = event.prevLocation(); + ObjectNode payload = objectNode() .put("id", host.id().toString()) .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType) .put("ingress", compactLinkString(edgeLink(host, true))) .put("egress", compactLinkString(edgeLink(host, false))); payload.set("cp", hostConnect(host.location())); + if (prevLoc != null) { + payload.set("prevCp", hostConnect(event.prevLocation())); + } payload.set("labels", labels(ip(host.ipAddresses()), host.mac().toString())); payload.set("props", props(host.annotations())); addGeoLocation(host, payload); addMetaUi(host.id().toString(), payload); - String type = (event.type() == HOST_ADDED) ? "addHost" : - ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost"); + String type = HOST_EVENT.get(event.type()); return JsonUtils.envelope(type, 0, payload); } diff --git a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoEvent.js index 2957629a..9b07f878 100644 --- a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoEvent.js +++ b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoEvent.js @@ -55,6 +55,7 @@ removeDevice: tfs, addHost: tfs, updateHost: tfs, + moveHost: tfs, removeHost: tfs, addLink: tfs, updateLink: tfs, diff --git a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js index 844d7dc9..175ee796 100644 --- a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js +++ b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js @@ -187,6 +187,35 @@ } } + function moveHost(data) { + var id = data.id, + d = lu[id], + lnk; + if (d) { + // first remove the old host link + removeLinkElement(d.linkData); + + // merge new data + angular.extend(d, data); + if (tms.positionNode(d, true)) { + sendUpdateMeta(d); + } + + // now create a new host link + lnk = tms.createHostLink(data); + if (lnk) { + d.linkData = lnk; + network.links.push(lnk); + lu[d.ingress] = lnk; + lu[d.egress] = lnk; + } + + updateNodes(); + updateLinks(); + fResume(); + } + } + function removeHost(data) { var id = data.id, d = lu[id]; @@ -1142,6 +1171,7 @@ removeDevice: removeDevice, addHost: addHost, updateHost: updateHost, + moveHost: moveHost, removeHost: removeHost, addLink: addLink, updateLink: updateLink, |