blob: 0ff5238943264d1c0e31117e315edc28158674df [file] [log] [blame]
Thomas Vachuska90b453f2015-01-30 18:57:14 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Thomas Vachuska90b453f2015-01-30 18:57:14 -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.store.app;
17
Yuta HIGUCHI80292052015-02-10 23:11:59 -080018import com.google.common.base.Charsets;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070019import com.google.common.base.MoreObjects;
20import com.google.common.base.Preconditions;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080021import com.google.common.collect.ImmutableSet;
Andrea Campanellac8eca242016-04-06 19:34:32 -070022import com.google.common.collect.Lists;
Thomas Vachuska761f0042015-11-11 19:10:17 -080023import com.google.common.collect.Maps;
24import com.google.common.collect.Multimap;
25import com.google.common.collect.Sets;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070026
Thomas Vachuska90b453f2015-01-30 18:57:14 -080027import org.apache.felix.scr.annotations.Activate;
28import org.apache.felix.scr.annotations.Component;
29import org.apache.felix.scr.annotations.Deactivate;
30import org.apache.felix.scr.annotations.Reference;
31import org.apache.felix.scr.annotations.ReferenceCardinality;
32import org.apache.felix.scr.annotations.Service;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080033import org.onosproject.app.ApplicationDescription;
34import org.onosproject.app.ApplicationEvent;
35import org.onosproject.app.ApplicationException;
36import org.onosproject.app.ApplicationState;
37import org.onosproject.app.ApplicationStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -080038import org.onosproject.app.ApplicationStoreDelegate;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080039import org.onosproject.cluster.ClusterService;
40import org.onosproject.cluster.ControllerNode;
41import org.onosproject.common.app.ApplicationArchive;
42import org.onosproject.core.Application;
43import org.onosproject.core.ApplicationId;
sangyun-han6d33e802016-08-05 13:36:33 +090044import org.onosproject.app.ApplicationIdStore;
Thomas Vachuska761f0042015-11-11 19:10:17 -080045import org.onosproject.core.CoreService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080046import org.onosproject.core.DefaultApplication;
Changhoon Yoonb856b812015-08-10 03:47:19 +090047import org.onosproject.security.Permission;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080048import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080049import org.onosproject.store.cluster.messaging.MessageSubject;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080050import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070051import org.onosproject.store.service.ConsistentMap;
52import org.onosproject.store.service.MapEvent;
53import org.onosproject.store.service.MapEventListener;
54import org.onosproject.store.service.Serializer;
Madan Jampani01e05fb2015-08-13 13:29:36 -070055import org.onosproject.store.service.StorageException;
Jonathan Hart6ec029a2015-03-24 17:12:35 -070056import org.onosproject.store.service.StorageService;
Madan Jampanic5a17542016-08-22 10:26:11 -070057import org.onosproject.store.service.Topic;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070058import org.onosproject.store.service.Versioned;
59import org.onosproject.store.service.DistributedPrimitive.Status;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080060import org.slf4j.Logger;
61
62import java.io.ByteArrayInputStream;
Madan Jampani01e05fb2015-08-13 13:29:36 -070063import java.io.IOException;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080064import java.io.InputStream;
Andrea Campanellac8eca242016-04-06 19:34:32 -070065import java.util.List;
66import java.util.Optional;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080067import java.util.Set;
68import java.util.concurrent.CountDownLatch;
Madan Jampani2af244a2015-02-22 13:12:01 -080069import java.util.concurrent.ExecutorService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080070import java.util.concurrent.ScheduledExecutorService;
Jonathan Hartd6fb0532016-01-21 17:28:20 -080071import java.util.concurrent.TimeUnit;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070072import java.util.concurrent.atomic.AtomicBoolean;
73import java.util.function.Consumer;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070074import java.util.function.Function;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070075import java.util.stream.Collectors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070076
Thomas Vachuska761f0042015-11-11 19:10:17 -080077import static com.google.common.collect.Multimaps.newSetMultimap;
78import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080079import static com.google.common.io.ByteStreams.toByteArray;
Thomas Vachuska7cb92392017-10-02 13:17:28 -070080import static java.util.concurrent.Executors.newSingleThreadExecutor;
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -080081import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080082import static java.util.concurrent.TimeUnit.MILLISECONDS;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080083import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070084import static org.onlab.util.Tools.randomDelay;
Thomas Vachuskad0d58542015-06-03 12:38:44 -070085import static org.onosproject.app.ApplicationEvent.Type.*;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070086import static org.onosproject.store.app.DistributedApplicationStore.InternalState.*;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080087import static org.slf4j.LoggerFactory.getLogger;
88
89/**
Madan Jampani6c02d9e2016-05-24 15:09:47 -070090 * Manages inventory of applications in a distributed data store providing
91 * stronger consistency guarantees.
Thomas Vachuska90b453f2015-01-30 18:57:14 -080092 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -070093@Component(immediate = true)
Thomas Vachuska90b453f2015-01-30 18:57:14 -080094@Service
Madan Jampani6c02d9e2016-05-24 15:09:47 -070095public class DistributedApplicationStore extends ApplicationArchive
Thomas Vachuska90b453f2015-01-30 18:57:14 -080096 implements ApplicationStore {
97
98 private final Logger log = getLogger(getClass());
99
100 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
101
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700102 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700103 private static final int RETRY_DELAY_MS = 2_000;
104
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800105 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800106
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800107 private static final int APP_LOAD_DELAY_MS = 500;
108
Andrea Campanellac8eca242016-04-06 19:34:32 -0700109 private static List<String> pendingApps = Lists.newArrayList();
110
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800111 public enum InternalState {
112 INSTALLED, ACTIVATED, DEACTIVATED
113 }
114
Madan Jampani6b5b7172015-02-23 13:02:26 -0800115 private ScheduledExecutorService executor;
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700116 private ExecutorService messageHandlingExecutor, activationExecutor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800117
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700118 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Madan Jampanic5a17542016-08-22 10:26:11 -0700119 private Topic<Application> appActivationTopic;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected ClusterCommunicationService clusterCommunicator;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
125 protected ClusterService clusterService;
126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700128 protected StorageService storageService;
129
130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700131 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800132
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700133 private final InternalAppsListener appsListener = new InternalAppsListener();
Madan Jampanic5a17542016-08-22 10:26:11 -0700134 private final Consumer<Application> appActivator = new AppActivator();
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700135
136 private Consumer<Status> statusChangeListener;
137
Thomas Vachuska761f0042015-11-11 19:10:17 -0800138 // Multimap to track which apps are required by others apps
139 // app -> { required-by, ... }
140 // Apps explicitly activated will be required by the CORE app
141 private final Multimap<ApplicationId, ApplicationId> requiredBy =
142 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
143
144 private ApplicationId coreAppId;
145
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800146 @Activate
147 public void activate() {
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700148 messageHandlingExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100149 "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700150 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100151 bytes -> new String(bytes, Charsets.UTF_8),
152 name -> {
153 try {
154 log.info("Sending bits for application {}", name);
155 return toByteArray(getApplicationInputStream(name));
156 } catch (IOException e) {
157 throw new StorageException(e);
158 }
159 },
160 Function.identity(),
161 messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800162
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700163 apps = storageService.<ApplicationId, InternalApplicationHolder>consistentMapBuilder()
164 .withName("onos-apps")
165 .withRelaxedReadConsistency()
166 .withSerializer(Serializer.using(KryoNamespaces.API,
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100167 InternalApplicationHolder.class,
168 InternalState.class))
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700169 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800170
Madan Jampanic5a17542016-08-22 10:26:11 -0700171 appActivationTopic = storageService.getTopic("onos-apps-activation-topic",
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100172 Serializer.using(KryoNamespaces.API));
Madan Jampaniae538ff2016-08-19 12:42:43 -0700173
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700174 activationExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100175 "app-activation", log));
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700176 appActivationTopic.subscribe(appActivator, activationExecutor);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700177
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700178 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
179 statusChangeListener = status -> {
180 if (status == Status.ACTIVE) {
181 executor.execute(this::bootstrapExistingApplications);
182 }
183 };
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700184 apps.addListener(appsListener, activationExecutor);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700185 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800186 coreAppId = getId(CoreService.CORE_APP_NAME);
Jordan Haltermanffceb122018-01-09 13:02:45 -0800187
188 activateExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800189 log.info("Started");
190 }
191
Thomas Vachuskacf960112015-03-06 22:36:51 -0800192 /**
Jordan Haltermanffceb122018-01-09 13:02:45 -0800193 * Activates applications locally on startup.
194 */
195 private void activateExistingApplications() {
196 getApplicationNames().forEach(appName -> {
197 // Only update the application state if the application has already been installed.
198 ApplicationId appId = getId(appName);
199 if (appId != null) {
200 Application application = getApplication(appId);
201 if (application != null) {
202 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(application.id()));
203 if (appHolder != null && appHolder.state == ACTIVATED && !isActive(appName)) {
204 setActive(appName);
205 updateTime(appName);
206 }
207 }
208 }
209 });
210 }
211
212 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700213 * Processes existing applications from the distributed map. This is done to
214 * account for events that this instance may be have missed due to a staggered start.
215 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700216 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700217 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700218 }
219
220 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800221 * Loads the application inventory from the disk and activates apps if
222 * they are marked to be active.
223 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800224 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800225 getApplicationNames().forEach(appName -> {
226 Application app = loadFromDisk(appName);
227 if (app != null && isActive(app.id().name())) {
228 activate(app.id(), false);
229 // TODO Load app permissions
230 }
231 });
232 }
233
234 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700235 pendingApps.add(appName);
236
Charles Chane889e2d2015-11-17 12:01:02 -0800237 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
238 try {
239 // Directly return if app already exists
240 ApplicationId appId = getId(appName);
241 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800242 Application application = getApplication(appId);
243 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700244 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800245 return application;
246 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700247 }
Charles Chane889e2d2015-11-17 12:01:02 -0800248
249 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700250
251 Optional<String> loop = appDesc.requiredApps().stream()
252 .filter(app -> pendingApps.contains(app)).findAny();
253 if (loop.isPresent()) {
254 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
255 pendingApps.remove(appName);
256 return null;
257 }
258
Charles Chane889e2d2015-11-17 12:01:02 -0800259 boolean success = appDesc.requiredApps().stream()
260 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700261 pendingApps.remove(appName);
262
Charles Chane889e2d2015-11-17 12:01:02 -0800263 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700264
Charles Chane889e2d2015-11-17 12:01:02 -0800265 } catch (Exception e) {
266 log.warn("Unable to load application {} from disk; retrying", appName);
267 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800268 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800269 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700270 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800271 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800272 }
273
274 @Deactivate
275 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800276 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700277 apps.removeStatusChangeListener(statusChangeListener);
278 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700279 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800280 messageHandlingExecutor.shutdown();
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700281 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800282 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800283 log.info("Stopped");
284 }
285
286 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800287 public void setDelegate(ApplicationStoreDelegate delegate) {
288 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700289 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700290 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800291 }
292
293 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800294 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700295 return ImmutableSet.copyOf(apps.values()
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100296 .stream()
297 .map(Versioned::value)
298 .map(InternalApplicationHolder::app)
299 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800300 }
301
302 @Override
303 public ApplicationId getId(String name) {
304 return idStore.getAppId(name);
305 }
306
307 @Override
308 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700309 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
310 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800311 }
312
313 @Override
314 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700315 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
316 InternalState state = appHolder != null ? appHolder.state() : null;
317 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800318 }
319
320 @Override
321 public Application create(InputStream appDescStream) {
322 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800323 if (hasPrerequisites(appDesc)) {
324 return create(appDesc, true);
325 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700326 // Purge bits off disk if we don't have prerequisites to allow app to be
327 // reinstalled later
328 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800329 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
330 }
331
332 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800333 for (String required : app.requiredApps()) {
334 ApplicationId id = getId(required);
335 if (id == null || getApplication(id) == null) {
336 log.error("{} required for {} not available", required, app.name());
337 return false;
338 }
339 }
340 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800341 }
342
Thomas Vachuska161baf52015-03-27 16:15:39 -0700343 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800344 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700345 if (updateTime) {
346 updateTime(app.id().name());
347 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700348 InternalApplicationHolder previousApp =
349 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
350 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800351 }
352
353 @Override
354 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700355 uninstallDependentApps(appId);
356 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800357 }
358
Thomas Vachuska761f0042015-11-11 19:10:17 -0800359 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700360 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800361 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700362 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800363 .forEach(a -> remove(a.id()));
364 }
365
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800366 @Override
367 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800368 activate(appId, coreAppId);
369 }
370
371 private void activate(ApplicationId appId, ApplicationId forAppId) {
372 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700373 activate(appId, true);
374 }
375
Thomas Vachuska761f0042015-11-11 19:10:17 -0800376
Thomas Vachuska161baf52015-03-27 16:15:39 -0700377 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700378 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
379 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700380 if (updateTime) {
381 updateTime(appId.name());
382 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700383 activateRequiredApps(vAppHolder.value().app());
384
385 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
386 (k, v) -> new InternalApplicationHolder(
387 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700388 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska6b826f42017-08-29 15:21:57 -0700389 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800390 }
391 }
392
Thomas Vachuska761f0042015-11-11 19:10:17 -0800393 // Activates all apps required by this application.
394 private void activateRequiredApps(Application app) {
395 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
396 }
397
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800398 @Override
399 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700400 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800401 deactivate(appId, coreAppId);
402 }
403
404 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
405 requiredBy.remove(appId, forAppId);
406 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700407 AtomicBoolean stateChanged = new AtomicBoolean(false);
408 apps.computeIf(appId,
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100409 v -> v != null && v.state() != DEACTIVATED,
410 (k, v) -> {
411 stateChanged.set(true);
412 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
413 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700414 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800415 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700416 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800417 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800418 }
419 }
420
Thomas Vachuska761f0042015-11-11 19:10:17 -0800421 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700422 private void deactivateDependentApps(ApplicationId appId) {
423 apps.values()
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100424 .stream()
425 .map(Versioned::value)
426 .filter(a -> a.state() == ACTIVATED)
427 .filter(a -> a.app().requiredApps().contains(appId.name()))
428 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800429 }
430
431 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700432 private void deactivateRequiredApps(ApplicationId appId) {
433 getApplication(appId).requiredApps()
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100434 .stream()
435 .map(this::getId)
436 .map(apps::get)
437 .map(Versioned::value)
438 .filter(a -> a.state() == ACTIVATED)
439 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800440 }
441
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800442 @Override
443 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700444 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
445 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800446 }
447
448 @Override
449 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700450 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
451 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100452 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
453 (k, v) -> {
454 permissionsChanged.set(true);
455 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
456 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700457 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700458 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800459 }
460 }
461
Madan Jampanic5a17542016-08-22 10:26:11 -0700462 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700463 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700464 public void accept(Application app) {
Thomas Vachuska6b826f42017-08-29 15:21:57 -0700465 if (app != null) { // FIXME: Once ONOS-6977 is fixed
466 String appName = app.id().name();
467 installAppIfNeeded(app);
468 setActive(appName);
469 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
470 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700471 }
472 }
473
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800474 /**
475 * Listener to application state distributed map changes.
476 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700477 private final class InternalAppsListener
478 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800479 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700480 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700481 if (delegate == null) {
482 return;
483 }
484
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700485 ApplicationId appId = event.key();
486 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
487 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100488 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
489 newApp.state() == oldApp.state())) {
490 log.warn("Can't update the application {}", event.key());
Ray Milkeyfd4f8d32018-01-17 15:24:52 -0800491 return;
492 }
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100493 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700494 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100495 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700496 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700497 purgeApplication(appId.name());
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100498 } else {
499 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800500 }
501 }
502 }
503
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700504 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700505 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700506 if (state == INSTALLED) {
507 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700508 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700509 } else if (state == DEACTIVATED) {
510 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700511 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700512 }
513 }
514
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800515 /**
516 * Determines if the application bits are available locally.
517 */
518 private boolean appBitsAvailable(Application app) {
519 try {
520 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
521 return appDesc.version().equals(app.version());
522 } catch (ApplicationException e) {
523 return false;
524 }
525 }
526
527 /**
528 * Fetches the bits from the cluster peers if necessary.
529 */
530 private void fetchBitsIfNeeded(Application app) {
531 if (!appBitsAvailable(app)) {
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700532 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800533 }
534 }
535
536 /**
537 * Installs the application if necessary from the application peers.
538 */
539 private void installAppIfNeeded(Application app) {
540 if (!appBitsAvailable(app)) {
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700541 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800542 }
543 }
544
545 /**
546 * Fetches the bits from the cluster peers.
547 */
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700548 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800549 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800550 CountDownLatch latch = new CountDownLatch(1);
551
552 // FIXME: send message with name & version to make sure we don't get served old bits
553
554 log.info("Downloading bits for application {}", app.id().name());
555 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700556 if (latch.getCount() == 0) {
557 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800558 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700559 if (node.equals(localNode)) {
560 continue;
561 }
562 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100563 APP_BITS_REQUEST,
564 s -> s.getBytes(Charsets.UTF_8),
565 Function.identity(),
566 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700567 .whenCompleteAsync((bits, error) -> {
568 if (error == null && latch.getCount() > 0) {
569 saveApplication(new ByteArrayInputStream(bits));
570 log.info("Downloaded bits for application {} from node {}",
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100571 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700572 latch.countDown();
Thomas Vachuska7cb92392017-10-02 13:17:28 -0700573 if (delegateInstallation) {
574 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
575 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700576 } else if (error != null) {
577 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanella6bf308c2018-01-26 12:45:24 +0100578 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700579 }
580 }, executor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800581 }
582
583 try {
584 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
585 log.warn("Unable to fetch bits for application {}", app.id().name());
586 }
587 } catch (InterruptedException e) {
588 log.warn("Interrupted while fetching bits for application {}", app.id().name());
589 }
590 }
591
592 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800593 * Produces a registered application from the supplied description.
594 */
595 private Application registerApp(ApplicationDescription appDesc) {
596 ApplicationId appId = idStore.registerApplication(appDesc.name());
Simon Huntafae2f72016-03-04 21:18:23 -0800597 return new DefaultApplication(appId,
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700598 appDesc.version(),
599 appDesc.title(),
600 appDesc.description(),
601 appDesc.origin(),
602 appDesc.category(),
603 appDesc.url(),
604 appDesc.readme(),
605 appDesc.icon(),
606 appDesc.role(),
607 appDesc.permissions(),
608 appDesc.featuresRepo(),
609 appDesc.features(),
610 appDesc.requiredApps());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800611 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700612
613 /**
614 * Internal class for holding app information.
615 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700616 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700617 private final Application app;
618 private final InternalState state;
619 private final Set<Permission> permissions;
620
621 @SuppressWarnings("unused")
622 private InternalApplicationHolder() {
623 app = null;
624 state = null;
625 permissions = null;
626 }
627
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700628 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700629 this.app = Preconditions.checkNotNull(app);
630 this.state = state;
631 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
632 }
633
634 public Application app() {
635 return app;
636 }
637
638 public InternalState state() {
639 return state;
640 }
641
642 public Set<Permission> permissions() {
643 return permissions;
644 }
645
646 @Override
647 public String toString() {
648 return MoreObjects.toStringHelper(getClass())
649 .add("app", app.id())
650 .add("state", state)
651 .toString();
652 }
653 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800654}