Thomas Vachuska
Committed by Gerrit Code Review

ONOS-1684 Added support for app dependencies.

Change-Id: Iae318c24c3c9bd43d84318c79ac420fc85d5d599
...@@ -41,7 +41,7 @@ public class ApplicationsListCommand extends AbstractShellCommand { ...@@ -41,7 +41,7 @@ public class ApplicationsListCommand extends AbstractShellCommand {
41 41
42 private static final String FMT = 42 private static final String FMT =
43 "%s id=%d, name=%s, version=%s, origin=%s, description=%s, " + 43 "%s id=%d, name=%s, version=%s, origin=%s, description=%s, " +
44 - "features=%s, featuresRepo=%s, permissions=%s"; 44 + "features=%s, featuresRepo=%s, apps=%s, permissions=%s";
45 45
46 private static final String SHORT_FMT = 46 private static final String SHORT_FMT =
47 "%s %3d %-32s %-8s %s"; 47 "%s %3d %-32s %-8s %s";
...@@ -76,7 +76,7 @@ public class ApplicationsListCommand extends AbstractShellCommand { ...@@ -76,7 +76,7 @@ public class ApplicationsListCommand extends AbstractShellCommand {
76 app.id().id(), app.id().name(), app.version(), app.origin(), 76 app.id().id(), app.id().name(), app.version(), app.origin(),
77 app.description(), app.features(), 77 app.description(), app.features(),
78 app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "", 78 app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "",
79 - app.permissions()); 79 + app.requiredApps(), app.permissions());
80 } 80 }
81 } 81 }
82 } 82 }
......
...@@ -86,4 +86,11 @@ public interface ApplicationDescription { ...@@ -86,4 +86,11 @@ public interface ApplicationDescription {
86 * @return application features 86 * @return application features
87 */ 87 */
88 List<String> features(); 88 List<String> features();
89 +
90 + /**
91 + * Returns list of required application names.
92 + *
93 + * @return list of application names
94 + */
95 + List<String> requiredApps();
89 } 96 }
......
...@@ -76,7 +76,7 @@ public interface ApplicationStore extends Store<ApplicationEvent, ApplicationSto ...@@ -76,7 +76,7 @@ public interface ApplicationStore extends Store<ApplicationEvent, ApplicationSto
76 void remove(ApplicationId appId); 76 void remove(ApplicationId appId);
77 77
78 /** 78 /**
79 - * Mark the application as actived. 79 + * Mark the application as active.
80 * 80 *
81 * @param appId application identifier 81 * @param appId application identifier
82 */ 82 */
......
...@@ -41,6 +41,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -41,6 +41,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
41 private final Set<Permission> permissions; 41 private final Set<Permission> permissions;
42 private final Optional<URI> featuresRepo; 42 private final Optional<URI> featuresRepo;
43 private final List<String> features; 43 private final List<String> features;
44 + private final List<String> requiredApps;
44 45
45 /** 46 /**
46 * Creates a new application descriptor using the supplied data. 47 * Creates a new application descriptor using the supplied data.
...@@ -53,11 +54,13 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -53,11 +54,13 @@ public class DefaultApplicationDescription implements ApplicationDescription {
53 * @param permissions requested permissions 54 * @param permissions requested permissions
54 * @param featuresRepo optional features repo URI 55 * @param featuresRepo optional features repo URI
55 * @param features application features 56 * @param features application features
57 + * @param requiredApps list of required application names
56 */ 58 */
57 public DefaultApplicationDescription(String name, Version version, 59 public DefaultApplicationDescription(String name, Version version,
58 String description, String origin, 60 String description, String origin,
59 ApplicationRole role, Set<Permission> permissions, 61 ApplicationRole role, Set<Permission> permissions,
60 - URI featuresRepo, List<String> features) { 62 + URI featuresRepo, List<String> features,
63 + List<String> requiredApps) {
61 this.name = checkNotNull(name, "Name cannot be null"); 64 this.name = checkNotNull(name, "Name cannot be null");
62 this.version = checkNotNull(version, "Version cannot be null"); 65 this.version = checkNotNull(version, "Version cannot be null");
63 this.description = checkNotNull(description, "Description cannot be null"); 66 this.description = checkNotNull(description, "Description cannot be null");
...@@ -66,6 +69,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -66,6 +69,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
66 this.permissions = checkNotNull(permissions, "Permissions cannot be null"); 69 this.permissions = checkNotNull(permissions, "Permissions cannot be null");
67 this.featuresRepo = Optional.ofNullable(featuresRepo); 70 this.featuresRepo = Optional.ofNullable(featuresRepo);
68 this.features = checkNotNull(features, "Features cannot be null"); 71 this.features = checkNotNull(features, "Features cannot be null");
72 + this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null");
69 checkArgument(!features.isEmpty(), "There must be at least one feature"); 73 checkArgument(!features.isEmpty(), "There must be at least one feature");
70 } 74 }
71 75
...@@ -110,6 +114,11 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -110,6 +114,11 @@ public class DefaultApplicationDescription implements ApplicationDescription {
110 } 114 }
111 115
112 @Override 116 @Override
117 + public List<String> requiredApps() {
118 + return requiredApps;
119 + }
120 +
121 + @Override
113 public String toString() { 122 public String toString() {
114 return toStringHelper(this) 123 return toStringHelper(this)
115 .add("name", name) 124 .add("name", name)
...@@ -120,6 +129,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -120,6 +129,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
120 .add("permissions", permissions) 129 .add("permissions", permissions)
121 .add("featuresRepo", featuresRepo) 130 .add("featuresRepo", featuresRepo)
122 .add("features", features) 131 .add("features", features)
132 + .add("requiredApps", requiredApps)
123 .toString(); 133 .toString();
124 } 134 }
125 } 135 }
......
...@@ -84,4 +84,11 @@ public interface Application { ...@@ -84,4 +84,11 @@ public interface Application {
84 * @return application features 84 * @return application features
85 */ 85 */
86 List<String> features(); 86 List<String> features();
87 +
88 + /**
89 + * Returns list of required application names.
90 + *
91 + * @return list of application names
92 + */
93 + List<String> requiredApps();
87 } 94 }
......
...@@ -40,6 +40,7 @@ public class DefaultApplication implements Application { ...@@ -40,6 +40,7 @@ public class DefaultApplication implements Application {
40 private final Set<Permission> permissions; 40 private final Set<Permission> permissions;
41 private final Optional<URI> featuresRepo; 41 private final Optional<URI> featuresRepo;
42 private final List<String> features; 42 private final List<String> features;
43 + private final List<String> requiredApps;
43 44
44 /** 45 /**
45 * Creates a new application descriptor using the supplied data. 46 * Creates a new application descriptor using the supplied data.
...@@ -52,11 +53,13 @@ public class DefaultApplication implements Application { ...@@ -52,11 +53,13 @@ public class DefaultApplication implements Application {
52 * @param permissions requested permissions 53 * @param permissions requested permissions
53 * @param featuresRepo optional features repo URI 54 * @param featuresRepo optional features repo URI
54 * @param features application features 55 * @param features application features
56 + * @param requiredApps list of required application names
55 */ 57 */
56 public DefaultApplication(ApplicationId appId, Version version, 58 public DefaultApplication(ApplicationId appId, Version version,
57 String description, String origin, 59 String description, String origin,
58 ApplicationRole role, Set<Permission> permissions, 60 ApplicationRole role, Set<Permission> permissions,
59 - Optional<URI> featuresRepo, List<String> features) { 61 + Optional<URI> featuresRepo, List<String> features,
62 + List<String> requiredApps) {
60 this.appId = checkNotNull(appId, "ID cannot be null"); 63 this.appId = checkNotNull(appId, "ID cannot be null");
61 this.version = checkNotNull(version, "Version cannot be null"); 64 this.version = checkNotNull(version, "Version cannot be null");
62 this.description = checkNotNull(description, "Description cannot be null"); 65 this.description = checkNotNull(description, "Description cannot be null");
...@@ -65,6 +68,7 @@ public class DefaultApplication implements Application { ...@@ -65,6 +68,7 @@ public class DefaultApplication implements Application {
65 this.permissions = checkNotNull(permissions, "Permissions cannot be null"); 68 this.permissions = checkNotNull(permissions, "Permissions cannot be null");
66 this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null"); 69 this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null");
67 this.features = checkNotNull(features, "Features cannot be null"); 70 this.features = checkNotNull(features, "Features cannot be null");
71 + this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null");
68 checkArgument(!features.isEmpty(), "There must be at least one feature"); 72 checkArgument(!features.isEmpty(), "There must be at least one feature");
69 } 73 }
70 74
...@@ -109,9 +113,14 @@ public class DefaultApplication implements Application { ...@@ -109,9 +113,14 @@ public class DefaultApplication implements Application {
109 } 113 }
110 114
111 @Override 115 @Override
116 + public List<String> requiredApps() {
117 + return requiredApps;
118 + }
119 +
120 + @Override
112 public int hashCode() { 121 public int hashCode() {
113 return Objects.hash(appId, version, description, origin, role, permissions, 122 return Objects.hash(appId, version, description, origin, role, permissions,
114 - featuresRepo, features); 123 + featuresRepo, features, requiredApps);
115 } 124 }
116 125
117 @Override 126 @Override
...@@ -130,7 +139,8 @@ public class DefaultApplication implements Application { ...@@ -130,7 +139,8 @@ public class DefaultApplication implements Application {
130 Objects.equals(this.role, other.role) && 139 Objects.equals(this.role, other.role) &&
131 Objects.equals(this.permissions, other.permissions) && 140 Objects.equals(this.permissions, other.permissions) &&
132 Objects.equals(this.featuresRepo, other.featuresRepo) && 141 Objects.equals(this.featuresRepo, other.featuresRepo) &&
133 - Objects.equals(this.features, other.features); 142 + Objects.equals(this.features, other.features) &&
143 + Objects.equals(this.requiredApps, other.requiredApps);
134 } 144 }
135 145
136 @Override 146 @Override
...@@ -144,6 +154,7 @@ public class DefaultApplication implements Application { ...@@ -144,6 +154,7 @@ public class DefaultApplication implements Application {
144 .add("permissions", permissions) 154 .add("permissions", permissions)
145 .add("featuresRepo", featuresRepo) 155 .add("featuresRepo", featuresRepo)
146 .add("features", features) 156 .add("features", features)
157 + .add("requiredApps", requiredApps)
147 .toString(); 158 .toString();
148 } 159 }
149 } 160 }
......
...@@ -33,7 +33,7 @@ public class ApplicationEventTest extends AbstractEventTest { ...@@ -33,7 +33,7 @@ public class ApplicationEventTest extends AbstractEventTest {
33 33
34 private Application createApp() { 34 private Application createApp() {
35 return new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, 35 return new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
36 - PERMS, Optional.of(FURL), FEATURES); 36 + PERMS, Optional.of(FURL), FEATURES, APPS);
37 } 37 }
38 38
39 @Test 39 @Test
......
...@@ -46,12 +46,13 @@ public class DefaultApplicationDescriptionTest { ...@@ -46,12 +46,13 @@ public class DefaultApplicationDescriptionTest {
46 new Permission(AppPermission.class.getName(), "FLOWRULE_READ")); 46 new Permission(AppPermission.class.getName(), "FLOWRULE_READ"));
47 public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features"); 47 public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
48 public static final List<String> FEATURES = ImmutableList.of("foo", "bar"); 48 public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
49 + public static final List<String> APPS = ImmutableList.of("fifi");
49 50
50 @Test 51 @Test
51 public void basics() { 52 public void basics() {
52 ApplicationDescription app = 53 ApplicationDescription app =
53 new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN, 54 new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN,
54 - ROLE, PERMS, FURL, FEATURES); 55 + ROLE, PERMS, FURL, FEATURES, APPS);
55 assertEquals("incorrect id", APP_NAME, app.name()); 56 assertEquals("incorrect id", APP_NAME, app.name());
56 assertEquals("incorrect version", VER, app.version()); 57 assertEquals("incorrect version", VER, app.version());
57 assertEquals("incorrect description", DESC, app.description()); 58 assertEquals("incorrect description", DESC, app.description());
...@@ -60,6 +61,7 @@ public class DefaultApplicationDescriptionTest { ...@@ -60,6 +61,7 @@ public class DefaultApplicationDescriptionTest {
60 assertEquals("incorrect permissions", PERMS, app.permissions()); 61 assertEquals("incorrect permissions", PERMS, app.permissions());
61 assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); 62 assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
62 assertEquals("incorrect features", FEATURES, app.features()); 63 assertEquals("incorrect features", FEATURES, app.features());
64 + assertEquals("incorrect apps", APPS, app.requiredApps());
63 assertTrue("incorrect toString", app.toString().contains(APP_NAME)); 65 assertTrue("incorrect toString", app.toString().contains(APP_NAME));
64 } 66 }
65 67
......
...@@ -34,7 +34,7 @@ public class DefaultApplicationTest { ...@@ -34,7 +34,7 @@ public class DefaultApplicationTest {
34 @Test 34 @Test
35 public void basics() { 35 public void basics() {
36 Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, 36 Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
37 - PERMS, Optional.of(FURL), FEATURES); 37 + PERMS, Optional.of(FURL), FEATURES, APPS);
38 assertEquals("incorrect id", APP_ID, app.id()); 38 assertEquals("incorrect id", APP_ID, app.id());
39 assertEquals("incorrect version", VER, app.version()); 39 assertEquals("incorrect version", VER, app.version());
40 assertEquals("incorrect description", DESC, app.description()); 40 assertEquals("incorrect description", DESC, app.description());
...@@ -43,19 +43,20 @@ public class DefaultApplicationTest { ...@@ -43,19 +43,20 @@ public class DefaultApplicationTest {
43 assertEquals("incorrect permissions", PERMS, app.permissions()); 43 assertEquals("incorrect permissions", PERMS, app.permissions());
44 assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); 44 assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
45 assertEquals("incorrect features", FEATURES, app.features()); 45 assertEquals("incorrect features", FEATURES, app.features());
46 + assertEquals("incorrect apps", APPS, app.requiredApps());
46 assertTrue("incorrect toString", app.toString().contains(APP_NAME)); 47 assertTrue("incorrect toString", app.toString().contains(APP_NAME));
47 } 48 }
48 49
49 @Test 50 @Test
50 public void testEquality() { 51 public void testEquality() {
51 Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, 52 Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
52 - PERMS, Optional.of(FURL), FEATURES); 53 + PERMS, Optional.of(FURL), FEATURES, APPS);
53 Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, 54 Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
54 - PERMS, Optional.of(FURL), FEATURES); 55 + PERMS, Optional.of(FURL), FEATURES, APPS);
55 Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, 56 Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
56 - PERMS, Optional.empty(), FEATURES); 57 + PERMS, Optional.empty(), FEATURES, APPS);
57 Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd", ROLE, 58 Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd", ROLE,
58 - PERMS, Optional.of(FURL), FEATURES); 59 + PERMS, Optional.of(FURL), FEATURES, APPS);
59 new EqualsTester().addEqualityGroup(a1, a2) 60 new EqualsTester().addEqualityGroup(a1, a2)
60 .addEqualityGroup(a3).addEqualityGroup(a4).testEquals(); 61 .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
61 } 62 }
......
...@@ -32,18 +32,18 @@ public final class ApplicationCodec extends JsonCodec<Application> { ...@@ -32,18 +32,18 @@ public final class ApplicationCodec extends JsonCodec<Application> {
32 public ObjectNode encode(Application app, CodecContext context) { 32 public ObjectNode encode(Application app, CodecContext context) {
33 checkNotNull(app, "Application cannot be null"); 33 checkNotNull(app, "Application cannot be null");
34 ApplicationService service = context.getService(ApplicationService.class); 34 ApplicationService service = context.getService(ApplicationService.class);
35 - ObjectNode result = context.mapper().createObjectNode() 35 + return context.mapper().createObjectNode()
36 .put("name", app.id().name()) 36 .put("name", app.id().name())
37 .put("id", app.id().id()) 37 .put("id", app.id().id())
38 .put("version", app.version().toString()) 38 .put("version", app.version().toString())
39 .put("description", app.description()) 39 .put("description", app.description())
40 .put("origin", app.origin()) 40 .put("origin", app.origin())
41 - .put("permissions", app.permissions().toString()) 41 + .put("permissions", app.permissions().toString()) // FIXME: change to an array
42 .put("featuresRepo", app.featuresRepo().isPresent() ? 42 .put("featuresRepo", app.featuresRepo().isPresent() ?
43 app.featuresRepo().get().toString() : "") 43 app.featuresRepo().get().toString() : "")
44 - .put("features", app.features().toString()) 44 + .put("features", app.features().toString()) // FIXME: change to an array
45 + .put("requiredApps", app.requiredApps().toString()) // FIXME: change to an array
45 .put("state", service.getState(app.id()).toString()); 46 .put("state", service.getState(app.id()).toString());
46 - return result;
47 } 47 }
48 48
49 } 49 }
......
...@@ -17,6 +17,7 @@ package org.onosproject.common.app; ...@@ -17,6 +17,7 @@ package org.onosproject.common.app;
17 17
18 import com.google.common.collect.ImmutableList; 18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.ImmutableSet;
20 +import com.google.common.collect.Lists;
20 import com.google.common.io.ByteStreams; 21 import com.google.common.io.ByteStreams;
21 import com.google.common.io.Files; 22 import com.google.common.io.Files;
22 import org.apache.commons.configuration.ConfigurationException; 23 import org.apache.commons.configuration.ConfigurationException;
...@@ -33,7 +34,6 @@ import org.onosproject.core.Version; ...@@ -33,7 +34,6 @@ import org.onosproject.core.Version;
33 import org.onosproject.security.AppPermission; 34 import org.onosproject.security.AppPermission;
34 import org.onosproject.security.Permission; 35 import org.onosproject.security.Permission;
35 import org.onosproject.store.AbstractStore; 36 import org.onosproject.store.AbstractStore;
36 -
37 import org.slf4j.Logger; 37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory; 38 import org.slf4j.LoggerFactory;
39 39
...@@ -46,7 +46,6 @@ import java.io.InputStream; ...@@ -46,7 +46,6 @@ import java.io.InputStream;
46 import java.net.URI; 46 import java.net.URI;
47 import java.nio.charset.Charset; 47 import java.nio.charset.Charset;
48 import java.nio.file.NoSuchFileException; 48 import java.nio.file.NoSuchFileException;
49 -import java.util.ArrayList;
50 import java.util.List; 49 import java.util.List;
51 import java.util.Locale; 50 import java.util.Locale;
52 import java.util.Set; 51 import java.util.Set;
...@@ -79,6 +78,7 @@ public class ApplicationArchive ...@@ -79,6 +78,7 @@ public class ApplicationArchive
79 private static final String VERSION = "[@version]"; 78 private static final String VERSION = "[@version]";
80 private static final String FEATURES_REPO = "[@featuresRepo]"; 79 private static final String FEATURES_REPO = "[@featuresRepo]";
81 private static final String FEATURES = "[@features]"; 80 private static final String FEATURES = "[@features]";
81 + private static final String APPS = "[@apps]";
82 private static final String DESCRIPTION = "description"; 82 private static final String DESCRIPTION = "description";
83 83
84 private static final String ROLE = "security.role"; 84 private static final String ROLE = "security.role";
...@@ -291,8 +291,13 @@ public class ApplicationArchive ...@@ -291,8 +291,13 @@ public class ApplicationArchive
291 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null; 291 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
292 List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(",")); 292 List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(","));
293 293
294 + String apps = cfg.getString(APPS, "");
295 + List<String> requiredApps = apps.isEmpty() ?
296 + ImmutableList.of() : ImmutableList.copyOf(apps.split(","));
297 +
294 return new DefaultApplicationDescription(name, version, desc, origin, role, 298 return new DefaultApplicationDescription(name, version, desc, origin, role,
295 - perms, featuresRepo, features); 299 + perms, featuresRepo, features,
300 + requiredApps);
296 } 301 }
297 302
298 // Expands the specified ZIP stream into app-specific directory. 303 // Expands the specified ZIP stream into app-specific directory.
...@@ -390,7 +395,7 @@ public class ApplicationArchive ...@@ -390,7 +395,7 @@ public class ApplicationArchive
390 395
391 // Returns the set of Permissions specified in the app.xml file 396 // Returns the set of Permissions specified in the app.xml file
392 private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) { 397 private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) {
393 - List<Permission> permissionList = new ArrayList(); 398 + List<Permission> permissionList = Lists.newArrayList();
394 399
395 for (Object o : cfg.getList(APP_PERMISSIONS)) { 400 for (Object o : cfg.getList(APP_PERMISSIONS)) {
396 String name = (String) o; 401 String name = (String) o;
......
...@@ -75,7 +75,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic ...@@ -75,7 +75,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic
75 new DefaultApplication(appId, appDesc.version(), 75 new DefaultApplication(appId, appDesc.version(),
76 appDesc.description(), appDesc.origin(), 76 appDesc.description(), appDesc.origin(),
77 appDesc.role(), appDesc.permissions(), 77 appDesc.role(), appDesc.permissions(),
78 - appDesc.featuresRepo(), appDesc.features()); 78 + appDesc.featuresRepo(), appDesc.features(),
79 + appDesc.requiredApps());
79 apps.put(appId, app); 80 apps.put(appId, app);
80 states.put(appId, isActive(name) ? INSTALLED : ACTIVE); 81 states.put(appId, isActive(name) ? INSTALLED : ACTIVE);
81 // load app permissions 82 // load app permissions
...@@ -117,7 +118,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic ...@@ -117,7 +118,8 @@ public class SimpleApplicationStore extends ApplicationArchive implements Applic
117 DefaultApplication app = 118 DefaultApplication app =
118 new DefaultApplication(appId, appDesc.version(), appDesc.description(), 119 new DefaultApplication(appId, appDesc.version(), appDesc.description(),
119 appDesc.origin(), appDesc.role(), appDesc.permissions(), 120 appDesc.origin(), appDesc.role(), appDesc.permissions(),
120 - appDesc.featuresRepo(), appDesc.features()); 121 + appDesc.featuresRepo(), appDesc.features(),
122 + appDesc.requiredApps());
121 apps.put(appId, app); 123 apps.put(appId, app);
122 states.put(appId, INSTALLED); 124 states.put(appId, INSTALLED);
123 delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); 125 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
......
...@@ -212,6 +212,7 @@ public class ApplicationManager ...@@ -212,6 +212,7 @@ public class ApplicationManager
212 // The following methods are fully synchronized to guard against remote vs. 212 // The following methods are fully synchronized to guard against remote vs.
213 // locally induced feature service interactions. 213 // locally induced feature service interactions.
214 214
215 + // Installs all feature repositories required by the specified app.
215 private synchronized boolean installAppArtifacts(Application app) throws Exception { 216 private synchronized boolean installAppArtifacts(Application app) throws Exception {
216 if (app.featuresRepo().isPresent() && 217 if (app.featuresRepo().isPresent() &&
217 featuresService.getRepository(app.featuresRepo().get()) == null) { 218 featuresService.getRepository(app.featuresRepo().get()) == null) {
...@@ -221,6 +222,7 @@ public class ApplicationManager ...@@ -221,6 +222,7 @@ public class ApplicationManager
221 return false; 222 return false;
222 } 223 }
223 224
225 + // Uninstalls all the feature repositories required by the specified app.
224 private synchronized boolean uninstallAppArtifacts(Application app) throws Exception { 226 private synchronized boolean uninstallAppArtifacts(Application app) throws Exception {
225 if (app.featuresRepo().isPresent() && 227 if (app.featuresRepo().isPresent() &&
226 featuresService.getRepository(app.featuresRepo().get()) != null) { 228 featuresService.getRepository(app.featuresRepo().get()) != null) {
...@@ -230,6 +232,7 @@ public class ApplicationManager ...@@ -230,6 +232,7 @@ public class ApplicationManager
230 return false; 232 return false;
231 } 233 }
232 234
235 + // Installs all features that define the specified app.
233 private synchronized boolean installAppFeatures(Application app) throws Exception { 236 private synchronized boolean installAppFeatures(Application app) throws Exception {
234 boolean changed = false; 237 boolean changed = false;
235 for (String name : app.features()) { 238 for (String name : app.features()) {
...@@ -246,6 +249,7 @@ public class ApplicationManager ...@@ -246,6 +249,7 @@ public class ApplicationManager
246 return changed; 249 return changed;
247 } 250 }
248 251
252 + // Uninstalls all features that define the specified app.
249 private synchronized boolean uninstallAppFeatures(Application app) throws Exception { 253 private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
250 boolean changed = false; 254 boolean changed = false;
251 invokeHook(deactivateHooks.get(app.id().name()), app.id()); 255 invokeHook(deactivateHooks.get(app.id().name()), app.id());
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onosproject.app.impl; 16 package org.onosproject.app.impl;
17 17
18 +import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.ImmutableSet;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Before; 21 import org.junit.Before;
...@@ -138,7 +139,7 @@ public class ApplicationManagerTest { ...@@ -138,7 +139,7 @@ public class ApplicationManagerTest {
138 @Override 139 @Override
139 public Application create(InputStream appDescStream) { 140 public Application create(InputStream appDescStream) {
140 app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS, 141 app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS,
141 - Optional.of(FURL), FEATURES); 142 + Optional.of(FURL), FEATURES, ImmutableList.of());
142 state = INSTALLED; 143 state = INSTALLED;
143 delegate.notify(new ApplicationEvent(APP_INSTALLED, app)); 144 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
144 return app; 145 return app;
...@@ -177,6 +178,11 @@ public class ApplicationManagerTest { ...@@ -177,6 +178,11 @@ public class ApplicationManagerTest {
177 state = INSTALLED; 178 state = INSTALLED;
178 delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app)); 179 delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
179 } 180 }
181 +
182 + @Override
183 + public ApplicationId getId(String name) {
184 + return new DefaultApplicationId(0, name);
185 + }
180 } 186 }
181 187
182 private class TestFeaturesService extends FeaturesServiceAdapter { 188 private class TestFeaturesService extends FeaturesServiceAdapter {
......
...@@ -17,7 +17,9 @@ package org.onosproject.store.app; ...@@ -17,7 +17,9 @@ package org.onosproject.store.app;
17 17
18 import com.google.common.base.Charsets; 18 import com.google.common.base.Charsets;
19 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.ImmutableSet;
20 - 20 +import com.google.common.collect.Maps;
21 +import com.google.common.collect.Multimap;
22 +import com.google.common.collect.Sets;
21 import org.apache.felix.scr.annotations.Activate; 23 import org.apache.felix.scr.annotations.Activate;
22 import org.apache.felix.scr.annotations.Component; 24 import org.apache.felix.scr.annotations.Component;
23 import org.apache.felix.scr.annotations.Deactivate; 25 import org.apache.felix.scr.annotations.Deactivate;
...@@ -37,6 +39,7 @@ import org.onosproject.common.app.ApplicationArchive; ...@@ -37,6 +39,7 @@ import org.onosproject.common.app.ApplicationArchive;
37 import org.onosproject.core.Application; 39 import org.onosproject.core.Application;
38 import org.onosproject.core.ApplicationId; 40 import org.onosproject.core.ApplicationId;
39 import org.onosproject.core.ApplicationIdStore; 41 import org.onosproject.core.ApplicationIdStore;
42 +import org.onosproject.core.CoreService;
40 import org.onosproject.core.DefaultApplication; 43 import org.onosproject.core.DefaultApplication;
41 import org.onosproject.security.Permission; 44 import org.onosproject.security.Permission;
42 import org.onosproject.store.cluster.messaging.ClusterCommunicationService; 45 import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
...@@ -61,6 +64,8 @@ import java.util.concurrent.Executors; ...@@ -61,6 +64,8 @@ import java.util.concurrent.Executors;
61 import java.util.concurrent.ScheduledExecutorService; 64 import java.util.concurrent.ScheduledExecutorService;
62 import java.util.function.Function; 65 import java.util.function.Function;
63 66
67 +import static com.google.common.collect.Multimaps.newSetMultimap;
68 +import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
64 import static com.google.common.io.ByteStreams.toByteArray; 69 import static com.google.common.io.ByteStreams.toByteArray;
65 import static java.util.concurrent.TimeUnit.MILLISECONDS; 70 import static java.util.concurrent.TimeUnit.MILLISECONDS;
66 import static org.onlab.util.Tools.groupedThreads; 71 import static org.onlab.util.Tools.groupedThreads;
...@@ -115,6 +120,14 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -115,6 +120,14 @@ public class GossipApplicationStore extends ApplicationArchive
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected ApplicationIdStore idStore; 121 protected ApplicationIdStore idStore;
117 122
123 + // Multimap to track which apps are required by others apps
124 + // app -> { required-by, ... }
125 + // Apps explicitly activated will be required by the CORE app
126 + private final Multimap<ApplicationId, ApplicationId> requiredBy =
127 + synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
128 +
129 + private ApplicationId coreAppId;
130 +
118 @Activate 131 @Activate
119 public void activate() { 132 public void activate() {
120 KryoNamespace.Builder serializer = KryoNamespace.newBuilder() 133 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
...@@ -161,6 +174,7 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -161,6 +174,7 @@ public class GossipApplicationStore extends ApplicationArchive
161 .withTimestampProvider((k, v) -> clockService.getTimestamp()) 174 .withTimestampProvider((k, v) -> clockService.getTimestamp())
162 .build(); 175 .build();
163 176
177 + coreAppId = getId(CoreService.CORE_APP_NAME);
164 log.info("Started"); 178 log.info("Started");
165 } 179 }
166 180
...@@ -174,6 +188,7 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -174,6 +188,7 @@ public class GossipApplicationStore extends ApplicationArchive
174 try { 188 try {
175 Application app = create(getApplicationDescription(name), false); 189 Application app = create(getApplicationDescription(name), false);
176 if (app != null && isActive(app.id().name())) { 190 if (app != null && isActive(app.id().name())) {
191 + requiredBy.put(app.id(), coreAppId);
177 activate(app.id(), false); 192 activate(app.id(), false);
178 // load app permissions 193 // load app permissions
179 } 194 }
...@@ -200,7 +215,6 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -200,7 +215,6 @@ public class GossipApplicationStore extends ApplicationArchive
200 public void setDelegate(ApplicationStoreDelegate delegate) { 215 public void setDelegate(ApplicationStoreDelegate delegate) {
201 super.setDelegate(delegate); 216 super.setDelegate(delegate);
202 loadFromDisk(); 217 loadFromDisk();
203 -// executor.schedule(this::pruneUninstalledApps, LOAD_TIMEOUT_MS, MILLISECONDS);
204 } 218 }
205 219
206 @Override 220 @Override
...@@ -229,8 +243,16 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -229,8 +243,16 @@ public class GossipApplicationStore extends ApplicationArchive
229 @Override 243 @Override
230 public Application create(InputStream appDescStream) { 244 public Application create(InputStream appDescStream) {
231 ApplicationDescription appDesc = saveApplication(appDescStream); 245 ApplicationDescription appDesc = saveApplication(appDescStream);
246 + if (hasPrerequisites(appDesc)) {
232 return create(appDesc, true); 247 return create(appDesc, true);
233 } 248 }
249 + throw new ApplicationException("Missing dependencies for app " + appDesc.name());
250 + }
251 +
252 + private boolean hasPrerequisites(ApplicationDescription app) {
253 + return !app.requiredApps().stream().map(n -> getId(n))
254 + .anyMatch(id -> id == null || getApplication(id) == null);
255 + }
234 256
235 private Application create(ApplicationDescription appDesc, boolean updateTime) { 257 private Application create(ApplicationDescription appDesc, boolean updateTime) {
236 Application app = registerApp(appDesc); 258 Application app = registerApp(appDesc);
...@@ -246,35 +268,79 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -246,35 +268,79 @@ public class GossipApplicationStore extends ApplicationArchive
246 public void remove(ApplicationId appId) { 268 public void remove(ApplicationId appId) {
247 Application app = apps.get(appId); 269 Application app = apps.get(appId);
248 if (app != null) { 270 if (app != null) {
271 + uninstallDependentApps(app);
249 apps.remove(appId); 272 apps.remove(appId);
250 states.remove(app); 273 states.remove(app);
251 permissions.remove(app); 274 permissions.remove(app);
252 } 275 }
253 } 276 }
254 277
278 + // Uninstalls all apps that depend on the given app.
279 + private void uninstallDependentApps(Application app) {
280 + getApplications().stream()
281 + .filter(a -> a.requiredApps().contains(app.id().name()))
282 + .forEach(a -> remove(a.id()));
283 + }
284 +
255 @Override 285 @Override
256 public void activate(ApplicationId appId) { 286 public void activate(ApplicationId appId) {
287 + activate(appId, coreAppId);
288 + }
289 +
290 + private void activate(ApplicationId appId, ApplicationId forAppId) {
291 + requiredBy.put(appId, forAppId);
257 activate(appId, true); 292 activate(appId, true);
258 } 293 }
259 294
295 +
260 private void activate(ApplicationId appId, boolean updateTime) { 296 private void activate(ApplicationId appId, boolean updateTime) {
261 Application app = apps.get(appId); 297 Application app = apps.get(appId);
262 if (app != null) { 298 if (app != null) {
263 if (updateTime) { 299 if (updateTime) {
264 updateTime(appId.name()); 300 updateTime(appId.name());
265 } 301 }
302 + activateRequiredApps(app);
266 states.put(app, ACTIVATED); 303 states.put(app, ACTIVATED);
267 } 304 }
268 } 305 }
269 306
307 + // Activates all apps required by this application.
308 + private void activateRequiredApps(Application app) {
309 + app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
310 + }
311 +
270 @Override 312 @Override
271 public void deactivate(ApplicationId appId) { 313 public void deactivate(ApplicationId appId) {
314 + deactivateDependentApps(getApplication(appId));
315 + deactivate(appId, coreAppId);
316 + }
317 +
318 + private void deactivate(ApplicationId appId, ApplicationId forAppId) {
319 + requiredBy.remove(appId, forAppId);
320 + if (requiredBy.get(appId).isEmpty()) {
272 Application app = apps.get(appId); 321 Application app = apps.get(appId);
273 if (app != null) { 322 if (app != null) {
274 updateTime(appId.name()); 323 updateTime(appId.name());
275 states.put(app, DEACTIVATED); 324 states.put(app, DEACTIVATED);
325 + deactivateRequiredApps(app);
276 } 326 }
277 } 327 }
328 + }
329 +
330 + // Deactivates all apps that require this application.
331 + private void deactivateDependentApps(Application app) {
332 + getApplications().stream()
333 + .filter(a -> states.get(a) == ACTIVATED)
334 + .filter(a -> a.requiredApps().contains(app.id().name()))
335 + .forEach(a -> deactivate(a.id()));
336 + }
337 +
338 + // Deactivates all apps required by this application.
339 + private void deactivateRequiredApps(Application app) {
340 + app.requiredApps().stream().map(this::getId).map(this::getApplication)
341 + .filter(a -> states.get(a) == ACTIVATED)
342 + .forEach(a -> deactivate(a.id(), app.id()));
343 + }
278 344
279 @Override 345 @Override
280 public Set<Permission> getPermissions(ApplicationId appId) { 346 public Set<Permission> getPermissions(ApplicationId appId) {
...@@ -424,6 +490,7 @@ public class GossipApplicationStore extends ApplicationArchive ...@@ -424,6 +490,7 @@ public class GossipApplicationStore extends ApplicationArchive
424 ApplicationId appId = idStore.registerApplication(appDesc.name()); 490 ApplicationId appId = idStore.registerApplication(appDesc.name());
425 return new DefaultApplication(appId, appDesc.version(), appDesc.description(), 491 return new DefaultApplication(appId, appDesc.version(), appDesc.description(),
426 appDesc.origin(), appDesc.role(), appDesc.permissions(), 492 appDesc.origin(), appDesc.role(), appDesc.permissions(),
427 - appDesc.featuresRepo(), appDesc.features()); 493 + appDesc.featuresRepo(), appDesc.features(),
494 + appDesc.requiredApps());
428 } 495 }
429 } 496 }
......
...@@ -62,6 +62,7 @@ public class OnosAppMojo extends AbstractMojo { ...@@ -62,6 +62,7 @@ public class OnosAppMojo extends AbstractMojo {
62 62
63 private static final String ONOS_APP_NAME = "onos.app.name"; 63 private static final String ONOS_APP_NAME = "onos.app.name";
64 private static final String ONOS_APP_ORIGIN = "onos.app.origin"; 64 private static final String ONOS_APP_ORIGIN = "onos.app.origin";
65 + private static final String ONOS_APP_REQUIRES = "onos.app.requires";
65 66
66 private static final String JAR = "jar"; 67 private static final String JAR = "jar";
67 private static final String XML = "xml"; 68 private static final String XML = "xml";
...@@ -80,6 +81,7 @@ public class OnosAppMojo extends AbstractMojo { ...@@ -80,6 +81,7 @@ public class OnosAppMojo extends AbstractMojo {
80 81
81 private String name; 82 private String name;
82 private String origin; 83 private String origin;
84 + private String requiredApps;
83 private String version = DEFAULT_VERSION; 85 private String version = DEFAULT_VERSION;
84 private String featuresRepo = DEFAULT_FEATURES_REPO; 86 private String featuresRepo = DEFAULT_FEATURES_REPO;
85 private List<String> artifacts; 87 private List<String> artifacts;
...@@ -160,6 +162,9 @@ public class OnosAppMojo extends AbstractMojo { ...@@ -160,6 +162,9 @@ public class OnosAppMojo extends AbstractMojo {
160 origin = (String) project.getProperties().get(ONOS_APP_ORIGIN); 162 origin = (String) project.getProperties().get(ONOS_APP_ORIGIN);
161 origin = origin != null ? origin : DEFAULT_ORIGIN; 163 origin = origin != null ? origin : DEFAULT_ORIGIN;
162 164
165 + requiredApps = (String) project.getProperties().get(ONOS_APP_REQUIRES);
166 + requiredApps = requiredApps == null ? "" : requiredApps;
167 +
163 if (appFile.exists()) { 168 if (appFile.exists()) {
164 loadAppFile(appFile); 169 loadAppFile(appFile);
165 } else { 170 } else {
...@@ -338,6 +343,7 @@ public class OnosAppMojo extends AbstractMojo { ...@@ -338,6 +343,7 @@ public class OnosAppMojo extends AbstractMojo {
338 return string == null ? null : 343 return string == null ? null :
339 string.replaceAll("\\$\\{onos.app.name\\}", name) 344 string.replaceAll("\\$\\{onos.app.name\\}", name)
340 .replaceAll("\\$\\{onos.app.origin\\}", origin) 345 .replaceAll("\\$\\{onos.app.origin\\}", origin)
346 + .replaceAll("\\$\\{onos.app.requires\\}", requiredApps)
341 .replaceAll("\\$\\{project.groupId\\}", projectGroupId) 347 .replaceAll("\\$\\{project.groupId\\}", projectGroupId)
342 .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId) 348 .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
343 .replaceAll("\\$\\{project.version\\}", projectVersion) 349 .replaceAll("\\$\\{project.version\\}", projectVersion)
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
16 --> 16 -->
17 <app name="${onos.app.name}" origin="${onos.app.origin}" version="${project.version}" 17 <app name="${onos.app.name}" origin="${onos.app.origin}" version="${project.version}"
18 featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" 18 featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
19 - features="${project.artifactId}"> 19 + features="${project.artifactId}" apps="${onos.app.requires}">
20 <description>${project.description}</description> 20 <description>${project.description}</description>
21 <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact> 21 <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
22 </app> 22 </app>
......
...@@ -85,19 +85,19 @@ public class ApplicationsResourceTest extends ResourceTest { ...@@ -85,19 +85,19 @@ public class ApplicationsResourceTest extends ResourceTest {
85 private Application app1 = 85 private Application app1 =
86 new DefaultApplication(id1, VER, 86 new DefaultApplication(id1, VER,
87 "app1", "origin1", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), 87 "app1", "origin1", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
88 - ImmutableList.of("My Feature")); 88 + ImmutableList.of("My Feature"), ImmutableList.of());
89 private Application app2 = 89 private Application app2 =
90 new DefaultApplication(id2, VER, 90 new DefaultApplication(id2, VER,
91 "app2", "origin2", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), 91 "app2", "origin2", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
92 - ImmutableList.of("My Feature")); 92 + ImmutableList.of("My Feature"), ImmutableList.of());
93 private Application app3 = 93 private Application app3 =
94 new DefaultApplication(id3, VER, 94 new DefaultApplication(id3, VER,
95 "app3", "origin3", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), 95 "app3", "origin3", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
96 - ImmutableList.of("My Feature")); 96 + ImmutableList.of("My Feature"), ImmutableList.of());
97 private Application app4 = 97 private Application app4 =
98 new DefaultApplication(id4, VER, 98 new DefaultApplication(id4, VER,
99 "app4", "origin4", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL), 99 "app4", "origin4", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
100 - ImmutableList.of("My Feature")); 100 + ImmutableList.of("My Feature"), ImmutableList.of());
101 101
102 /** 102 /**
103 * Hamcrest matcher to check that an application representation in JSON matches 103 * Hamcrest matcher to check that an application representation in JSON matches
......