blob: 37930998f35e36c01cd150c2cc68d15a9ca11df9 [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));
Thomas Vachuska1bb2a282018-10-30 12:15:05 -0700166 } catch (ApplicationException e) {
167 log.warn("Bits for application {} are not available on this node yet", name);
168 return null;
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100169 } catch (IOException e) {
170 throw new StorageException(e);
171 }
172 },
173 Function.identity(),
174 messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800175
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700176 apps = storageService.<ApplicationId, InternalApplicationHolder>consistentMapBuilder()
177 .withName("onos-apps")
178 .withRelaxedReadConsistency()
179 .withSerializer(Serializer.using(KryoNamespaces.API,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100180 InternalApplicationHolder.class,
181 InternalState.class))
Jordan Haltermana84936d2018-04-04 15:45:47 -0700182 .withVersion(versionService.version())
183 .withRevisionType(RevisionType.PROPAGATE)
184 .withCompatibilityFunction(this::convertApplication)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700185 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800186
Jordan Halterman400bbe52018-04-05 23:07:47 -0700187 appActivationTopic = storageService.<Application>topicBuilder()
188 .withName("onos-apps-activation-topic")
189 .withSerializer(Serializer.using(KryoNamespaces.API))
190 .withVersion(versionService.version())
191 .withRevisionType(RevisionType.PROPAGATE)
192 .withCompatibilityFunction(this::convertApplication)
193 .build();
Madan Jampaniae538ff2016-08-19 12:42:43 -0700194
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700195 activationExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100196 "app-activation", log));
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700197 appActivationTopic.subscribe(appActivator, activationExecutor);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700198
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700199 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
200 statusChangeListener = status -> {
201 if (status == Status.ACTIVE) {
202 executor.execute(this::bootstrapExistingApplications);
203 }
204 };
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700205 apps.addListener(appsListener, activationExecutor);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700206 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800207 coreAppId = getId(CoreService.CORE_APP_NAME);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700208
Jordan Haltermana84936d2018-04-04 15:45:47 -0700209 activateExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800210 log.info("Started");
211 }
212
Thomas Vachuskacf960112015-03-06 22:36:51 -0800213 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700214 * Converts the versions of stored applications propagated from the prior version to the local application versions.
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700215 */
Jordan Haltermana84936d2018-04-04 15:45:47 -0700216 private InternalApplicationHolder convertApplication(InternalApplicationHolder appHolder, Version version) {
217 // Load the application description from disk. If the version doesn't match the persisted
218 // version, update the stored application with the new version.
219 ApplicationDescription appDesc = getApplicationDescription(appHolder.app.id().name());
220 if (!appDesc.version().equals(appHolder.app().version())) {
Jordan Halterman400bbe52018-04-05 23:07:47 -0700221 Application newApplication = DefaultApplication.builder(appDesc)
222 .withAppId(appHolder.app.id())
Jordan Haltermana84936d2018-04-04 15:45:47 -0700223 .build();
224 return new InternalApplicationHolder(
225 newApplication, appHolder.state, appHolder.permissions);
226 }
227 return appHolder;
228 }
229
230 /**
Jordan Halterman400bbe52018-04-05 23:07:47 -0700231 * Converts the versions of stored applications propagated from the prior version to the local application versions.
232 */
233 private Application convertApplication(Application app, Version version) {
234 // Load the application description from disk. If the version doesn't match the persisted
235 // version, update the stored application with the new version.
236 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
237 if (!appDesc.version().equals(app.version())) {
238 return DefaultApplication.builder(appDesc)
239 .withAppId(app.id())
240 .build();
241 }
242 return app;
243 }
244
245 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700246 * Activates applications that should be activated according to the distributed store.
247 */
248 private void activateExistingApplications() {
Jon Hall8069fdd2018-02-02 10:48:20 -0800249 getApplicationNames().forEach(appName -> {
250 // Only update the application version if the application has already been installed.
251 ApplicationId appId = getId(appName);
252 if (appId != null) {
Jordan Haltermana84936d2018-04-04 15:45:47 -0700253 ApplicationDescription appDesc = getApplicationDescription(appName);
254 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700255
Jordan Haltermana84936d2018-04-04 15:45:47 -0700256 // If the application has already been activated, set the local state to active.
257 if (appHolder != null
258 && appDesc.version().equals(appHolder.app().version())
259 && appHolder.state == ACTIVATED) {
260 setActive(appName);
261 updateTime(appName);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700262 }
Jon Hall8069fdd2018-02-02 10:48:20 -0800263 }
264 });
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700265 }
266
Jon Hall8069fdd2018-02-02 10:48:20 -0800267
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700268 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700269 * Processes existing applications from the distributed map. This is done to
270 * account for events that this instance may be have missed due to a staggered start.
271 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700272 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700273 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700274 }
275
276 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800277 * Loads the application inventory from the disk and activates apps if
278 * they are marked to be active.
279 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800280 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800281 getApplicationNames().forEach(appName -> {
282 Application app = loadFromDisk(appName);
283 if (app != null && isActive(app.id().name())) {
Thomas Vachuskaad2965b2018-04-02 15:23:31 -0700284 // For now, apps loaded from disk will be marked as having been
285 // activated explicitly, which means they won't deactivate
286 // implicitly when all dependent apps have been deactivated.
287 requiredBy.put(app.id(), coreAppId);
Charles Chane889e2d2015-11-17 12:01:02 -0800288 activate(app.id(), false);
289 // TODO Load app permissions
290 }
291 });
292 }
293
294 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700295 pendingApps.add(appName);
296
Charles Chane889e2d2015-11-17 12:01:02 -0800297 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
298 try {
299 // Directly return if app already exists
300 ApplicationId appId = getId(appName);
301 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800302 Application application = getApplication(appId);
303 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700304 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800305 return application;
306 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700307 }
Charles Chane889e2d2015-11-17 12:01:02 -0800308
309 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700310
311 Optional<String> loop = appDesc.requiredApps().stream()
312 .filter(app -> pendingApps.contains(app)).findAny();
313 if (loop.isPresent()) {
314 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
315 pendingApps.remove(appName);
316 return null;
317 }
318
Charles Chane889e2d2015-11-17 12:01:02 -0800319 boolean success = appDesc.requiredApps().stream()
320 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700321 pendingApps.remove(appName);
322
Charles Chane889e2d2015-11-17 12:01:02 -0800323 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700324
Charles Chane889e2d2015-11-17 12:01:02 -0800325 } catch (Exception e) {
Yuta HIGUCHI1d6fef32018-05-07 18:24:20 -0700326 log.warn("Unable to load application {} from disk: {}; retrying",
327 appName,
328 Throwables.getRootCause(e).getMessage());
329 log.debug("Full error details:", e);
Charles Chane889e2d2015-11-17 12:01:02 -0800330 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800331 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800332 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700333 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800334 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800335 }
336
337 @Deactivate
338 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800339 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700340 apps.removeStatusChangeListener(statusChangeListener);
341 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700342 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800343 messageHandlingExecutor.shutdown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700344 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800345 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800346 log.info("Stopped");
347 }
348
349 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800350 public void setDelegate(ApplicationStoreDelegate delegate) {
351 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700352 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700353 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800354 }
355
356 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800357 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700358 return ImmutableSet.copyOf(apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100359 .stream()
360 .map(Versioned::value)
361 .map(InternalApplicationHolder::app)
362 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800363 }
364
365 @Override
366 public ApplicationId getId(String name) {
367 return idStore.getAppId(name);
368 }
369
370 @Override
371 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700372 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
373 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800374 }
375
376 @Override
377 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700378 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
379 InternalState state = appHolder != null ? appHolder.state() : null;
380 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800381 }
382
383 @Override
384 public Application create(InputStream appDescStream) {
385 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800386 if (hasPrerequisites(appDesc)) {
387 return create(appDesc, true);
388 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700389 // Purge bits off disk if we don't have prerequisites to allow app to be
390 // reinstalled later
391 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800392 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
393 }
394
395 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800396 for (String required : app.requiredApps()) {
397 ApplicationId id = getId(required);
398 if (id == null || getApplication(id) == null) {
399 log.error("{} required for {} not available", required, app.name());
400 return false;
401 }
402 }
403 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800404 }
405
Thomas Vachuska161baf52015-03-27 16:15:39 -0700406 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800407 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700408 if (updateTime) {
409 updateTime(app.id().name());
410 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700411 InternalApplicationHolder previousApp =
412 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
413 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800414 }
415
416 @Override
417 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700418 uninstallDependentApps(appId);
419 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800420 }
421
Thomas Vachuska761f0042015-11-11 19:10:17 -0800422 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700423 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800424 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700425 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800426 .forEach(a -> remove(a.id()));
427 }
428
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800429 @Override
430 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800431 activate(appId, coreAppId);
432 }
433
434 private void activate(ApplicationId appId, ApplicationId forAppId) {
435 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700436 activate(appId, true);
437 }
438
439 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700440 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
441 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700442 if (updateTime) {
443 updateTime(appId.name());
444 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700445 activateRequiredApps(vAppHolder.value().app());
446
447 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
448 (k, v) -> new InternalApplicationHolder(
449 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700450 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska484ae542017-08-29 15:21:57 -0700451 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800452 }
453 }
454
Thomas Vachuska761f0042015-11-11 19:10:17 -0800455 // Activates all apps required by this application.
456 private void activateRequiredApps(Application app) {
457 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
458 }
459
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800460 @Override
461 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700462 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800463 deactivate(appId, coreAppId);
464 }
465
466 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
467 requiredBy.remove(appId, forAppId);
468 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700469 AtomicBoolean stateChanged = new AtomicBoolean(false);
470 apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100471 v -> v != null && v.state() != DEACTIVATED,
472 (k, v) -> {
473 stateChanged.set(true);
474 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
475 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700476 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800477 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700478 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800479 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800480 }
481 }
482
Thomas Vachuska761f0042015-11-11 19:10:17 -0800483 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700484 private void deactivateDependentApps(ApplicationId appId) {
485 apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100486 .stream()
487 .map(Versioned::value)
488 .filter(a -> a.state() == ACTIVATED)
489 .filter(a -> a.app().requiredApps().contains(appId.name()))
490 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800491 }
492
493 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700494 private void deactivateRequiredApps(ApplicationId appId) {
495 getApplication(appId).requiredApps()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100496 .stream()
497 .map(this::getId)
498 .map(apps::get)
499 .map(Versioned::value)
500 .filter(a -> a.state() == ACTIVATED)
501 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800502 }
503
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800504 @Override
505 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700506 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
507 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800508 }
509
510 @Override
511 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700512 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
513 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100514 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
515 (k, v) -> {
516 permissionsChanged.set(true);
517 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
518 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700519 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700520 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800521 }
522 }
523
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700524 @Override
525 public InputStream getApplicationArchive(ApplicationId appId) {
526 return getApplicationInputStream(appId.name());
527 }
528
Madan Jampanic5a17542016-08-22 10:26:11 -0700529 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700530 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700531 public void accept(Application app) {
Thomas Vachuska484ae542017-08-29 15:21:57 -0700532 if (app != null) { // FIXME: Once ONOS-6977 is fixed
533 String appName = app.id().name();
534 installAppIfNeeded(app);
535 setActive(appName);
536 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
537 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700538 }
539 }
540
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800541 /**
542 * Listener to application state distributed map changes.
543 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700544 private final class InternalAppsListener
545 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800546 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700547 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700548 if (delegate == null) {
549 return;
550 }
551
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700552 ApplicationId appId = event.key();
553 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
554 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100555 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
556 newApp.state() == oldApp.state())) {
557 log.warn("Can't update the application {}", event.key());
Ray Milkey74e59132018-01-17 15:24:52 -0800558 return;
559 }
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100560 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700561 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100562 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Deepa Vadireddy9719eb02017-11-23 10:12:30 +0000563 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100564 purgeApplication(appId.name());
565 } else {
566 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800567 }
568 }
569 }
570
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700571 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700572 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700573 if (state == INSTALLED) {
574 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700575 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700576 } else if (state == DEACTIVATED) {
577 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700578 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700579 }
580 }
581
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800582 /**
583 * Determines if the application bits are available locally.
584 */
585 private boolean appBitsAvailable(Application app) {
586 try {
587 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
588 return appDesc.version().equals(app.version());
589 } catch (ApplicationException e) {
590 return false;
591 }
592 }
593
594 /**
595 * Fetches the bits from the cluster peers if necessary.
596 */
597 private void fetchBitsIfNeeded(Application app) {
598 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700599 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800600 }
601 }
602
603 /**
604 * Installs the application if necessary from the application peers.
605 */
606 private void installAppIfNeeded(Application app) {
607 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700608 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800609 }
610 }
611
612 /**
613 * Fetches the bits from the cluster peers.
614 */
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700615 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800616 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800617 CountDownLatch latch = new CountDownLatch(1);
618
619 // FIXME: send message with name & version to make sure we don't get served old bits
620
621 log.info("Downloading bits for application {}", app.id().name());
622 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700623 if (latch.getCount() == 0) {
624 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800625 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700626 if (node.equals(localNode)) {
627 continue;
628 }
629 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100630 APP_BITS_REQUEST,
631 s -> s.getBytes(Charsets.UTF_8),
632 Function.identity(),
633 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700634 .whenCompleteAsync((bits, error) -> {
635 if (error == null && latch.getCount() > 0) {
636 saveApplication(new ByteArrayInputStream(bits));
637 log.info("Downloaded bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100638 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700639 latch.countDown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700640 if (delegateInstallation) {
641 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
642 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700643 } else if (error != null) {
644 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100645 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700646 }
Jordan Halterman85f560d2017-09-20 11:57:58 -0700647 }, messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800648 }
649
650 try {
651 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
652 log.warn("Unable to fetch bits for application {}", app.id().name());
653 }
654 } catch (InterruptedException e) {
655 log.warn("Interrupted while fetching bits for application {}", app.id().name());
Ray Milkey5c7d4882018-02-05 14:50:39 -0800656 Thread.currentThread().interrupt();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800657 }
658 }
659
660 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800661 * Produces a registered application from the supplied description.
662 */
663 private Application registerApp(ApplicationDescription appDesc) {
664 ApplicationId appId = idStore.registerApplication(appDesc.name());
Ray Milkey47c95412017-09-15 10:40:48 -0700665 return DefaultApplication
666 .builder(appDesc)
667 .withAppId(appId)
668 .build();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800669 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700670
671 /**
672 * Internal class for holding app information.
673 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700674 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700675 private final Application app;
676 private final InternalState state;
677 private final Set<Permission> permissions;
678
679 @SuppressWarnings("unused")
680 private InternalApplicationHolder() {
681 app = null;
682 state = null;
683 permissions = null;
684 }
685
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700686 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700687 this.app = Preconditions.checkNotNull(app);
688 this.state = state;
689 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
690 }
691
692 public Application app() {
693 return app;
694 }
695
696 public InternalState state() {
697 return state;
698 }
699
700 public Set<Permission> permissions() {
701 return permissions;
702 }
703
704 @Override
705 public String toString() {
706 return MoreObjects.toStringHelper(getClass())
707 .add("app", app.id())
708 .add("state", state)
709 .toString();
710 }
711 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800712}