blob: 9f9cb2d980a55ac09fd27a3830792fdce9f6db26 [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 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;
44import org.onosproject.core.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 Jampaniae538ff2016-08-19 12:42:43 -070051import org.onosproject.store.service.AtomicValue;
52import org.onosproject.store.service.AtomicValueEvent;
53import org.onosproject.store.service.AtomicValueEventListener;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070054import org.onosproject.store.service.ConsistentMap;
55import org.onosproject.store.service.MapEvent;
56import org.onosproject.store.service.MapEventListener;
57import org.onosproject.store.service.Serializer;
Madan Jampani01e05fb2015-08-13 13:29:36 -070058import org.onosproject.store.service.StorageException;
Jonathan Hart6ec029a2015-03-24 17:12:35 -070059import org.onosproject.store.service.StorageService;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070060import org.onosproject.store.service.Versioned;
61import org.onosproject.store.service.DistributedPrimitive.Status;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080062import org.slf4j.Logger;
63
64import java.io.ByteArrayInputStream;
Madan Jampani01e05fb2015-08-13 13:29:36 -070065import java.io.IOException;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080066import java.io.InputStream;
Andrea Campanellac8eca242016-04-06 19:34:32 -070067import java.util.List;
68import java.util.Optional;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080069import java.util.Set;
70import java.util.concurrent.CountDownLatch;
Madan Jampani2af244a2015-02-22 13:12:01 -080071import java.util.concurrent.ExecutorService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080072import java.util.concurrent.Executors;
73import java.util.concurrent.ScheduledExecutorService;
Jonathan Hartd6fb0532016-01-21 17:28:20 -080074import java.util.concurrent.TimeUnit;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070075import java.util.concurrent.atomic.AtomicBoolean;
76import java.util.function.Consumer;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070077import java.util.function.Function;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070078import java.util.stream.Collectors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070079
Thomas Vachuska761f0042015-11-11 19:10:17 -080080import static com.google.common.collect.Multimaps.newSetMultimap;
81import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080082import static com.google.common.io.ByteStreams.toByteArray;
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -080083import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080084import static java.util.concurrent.TimeUnit.MILLISECONDS;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080085import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070086import static org.onlab.util.Tools.randomDelay;
Thomas Vachuskad0d58542015-06-03 12:38:44 -070087import static org.onosproject.app.ApplicationEvent.Type.*;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070088import static org.onosproject.store.app.DistributedApplicationStore.InternalState.*;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080089import static org.slf4j.LoggerFactory.getLogger;
90
91/**
Madan Jampani6c02d9e2016-05-24 15:09:47 -070092 * Manages inventory of applications in a distributed data store providing
93 * stronger consistency guarantees.
Thomas Vachuska90b453f2015-01-30 18:57:14 -080094 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -070095@Component(immediate = true)
Thomas Vachuska90b453f2015-01-30 18:57:14 -080096@Service
Madan Jampani6c02d9e2016-05-24 15:09:47 -070097public class DistributedApplicationStore extends ApplicationArchive
Thomas Vachuska90b453f2015-01-30 18:57:14 -080098 implements ApplicationStore {
99
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700100 // FIXME: eliminate the need for this
101 private static final int FIXME_ACTIVATION_DELAY = 500;
102
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800103 private final Logger log = getLogger(getClass());
104
105 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
106
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700107 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700108 private static final int RETRY_DELAY_MS = 2_000;
109
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800110 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800111
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800112 private static final int APP_LOAD_DELAY_MS = 500;
113
Andrea Campanellac8eca242016-04-06 19:34:32 -0700114 private static List<String> pendingApps = Lists.newArrayList();
115
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800116 public enum InternalState {
117 INSTALLED, ACTIVATED, DEACTIVATED
118 }
119
Madan Jampani6b5b7172015-02-23 13:02:26 -0800120 private ScheduledExecutorService executor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800121 private ExecutorService messageHandlingExecutor;
122
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700123 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Madan Jampaniae538ff2016-08-19 12:42:43 -0700124 private AtomicValue<Application> nextAppToActivate;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected ClusterCommunicationService clusterCommunicator;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
130 protected ClusterService clusterService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700133 protected StorageService storageService;
134
135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700136 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800137
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700138 private final InternalAppsListener appsListener = new InternalAppsListener();
Madan Jampaniae538ff2016-08-19 12:42:43 -0700139 private final NextAppToActivateValueListener nextAppToActivateListener = new NextAppToActivateValueListener();
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700140
141 private Consumer<Status> statusChangeListener;
142
Thomas Vachuska761f0042015-11-11 19:10:17 -0800143 // Multimap to track which apps are required by others apps
144 // app -> { required-by, ... }
145 // Apps explicitly activated will be required by the CORE app
146 private final Multimap<ApplicationId, ApplicationId> requiredBy =
147 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
148
149 private ApplicationId coreAppId;
150
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800151 @Activate
152 public void activate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800153 messageHandlingExecutor = Executors.newSingleThreadExecutor(
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -0800154 groupedThreads("onos/store/app", "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700155 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
156 bytes -> new String(bytes, Charsets.UTF_8),
157 name -> {
158 try {
159 return toByteArray(getApplicationInputStream(name));
160 } catch (IOException e) {
161 throw new StorageException(e);
162 }
163 },
164 Function.identity(),
165 messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800166
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700167 apps = storageService.<ApplicationId, InternalApplicationHolder>consistentMapBuilder()
168 .withName("onos-apps")
169 .withRelaxedReadConsistency()
170 .withSerializer(Serializer.using(KryoNamespaces.API,
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700171 InternalApplicationHolder.class,
172 InternalState.class))
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700173 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800174
Madan Jampaniae538ff2016-08-19 12:42:43 -0700175 nextAppToActivate = storageService.<Application>atomicValueBuilder()
176 .withName("onos-apps-activation-order-value")
177 .withSerializer(Serializer.using(KryoNamespaces.API))
178 .build()
179 .asAtomicValue();
180
181 nextAppToActivate.addListener(nextAppToActivateListener);
182
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700183 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
184 statusChangeListener = status -> {
185 if (status == Status.ACTIVE) {
186 executor.execute(this::bootstrapExistingApplications);
187 }
188 };
189 apps.addListener(appsListener, messageHandlingExecutor);
190 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800191 coreAppId = getId(CoreService.CORE_APP_NAME);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800192 log.info("Started");
193 }
194
Thomas Vachuskacf960112015-03-06 22:36:51 -0800195 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700196 * Processes existing applications from the distributed map. This is done to
197 * account for events that this instance may be have missed due to a staggered start.
198 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700199 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700200 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
201
202 }
203
204 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800205 * Loads the application inventory from the disk and activates apps if
206 * they are marked to be active.
207 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800208 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800209 getApplicationNames().forEach(appName -> {
210 Application app = loadFromDisk(appName);
211 if (app != null && isActive(app.id().name())) {
212 activate(app.id(), false);
213 // TODO Load app permissions
214 }
215 });
216 }
217
218 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700219 pendingApps.add(appName);
220
Charles Chane889e2d2015-11-17 12:01:02 -0800221 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
222 try {
223 // Directly return if app already exists
224 ApplicationId appId = getId(appName);
225 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800226 Application application = getApplication(appId);
227 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700228 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800229 return application;
230 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700231 }
Charles Chane889e2d2015-11-17 12:01:02 -0800232
233 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700234
235 Optional<String> loop = appDesc.requiredApps().stream()
236 .filter(app -> pendingApps.contains(app)).findAny();
237 if (loop.isPresent()) {
238 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
239 pendingApps.remove(appName);
240 return null;
241 }
242
Charles Chane889e2d2015-11-17 12:01:02 -0800243 boolean success = appDesc.requiredApps().stream()
244 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700245 pendingApps.remove(appName);
246
Charles Chane889e2d2015-11-17 12:01:02 -0800247 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700248
Charles Chane889e2d2015-11-17 12:01:02 -0800249 } catch (Exception e) {
250 log.warn("Unable to load application {} from disk; retrying", appName);
251 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800252 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800253 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700254 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800255 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800256 }
257
258 @Deactivate
259 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800260 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700261 apps.removeStatusChangeListener(statusChangeListener);
262 apps.removeListener(appsListener);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700263 nextAppToActivate.removeListener(nextAppToActivateListener);
Madan Jampani2af244a2015-02-22 13:12:01 -0800264 messageHandlingExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800265 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800266 log.info("Stopped");
267 }
268
269 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800270 public void setDelegate(ApplicationStoreDelegate delegate) {
271 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700272 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700273 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800274 }
275
276 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800277 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700278 return ImmutableSet.copyOf(apps.values()
279 .stream()
280 .map(Versioned::value)
281 .map(InternalApplicationHolder::app)
282 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800283 }
284
285 @Override
286 public ApplicationId getId(String name) {
287 return idStore.getAppId(name);
288 }
289
290 @Override
291 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700292 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
293 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800294 }
295
296 @Override
297 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700298 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
299 InternalState state = appHolder != null ? appHolder.state() : null;
300 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800301 }
302
303 @Override
304 public Application create(InputStream appDescStream) {
305 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800306 if (hasPrerequisites(appDesc)) {
307 return create(appDesc, true);
308 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700309 // Purge bits off disk if we don't have prerequisites to allow app to be
310 // reinstalled later
311 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800312 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
313 }
314
315 private boolean hasPrerequisites(ApplicationDescription app) {
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700316 return !app.requiredApps().stream().map(this::getId)
Thomas Vachuska761f0042015-11-11 19:10:17 -0800317 .anyMatch(id -> id == null || getApplication(id) == null);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800318 }
319
Thomas Vachuska161baf52015-03-27 16:15:39 -0700320 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800321 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700322 if (updateTime) {
323 updateTime(app.id().name());
324 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700325 InternalApplicationHolder previousApp =
326 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
327 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800328 }
329
330 @Override
331 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700332 uninstallDependentApps(appId);
333 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800334 }
335
Thomas Vachuska761f0042015-11-11 19:10:17 -0800336 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700337 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800338 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700339 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800340 .forEach(a -> remove(a.id()));
341 }
342
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800343 @Override
344 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800345 activate(appId, coreAppId);
346 }
347
348 private void activate(ApplicationId appId, ApplicationId forAppId) {
349 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700350 activate(appId, true);
351 }
352
Thomas Vachuska761f0042015-11-11 19:10:17 -0800353
Thomas Vachuska161baf52015-03-27 16:15:39 -0700354 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700355 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
356 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700357 if (updateTime) {
358 updateTime(appId.name());
359 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700360 activateRequiredApps(vAppHolder.value().app());
361
362 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
363 (k, v) -> new InternalApplicationHolder(
364 v.app(), ACTIVATED, v.permissions()));
Madan Jampaniae538ff2016-08-19 12:42:43 -0700365 nextAppToActivate.set(vAppHolder.value().app());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800366 }
367 }
368
Thomas Vachuska761f0042015-11-11 19:10:17 -0800369 // Activates all apps required by this application.
370 private void activateRequiredApps(Application app) {
371 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
372 }
373
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800374 @Override
375 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700376 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800377 deactivate(appId, coreAppId);
378 }
379
380 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
381 requiredBy.remove(appId, forAppId);
382 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700383 AtomicBoolean stateChanged = new AtomicBoolean(false);
384 apps.computeIf(appId,
385 v -> v != null && v.state() != DEACTIVATED,
386 (k, v) -> {
387 stateChanged.set(true);
388 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
389 });
390 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800391 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700392 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800393 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800394 }
395 }
396
Thomas Vachuska761f0042015-11-11 19:10:17 -0800397 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700398 private void deactivateDependentApps(ApplicationId appId) {
399 apps.values()
400 .stream()
401 .map(Versioned::value)
402 .filter(a -> a.state() == ACTIVATED)
403 .filter(a -> a.app().requiredApps().contains(appId.name()))
404 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800405 }
406
407 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700408 private void deactivateRequiredApps(ApplicationId appId) {
409 getApplication(appId).requiredApps()
410 .stream()
411 .map(this::getId)
412 .map(apps::get)
413 .map(Versioned::value)
414 .filter(a -> a.state() == ACTIVATED)
415 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800416 }
417
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800418 @Override
419 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700420 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
421 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800422 }
423
424 @Override
425 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700426 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
427 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
428 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
429 (k, v) -> {
430 permissionsChanged.set(true);
431 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
432 });
433 if (permissionsChanged.get()) {
434 delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800435 }
436 }
437
Madan Jampaniae538ff2016-08-19 12:42:43 -0700438 private class NextAppToActivateValueListener implements AtomicValueEventListener<Application> {
439
440 @Override
441 public void event(AtomicValueEvent<Application> event) {
442 messageHandlingExecutor.execute(() -> {
443 Application app = event.newValue();
444 String appName = app.id().name();
445 installAppIfNeeded(app);
446 setActive(appName);
447 delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
448 });
449 }
450 }
451
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800452 /**
453 * Listener to application state distributed map changes.
454 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700455 private final class InternalAppsListener
456 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800457 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700458 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700459 if (delegate == null) {
460 return;
461 }
462
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700463 ApplicationId appId = event.key();
464 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
465 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
466 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
467 if (event.type() == MapEvent.Type.UPDATE && newApp.state() == oldApp.state()) {
468 return;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800469 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700470 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
471 } else if (event.type() == MapEvent.Type.REMOVE) {
472 delegate.notify(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
473 purgeApplication(appId.name());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800474 }
475 }
476 }
477
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700478 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700479 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700480 if (state == INSTALLED) {
481 fetchBitsIfNeeded(app);
482 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700483 } else if (state == DEACTIVATED) {
484 clearActive(appId.name());
485 delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
486 }
487 }
488
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800489 /**
490 * Determines if the application bits are available locally.
491 */
492 private boolean appBitsAvailable(Application app) {
493 try {
494 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
495 return appDesc.version().equals(app.version());
496 } catch (ApplicationException e) {
497 return false;
498 }
499 }
500
501 /**
502 * Fetches the bits from the cluster peers if necessary.
503 */
504 private void fetchBitsIfNeeded(Application app) {
505 if (!appBitsAvailable(app)) {
506 fetchBits(app);
507 }
508 }
509
510 /**
511 * Installs the application if necessary from the application peers.
512 */
513 private void installAppIfNeeded(Application app) {
514 if (!appBitsAvailable(app)) {
515 fetchBits(app);
516 delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
517 }
518 }
519
520 /**
521 * Fetches the bits from the cluster peers.
522 */
523 private void fetchBits(Application app) {
524 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800525 CountDownLatch latch = new CountDownLatch(1);
526
527 // FIXME: send message with name & version to make sure we don't get served old bits
528
529 log.info("Downloading bits for application {}", app.id().name());
530 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700531 if (latch.getCount() == 0) {
532 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800533 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700534 if (node.equals(localNode)) {
535 continue;
536 }
537 clusterCommunicator.sendAndReceive(app.id().name(),
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700538 APP_BITS_REQUEST,
539 s -> s.getBytes(Charsets.UTF_8),
540 Function.identity(),
541 node.id())
542 .whenCompleteAsync((bits, error) -> {
543 if (error == null && latch.getCount() > 0) {
544 saveApplication(new ByteArrayInputStream(bits));
545 log.info("Downloaded bits for application {} from node {}",
546 app.id().name(), node.id());
547 latch.countDown();
548 } else if (error != null) {
549 log.warn("Unable to fetch bits for application {} from node {}",
550 app.id().name(), node.id());
551 }
552 }, executor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800553 }
554
555 try {
556 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
557 log.warn("Unable to fetch bits for application {}", app.id().name());
558 }
559 } catch (InterruptedException e) {
560 log.warn("Interrupted while fetching bits for application {}", app.id().name());
561 }
562 }
563
564 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800565 * Produces a registered application from the supplied description.
566 */
567 private Application registerApp(ApplicationDescription appDesc) {
568 ApplicationId appId = idStore.registerApplication(appDesc.name());
Simon Huntafae2f72016-03-04 21:18:23 -0800569 return new DefaultApplication(appId,
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700570 appDesc.version(),
571 appDesc.title(),
572 appDesc.description(),
573 appDesc.origin(),
574 appDesc.category(),
575 appDesc.url(),
576 appDesc.readme(),
577 appDesc.icon(),
578 appDesc.role(),
579 appDesc.permissions(),
580 appDesc.featuresRepo(),
581 appDesc.features(),
582 appDesc.requiredApps());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800583 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700584
585 /**
586 * Internal class for holding app information.
587 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700588 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700589 private final Application app;
590 private final InternalState state;
591 private final Set<Permission> permissions;
592
593 @SuppressWarnings("unused")
594 private InternalApplicationHolder() {
595 app = null;
596 state = null;
597 permissions = null;
598 }
599
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700600 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700601 this.app = Preconditions.checkNotNull(app);
602 this.state = state;
603 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
604 }
605
606 public Application app() {
607 return app;
608 }
609
610 public InternalState state() {
611 return state;
612 }
613
614 public Set<Permission> permissions() {
615 return permissions;
616 }
617
618 @Override
619 public String toString() {
620 return MoreObjects.toStringHelper(getClass())
621 .add("app", app.id())
622 .add("state", state)
623 .toString();
624 }
625 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800626}