blob: 564c3beba26709101f70096378fcf1207418f608 [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;
Jordan Haltermanfedad5b2017-10-09 23:16:27 -070060import org.onosproject.upgrade.UpgradeService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080061import org.slf4j.Logger;
62
63import java.io.ByteArrayInputStream;
Madan Jampani01e05fb2015-08-13 13:29:36 -070064import java.io.IOException;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080065import java.io.InputStream;
Andrea Campanellac8eca242016-04-06 19:34:32 -070066import java.util.List;
67import java.util.Optional;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080068import java.util.Set;
69import java.util.concurrent.CountDownLatch;
Madan Jampani2af244a2015-02-22 13:12:01 -080070import java.util.concurrent.ExecutorService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080071import 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;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -070081import static java.util.concurrent.Executors.newSingleThreadExecutor;
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -080082import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080083import static java.util.concurrent.TimeUnit.MILLISECONDS;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080084import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070085import static org.onlab.util.Tools.randomDelay;
Thomas Vachuskad0d58542015-06-03 12:38:44 -070086import static org.onosproject.app.ApplicationEvent.Type.*;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070087import static org.onosproject.store.app.DistributedApplicationStore.InternalState.*;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080088import static org.slf4j.LoggerFactory.getLogger;
89
90/**
Madan Jampani6c02d9e2016-05-24 15:09:47 -070091 * Manages inventory of applications in a distributed data store providing
92 * stronger consistency guarantees.
Thomas Vachuska90b453f2015-01-30 18:57:14 -080093 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -070094@Component(immediate = true)
Thomas Vachuska90b453f2015-01-30 18:57:14 -080095@Service
Madan Jampani6c02d9e2016-05-24 15:09:47 -070096public class DistributedApplicationStore extends ApplicationArchive
Thomas Vachuska90b453f2015-01-30 18:57:14 -080097 implements ApplicationStore {
98
99 private final Logger log = getLogger(getClass());
100
101 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
102
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700103 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700104 private static final int RETRY_DELAY_MS = 2_000;
105
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800106 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800107
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800108 private static final int APP_LOAD_DELAY_MS = 500;
109
Andrea Campanellac8eca242016-04-06 19:34:32 -0700110 private static List<String> pendingApps = Lists.newArrayList();
111
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800112 public enum InternalState {
113 INSTALLED, ACTIVATED, DEACTIVATED
114 }
115
Madan Jampani6b5b7172015-02-23 13:02:26 -0800116 private ScheduledExecutorService executor;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700117 private ExecutorService messageHandlingExecutor, activationExecutor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800118
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700119 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Madan Jampanic5a17542016-08-22 10:26:11 -0700120 private Topic<Application> appActivationTopic;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected ClusterCommunicationService clusterCommunicator;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected ClusterService clusterService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700129 protected StorageService storageService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700132 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800133
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected UpgradeService upgradeService;
136
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700137 private final InternalAppsListener appsListener = new InternalAppsListener();
Madan Jampanic5a17542016-08-22 10:26:11 -0700138 private final Consumer<Application> appActivator = new AppActivator();
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700139
140 private Consumer<Status> statusChangeListener;
141
Thomas Vachuska761f0042015-11-11 19:10:17 -0800142 // Multimap to track which apps are required by others apps
143 // app -> { required-by, ... }
144 // Apps explicitly activated will be required by the CORE app
145 private final Multimap<ApplicationId, ApplicationId> requiredBy =
146 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
147
148 private ApplicationId coreAppId;
149
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800150 @Activate
151 public void activate() {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700152 messageHandlingExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100153 "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700154 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100155 bytes -> new String(bytes, Charsets.UTF_8),
156 name -> {
157 try {
158 log.info("Sending bits for application {}", name);
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,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100171 InternalApplicationHolder.class,
172 InternalState.class))
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700173 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800174
Madan Jampanic5a17542016-08-22 10:26:11 -0700175 appActivationTopic = storageService.getTopic("onos-apps-activation-topic",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100176 Serializer.using(KryoNamespaces.API));
Madan Jampaniae538ff2016-08-19 12:42:43 -0700177
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700178 activationExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100179 "app-activation", log));
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700180 appActivationTopic.subscribe(appActivator, activationExecutor);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700181
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700182 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
183 statusChangeListener = status -> {
184 if (status == Status.ACTIVE) {
185 executor.execute(this::bootstrapExistingApplications);
186 }
187 };
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700188 apps.addListener(appsListener, activationExecutor);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700189 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800190 coreAppId = getId(CoreService.CORE_APP_NAME);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700191
192 upgradeExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800193 log.info("Started");
194 }
195
Thomas Vachuskacf960112015-03-06 22:36:51 -0800196 /**
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700197 * Upgrades application versions for existing applications that are stored on disk after an upgrade.
198 */
199 private void upgradeExistingApplications() {
Jordan Haltermanf5f40942018-01-09 13:02:45 -0800200 if (upgradeService.isUpgrading() && (upgradeService.isLocalActive() || upgradeService.isLocalUpgraded())) {
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700201 getApplicationNames().forEach(appName -> {
202 // Only update the application version if the application has already been installed.
203 ApplicationId appId = getId(appName);
204 if (appId != null) {
205 Application application = getApplication(appId);
206 if (application != null) {
207 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(application.id()));
208
209 // Load the application description from disk. If the version doesn't match the persisted
210 // version, update the stored application with the new version.
211 ApplicationDescription appDesc = getApplicationDescription(appName);
212 if (!appDesc.version().equals(application.version())) {
213 Application newApplication = DefaultApplication.builder(application)
214 .withVersion(appDesc.version())
215 .build();
216 InternalApplicationHolder newHolder = new InternalApplicationHolder(
217 newApplication, appHolder.state, appHolder.permissions);
218 apps.put(newApplication.id(), newHolder);
219 }
220
221 // If the application was activated in the previous version, set the local state to active.
222 if (appHolder.state == ACTIVATED) {
223 setActive(appName);
224 updateTime(appName);
225 }
226 }
227 }
228 });
229 }
230 }
231
232 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700233 * Processes existing applications from the distributed map. This is done to
234 * account for events that this instance may be have missed due to a staggered start.
235 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700236 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700237 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700238 }
239
240 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800241 * Loads the application inventory from the disk and activates apps if
242 * they are marked to be active.
243 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800244 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800245 getApplicationNames().forEach(appName -> {
246 Application app = loadFromDisk(appName);
247 if (app != null && isActive(app.id().name())) {
248 activate(app.id(), false);
249 // TODO Load app permissions
250 }
251 });
252 }
253
254 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700255 pendingApps.add(appName);
256
Charles Chane889e2d2015-11-17 12:01:02 -0800257 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
258 try {
259 // Directly return if app already exists
260 ApplicationId appId = getId(appName);
261 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800262 Application application = getApplication(appId);
263 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700264 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800265 return application;
266 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700267 }
Charles Chane889e2d2015-11-17 12:01:02 -0800268
269 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700270
271 Optional<String> loop = appDesc.requiredApps().stream()
272 .filter(app -> pendingApps.contains(app)).findAny();
273 if (loop.isPresent()) {
274 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
275 pendingApps.remove(appName);
276 return null;
277 }
278
Charles Chane889e2d2015-11-17 12:01:02 -0800279 boolean success = appDesc.requiredApps().stream()
280 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700281 pendingApps.remove(appName);
282
Charles Chane889e2d2015-11-17 12:01:02 -0800283 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700284
Charles Chane889e2d2015-11-17 12:01:02 -0800285 } catch (Exception e) {
286 log.warn("Unable to load application {} from disk; retrying", appName);
287 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800288 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800289 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700290 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800291 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800292 }
293
294 @Deactivate
295 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800296 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700297 apps.removeStatusChangeListener(statusChangeListener);
298 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700299 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800300 messageHandlingExecutor.shutdown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700301 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800302 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800303 log.info("Stopped");
304 }
305
306 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800307 public void setDelegate(ApplicationStoreDelegate delegate) {
308 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700309 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700310 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800311 }
312
313 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800314 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700315 return ImmutableSet.copyOf(apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100316 .stream()
317 .map(Versioned::value)
318 .map(InternalApplicationHolder::app)
319 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800320 }
321
322 @Override
323 public ApplicationId getId(String name) {
324 return idStore.getAppId(name);
325 }
326
327 @Override
328 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700329 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
330 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800331 }
332
333 @Override
334 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700335 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
336 InternalState state = appHolder != null ? appHolder.state() : null;
337 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800338 }
339
340 @Override
341 public Application create(InputStream appDescStream) {
342 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800343 if (hasPrerequisites(appDesc)) {
344 return create(appDesc, true);
345 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700346 // Purge bits off disk if we don't have prerequisites to allow app to be
347 // reinstalled later
348 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800349 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
350 }
351
352 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800353 for (String required : app.requiredApps()) {
354 ApplicationId id = getId(required);
355 if (id == null || getApplication(id) == null) {
356 log.error("{} required for {} not available", required, app.name());
357 return false;
358 }
359 }
360 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800361 }
362
Thomas Vachuska161baf52015-03-27 16:15:39 -0700363 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800364 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700365 if (updateTime) {
366 updateTime(app.id().name());
367 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700368 InternalApplicationHolder previousApp =
369 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
370 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800371 }
372
373 @Override
374 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700375 uninstallDependentApps(appId);
376 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800377 }
378
Thomas Vachuska761f0042015-11-11 19:10:17 -0800379 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700380 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800381 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700382 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800383 .forEach(a -> remove(a.id()));
384 }
385
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800386 @Override
387 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800388 activate(appId, coreAppId);
389 }
390
391 private void activate(ApplicationId appId, ApplicationId forAppId) {
392 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700393 activate(appId, true);
394 }
395
396 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700397 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
398 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700399 if (updateTime) {
400 updateTime(appId.name());
401 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700402 activateRequiredApps(vAppHolder.value().app());
403
404 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
405 (k, v) -> new InternalApplicationHolder(
406 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700407 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska484ae542017-08-29 15:21:57 -0700408 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800409 }
410 }
411
Thomas Vachuska761f0042015-11-11 19:10:17 -0800412 // Activates all apps required by this application.
413 private void activateRequiredApps(Application app) {
414 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
415 }
416
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800417 @Override
418 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700419 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800420 deactivate(appId, coreAppId);
421 }
422
423 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
424 requiredBy.remove(appId, forAppId);
425 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700426 AtomicBoolean stateChanged = new AtomicBoolean(false);
427 apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100428 v -> v != null && v.state() != DEACTIVATED,
429 (k, v) -> {
430 stateChanged.set(true);
431 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
432 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700433 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800434 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700435 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800436 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800437 }
438 }
439
Thomas Vachuska761f0042015-11-11 19:10:17 -0800440 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700441 private void deactivateDependentApps(ApplicationId appId) {
442 apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100443 .stream()
444 .map(Versioned::value)
445 .filter(a -> a.state() == ACTIVATED)
446 .filter(a -> a.app().requiredApps().contains(appId.name()))
447 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800448 }
449
450 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700451 private void deactivateRequiredApps(ApplicationId appId) {
452 getApplication(appId).requiredApps()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100453 .stream()
454 .map(this::getId)
455 .map(apps::get)
456 .map(Versioned::value)
457 .filter(a -> a.state() == ACTIVATED)
458 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800459 }
460
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800461 @Override
462 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700463 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
464 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800465 }
466
467 @Override
468 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700469 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
470 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100471 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
472 (k, v) -> {
473 permissionsChanged.set(true);
474 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
475 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700476 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700477 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800478 }
479 }
480
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700481 @Override
482 public InputStream getApplicationArchive(ApplicationId appId) {
483 return getApplicationInputStream(appId.name());
484 }
485
Madan Jampanic5a17542016-08-22 10:26:11 -0700486 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700487 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700488 public void accept(Application app) {
Thomas Vachuska484ae542017-08-29 15:21:57 -0700489 if (app != null) { // FIXME: Once ONOS-6977 is fixed
490 String appName = app.id().name();
491 installAppIfNeeded(app);
492 setActive(appName);
493 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
494 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700495 }
496 }
497
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800498 /**
499 * Listener to application state distributed map changes.
500 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700501 private final class InternalAppsListener
502 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800503 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700504 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700505 if (delegate == null) {
506 return;
507 }
508
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700509 ApplicationId appId = event.key();
510 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
511 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100512 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
513 newApp.state() == oldApp.state())) {
514 log.warn("Can't update the application {}", event.key());
Ray Milkey74e59132018-01-17 15:24:52 -0800515 return;
516 }
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100517 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700518 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100519 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Deepa Vadireddy9719eb02017-11-23 10:12:30 +0000520 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100521 purgeApplication(appId.name());
522 } else {
523 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800524 }
525 }
526 }
527
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700528 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700529 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700530 if (state == INSTALLED) {
531 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700532 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700533 } else if (state == DEACTIVATED) {
534 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700535 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700536 }
537 }
538
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800539 /**
540 * Determines if the application bits are available locally.
541 */
542 private boolean appBitsAvailable(Application app) {
543 try {
544 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
545 return appDesc.version().equals(app.version());
546 } catch (ApplicationException e) {
547 return false;
548 }
549 }
550
551 /**
552 * Fetches the bits from the cluster peers if necessary.
553 */
554 private void fetchBitsIfNeeded(Application app) {
555 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700556 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800557 }
558 }
559
560 /**
561 * Installs the application if necessary from the application peers.
562 */
563 private void installAppIfNeeded(Application app) {
564 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700565 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800566 }
567 }
568
569 /**
570 * Fetches the bits from the cluster peers.
571 */
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700572 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800573 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800574 CountDownLatch latch = new CountDownLatch(1);
575
576 // FIXME: send message with name & version to make sure we don't get served old bits
577
578 log.info("Downloading bits for application {}", app.id().name());
579 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700580 if (latch.getCount() == 0) {
581 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800582 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700583 if (node.equals(localNode)) {
584 continue;
585 }
586 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100587 APP_BITS_REQUEST,
588 s -> s.getBytes(Charsets.UTF_8),
589 Function.identity(),
590 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700591 .whenCompleteAsync((bits, error) -> {
592 if (error == null && latch.getCount() > 0) {
593 saveApplication(new ByteArrayInputStream(bits));
594 log.info("Downloaded bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100595 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700596 latch.countDown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700597 if (delegateInstallation) {
598 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
599 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700600 } else if (error != null) {
601 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100602 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700603 }
Jordan Halterman85f560d2017-09-20 11:57:58 -0700604 }, messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800605 }
606
607 try {
608 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
609 log.warn("Unable to fetch bits for application {}", app.id().name());
610 }
611 } catch (InterruptedException e) {
612 log.warn("Interrupted while fetching bits for application {}", app.id().name());
Ray Milkey5c7d4882018-02-05 14:50:39 -0800613 Thread.currentThread().interrupt();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800614 }
615 }
616
617 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800618 * Produces a registered application from the supplied description.
619 */
620 private Application registerApp(ApplicationDescription appDesc) {
621 ApplicationId appId = idStore.registerApplication(appDesc.name());
Ray Milkey47c95412017-09-15 10:40:48 -0700622 return DefaultApplication
623 .builder(appDesc)
624 .withAppId(appId)
625 .build();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800626 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700627
628 /**
629 * Internal class for holding app information.
630 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700631 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700632 private final Application app;
633 private final InternalState state;
634 private final Set<Permission> permissions;
635
636 @SuppressWarnings("unused")
637 private InternalApplicationHolder() {
638 app = null;
639 state = null;
640 permissions = null;
641 }
642
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700643 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700644 this.app = Preconditions.checkNotNull(app);
645 this.state = state;
646 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
647 }
648
649 public Application app() {
650 return app;
651 }
652
653 public InternalState state() {
654 return state;
655 }
656
657 public Set<Permission> permissions() {
658 return permissions;
659 }
660
661 @Override
662 public String toString() {
663 return MoreObjects.toStringHelper(getClass())
664 .add("app", app.id())
665 .add("state", state)
666 .toString();
667 }
668 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800669}