blob: 2737b5866471e6a547038f69d3f055682ca2b8ae [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
Thomas Vachuskac6fe1ea2018-12-17 10:44:18 -0800209 downloadMissingApplications();
Jordan Haltermana84936d2018-04-04 15:45:47 -0700210 activateExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800211 log.info("Started");
212 }
213
Thomas Vachuskacf960112015-03-06 22:36:51 -0800214 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700215 * Converts the versions of stored applications propagated from the prior version to the local application versions.
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700216 */
Jordan Haltermana84936d2018-04-04 15:45:47 -0700217 private InternalApplicationHolder convertApplication(InternalApplicationHolder appHolder, Version version) {
218 // Load the application description from disk. If the version doesn't match the persisted
219 // version, update the stored application with the new version.
220 ApplicationDescription appDesc = getApplicationDescription(appHolder.app.id().name());
221 if (!appDesc.version().equals(appHolder.app().version())) {
Jordan Halterman400bbe52018-04-05 23:07:47 -0700222 Application newApplication = DefaultApplication.builder(appDesc)
223 .withAppId(appHolder.app.id())
Jordan Haltermana84936d2018-04-04 15:45:47 -0700224 .build();
225 return new InternalApplicationHolder(
226 newApplication, appHolder.state, appHolder.permissions);
227 }
228 return appHolder;
229 }
230
231 /**
Jordan Halterman400bbe52018-04-05 23:07:47 -0700232 * Converts the versions of stored applications propagated from the prior version to the local application versions.
233 */
234 private Application convertApplication(Application app, Version version) {
235 // Load the application description from disk. If the version doesn't match the persisted
236 // version, update the stored application with the new version.
237 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
238 if (!appDesc.version().equals(app.version())) {
239 return DefaultApplication.builder(appDesc)
240 .withAppId(app.id())
241 .build();
242 }
243 return app;
244 }
245
246 /**
Thomas Vachuskac6fe1ea2018-12-17 10:44:18 -0800247 * Downloads any missing bits for installed applications.
248 */
249 private void downloadMissingApplications() {
250 apps.asJavaMap().forEach((appId, holder) -> fetchBitsIfNeeded(holder.app));
251 }
252
253 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700254 * Activates applications that should be activated according to the distributed store.
255 */
256 private void activateExistingApplications() {
Jon Hall8069fdd2018-02-02 10:48:20 -0800257 getApplicationNames().forEach(appName -> {
258 // Only update the application version if the application has already been installed.
259 ApplicationId appId = getId(appName);
260 if (appId != null) {
Jordan Haltermana84936d2018-04-04 15:45:47 -0700261 ApplicationDescription appDesc = getApplicationDescription(appName);
262 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700263
Jordan Haltermana84936d2018-04-04 15:45:47 -0700264 // If the application has already been activated, set the local state to active.
265 if (appHolder != null
266 && appDesc.version().equals(appHolder.app().version())
267 && appHolder.state == ACTIVATED) {
268 setActive(appName);
269 updateTime(appName);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700270 }
Jon Hall8069fdd2018-02-02 10:48:20 -0800271 }
272 });
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700273 }
274
Jon Hall8069fdd2018-02-02 10:48:20 -0800275
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700276 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700277 * Processes existing applications from the distributed map. This is done to
278 * account for events that this instance may be have missed due to a staggered start.
279 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700280 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700281 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700282 }
283
284 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800285 * Loads the application inventory from the disk and activates apps if
286 * they are marked to be active.
287 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800288 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800289 getApplicationNames().forEach(appName -> {
290 Application app = loadFromDisk(appName);
291 if (app != null && isActive(app.id().name())) {
Thomas Vachuskaad2965b2018-04-02 15:23:31 -0700292 // For now, apps loaded from disk will be marked as having been
293 // activated explicitly, which means they won't deactivate
294 // implicitly when all dependent apps have been deactivated.
295 requiredBy.put(app.id(), coreAppId);
Charles Chane889e2d2015-11-17 12:01:02 -0800296 activate(app.id(), false);
297 // TODO Load app permissions
298 }
299 });
300 }
301
302 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700303 pendingApps.add(appName);
304
Charles Chane889e2d2015-11-17 12:01:02 -0800305 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
306 try {
307 // Directly return if app already exists
308 ApplicationId appId = getId(appName);
309 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800310 Application application = getApplication(appId);
311 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700312 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800313 return application;
314 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700315 }
Charles Chane889e2d2015-11-17 12:01:02 -0800316
317 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700318
319 Optional<String> loop = appDesc.requiredApps().stream()
320 .filter(app -> pendingApps.contains(app)).findAny();
321 if (loop.isPresent()) {
322 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
323 pendingApps.remove(appName);
324 return null;
325 }
326
Charles Chane889e2d2015-11-17 12:01:02 -0800327 boolean success = appDesc.requiredApps().stream()
328 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700329 pendingApps.remove(appName);
330
Ray Milkey3638de82019-02-01 12:51:45 -0800331 if (success) {
332 return create(appDesc, false);
333 } else {
334 log.error("Unable to load dependencies for application {}", appName);
335 return null;
336 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700337
Charles Chane889e2d2015-11-17 12:01:02 -0800338 } catch (Exception e) {
Yuta HIGUCHI1d6fef32018-05-07 18:24:20 -0700339 log.warn("Unable to load application {} from disk: {}; retrying",
340 appName,
341 Throwables.getRootCause(e).getMessage());
342 log.debug("Full error details:", e);
Charles Chane889e2d2015-11-17 12:01:02 -0800343 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800344 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800345 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700346 pendingApps.remove(appName);
Ray Milkey3638de82019-02-01 12:51:45 -0800347 log.error("Unable to load application {}", appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800348 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800349 }
350
351 @Deactivate
352 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800353 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700354 apps.removeStatusChangeListener(statusChangeListener);
355 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700356 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800357 messageHandlingExecutor.shutdown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700358 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800359 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800360 log.info("Stopped");
361 }
362
363 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800364 public void setDelegate(ApplicationStoreDelegate delegate) {
365 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700366 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700367 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800368 }
369
370 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800371 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700372 return ImmutableSet.copyOf(apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100373 .stream()
374 .map(Versioned::value)
375 .map(InternalApplicationHolder::app)
376 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800377 }
378
379 @Override
380 public ApplicationId getId(String name) {
381 return idStore.getAppId(name);
382 }
383
384 @Override
385 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700386 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
387 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800388 }
389
390 @Override
391 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700392 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
393 InternalState state = appHolder != null ? appHolder.state() : null;
394 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800395 }
396
397 @Override
398 public Application create(InputStream appDescStream) {
399 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800400 if (hasPrerequisites(appDesc)) {
401 return create(appDesc, true);
402 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700403 // Purge bits off disk if we don't have prerequisites to allow app to be
404 // reinstalled later
405 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800406 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
407 }
408
409 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800410 for (String required : app.requiredApps()) {
411 ApplicationId id = getId(required);
412 if (id == null || getApplication(id) == null) {
413 log.error("{} required for {} not available", required, app.name());
414 return false;
415 }
416 }
417 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800418 }
419
Thomas Vachuska161baf52015-03-27 16:15:39 -0700420 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800421 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700422 if (updateTime) {
423 updateTime(app.id().name());
424 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700425 InternalApplicationHolder previousApp =
426 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
427 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800428 }
429
430 @Override
431 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700432 uninstallDependentApps(appId);
433 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800434 }
435
Thomas Vachuska761f0042015-11-11 19:10:17 -0800436 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700437 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800438 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700439 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800440 .forEach(a -> remove(a.id()));
441 }
442
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800443 @Override
444 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800445 activate(appId, coreAppId);
446 }
447
448 private void activate(ApplicationId appId, ApplicationId forAppId) {
449 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700450 activate(appId, true);
451 }
452
453 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700454 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
455 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700456 if (updateTime) {
457 updateTime(appId.name());
458 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700459 activateRequiredApps(vAppHolder.value().app());
460
461 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
462 (k, v) -> new InternalApplicationHolder(
463 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700464 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska484ae542017-08-29 15:21:57 -0700465 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800466 }
467 }
468
Thomas Vachuska761f0042015-11-11 19:10:17 -0800469 // Activates all apps required by this application.
470 private void activateRequiredApps(Application app) {
471 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
472 }
473
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800474 @Override
475 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700476 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800477 deactivate(appId, coreAppId);
478 }
479
480 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
481 requiredBy.remove(appId, forAppId);
482 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700483 AtomicBoolean stateChanged = new AtomicBoolean(false);
484 apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100485 v -> v != null && v.state() != DEACTIVATED,
486 (k, v) -> {
487 stateChanged.set(true);
488 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
489 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700490 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800491 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700492 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800493 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800494 }
495 }
496
Thomas Vachuska761f0042015-11-11 19:10:17 -0800497 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700498 private void deactivateDependentApps(ApplicationId appId) {
499 apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100500 .stream()
501 .map(Versioned::value)
502 .filter(a -> a.state() == ACTIVATED)
503 .filter(a -> a.app().requiredApps().contains(appId.name()))
504 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800505 }
506
507 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700508 private void deactivateRequiredApps(ApplicationId appId) {
509 getApplication(appId).requiredApps()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100510 .stream()
511 .map(this::getId)
512 .map(apps::get)
513 .map(Versioned::value)
514 .filter(a -> a.state() == ACTIVATED)
515 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800516 }
517
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800518 @Override
519 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700520 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
521 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800522 }
523
524 @Override
525 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700526 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
527 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100528 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
529 (k, v) -> {
530 permissionsChanged.set(true);
531 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
532 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700533 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700534 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800535 }
536 }
537
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700538 @Override
539 public InputStream getApplicationArchive(ApplicationId appId) {
540 return getApplicationInputStream(appId.name());
541 }
542
Madan Jampanic5a17542016-08-22 10:26:11 -0700543 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700544 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700545 public void accept(Application app) {
Thomas Vachuska484ae542017-08-29 15:21:57 -0700546 if (app != null) { // FIXME: Once ONOS-6977 is fixed
547 String appName = app.id().name();
548 installAppIfNeeded(app);
549 setActive(appName);
550 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
551 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700552 }
553 }
554
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800555 /**
556 * Listener to application state distributed map changes.
557 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700558 private final class InternalAppsListener
559 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800560 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700561 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700562 if (delegate == null) {
563 return;
564 }
565
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700566 ApplicationId appId = event.key();
567 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
568 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100569 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
570 newApp.state() == oldApp.state())) {
571 log.warn("Can't update the application {}", event.key());
Ray Milkey74e59132018-01-17 15:24:52 -0800572 return;
573 }
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100574 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700575 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100576 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Deepa Vadireddy9719eb02017-11-23 10:12:30 +0000577 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100578 purgeApplication(appId.name());
579 } else {
580 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800581 }
582 }
583 }
584
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700585 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700586 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700587 if (state == INSTALLED) {
588 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700589 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700590 } else if (state == DEACTIVATED) {
591 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700592 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700593 }
594 }
595
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800596 /**
597 * Determines if the application bits are available locally.
598 */
599 private boolean appBitsAvailable(Application app) {
600 try {
601 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
602 return appDesc.version().equals(app.version());
603 } catch (ApplicationException e) {
604 return false;
605 }
606 }
607
608 /**
609 * Fetches the bits from the cluster peers if necessary.
610 */
611 private void fetchBitsIfNeeded(Application app) {
612 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700613 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800614 }
615 }
616
617 /**
618 * Installs the application if necessary from the application peers.
619 */
620 private void installAppIfNeeded(Application app) {
621 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700622 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800623 }
624 }
625
626 /**
627 * Fetches the bits from the cluster peers.
628 */
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700629 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800630 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800631 CountDownLatch latch = new CountDownLatch(1);
632
633 // FIXME: send message with name & version to make sure we don't get served old bits
634
635 log.info("Downloading bits for application {}", app.id().name());
636 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700637 if (latch.getCount() == 0) {
638 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800639 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700640 if (node.equals(localNode)) {
641 continue;
642 }
643 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100644 APP_BITS_REQUEST,
645 s -> s.getBytes(Charsets.UTF_8),
646 Function.identity(),
647 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700648 .whenCompleteAsync((bits, error) -> {
649 if (error == null && latch.getCount() > 0) {
650 saveApplication(new ByteArrayInputStream(bits));
651 log.info("Downloaded bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100652 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700653 latch.countDown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700654 if (delegateInstallation) {
655 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
656 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700657 } else if (error != null) {
658 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100659 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700660 }
Jordan Halterman85f560d2017-09-20 11:57:58 -0700661 }, messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800662 }
663
664 try {
665 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
666 log.warn("Unable to fetch bits for application {}", app.id().name());
667 }
668 } catch (InterruptedException e) {
669 log.warn("Interrupted while fetching bits for application {}", app.id().name());
Ray Milkey5c7d4882018-02-05 14:50:39 -0800670 Thread.currentThread().interrupt();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800671 }
672 }
673
674 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800675 * Produces a registered application from the supplied description.
676 */
677 private Application registerApp(ApplicationDescription appDesc) {
678 ApplicationId appId = idStore.registerApplication(appDesc.name());
Ray Milkey47c95412017-09-15 10:40:48 -0700679 return DefaultApplication
680 .builder(appDesc)
681 .withAppId(appId)
682 .build();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800683 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700684
685 /**
686 * Internal class for holding app information.
687 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700688 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700689 private final Application app;
690 private final InternalState state;
691 private final Set<Permission> permissions;
692
693 @SuppressWarnings("unused")
694 private InternalApplicationHolder() {
695 app = null;
696 state = null;
697 permissions = null;
698 }
699
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700700 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700701 this.app = Preconditions.checkNotNull(app);
702 this.state = state;
703 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
704 }
705
706 public Application app() {
707 return app;
708 }
709
710 public InternalState state() {
711 return state;
712 }
713
714 public Set<Permission> permissions() {
715 return permissions;
716 }
717
718 @Override
719 public String toString() {
720 return MoreObjects.toStringHelper(getClass())
721 .add("app", app.id())
722 .add("state", state)
723 .toString();
724 }
725 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800726}