blob: 8735b8cc025c9991445a3bf50e2f895fcf2a7a62 [file] [log] [blame]
Thomas Vachuska02aeb032015-01-06 22:36:30 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuska02aeb032015-01-06 22:36:30 -08003 *
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
Jonathan Hartc32585f2016-06-16 14:01:27 -070018import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080020import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080026import org.apache.karaf.features.Feature;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080027import org.apache.karaf.features.FeaturesService;
28import org.onosproject.app.ApplicationAdminService;
29import org.onosproject.app.ApplicationEvent;
30import org.onosproject.app.ApplicationListener;
31import org.onosproject.app.ApplicationService;
32import org.onosproject.app.ApplicationState;
33import org.onosproject.app.ApplicationStore;
34import org.onosproject.app.ApplicationStoreDelegate;
35import org.onosproject.core.Application;
36import org.onosproject.core.ApplicationId;
Thomas Vachuskac65dd712015-11-04 17:19:10 -080037import org.onosproject.event.AbstractListenerManager;
Changhoon Yoonb856b812015-08-10 03:47:19 +090038import org.onosproject.security.Permission;
39import org.onosproject.security.SecurityUtil;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080040import org.slf4j.Logger;
41
Jonathan Hartc32585f2016-06-16 14:01:27 -070042import java.io.InputStream;
43import java.util.Set;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080044
45import static com.google.common.base.Preconditions.checkNotNull;
Ray Milkey9f87e512016-01-05 10:00:22 -080046import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
47import static org.onosproject.app.ApplicationEvent.Type.APP_DEACTIVATED;
48import static org.onosproject.app.ApplicationEvent.Type.APP_INSTALLED;
49import static org.onosproject.app.ApplicationEvent.Type.APP_UNINSTALLED;
Changhoon Yoon541ef712015-05-23 17:18:34 +090050import static org.onosproject.security.AppGuard.checkPermission;
Thomas Vachuskac65dd712015-11-04 17:19:10 -080051import static org.onosproject.security.AppPermission.Type.APP_READ;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080052import static org.slf4j.LoggerFactory.getLogger;
53
54/**
55 * Implementation of the application management service.
56 */
57@Component(immediate = true)
58@Service
Thomas Vachuska42e8cce2015-07-29 19:25:18 -070059public class ApplicationManager
60 extends AbstractListenerManager<ApplicationEvent, ApplicationListener>
61 implements ApplicationService, ApplicationAdminService {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080062
63 private final Logger log = getLogger(getClass());
64
65 private static final String APP_ID_NULL = "Application ID cannot be null";
66
Thomas Vachuska02aeb032015-01-06 22:36:30 -080067 private final ApplicationStoreDelegate delegate = new InternalStoreDelegate();
68
69 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
70 protected ApplicationStore store;
71
72 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
73 protected FeaturesService featuresService;
74
Thomas Vachuska62f04a42015-04-22 14:38:34 -070075 private boolean initializing;
76
Thomas Vachuskac65dd712015-11-04 17:19:10 -080077 // Application supplied hooks for pre-activation processing.
Jonathan Hartc32585f2016-06-16 14:01:27 -070078 private final Multimap<String, Runnable> deactivateHooks = HashMultimap.create();
Thomas Vachuskac65dd712015-11-04 17:19:10 -080079
Thomas Vachuska02aeb032015-01-06 22:36:30 -080080 @Activate
81 public void activate() {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080082 eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry);
Thomas Vachuska62f04a42015-04-22 14:38:34 -070083
84 initializing = true;
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070085 store.setDelegate(delegate);
Thomas Vachuska62f04a42015-04-22 14:38:34 -070086 initializing = false;
87
Thomas Vachuska02aeb032015-01-06 22:36:30 -080088 log.info("Started");
89 }
90
91 @Deactivate
92 public void deactivate() {
Thomas Vachuska02aeb032015-01-06 22:36:30 -080093 eventDispatcher.removeSink(ApplicationEvent.class);
Thomas Vachuska8dc1a692015-03-31 01:01:37 -070094 store.unsetDelegate(delegate);
Thomas Vachuska02aeb032015-01-06 22:36:30 -080095 log.info("Stopped");
96 }
97
98 @Override
99 public Set<Application> getApplications() {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900100 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800101 return store.getApplications();
102 }
103
104 @Override
105 public ApplicationId getId(String name) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900106 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800107 checkNotNull(name, "Name cannot be null");
108 return store.getId(name);
109 }
110
111 @Override
112 public Application getApplication(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900113 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800114 checkNotNull(appId, APP_ID_NULL);
115 return store.getApplication(appId);
116 }
117
118 @Override
119 public ApplicationState getState(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900120 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800121 checkNotNull(appId, APP_ID_NULL);
122 return store.getState(appId);
123 }
124
125 @Override
126 public Set<Permission> getPermissions(ApplicationId appId) {
Changhoon Yoonb856b812015-08-10 03:47:19 +0900127 checkPermission(APP_READ);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800128 checkNotNull(appId, APP_ID_NULL);
129 return store.getPermissions(appId);
130 }
131
132 @Override
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800133 public void registerDeactivateHook(ApplicationId appId, Runnable hook) {
134 checkPermission(APP_READ);
135 checkNotNull(appId, APP_ID_NULL);
136 checkNotNull(hook, "Hook cannot be null");
137 deactivateHooks.put(appId.name(), hook);
138 }
139
140 @Override
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800141 public Application install(InputStream appDescStream) {
142 checkNotNull(appDescStream, "Application archive stream cannot be null");
Changhoon Yoonb856b812015-08-10 03:47:19 +0900143 Application app = store.create(appDescStream);
144 SecurityUtil.register(app.id());
145 return app;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800146 }
147
148 @Override
149 public void uninstall(ApplicationId appId) {
150 checkNotNull(appId, APP_ID_NULL);
151 try {
152 store.remove(appId);
153 } catch (Exception e) {
154 log.warn("Unable to purge application directory for {}", appId.name());
155 }
156 }
157
158 @Override
159 public void activate(ApplicationId appId) {
160 checkNotNull(appId, APP_ID_NULL);
Changhoon Yoonb856b812015-08-10 03:47:19 +0900161 if (!SecurityUtil.isAppSecured(appId)) {
162 return;
163 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800164 store.activate(appId);
165 }
166
167 @Override
168 public void deactivate(ApplicationId appId) {
169 checkNotNull(appId, APP_ID_NULL);
170 store.deactivate(appId);
171 }
172
173 @Override
174 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
175 checkNotNull(appId, APP_ID_NULL);
176 checkNotNull(permissions, "Permissions cannot be null");
177 store.setPermissions(appId, permissions);
178 }
179
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800180 private class InternalStoreDelegate implements ApplicationStoreDelegate {
181 @Override
182 public void notify(ApplicationEvent event) {
183 ApplicationEvent.Type type = event.type();
184 Application app = event.subject();
185 try {
186 if (type == APP_ACTIVATED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700187 if (installAppFeatures(app)) {
188 log.info("Application {} has been activated", app.id().name());
189 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800190
191 } else if (type == APP_DEACTIVATED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700192 if (uninstallAppFeatures(app)) {
193 log.info("Application {} has been deactivated", app.id().name());
194 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800195
196 } else if (type == APP_INSTALLED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700197 if (installAppArtifacts(app)) {
198 log.info("Application {} has been installed", app.id().name());
199 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800200
201 } else if (type == APP_UNINSTALLED) {
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700202 if (uninstallAppFeatures(app) || uninstallAppArtifacts(app)) {
203 log.info("Application {} has been uninstalled", app.id().name());
204 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800205
206 }
Thomas Vachuska42e8cce2015-07-29 19:25:18 -0700207 post(event);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800208
209 } catch (Exception e) {
210 log.warn("Unable to perform operation on application " + app.id().name(), e);
211 }
212 }
213 }
214
Thomas Vachuska0d6af8d2015-03-23 15:54:47 -0700215 // The following methods are fully synchronized to guard against remote vs.
216 // locally induced feature service interactions.
217
Thomas Vachuska761f0042015-11-11 19:10:17 -0800218 // Installs all feature repositories required by the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700219 private synchronized boolean installAppArtifacts(Application app) throws Exception {
220 if (app.featuresRepo().isPresent() &&
221 featuresService.getRepository(app.featuresRepo().get()) == null) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800222 featuresService.addRepository(app.featuresRepo().get());
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700223 return true;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800224 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700225 return false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800226 }
227
Thomas Vachuska761f0042015-11-11 19:10:17 -0800228 // Uninstalls all the feature repositories required by the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700229 private synchronized boolean uninstallAppArtifacts(Application app) throws Exception {
230 if (app.featuresRepo().isPresent() &&
231 featuresService.getRepository(app.featuresRepo().get()) != null) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800232 featuresService.removeRepository(app.featuresRepo().get());
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700233 return true;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800234 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700235 return false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800236 }
237
Thomas Vachuska761f0042015-11-11 19:10:17 -0800238 // Installs all features that define the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700239 private synchronized boolean installAppFeatures(Application app) throws Exception {
240 boolean changed = false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800241 for (String name : app.features()) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800242 Feature feature = featuresService.getFeature(name);
Thomas Vachuskab56b9172015-11-24 11:24:23 -0800243
244 // If we see an attempt at activation of a non-existent feature
245 // attempt to install the app artifacts first and then retry.
246 // This can be triggered by a race condition between different ONOS
247 // instances "installing" the apps from disk at their own pace.
248 // Perhaps there is a more elegant solution to be explored in the
249 // future.
250 if (feature == null) {
251 installAppArtifacts(app);
252 feature = featuresService.getFeature(name);
253 }
254
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700255 if (feature != null && !featuresService.isInstalled(feature)) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800256 featuresService.installFeature(name);
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700257 changed = true;
Thomas Vachuskab56b9172015-11-24 11:24:23 -0800258 } else if (feature == null) {
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700259 log.warn("Feature {} not found", name);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800260 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800261 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700262 return changed;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800263 }
264
Thomas Vachuska761f0042015-11-11 19:10:17 -0800265 // Uninstalls all features that define the specified app.
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700266 private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
267 boolean changed = false;
Jonathan Hartc32585f2016-06-16 14:01:27 -0700268 deactivateHooks.removeAll(app.id().name()).forEach(hook -> invokeHook(hook, app.id()));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800269 for (String name : app.features()) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800270 Feature feature = featuresService.getFeature(name);
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700271 if (feature != null && featuresService.isInstalled(feature)) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800272 featuresService.uninstallFeature(name);
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700273 changed = true;
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700274 } else if (feature == null) {
275 log.warn("Feature {} not found", name);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800276 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800277 }
Thomas Vachuska9ff88a92015-05-13 13:57:18 -0700278 return changed;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800279 }
280
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800281 // Invokes the specified function, if not null.
Ray Milkeyaef45852016-01-11 17:13:19 -0800282 @java.lang.SuppressWarnings("squid:S1217") // We really do mean to call run()
Thomas Vachuskac65dd712015-11-04 17:19:10 -0800283 private void invokeHook(Runnable hook, ApplicationId appId) {
284 if (hook != null) {
285 try {
286 hook.run();
287 } catch (Exception e) {
288 log.warn("Deactivate hook for application {} encountered an error",
289 appId.name(), e);
290 }
291 }
292 }
293
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800294}