blob: a04381495621df4b5b299b32b2ea8ddb5ce76721 [file] [log] [blame]
Thomas Vachuska90b453f2015-01-30 18:57:14 -08001/*
Madan Jampani6c02d9e2016-05-24 15:09:47 -07002 * Copyright 2016-present Open Networking Laboratory
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 Vachuskabddbb252016-08-08 14:25:05 -070033import org.onlab.util.Tools;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080034import org.onosproject.app.ApplicationDescription;
35import org.onosproject.app.ApplicationEvent;
36import org.onosproject.app.ApplicationException;
37import org.onosproject.app.ApplicationState;
38import org.onosproject.app.ApplicationStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -080039import org.onosproject.app.ApplicationStoreDelegate;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080040import org.onosproject.cluster.ClusterService;
41import org.onosproject.cluster.ControllerNode;
42import org.onosproject.common.app.ApplicationArchive;
43import org.onosproject.core.Application;
44import org.onosproject.core.ApplicationId;
45import org.onosproject.core.ApplicationIdStore;
Thomas Vachuska761f0042015-11-11 19:10:17 -080046import org.onosproject.core.CoreService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080047import org.onosproject.core.DefaultApplication;
Changhoon Yoonb856b812015-08-10 03:47:19 +090048import org.onosproject.security.Permission;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080049import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080050import org.onosproject.store.cluster.messaging.MessageSubject;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080051import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070052import org.onosproject.store.service.ConsistentMap;
53import org.onosproject.store.service.MapEvent;
54import org.onosproject.store.service.MapEventListener;
55import org.onosproject.store.service.Serializer;
Madan Jampani01e05fb2015-08-13 13:29:36 -070056import org.onosproject.store.service.StorageException;
Jonathan Hart6ec029a2015-03-24 17:12:35 -070057import org.onosproject.store.service.StorageService;
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.Executors;
71import java.util.concurrent.ScheduledExecutorService;
Jonathan Hartd6fb0532016-01-21 17:28:20 -080072import java.util.concurrent.TimeUnit;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070073import java.util.concurrent.atomic.AtomicBoolean;
74import java.util.function.Consumer;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070075import java.util.function.Function;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070076import java.util.stream.Collectors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070077
Thomas Vachuska761f0042015-11-11 19:10:17 -080078import static com.google.common.collect.Multimaps.newSetMultimap;
79import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080080import static com.google.common.io.ByteStreams.toByteArray;
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
Thomas Vachuskabddbb252016-08-08 14:25:05 -070098 // FIXME: eliminate the need for this
99 private static final int FIXME_ACTIVATION_DELAY = 500;
100
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800101 private final Logger log = getLogger(getClass());
102
103 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
104
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700105 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700106 private static final int RETRY_DELAY_MS = 2_000;
107
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800108 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800109
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800110 private static final int APP_LOAD_DELAY_MS = 500;
111
Andrea Campanellac8eca242016-04-06 19:34:32 -0700112 private static List<String> pendingApps = Lists.newArrayList();
113
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800114 public enum InternalState {
115 INSTALLED, ACTIVATED, DEACTIVATED
116 }
117
Madan Jampani6b5b7172015-02-23 13:02:26 -0800118 private ScheduledExecutorService executor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800119 private ExecutorService messageHandlingExecutor;
120
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700121 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 protected ClusterCommunicationService clusterCommunicator;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected ClusterService clusterService;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700130 protected StorageService storageService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700133 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800134
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700135 private final InternalAppsListener appsListener = new InternalAppsListener();
136
137 private Consumer<Status> statusChangeListener;
138
Thomas Vachuska761f0042015-11-11 19:10:17 -0800139 // Multimap to track which apps are required by others apps
140 // app -> { required-by, ... }
141 // Apps explicitly activated will be required by the CORE app
142 private final Multimap<ApplicationId, ApplicationId> requiredBy =
143 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
144
145 private ApplicationId coreAppId;
146
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800147 @Activate
148 public void activate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800149 messageHandlingExecutor = Executors.newSingleThreadExecutor(
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -0800150 groupedThreads("onos/store/app", "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700151 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
152 bytes -> new String(bytes, Charsets.UTF_8),
153 name -> {
154 try {
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,
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700167 InternalApplicationHolder.class,
168 InternalState.class))
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700169 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800170
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700171 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
172 statusChangeListener = status -> {
173 if (status == Status.ACTIVE) {
174 executor.execute(this::bootstrapExistingApplications);
175 }
176 };
177 apps.addListener(appsListener, messageHandlingExecutor);
178 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800179 coreAppId = getId(CoreService.CORE_APP_NAME);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800180 log.info("Started");
181 }
182
Thomas Vachuskacf960112015-03-06 22:36:51 -0800183 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700184 * Processes existing applications from the distributed map. This is done to
185 * account for events that this instance may be have missed due to a staggered start.
186 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700187 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700188 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
189
190 }
191
192 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800193 * Loads the application inventory from the disk and activates apps if
194 * they are marked to be active.
195 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800196 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800197 getApplicationNames().forEach(appName -> {
198 Application app = loadFromDisk(appName);
199 if (app != null && isActive(app.id().name())) {
200 activate(app.id(), false);
201 // TODO Load app permissions
202 }
203 });
204 }
205
206 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700207 pendingApps.add(appName);
208
Charles Chane889e2d2015-11-17 12:01:02 -0800209 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
210 try {
211 // Directly return if app already exists
212 ApplicationId appId = getId(appName);
213 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800214 Application application = getApplication(appId);
215 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700216 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800217 return application;
218 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700219 }
Charles Chane889e2d2015-11-17 12:01:02 -0800220
221 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700222
223 Optional<String> loop = appDesc.requiredApps().stream()
224 .filter(app -> pendingApps.contains(app)).findAny();
225 if (loop.isPresent()) {
226 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
227 pendingApps.remove(appName);
228 return null;
229 }
230
Charles Chane889e2d2015-11-17 12:01:02 -0800231 boolean success = appDesc.requiredApps().stream()
232 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700233 pendingApps.remove(appName);
234
Charles Chane889e2d2015-11-17 12:01:02 -0800235 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700236
Charles Chane889e2d2015-11-17 12:01:02 -0800237 } catch (Exception e) {
238 log.warn("Unable to load application {} from disk; retrying", appName);
239 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800240 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800241 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700242 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800243 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800244 }
245
246 @Deactivate
247 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800248 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700249 apps.removeStatusChangeListener(statusChangeListener);
250 apps.removeListener(appsListener);
Madan Jampani2af244a2015-02-22 13:12:01 -0800251 messageHandlingExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800252 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800253 log.info("Stopped");
254 }
255
256 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800257 public void setDelegate(ApplicationStoreDelegate delegate) {
258 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700259 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700260 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800261 }
262
263 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800264 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700265 return ImmutableSet.copyOf(apps.values()
266 .stream()
267 .map(Versioned::value)
268 .map(InternalApplicationHolder::app)
269 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800270 }
271
272 @Override
273 public ApplicationId getId(String name) {
274 return idStore.getAppId(name);
275 }
276
277 @Override
278 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700279 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
280 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800281 }
282
283 @Override
284 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700285 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
286 InternalState state = appHolder != null ? appHolder.state() : null;
287 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800288 }
289
290 @Override
291 public Application create(InputStream appDescStream) {
292 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800293 if (hasPrerequisites(appDesc)) {
294 return create(appDesc, true);
295 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700296 // Purge bits off disk if we don't have prerequisites to allow app to be
297 // reinstalled later
298 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800299 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
300 }
301
302 private boolean hasPrerequisites(ApplicationDescription app) {
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700303 return !app.requiredApps().stream().map(this::getId)
Thomas Vachuska761f0042015-11-11 19:10:17 -0800304 .anyMatch(id -> id == null || getApplication(id) == null);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800305 }
306
Thomas Vachuska161baf52015-03-27 16:15:39 -0700307 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800308 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700309 if (updateTime) {
310 updateTime(app.id().name());
311 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700312 InternalApplicationHolder previousApp =
313 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
314 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800315 }
316
317 @Override
318 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700319 uninstallDependentApps(appId);
320 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800321 }
322
Thomas Vachuska761f0042015-11-11 19:10:17 -0800323 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700324 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800325 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700326 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800327 .forEach(a -> remove(a.id()));
328 }
329
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800330 @Override
331 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800332 activate(appId, coreAppId);
333 }
334
335 private void activate(ApplicationId appId, ApplicationId forAppId) {
336 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700337 activate(appId, true);
338 }
339
Thomas Vachuska761f0042015-11-11 19:10:17 -0800340
Thomas Vachuska161baf52015-03-27 16:15:39 -0700341 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700342 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
343 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700344 if (updateTime) {
345 updateTime(appId.name());
346 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700347 activateRequiredApps(vAppHolder.value().app());
348
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700349 // FIXME: Take a breath before the post-order operation to allow required app
350 // activation events to fully propagate. There appears to be an out-of-order
351 // event delivery issue that needs to be fixed.
352 Tools.delay(FIXME_ACTIVATION_DELAY);
353
Jonathan Hartbd85f782016-06-20 16:13:37 -0700354 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
355 (k, v) -> new InternalApplicationHolder(
356 v.app(), ACTIVATED, v.permissions()));
357
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800358 }
359 }
360
Thomas Vachuska761f0042015-11-11 19:10:17 -0800361 // Activates all apps required by this application.
362 private void activateRequiredApps(Application app) {
363 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
364 }
365
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800366 @Override
367 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700368 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800369 deactivate(appId, coreAppId);
370 }
371
372 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
373 requiredBy.remove(appId, forAppId);
374 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700375 AtomicBoolean stateChanged = new AtomicBoolean(false);
376 apps.computeIf(appId,
377 v -> v != null && v.state() != DEACTIVATED,
378 (k, v) -> {
379 stateChanged.set(true);
380 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
381 });
382 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800383 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700384 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800385 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800386 }
387 }
388
Thomas Vachuska761f0042015-11-11 19:10:17 -0800389 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700390 private void deactivateDependentApps(ApplicationId appId) {
391 apps.values()
392 .stream()
393 .map(Versioned::value)
394 .filter(a -> a.state() == ACTIVATED)
395 .filter(a -> a.app().requiredApps().contains(appId.name()))
396 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800397 }
398
399 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700400 private void deactivateRequiredApps(ApplicationId appId) {
401 getApplication(appId).requiredApps()
402 .stream()
403 .map(this::getId)
404 .map(apps::get)
405 .map(Versioned::value)
406 .filter(a -> a.state() == ACTIVATED)
407 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800408 }
409
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800410 @Override
411 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700412 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
413 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800414 }
415
416 @Override
417 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700418 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
419 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
420 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
421 (k, v) -> {
422 permissionsChanged.set(true);
423 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
424 });
425 if (permissionsChanged.get()) {
426 delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800427 }
428 }
429
430 /**
431 * Listener to application state distributed map changes.
432 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700433 private final class InternalAppsListener
434 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800435 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700436 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700437 if (delegate == null) {
438 return;
439 }
440
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700441 ApplicationId appId = event.key();
442 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
443 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
444 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
445 if (event.type() == MapEvent.Type.UPDATE && newApp.state() == oldApp.state()) {
446 return;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800447 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700448 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
449 } else if (event.type() == MapEvent.Type.REMOVE) {
450 delegate.notify(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
451 purgeApplication(appId.name());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800452 }
453 }
454 }
455
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700456 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
457 if (state == INSTALLED) {
458 fetchBitsIfNeeded(app);
459 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
460 } else if (state == ACTIVATED) {
461 installAppIfNeeded(app);
462 setActive(appId.name());
463 delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
464 } else if (state == DEACTIVATED) {
465 clearActive(appId.name());
466 delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
467 }
468 }
469
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800470 /**
471 * Determines if the application bits are available locally.
472 */
473 private boolean appBitsAvailable(Application app) {
474 try {
475 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
476 return appDesc.version().equals(app.version());
477 } catch (ApplicationException e) {
478 return false;
479 }
480 }
481
482 /**
483 * Fetches the bits from the cluster peers if necessary.
484 */
485 private void fetchBitsIfNeeded(Application app) {
486 if (!appBitsAvailable(app)) {
487 fetchBits(app);
488 }
489 }
490
491 /**
492 * Installs the application if necessary from the application peers.
493 */
494 private void installAppIfNeeded(Application app) {
495 if (!appBitsAvailable(app)) {
496 fetchBits(app);
497 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
498 }
499 }
500
501 /**
502 * Fetches the bits from the cluster peers.
503 */
504 private void fetchBits(Application app) {
505 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800506 CountDownLatch latch = new CountDownLatch(1);
507
508 // FIXME: send message with name & version to make sure we don't get served old bits
509
510 log.info("Downloading bits for application {}", app.id().name());
511 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700512 if (latch.getCount() == 0) {
513 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800514 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700515 if (node.equals(localNode)) {
516 continue;
517 }
518 clusterCommunicator.sendAndReceive(app.id().name(),
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700519 APP_BITS_REQUEST,
520 s -> s.getBytes(Charsets.UTF_8),
521 Function.identity(),
522 node.id())
523 .whenCompleteAsync((bits, error) -> {
524 if (error == null && latch.getCount() > 0) {
525 saveApplication(new ByteArrayInputStream(bits));
526 log.info("Downloaded bits for application {} from node {}",
527 app.id().name(), node.id());
528 latch.countDown();
529 } else if (error != null) {
530 log.warn("Unable to fetch bits for application {} from node {}",
531 app.id().name(), node.id());
532 }
533 }, executor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800534 }
535
536 try {
537 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
538 log.warn("Unable to fetch bits for application {}", app.id().name());
539 }
540 } catch (InterruptedException e) {
541 log.warn("Interrupted while fetching bits for application {}", app.id().name());
542 }
543 }
544
545 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800546 * Produces a registered application from the supplied description.
547 */
548 private Application registerApp(ApplicationDescription appDesc) {
549 ApplicationId appId = idStore.registerApplication(appDesc.name());
Simon Huntafae2f72016-03-04 21:18:23 -0800550 return new DefaultApplication(appId,
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700551 appDesc.version(),
552 appDesc.title(),
553 appDesc.description(),
554 appDesc.origin(),
555 appDesc.category(),
556 appDesc.url(),
557 appDesc.readme(),
558 appDesc.icon(),
559 appDesc.role(),
560 appDesc.permissions(),
561 appDesc.featuresRepo(),
562 appDesc.features(),
563 appDesc.requiredApps());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800564 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700565
566 /**
567 * Internal class for holding app information.
568 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700569 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700570 private final Application app;
571 private final InternalState state;
572 private final Set<Permission> permissions;
573
574 @SuppressWarnings("unused")
575 private InternalApplicationHolder() {
576 app = null;
577 state = null;
578 permissions = null;
579 }
580
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700581 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700582 this.app = Preconditions.checkNotNull(app);
583 this.state = state;
584 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
585 }
586
587 public Application app() {
588 return app;
589 }
590
591 public InternalState state() {
592 return state;
593 }
594
595 public Set<Permission> permissions() {
596 return permissions;
597 }
598
599 @Override
600 public String toString() {
601 return MoreObjects.toStringHelper(getClass())
602 .add("app", app.id())
603 .add("state", state)
604 .toString();
605 }
606 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800607}