blob: 35555a1a0e7d9f9f22cb07289f1a38450025e3c5 [file] [log] [blame]
Thomas Vachuska02aeb032015-01-06 22:36:30 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.app.impl;
17
Ray Milkey9f87e512016-01-05 10:00:22 -080018import java.io.InputStream;
19import java.util.Map;
20import java.util.Set;
21
Thomas Vachuska02aeb032015-01-06 22:36:30 -080022import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080028import org.apache.karaf.features.Feature;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080029import org.apache.karaf.features.FeaturesService;
30import org.onosproject.app.ApplicationAdminService;
31import org.onosproject.app.ApplicationEvent;
32import org.onosproject.app.ApplicationListener;
33import org.onosproject.app.ApplicationService;
34import org.onosproject.app.ApplicationState;
35import org.onosproject.app.ApplicationStore;
36import org.onosproject.app.ApplicationStoreDelegate;
37import org.onosproject.core.Application;
38import org.onosproject.core.ApplicationId;
Thomas Vachuskac65dd712015-11-04 17:19:10 -080039import org.onosproject.event.AbstractListenerManager;
Changhoon Yoonb856b812015-08-10 03:47:19 +090040import org.onosproject.security.Permission;
41import org.onosproject.security.SecurityUtil;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080042import org.slf4j.Logger;
43
Ray Milkey9f87e512016-01-05 10:00:22 -080044import com.google.common.collect.Maps;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080045
46import static com.google.common.base.Preconditions.checkNotNull;
Ray Milkey9f87e512016-01-05 10:00:22 -080047import static org.onlab.util.SonarSuppressionConstants.SONAR_CALL_RUN;
48import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
49import static org.onosproject.app.ApplicationEvent.Type.APP_DEACTIVATED;
50import static org.onosproject.app.ApplicationEvent.Type.APP_INSTALLED;
51import static org.onosproject.app.ApplicationEvent.Type.APP_UNINSTALLED;
Changhoon Yoon541ef712015-05-23 17:18:34 +090052import static org.onosproject.security.AppGuard.checkPermission;
Thomas Vachuskac65dd712015-11-04 17:19:10 -080053import static org.onosproject.security.AppPermission.Type.APP_READ;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080054import static org.slf4j.LoggerFactory.getLogger;
55
56/**
57 * Implementation of the application management service.
58 */
59@Component(immediate = true)
60@Service
Thomas Vachuska42e8cce2015-07-29 19:25:18 -070061public class ApplicationManager
62 extends AbstractListenerManager<ApplicationEvent, ApplicationListener>
63 implements ApplicationService, ApplicationAdminService {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080064
65 private final Logger log = getLogger(getClass());
66
67 private static final String APP_ID_NULL = "Application ID cannot be null";
68
Thomas Vachuska02aeb032015-01-06 22:36:30 -080069 private final ApplicationStoreDelegate delegate = new InternalStoreDelegate();
70
71 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
72 protected ApplicationStore store;
73
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected FeaturesService featuresService;
76
Thomas Vachuska62f04a42015-04-22 14:38:34 -070077 private boolean initializing;
78
Thomas Vachuskac65dd712015-11-04 17:19:10 -080079 // Application supplied hooks for pre-activation processing.
80 private final Map<String, Runnable> deactivateHooks = Maps.newConcurrentMap();
81
Thomas Vachuska02aeb032015-01-06 22:36:30 -080082 @Activate
83 public void activate() {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080084 eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry);
Thomas Vachuska62f04a42015-04-22 14:38:34 -070085
86 initializing = true;
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070087 store.setDelegate(delegate);
Thomas Vachuska62f04a42015-04-22 14:38:34 -070088 initializing = false;
89
Thomas Vachuska02aeb032015-01-06 22:36:30 -080090 log.info("Started");
91 }
92
93 @Deactivate
94 public void deactivate() {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080095 eventDispatcher.removeSink(ApplicationEvent.class);
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070096 store.unsetDelegate(delegate);
Thomas Vachuska02aeb032015-01-06 22:36:30 -080097 log.info("Stopped");
98 }
99
100 @Override
101 public Set<Application> getApplications() {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900102 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800103 return store.getApplications();
104 }
105
106 @Override
107 public ApplicationId getId(String name) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900108 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800109 checkNotNull(name, "Name cannot be null");
110 return store.getId(name);
111 }
112
113 @Override
114 public Application getApplication(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900115 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800116 checkNotNull(appId, APP_ID_NULL);
117 return store.getApplication(appId);
118 }
119
120 @Override
121 public ApplicationState getState(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900122 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800123 checkNotNull(appId, APP_ID_NULL);
124 return store.getState(appId);
125 }
126
127 @Override
128 public Set<Permission> getPermissions(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900129 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800130 checkNotNull(appId, APP_ID_NULL);
131 return store.getPermissions(appId);
132 }
133
134 @Override
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800135 public void registerDeactivateHook(ApplicationId appId, Runnable hook) {
136 checkPermission(APP_READ);
137 checkNotNull(appId, APP_ID_NULL);
138 checkNotNull(hook, "Hook cannot be null");
139 deactivateHooks.put(appId.name(), hook);
140 }
141
142 @Override
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800143 public Application install(InputStream appDescStream) {
144 checkNotNull(appDescStream, "Application archive stream cannot be null");
Changhoon Yoonb856b812015-08-10 03:47:19 +0900145 Application app = store.create(appDescStream);
146 SecurityUtil.register(app.id());
147 return app;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800148 }
149
150 @Override
151 public void uninstall(ApplicationId appId) {
152 checkNotNull(appId, APP_ID_NULL);
153 try {
154 store.remove(appId);
155 } catch (Exception e) {
156 log.warn("Unable to purge application directory for {}", appId.name());
157 }
158 }
159
160 @Override
161 public void activate(ApplicationId appId) {
162 checkNotNull(appId, APP_ID_NULL);
Changhoon Yoonb856b812015-08-10 03:47:19 +0900163 if (!SecurityUtil.isAppSecured(appId)) {
164 return;
165 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800166 store.activate(appId);
167 }
168
169 @Override
170 public void deactivate(ApplicationId appId) {
171 checkNotNull(appId, APP_ID_NULL);
172 store.deactivate(appId);
173 }
174
175 @Override
176 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
177 checkNotNull(appId, APP_ID_NULL);
178 checkNotNull(permissions, "Permissions cannot be null");
179 store.setPermissions(appId, permissions);
180 }
181
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800182 private class InternalStoreDelegate implements ApplicationStoreDelegate {
183 @Override
184 public void notify(ApplicationEvent event) {
185 ApplicationEvent.Type type = event.type();
186 Application app = event.subject();
187 try {
188 if (type == APP_ACTIVATED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700189 if (installAppFeatures(app)) {
190 log.info("Application {} has been activated", app.id().name());
191 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800192
193 } else if (type == APP_DEACTIVATED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700194 if (uninstallAppFeatures(app)) {
195 log.info("Application {} has been deactivated", app.id().name());
196 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800197
198 } else if (type == APP_INSTALLED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700199 if (installAppArtifacts(app)) {
200 log.info("Application {} has been installed", app.id().name());
201 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800202
203 } else if (type == APP_UNINSTALLED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700204 if (uninstallAppFeatures(app) || uninstallAppArtifacts(app)) {
205 log.info("Application {} has been uninstalled", app.id().name());
206 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800207
208 }
Thomas Vachuska42e8cce2015-07-29 19:25:18 -0700209 post(event);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800210
211 } catch (Exception e) {
212 log.warn("Unable to perform operation on application " + app.id().name(), e);
213 }
214 }
215 }
216
Thomas Vachuska0d6af8d2015-03-23 15:54:47 -0700217 // The following methods are fully synchronized to guard against remote vs.
218 // locally induced feature service interactions.
219
Thomas Vachuska761f0042015-11-11 19:10:17 -0800220 // Installs all feature repositories required by the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700221 private synchronized boolean installAppArtifacts(Application app) throws Exception {
222 if (app.featuresRepo().isPresent() &&
223 featuresService.getRepository(app.featuresRepo().get()) == null) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800224 featuresService.addRepository(app.featuresRepo().get());
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700225 return true;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800226 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700227 return false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800228 }
229
Thomas Vachuska761f0042015-11-11 19:10:17 -0800230 // Uninstalls all the feature repositories required by the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700231 private synchronized boolean uninstallAppArtifacts(Application app) throws Exception {
232 if (app.featuresRepo().isPresent() &&
233 featuresService.getRepository(app.featuresRepo().get()) != null) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800234 featuresService.removeRepository(app.featuresRepo().get());
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700235 return true;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800236 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700237 return false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800238 }
239
Thomas Vachuska761f0042015-11-11 19:10:17 -0800240 // Installs all features that define the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700241 private synchronized boolean installAppFeatures(Application app) throws Exception {
242 boolean changed = false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800243 for (String name : app.features()) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800244 Feature feature = featuresService.getFeature(name);
Thomas Vachuskab56b9172015-11-24 11:24:23 -0800245
246 // If we see an attempt at activation of a non-existent feature
247 // attempt to install the app artifacts first and then retry.
248 // This can be triggered by a race condition between different ONOS
249 // instances "installing" the apps from disk at their own pace.
250 // Perhaps there is a more elegant solution to be explored in the
251 // future.
252 if (feature == null) {
253 installAppArtifacts(app);
254 feature = featuresService.getFeature(name);
255 }
256
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700257 if (feature != null && !featuresService.isInstalled(feature)) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800258 featuresService.installFeature(name);
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700259 changed = true;
Thomas Vachuskab56b9172015-11-24 11:24:23 -0800260 } else if (feature == null) {
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700261 log.warn("Feature {} not found", name);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800262 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800263 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700264 return changed;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800265 }
266
Thomas Vachuska761f0042015-11-11 19:10:17 -0800267 // Uninstalls all features that define the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700268 private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
269 boolean changed = false;
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800270 invokeHook(deactivateHooks.get(app.id().name()), app.id());
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800271 for (String name : app.features()) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800272 Feature feature = featuresService.getFeature(name);
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700273 if (feature != null && featuresService.isInstalled(feature)) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800274 featuresService.uninstallFeature(name);
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700275 changed = true;
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700276 } else if (feature == null) {
277 log.warn("Feature {} not found", name);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800278 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800279 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700280 return changed;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800281 }
282
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800283 // Invokes the specified function, if not null.
Ray Milkey9f87e512016-01-05 10:00:22 -0800284 @java.lang.SuppressWarnings(SONAR_CALL_RUN) // We really do mean to call run()
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800285 private void invokeHook(Runnable hook, ApplicationId appId) {
286 if (hook != null) {
287 try {
288 hook.run();
289 } catch (Exception e) {
290 log.warn("Deactivate hook for application {} encountered an error",
291 appId.name(), e);
292 }
293 }
294 }
295
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800296}