blob: 1f0d0288b6b8b2f2cd02eb71867e8958fa1f06d2 [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;
Yuta HIGUCHI1d6fef32018-05-07 18:24:20 -070021import com.google.common.base.Throwables;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080022import com.google.common.collect.ImmutableSet;
Andrea Campanellac8eca242016-04-06 19:34:32 -070023import com.google.common.collect.Lists;
Thomas Vachuska761f0042015-11-11 19:10:17 -080024import com.google.common.collect.Maps;
25import com.google.common.collect.Multimap;
26import com.google.common.collect.Sets;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080027import org.onosproject.app.ApplicationDescription;
28import org.onosproject.app.ApplicationEvent;
29import org.onosproject.app.ApplicationException;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070030import org.onosproject.app.ApplicationIdStore;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080031import org.onosproject.app.ApplicationState;
32import org.onosproject.app.ApplicationStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -080033import org.onosproject.app.ApplicationStoreDelegate;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080034import org.onosproject.cluster.ClusterService;
35import org.onosproject.cluster.ControllerNode;
36import org.onosproject.common.app.ApplicationArchive;
37import org.onosproject.core.Application;
38import org.onosproject.core.ApplicationId;
Thomas Vachuska761f0042015-11-11 19:10:17 -080039import org.onosproject.core.CoreService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080040import org.onosproject.core.DefaultApplication;
Jordan Haltermana84936d2018-04-04 15:45:47 -070041import org.onosproject.core.Version;
42import org.onosproject.core.VersionService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090043import org.onosproject.security.Permission;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080044import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080045import org.onosproject.store.cluster.messaging.MessageSubject;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080046import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070047import org.onosproject.store.service.ConsistentMap;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070048import org.onosproject.store.service.DistributedPrimitive.Status;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070049import org.onosproject.store.service.MapEvent;
50import org.onosproject.store.service.MapEventListener;
Jordan Haltermana84936d2018-04-04 15:45:47 -070051import org.onosproject.store.service.RevisionType;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070052import org.onosproject.store.service.Serializer;
Madan Jampani01e05fb2015-08-13 13:29:36 -070053import org.onosproject.store.service.StorageException;
Jonathan Hart6ec029a2015-03-24 17:12:35 -070054import org.onosproject.store.service.StorageService;
Madan Jampanic5a17542016-08-22 10:26:11 -070055import org.onosproject.store.service.Topic;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070056import org.onosproject.store.service.Versioned;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070057import org.osgi.service.component.annotations.Activate;
58import org.osgi.service.component.annotations.Component;
59import org.osgi.service.component.annotations.Deactivate;
60import org.osgi.service.component.annotations.Reference;
61import org.osgi.service.component.annotations.ReferenceCardinality;
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.ScheduledExecutorService;
Jonathan Hartd6fb0532016-01-21 17:28:20 -080073import java.util.concurrent.TimeUnit;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070074import java.util.concurrent.atomic.AtomicBoolean;
75import java.util.function.Consumer;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070076import java.util.function.Function;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070077import java.util.stream.Collectors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070078
Thomas Vachuska761f0042015-11-11 19:10:17 -080079import static com.google.common.collect.Multimaps.newSetMultimap;
80import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080081import static com.google.common.io.ByteStreams.toByteArray;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -070082import static java.util.concurrent.Executors.newSingleThreadExecutor;
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;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
88import static org.onosproject.app.ApplicationEvent.Type.APP_DEACTIVATED;
89import static org.onosproject.app.ApplicationEvent.Type.APP_INSTALLED;
90import static org.onosproject.app.ApplicationEvent.Type.APP_PERMISSIONS_CHANGED;
91import static org.onosproject.app.ApplicationEvent.Type.APP_UNINSTALLED;
92import static org.onosproject.store.app.DistributedApplicationStore.InternalState.ACTIVATED;
93import static org.onosproject.store.app.DistributedApplicationStore.InternalState.DEACTIVATED;
94import static org.onosproject.store.app.DistributedApplicationStore.InternalState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080095import static org.slf4j.LoggerFactory.getLogger;
96
97/**
Madan Jampani6c02d9e2016-05-24 15:09:47 -070098 * Manages inventory of applications in a distributed data store providing
99 * stronger consistency guarantees.
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800100 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700101@Component(immediate = true, service = ApplicationStore.class)
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700102public class DistributedApplicationStore extends ApplicationArchive
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800103 implements ApplicationStore {
104
105 private final Logger log = getLogger(getClass());
106
107 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
108
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700109 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700110 private static final int RETRY_DELAY_MS = 2_000;
111
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800112 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800113
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800114 private static final int APP_LOAD_DELAY_MS = 500;
115
Andrea Campanellac8eca242016-04-06 19:34:32 -0700116 private static List<String> pendingApps = Lists.newArrayList();
117
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800118 public enum InternalState {
119 INSTALLED, ACTIVATED, DEACTIVATED
120 }
121
Madan Jampani6b5b7172015-02-23 13:02:26 -0800122 private ScheduledExecutorService executor;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700123 private ExecutorService messageHandlingExecutor, activationExecutor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800124
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700125 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Madan Jampanic5a17542016-08-22 10:26:11 -0700126 private Topic<Application> appActivationTopic;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800127
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800129 protected ClusterCommunicationService clusterCommunicator;
130
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800132 protected ClusterService clusterService;
133
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700134 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700135 protected StorageService storageService;
136
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700138 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800139
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jordan Haltermana84936d2018-04-04 15:45:47 -0700141 protected VersionService versionService;
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700142
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700143 private final InternalAppsListener appsListener = new InternalAppsListener();
Madan Jampanic5a17542016-08-22 10:26:11 -0700144 private final Consumer<Application> appActivator = new AppActivator();
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700145
146 private Consumer<Status> statusChangeListener;
147
Thomas Vachuska761f0042015-11-11 19:10:17 -0800148 // Multimap to track which apps are required by others apps
149 // app -> { required-by, ... }
150 // Apps explicitly activated will be required by the CORE app
151 private final Multimap<ApplicationId, ApplicationId> requiredBy =
152 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
153
154 private ApplicationId coreAppId;
155
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800156 @Activate
157 public void activate() {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700158 messageHandlingExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100159 "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700160 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100161 bytes -> new String(bytes, Charsets.UTF_8),
162 name -> {
163 try {
164 log.info("Sending bits for application {}", name);
165 return toByteArray(getApplicationInputStream(name));
166 } catch (IOException e) {
167 throw new StorageException(e);
168 }
169 },
170 Function.identity(),
171 messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800172
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700173 apps = storageService.<ApplicationId, InternalApplicationHolder>consistentMapBuilder()
174 .withName("onos-apps")
175 .withRelaxedReadConsistency()
176 .withSerializer(Serializer.using(KryoNamespaces.API,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100177 InternalApplicationHolder.class,
178 InternalState.class))
Jordan Haltermana84936d2018-04-04 15:45:47 -0700179 .withVersion(versionService.version())
180 .withRevisionType(RevisionType.PROPAGATE)
181 .withCompatibilityFunction(this::convertApplication)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700182 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800183
Jordan Halterman400bbe52018-04-05 23:07:47 -0700184 appActivationTopic = storageService.<Application>topicBuilder()
185 .withName("onos-apps-activation-topic")
186 .withSerializer(Serializer.using(KryoNamespaces.API))
187 .withVersion(versionService.version())
188 .withRevisionType(RevisionType.PROPAGATE)
189 .withCompatibilityFunction(this::convertApplication)
190 .build();
Madan Jampaniae538ff2016-08-19 12:42:43 -0700191
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700192 activationExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100193 "app-activation", log));
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700194 appActivationTopic.subscribe(appActivator, activationExecutor);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700195
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700196 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
197 statusChangeListener = status -> {
198 if (status == Status.ACTIVE) {
199 executor.execute(this::bootstrapExistingApplications);
200 }
201 };
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700202 apps.addListener(appsListener, activationExecutor);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700203 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800204 coreAppId = getId(CoreService.CORE_APP_NAME);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700205
Jordan Haltermana84936d2018-04-04 15:45:47 -0700206 activateExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800207 log.info("Started");
208 }
209
Thomas Vachuskacf960112015-03-06 22:36:51 -0800210 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700211 * Converts the versions of stored applications propagated from the prior version to the local application versions.
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700212 */
Jordan Haltermana84936d2018-04-04 15:45:47 -0700213 private InternalApplicationHolder convertApplication(InternalApplicationHolder appHolder, Version version) {
214 // Load the application description from disk. If the version doesn't match the persisted
215 // version, update the stored application with the new version.
216 ApplicationDescription appDesc = getApplicationDescription(appHolder.app.id().name());
217 if (!appDesc.version().equals(appHolder.app().version())) {
Jordan Halterman400bbe52018-04-05 23:07:47 -0700218 Application newApplication = DefaultApplication.builder(appDesc)
219 .withAppId(appHolder.app.id())
Jordan Haltermana84936d2018-04-04 15:45:47 -0700220 .build();
221 return new InternalApplicationHolder(
222 newApplication, appHolder.state, appHolder.permissions);
223 }
224 return appHolder;
225 }
226
227 /**
Jordan Halterman400bbe52018-04-05 23:07:47 -0700228 * Converts the versions of stored applications propagated from the prior version to the local application versions.
229 */
230 private Application convertApplication(Application app, Version version) {
231 // Load the application description from disk. If the version doesn't match the persisted
232 // version, update the stored application with the new version.
233 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
234 if (!appDesc.version().equals(app.version())) {
235 return DefaultApplication.builder(appDesc)
236 .withAppId(app.id())
237 .build();
238 }
239 return app;
240 }
241
242 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700243 * Activates applications that should be activated according to the distributed store.
244 */
245 private void activateExistingApplications() {
Jon Hall8069fdd2018-02-02 10:48:20 -0800246 getApplicationNames().forEach(appName -> {
247 // Only update the application version if the application has already been installed.
248 ApplicationId appId = getId(appName);
249 if (appId != null) {
Jordan Haltermana84936d2018-04-04 15:45:47 -0700250 ApplicationDescription appDesc = getApplicationDescription(appName);
251 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700252
Jordan Haltermana84936d2018-04-04 15:45:47 -0700253 // If the application has already been activated, set the local state to active.
254 if (appHolder != null
255 && appDesc.version().equals(appHolder.app().version())
256 && appHolder.state == ACTIVATED) {
257 setActive(appName);
258 updateTime(appName);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700259 }
Jon Hall8069fdd2018-02-02 10:48:20 -0800260 }
261 });
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700262 }
263
Jon Hall8069fdd2018-02-02 10:48:20 -0800264
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700265 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700266 * Processes existing applications from the distributed map. This is done to
267 * account for events that this instance may be have missed due to a staggered start.
268 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700269 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700270 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700271 }
272
273 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800274 * Loads the application inventory from the disk and activates apps if
275 * they are marked to be active.
276 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800277 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800278 getApplicationNames().forEach(appName -> {
279 Application app = loadFromDisk(appName);
280 if (app != null && isActive(app.id().name())) {
Thomas Vachuskaad2965b2018-04-02 15:23:31 -0700281 // For now, apps loaded from disk will be marked as having been
282 // activated explicitly, which means they won't deactivate
283 // implicitly when all dependent apps have been deactivated.
284 requiredBy.put(app.id(), coreAppId);
Charles Chane889e2d2015-11-17 12:01:02 -0800285 activate(app.id(), false);
286 // TODO Load app permissions
287 }
288 });
289 }
290
291 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700292 pendingApps.add(appName);
293
Charles Chane889e2d2015-11-17 12:01:02 -0800294 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
295 try {
296 // Directly return if app already exists
297 ApplicationId appId = getId(appName);
298 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800299 Application application = getApplication(appId);
300 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700301 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800302 return application;
303 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700304 }
Charles Chane889e2d2015-11-17 12:01:02 -0800305
306 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700307
308 Optional<String> loop = appDesc.requiredApps().stream()
309 .filter(app -> pendingApps.contains(app)).findAny();
310 if (loop.isPresent()) {
311 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
312 pendingApps.remove(appName);
313 return null;
314 }
315
Charles Chane889e2d2015-11-17 12:01:02 -0800316 boolean success = appDesc.requiredApps().stream()
317 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700318 pendingApps.remove(appName);
319
Charles Chane889e2d2015-11-17 12:01:02 -0800320 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700321
Charles Chane889e2d2015-11-17 12:01:02 -0800322 } catch (Exception e) {
Yuta HIGUCHI1d6fef32018-05-07 18:24:20 -0700323 log.warn("Unable to load application {} from disk: {}; retrying",
324 appName,
325 Throwables.getRootCause(e).getMessage());
326 log.debug("Full error details:", e);
Charles Chane889e2d2015-11-17 12:01:02 -0800327 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800328 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800329 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700330 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800331 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800332 }
333
334 @Deactivate
335 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800336 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700337 apps.removeStatusChangeListener(statusChangeListener);
338 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700339 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800340 messageHandlingExecutor.shutdown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700341 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800342 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800343 log.info("Stopped");
344 }
345
346 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800347 public void setDelegate(ApplicationStoreDelegate delegate) {
348 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700349 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700350 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800351 }
352
353 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800354 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700355 return ImmutableSet.copyOf(apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100356 .stream()
357 .map(Versioned::value)
358 .map(InternalApplicationHolder::app)
359 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800360 }
361
362 @Override
363 public ApplicationId getId(String name) {
364 return idStore.getAppId(name);
365 }
366
367 @Override
368 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700369 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
370 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800371 }
372
373 @Override
374 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700375 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
376 InternalState state = appHolder != null ? appHolder.state() : null;
377 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800378 }
379
380 @Override
381 public Application create(InputStream appDescStream) {
382 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800383 if (hasPrerequisites(appDesc)) {
384 return create(appDesc, true);
385 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700386 // Purge bits off disk if we don't have prerequisites to allow app to be
387 // reinstalled later
388 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800389 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
390 }
391
392 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800393 for (String required : app.requiredApps()) {
394 ApplicationId id = getId(required);
395 if (id == null || getApplication(id) == null) {
396 log.error("{} required for {} not available", required, app.name());
397 return false;
398 }
399 }
400 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800401 }
402
Thomas Vachuska161baf52015-03-27 16:15:39 -0700403 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800404 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700405 if (updateTime) {
406 updateTime(app.id().name());
407 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700408 InternalApplicationHolder previousApp =
409 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
410 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800411 }
412
413 @Override
414 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700415 uninstallDependentApps(appId);
416 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800417 }
418
Thomas Vachuska761f0042015-11-11 19:10:17 -0800419 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700420 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800421 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700422 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800423 .forEach(a -> remove(a.id()));
424 }
425
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800426 @Override
427 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800428 activate(appId, coreAppId);
429 }
430
431 private void activate(ApplicationId appId, ApplicationId forAppId) {
432 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700433 activate(appId, true);
434 }
435
436 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700437 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
438 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700439 if (updateTime) {
440 updateTime(appId.name());
441 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700442 activateRequiredApps(vAppHolder.value().app());
443
444 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
445 (k, v) -> new InternalApplicationHolder(
446 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700447 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska484ae542017-08-29 15:21:57 -0700448 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800449 }
450 }
451
Thomas Vachuska761f0042015-11-11 19:10:17 -0800452 // Activates all apps required by this application.
453 private void activateRequiredApps(Application app) {
454 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
455 }
456
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800457 @Override
458 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700459 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800460 deactivate(appId, coreAppId);
461 }
462
463 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
464 requiredBy.remove(appId, forAppId);
465 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700466 AtomicBoolean stateChanged = new AtomicBoolean(false);
467 apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100468 v -> v != null && v.state() != DEACTIVATED,
469 (k, v) -> {
470 stateChanged.set(true);
471 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
472 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700473 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800474 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700475 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800476 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800477 }
478 }
479
Thomas Vachuska761f0042015-11-11 19:10:17 -0800480 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700481 private void deactivateDependentApps(ApplicationId appId) {
482 apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100483 .stream()
484 .map(Versioned::value)
485 .filter(a -> a.state() == ACTIVATED)
486 .filter(a -> a.app().requiredApps().contains(appId.name()))
487 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800488 }
489
490 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700491 private void deactivateRequiredApps(ApplicationId appId) {
492 getApplication(appId).requiredApps()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100493 .stream()
494 .map(this::getId)
495 .map(apps::get)
496 .map(Versioned::value)
497 .filter(a -> a.state() == ACTIVATED)
498 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800499 }
500
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800501 @Override
502 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700503 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
504 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800505 }
506
507 @Override
508 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700509 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
510 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100511 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
512 (k, v) -> {
513 permissionsChanged.set(true);
514 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
515 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700516 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700517 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800518 }
519 }
520
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700521 @Override
522 public InputStream getApplicationArchive(ApplicationId appId) {
523 return getApplicationInputStream(appId.name());
524 }
525
Madan Jampanic5a17542016-08-22 10:26:11 -0700526 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700527 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700528 public void accept(Application app) {
Thomas Vachuska484ae542017-08-29 15:21:57 -0700529 if (app != null) { // FIXME: Once ONOS-6977 is fixed
530 String appName = app.id().name();
531 installAppIfNeeded(app);
532 setActive(appName);
533 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
534 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700535 }
536 }
537
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800538 /**
539 * Listener to application state distributed map changes.
540 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700541 private final class InternalAppsListener
542 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800543 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700544 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700545 if (delegate == null) {
546 return;
547 }
548
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700549 ApplicationId appId = event.key();
550 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
551 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100552 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
553 newApp.state() == oldApp.state())) {
554 log.warn("Can't update the application {}", event.key());
Ray Milkey74e59132018-01-17 15:24:52 -0800555 return;
556 }
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100557 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700558 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100559 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Deepa Vadireddy9719eb02017-11-23 10:12:30 +0000560 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100561 purgeApplication(appId.name());
562 } else {
563 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800564 }
565 }
566 }
567
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700568 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700569 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700570 if (state == INSTALLED) {
571 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700572 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700573 } else if (state == DEACTIVATED) {
574 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700575 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700576 }
577 }
578
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800579 /**
580 * Determines if the application bits are available locally.
581 */
582 private boolean appBitsAvailable(Application app) {
583 try {
584 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
585 return appDesc.version().equals(app.version());
586 } catch (ApplicationException e) {
587 return false;
588 }
589 }
590
591 /**
592 * Fetches the bits from the cluster peers if necessary.
593 */
594 private void fetchBitsIfNeeded(Application app) {
595 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700596 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800597 }
598 }
599
600 /**
601 * Installs the application if necessary from the application peers.
602 */
603 private void installAppIfNeeded(Application app) {
604 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700605 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800606 }
607 }
608
609 /**
610 * Fetches the bits from the cluster peers.
611 */
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700612 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800613 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800614 CountDownLatch latch = new CountDownLatch(1);
615
616 // FIXME: send message with name & version to make sure we don't get served old bits
617
618 log.info("Downloading bits for application {}", app.id().name());
619 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700620 if (latch.getCount() == 0) {
621 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800622 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700623 if (node.equals(localNode)) {
624 continue;
625 }
626 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100627 APP_BITS_REQUEST,
628 s -> s.getBytes(Charsets.UTF_8),
629 Function.identity(),
630 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700631 .whenCompleteAsync((bits, error) -> {
632 if (error == null && latch.getCount() > 0) {
633 saveApplication(new ByteArrayInputStream(bits));
634 log.info("Downloaded bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100635 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700636 latch.countDown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700637 if (delegateInstallation) {
638 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
639 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700640 } else if (error != null) {
641 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100642 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700643 }
Jordan Halterman85f560d2017-09-20 11:57:58 -0700644 }, messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800645 }
646
647 try {
648 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
649 log.warn("Unable to fetch bits for application {}", app.id().name());
650 }
651 } catch (InterruptedException e) {
652 log.warn("Interrupted while fetching bits for application {}", app.id().name());
Ray Milkey5c7d4882018-02-05 14:50:39 -0800653 Thread.currentThread().interrupt();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800654 }
655 }
656
657 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800658 * Produces a registered application from the supplied description.
659 */
660 private Application registerApp(ApplicationDescription appDesc) {
661 ApplicationId appId = idStore.registerApplication(appDesc.name());
Ray Milkey47c95412017-09-15 10:40:48 -0700662 return DefaultApplication
663 .builder(appDesc)
664 .withAppId(appId)
665 .build();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800666 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700667
668 /**
669 * Internal class for holding app information.
670 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700671 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700672 private final Application app;
673 private final InternalState state;
674 private final Set<Permission> permissions;
675
676 @SuppressWarnings("unused")
677 private InternalApplicationHolder() {
678 app = null;
679 state = null;
680 permissions = null;
681 }
682
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700683 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700684 this.app = Preconditions.checkNotNull(app);
685 this.state = state;
686 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
687 }
688
689 public Application app() {
690 return app;
691 }
692
693 public InternalState state() {
694 return state;
695 }
696
697 public Set<Permission> permissions() {
698 return permissions;
699 }
700
701 @Override
702 public String toString() {
703 return MoreObjects.toStringHelper(getClass())
704 .add("app", app.id())
705 .add("state", state)
706 .toString();
707 }
708 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800709}