blob: 36569f9ae55594a9a145aa504717f5909d1f3dfe [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;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070027
Thomas Vachuska90b453f2015-01-30 18:57:14 -080028import org.apache.felix.scr.annotations.Activate;
29import org.apache.felix.scr.annotations.Component;
30import org.apache.felix.scr.annotations.Deactivate;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.apache.felix.scr.annotations.Service;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080034import org.onosproject.app.ApplicationDescription;
35import org.onosproject.app.ApplicationEvent;
36import org.onosproject.app.ApplicationException;
37import org.onosproject.app.ApplicationState;
38import org.onosproject.app.ApplicationStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -080039import org.onosproject.app.ApplicationStoreDelegate;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080040import org.onosproject.cluster.ClusterService;
41import org.onosproject.cluster.ControllerNode;
42import org.onosproject.common.app.ApplicationArchive;
43import org.onosproject.core.Application;
44import org.onosproject.core.ApplicationId;
sangyun-han6d33e802016-08-05 13:36:33 +090045import org.onosproject.app.ApplicationIdStore;
Thomas Vachuska761f0042015-11-11 19:10:17 -080046import org.onosproject.core.CoreService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080047import org.onosproject.core.DefaultApplication;
Jordan Haltermana84936d2018-04-04 15:45:47 -070048import org.onosproject.core.Version;
49import org.onosproject.core.VersionService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090050import org.onosproject.security.Permission;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080051import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080052import org.onosproject.store.cluster.messaging.MessageSubject;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080053import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070054import org.onosproject.store.service.ConsistentMap;
55import org.onosproject.store.service.MapEvent;
56import org.onosproject.store.service.MapEventListener;
Jordan Haltermana84936d2018-04-04 15:45:47 -070057import org.onosproject.store.service.RevisionType;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070058import org.onosproject.store.service.Serializer;
Madan Jampani01e05fb2015-08-13 13:29:36 -070059import org.onosproject.store.service.StorageException;
Jonathan Hart6ec029a2015-03-24 17:12:35 -070060import org.onosproject.store.service.StorageService;
Madan Jampanic5a17542016-08-22 10:26:11 -070061import org.onosproject.store.service.Topic;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070062import org.onosproject.store.service.Versioned;
63import org.onosproject.store.service.DistributedPrimitive.Status;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080064import org.slf4j.Logger;
65
66import java.io.ByteArrayInputStream;
Madan Jampani01e05fb2015-08-13 13:29:36 -070067import java.io.IOException;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080068import java.io.InputStream;
Andrea Campanellac8eca242016-04-06 19:34:32 -070069import java.util.List;
70import java.util.Optional;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080071import java.util.Set;
72import java.util.concurrent.CountDownLatch;
Madan Jampani2af244a2015-02-22 13:12:01 -080073import java.util.concurrent.ExecutorService;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080074import java.util.concurrent.ScheduledExecutorService;
Jonathan Hartd6fb0532016-01-21 17:28:20 -080075import java.util.concurrent.TimeUnit;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070076import java.util.concurrent.atomic.AtomicBoolean;
77import java.util.function.Consumer;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070078import java.util.function.Function;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070079import java.util.stream.Collectors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070080
Thomas Vachuska761f0042015-11-11 19:10:17 -080081import static com.google.common.collect.Multimaps.newSetMultimap;
82import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080083import static com.google.common.io.ByteStreams.toByteArray;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -070084import static java.util.concurrent.Executors.newSingleThreadExecutor;
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -080085import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080086import static java.util.concurrent.TimeUnit.MILLISECONDS;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080087import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070088import static org.onlab.util.Tools.randomDelay;
Thomas Vachuskad0d58542015-06-03 12:38:44 -070089import static org.onosproject.app.ApplicationEvent.Type.*;
Madan Jampani6c02d9e2016-05-24 15:09:47 -070090import static org.onosproject.store.app.DistributedApplicationStore.InternalState.*;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080091import static org.slf4j.LoggerFactory.getLogger;
92
93/**
Madan Jampani6c02d9e2016-05-24 15:09:47 -070094 * Manages inventory of applications in a distributed data store providing
95 * stronger consistency guarantees.
Thomas Vachuska90b453f2015-01-30 18:57:14 -080096 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -070097@Component(immediate = true)
Thomas Vachuska90b453f2015-01-30 18:57:14 -080098@Service
Madan Jampani6c02d9e2016-05-24 15:09:47 -070099public class DistributedApplicationStore extends ApplicationArchive
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800100 implements ApplicationStore {
101
102 private final Logger log = getLogger(getClass());
103
104 private static final MessageSubject APP_BITS_REQUEST = new MessageSubject("app-bits-request");
105
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700106 private static final int MAX_LOAD_RETRIES = 5;
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700107 private static final int RETRY_DELAY_MS = 2_000;
108
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800109 private static final int FETCH_TIMEOUT_MS = 10_000;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800110
Jonathan Hartd6fb0532016-01-21 17:28:20 -0800111 private static final int APP_LOAD_DELAY_MS = 500;
112
Andrea Campanellac8eca242016-04-06 19:34:32 -0700113 private static List<String> pendingApps = Lists.newArrayList();
114
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800115 public enum InternalState {
116 INSTALLED, ACTIVATED, DEACTIVATED
117 }
118
Madan Jampani6b5b7172015-02-23 13:02:26 -0800119 private ScheduledExecutorService executor;
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700120 private ExecutorService messageHandlingExecutor, activationExecutor;
Madan Jampani2af244a2015-02-22 13:12:01 -0800121
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700122 private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
Madan Jampanic5a17542016-08-22 10:26:11 -0700123 private Topic<Application> appActivationTopic;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected ClusterCommunicationService clusterCommunicator;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected ClusterService clusterService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700132 protected StorageService storageService;
133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani3e033bd2015-04-08 13:03:49 -0700135 protected ApplicationIdStore idStore;
Thomas Vachuskacf960112015-03-06 22:36:51 -0800136
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jordan Haltermana84936d2018-04-04 15:45:47 -0700138 protected VersionService versionService;
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700139
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700140 private final InternalAppsListener appsListener = new InternalAppsListener();
Madan Jampanic5a17542016-08-22 10:26:11 -0700141 private final Consumer<Application> appActivator = new AppActivator();
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700142
143 private Consumer<Status> statusChangeListener;
144
Thomas Vachuska761f0042015-11-11 19:10:17 -0800145 // Multimap to track which apps are required by others apps
146 // app -> { required-by, ... }
147 // Apps explicitly activated will be required by the CORE app
148 private final Multimap<ApplicationId, ApplicationId> requiredBy =
149 synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
150
151 private ApplicationId coreAppId;
152
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800153 @Activate
154 public void activate() {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700155 messageHandlingExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100156 "message-handler", log));
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700157 clusterCommunicator.addSubscriber(APP_BITS_REQUEST,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100158 bytes -> new String(bytes, Charsets.UTF_8),
159 name -> {
160 try {
161 log.info("Sending bits for application {}", name);
162 return toByteArray(getApplicationInputStream(name));
163 } catch (IOException e) {
164 throw new StorageException(e);
165 }
166 },
167 Function.identity(),
168 messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800169
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700170 apps = storageService.<ApplicationId, InternalApplicationHolder>consistentMapBuilder()
171 .withName("onos-apps")
172 .withRelaxedReadConsistency()
173 .withSerializer(Serializer.using(KryoNamespaces.API,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100174 InternalApplicationHolder.class,
175 InternalState.class))
Jordan Haltermana84936d2018-04-04 15:45:47 -0700176 .withVersion(versionService.version())
177 .withRevisionType(RevisionType.PROPAGATE)
178 .withCompatibilityFunction(this::convertApplication)
Jonathan Hart6ec029a2015-03-24 17:12:35 -0700179 .build();
Thomas Vachuskacf960112015-03-06 22:36:51 -0800180
Jordan Halterman400bbe52018-04-05 23:07:47 -0700181 appActivationTopic = storageService.<Application>topicBuilder()
182 .withName("onos-apps-activation-topic")
183 .withSerializer(Serializer.using(KryoNamespaces.API))
184 .withVersion(versionService.version())
185 .withRevisionType(RevisionType.PROPAGATE)
186 .withCompatibilityFunction(this::convertApplication)
187 .build();
Madan Jampaniae538ff2016-08-19 12:42:43 -0700188
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700189 activationExecutor = newSingleThreadExecutor(groupedThreads("onos/store/app",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100190 "app-activation", log));
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700191 appActivationTopic.subscribe(appActivator, activationExecutor);
Madan Jampaniae538ff2016-08-19 12:42:43 -0700192
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700193 executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
194 statusChangeListener = status -> {
195 if (status == Status.ACTIVE) {
196 executor.execute(this::bootstrapExistingApplications);
197 }
198 };
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700199 apps.addListener(appsListener, activationExecutor);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700200 apps.addStatusChangeListener(statusChangeListener);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800201 coreAppId = getId(CoreService.CORE_APP_NAME);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700202
Jordan Haltermana84936d2018-04-04 15:45:47 -0700203 activateExistingApplications();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800204 log.info("Started");
205 }
206
Thomas Vachuskacf960112015-03-06 22:36:51 -0800207 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700208 * Converts the versions of stored applications propagated from the prior version to the local application versions.
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700209 */
Jordan Haltermana84936d2018-04-04 15:45:47 -0700210 private InternalApplicationHolder convertApplication(InternalApplicationHolder appHolder, Version version) {
211 // Load the application description from disk. If the version doesn't match the persisted
212 // version, update the stored application with the new version.
213 ApplicationDescription appDesc = getApplicationDescription(appHolder.app.id().name());
214 if (!appDesc.version().equals(appHolder.app().version())) {
Jordan Halterman400bbe52018-04-05 23:07:47 -0700215 Application newApplication = DefaultApplication.builder(appDesc)
216 .withAppId(appHolder.app.id())
Jordan Haltermana84936d2018-04-04 15:45:47 -0700217 .build();
218 return new InternalApplicationHolder(
219 newApplication, appHolder.state, appHolder.permissions);
220 }
221 return appHolder;
222 }
223
224 /**
Jordan Halterman400bbe52018-04-05 23:07:47 -0700225 * Converts the versions of stored applications propagated from the prior version to the local application versions.
226 */
227 private Application convertApplication(Application app, Version version) {
228 // Load the application description from disk. If the version doesn't match the persisted
229 // version, update the stored application with the new version.
230 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
231 if (!appDesc.version().equals(app.version())) {
232 return DefaultApplication.builder(appDesc)
233 .withAppId(app.id())
234 .build();
235 }
236 return app;
237 }
238
239 /**
Jordan Haltermana84936d2018-04-04 15:45:47 -0700240 * Activates applications that should be activated according to the distributed store.
241 */
242 private void activateExistingApplications() {
Jon Hall8069fdd2018-02-02 10:48:20 -0800243 getApplicationNames().forEach(appName -> {
244 // Only update the application version if the application has already been installed.
245 ApplicationId appId = getId(appName);
246 if (appId != null) {
Jordan Haltermana84936d2018-04-04 15:45:47 -0700247 ApplicationDescription appDesc = getApplicationDescription(appName);
248 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700249
Jordan Haltermana84936d2018-04-04 15:45:47 -0700250 // If the application has already been activated, set the local state to active.
251 if (appHolder != null
252 && appDesc.version().equals(appHolder.app().version())
253 && appHolder.state == ACTIVATED) {
254 setActive(appName);
255 updateTime(appName);
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700256 }
Jon Hall8069fdd2018-02-02 10:48:20 -0800257 }
258 });
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700259 }
260
Jon Hall8069fdd2018-02-02 10:48:20 -0800261
Jordan Haltermanfedad5b2017-10-09 23:16:27 -0700262 /**
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700263 * Processes existing applications from the distributed map. This is done to
264 * account for events that this instance may be have missed due to a staggered start.
265 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700266 private void bootstrapExistingApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700267 apps.asJavaMap().forEach((appId, holder) -> setupApplicationAndNotify(appId, holder.app(), holder.state()));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700268 }
269
270 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800271 * Loads the application inventory from the disk and activates apps if
272 * they are marked to be active.
273 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800274 private void loadFromDisk() {
Charles Chane889e2d2015-11-17 12:01:02 -0800275 getApplicationNames().forEach(appName -> {
276 Application app = loadFromDisk(appName);
277 if (app != null && isActive(app.id().name())) {
Thomas Vachuskaad2965b2018-04-02 15:23:31 -0700278 // For now, apps loaded from disk will be marked as having been
279 // activated explicitly, which means they won't deactivate
280 // implicitly when all dependent apps have been deactivated.
281 requiredBy.put(app.id(), coreAppId);
Charles Chane889e2d2015-11-17 12:01:02 -0800282 activate(app.id(), false);
283 // TODO Load app permissions
284 }
285 });
286 }
287
288 private Application loadFromDisk(String appName) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700289 pendingApps.add(appName);
290
Charles Chane889e2d2015-11-17 12:01:02 -0800291 for (int i = 0; i < MAX_LOAD_RETRIES; i++) {
292 try {
293 // Directly return if app already exists
294 ApplicationId appId = getId(appName);
295 if (appId != null) {
Ray Milkey64591a62016-01-12 09:50:45 -0800296 Application application = getApplication(appId);
297 if (application != null) {
Andrea Campanellac8eca242016-04-06 19:34:32 -0700298 pendingApps.remove(appName);
Ray Milkey64591a62016-01-12 09:50:45 -0800299 return application;
300 }
Thomas Vachuska62f04a42015-04-22 14:38:34 -0700301 }
Charles Chane889e2d2015-11-17 12:01:02 -0800302
303 ApplicationDescription appDesc = getApplicationDescription(appName);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700304
305 Optional<String> loop = appDesc.requiredApps().stream()
306 .filter(app -> pendingApps.contains(app)).findAny();
307 if (loop.isPresent()) {
308 log.error("Circular app dependency detected: {} -> {}", pendingApps, loop.get());
309 pendingApps.remove(appName);
310 return null;
311 }
312
Charles Chane889e2d2015-11-17 12:01:02 -0800313 boolean success = appDesc.requiredApps().stream()
314 .noneMatch(requiredApp -> loadFromDisk(requiredApp) == null);
Andrea Campanellac8eca242016-04-06 19:34:32 -0700315 pendingApps.remove(appName);
316
Charles Chane889e2d2015-11-17 12:01:02 -0800317 return success ? create(appDesc, false) : null;
Andrea Campanellac8eca242016-04-06 19:34:32 -0700318
Charles Chane889e2d2015-11-17 12:01:02 -0800319 } catch (Exception e) {
Yuta HIGUCHI1d6fef32018-05-07 18:24:20 -0700320 log.warn("Unable to load application {} from disk: {}; retrying",
321 appName,
322 Throwables.getRootCause(e).getMessage());
323 log.debug("Full error details:", e);
Charles Chane889e2d2015-11-17 12:01:02 -0800324 randomDelay(RETRY_DELAY_MS); //FIXME: This is a deliberate hack; fix in Falcon
Thomas Vachuskacf960112015-03-06 22:36:51 -0800325 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800326 }
Andrea Campanellac8eca242016-04-06 19:34:32 -0700327 pendingApps.remove(appName);
Charles Chane889e2d2015-11-17 12:01:02 -0800328 return null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800329 }
330
331 @Deactivate
332 public void deactivate() {
Madan Jampani2af244a2015-02-22 13:12:01 -0800333 clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700334 apps.removeStatusChangeListener(statusChangeListener);
335 apps.removeListener(appsListener);
Madan Jampanic5a17542016-08-22 10:26:11 -0700336 appActivationTopic.unsubscribe(appActivator);
Madan Jampani2af244a2015-02-22 13:12:01 -0800337 messageHandlingExecutor.shutdown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700338 activationExecutor.shutdown();
Madan Jampani6b5b7172015-02-23 13:02:26 -0800339 executor.shutdown();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800340 log.info("Stopped");
341 }
342
343 @Override
Thomas Vachuskacf960112015-03-06 22:36:51 -0800344 public void setDelegate(ApplicationStoreDelegate delegate) {
345 super.setDelegate(delegate);
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700346 executor.execute(this::bootstrapExistingApplications);
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700347 executor.schedule((Runnable) this::loadFromDisk, APP_LOAD_DELAY_MS, TimeUnit.MILLISECONDS);
Thomas Vachuskacf960112015-03-06 22:36:51 -0800348 }
349
350 @Override
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800351 public Set<Application> getApplications() {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700352 return ImmutableSet.copyOf(apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100353 .stream()
354 .map(Versioned::value)
355 .map(InternalApplicationHolder::app)
356 .collect(Collectors.toSet()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800357 }
358
359 @Override
360 public ApplicationId getId(String name) {
361 return idStore.getAppId(name);
362 }
363
364 @Override
365 public Application getApplication(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700366 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
367 return appHolder != null ? appHolder.app() : null;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800368 }
369
370 @Override
371 public ApplicationState getState(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700372 InternalApplicationHolder appHolder = Versioned.valueOrNull(apps.get(appId));
373 InternalState state = appHolder != null ? appHolder.state() : null;
374 return state == null ? null : state == ACTIVATED ? ApplicationState.ACTIVE : ApplicationState.INSTALLED;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800375 }
376
377 @Override
378 public Application create(InputStream appDescStream) {
379 ApplicationDescription appDesc = saveApplication(appDescStream);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800380 if (hasPrerequisites(appDesc)) {
381 return create(appDesc, true);
382 }
Jonathan Hart14651b52016-06-07 17:59:53 -0700383 // Purge bits off disk if we don't have prerequisites to allow app to be
384 // reinstalled later
385 purgeApplication(appDesc.name());
Thomas Vachuska761f0042015-11-11 19:10:17 -0800386 throw new ApplicationException("Missing dependencies for app " + appDesc.name());
387 }
388
389 private boolean hasPrerequisites(ApplicationDescription app) {
Yuta HIGUCHI56a7a6b2017-02-22 15:17:42 -0800390 for (String required : app.requiredApps()) {
391 ApplicationId id = getId(required);
392 if (id == null || getApplication(id) == null) {
393 log.error("{} required for {} not available", required, app.name());
394 return false;
395 }
396 }
397 return true;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800398 }
399
Thomas Vachuska161baf52015-03-27 16:15:39 -0700400 private Application create(ApplicationDescription appDesc, boolean updateTime) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800401 Application app = registerApp(appDesc);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700402 if (updateTime) {
403 updateTime(app.id().name());
404 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700405 InternalApplicationHolder previousApp =
406 Versioned.valueOrNull(apps.putIfAbsent(app.id(), new InternalApplicationHolder(app, INSTALLED, null)));
407 return previousApp != null ? previousApp.app() : app;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800408 }
409
410 @Override
411 public void remove(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700412 uninstallDependentApps(appId);
413 apps.remove(appId);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800414 }
415
Thomas Vachuska761f0042015-11-11 19:10:17 -0800416 // Uninstalls all apps that depend on the given app.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700417 private void uninstallDependentApps(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800418 getApplications().stream()
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700419 .filter(a -> a.requiredApps().contains(appId.name()))
Thomas Vachuska761f0042015-11-11 19:10:17 -0800420 .forEach(a -> remove(a.id()));
421 }
422
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800423 @Override
424 public void activate(ApplicationId appId) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800425 activate(appId, coreAppId);
426 }
427
428 private void activate(ApplicationId appId, ApplicationId forAppId) {
429 requiredBy.put(appId, forAppId);
Thomas Vachuska161baf52015-03-27 16:15:39 -0700430 activate(appId, true);
431 }
432
433 private void activate(ApplicationId appId, boolean updateTime) {
Jonathan Hartbd85f782016-06-20 16:13:37 -0700434 Versioned<InternalApplicationHolder> vAppHolder = apps.get(appId);
435 if (vAppHolder != null) {
Thomas Vachuska161baf52015-03-27 16:15:39 -0700436 if (updateTime) {
437 updateTime(appId.name());
438 }
Jonathan Hartbd85f782016-06-20 16:13:37 -0700439 activateRequiredApps(vAppHolder.value().app());
440
441 apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
442 (k, v) -> new InternalApplicationHolder(
443 v.app(), ACTIVATED, v.permissions()));
Madan Jampanic5a17542016-08-22 10:26:11 -0700444 appActivationTopic.publish(vAppHolder.value().app());
Thomas Vachuska484ae542017-08-29 15:21:57 -0700445 appActivationTopic.publish(null); // FIXME: Once ONOS-6977 is fixed
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800446 }
447 }
448
Thomas Vachuska761f0042015-11-11 19:10:17 -0800449 // Activates all apps required by this application.
450 private void activateRequiredApps(Application app) {
451 app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
452 }
453
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800454 @Override
455 public void deactivate(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700456 deactivateDependentApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800457 deactivate(appId, coreAppId);
458 }
459
460 private void deactivate(ApplicationId appId, ApplicationId forAppId) {
461 requiredBy.remove(appId, forAppId);
462 if (requiredBy.get(appId).isEmpty()) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700463 AtomicBoolean stateChanged = new AtomicBoolean(false);
464 apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100465 v -> v != null && v.state() != DEACTIVATED,
466 (k, v) -> {
467 stateChanged.set(true);
468 return new InternalApplicationHolder(v.app(), DEACTIVATED, v.permissions());
469 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700470 if (stateChanged.get()) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800471 updateTime(appId.name());
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700472 deactivateRequiredApps(appId);
Thomas Vachuska761f0042015-11-11 19:10:17 -0800473 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800474 }
475 }
476
Thomas Vachuska761f0042015-11-11 19:10:17 -0800477 // Deactivates all apps that require this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700478 private void deactivateDependentApps(ApplicationId appId) {
479 apps.values()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100480 .stream()
481 .map(Versioned::value)
482 .filter(a -> a.state() == ACTIVATED)
483 .filter(a -> a.app().requiredApps().contains(appId.name()))
484 .forEach(a -> deactivate(a.app().id()));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800485 }
486
487 // Deactivates all apps required by this application.
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700488 private void deactivateRequiredApps(ApplicationId appId) {
489 getApplication(appId).requiredApps()
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100490 .stream()
491 .map(this::getId)
492 .map(apps::get)
493 .map(Versioned::value)
494 .filter(a -> a.state() == ACTIVATED)
495 .forEach(a -> deactivate(a.app().id(), appId));
Thomas Vachuska761f0042015-11-11 19:10:17 -0800496 }
497
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800498 @Override
499 public Set<Permission> getPermissions(ApplicationId appId) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700500 InternalApplicationHolder app = Versioned.valueOrNull(apps.get(appId));
501 return app != null ? ImmutableSet.copyOf(app.permissions()) : ImmutableSet.of();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800502 }
503
504 @Override
505 public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700506 AtomicBoolean permissionsChanged = new AtomicBoolean(false);
507 Versioned<InternalApplicationHolder> appHolder = apps.computeIf(appId,
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100508 v -> v != null && !Sets.symmetricDifference(v.permissions(), permissions).isEmpty(),
509 (k, v) -> {
510 permissionsChanged.set(true);
511 return new InternalApplicationHolder(v.app(), v.state(), ImmutableSet.copyOf(permissions));
512 });
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700513 if (permissionsChanged.get()) {
Ray Milkey2f6f9072016-08-23 13:18:50 -0700514 notifyDelegate(new ApplicationEvent(APP_PERMISSIONS_CHANGED, appHolder.value().app()));
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800515 }
516 }
517
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700518 @Override
519 public InputStream getApplicationArchive(ApplicationId appId) {
520 return getApplicationInputStream(appId.name());
521 }
522
Madan Jampanic5a17542016-08-22 10:26:11 -0700523 private class AppActivator implements Consumer<Application> {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700524 @Override
Madan Jampanic5a17542016-08-22 10:26:11 -0700525 public void accept(Application app) {
Thomas Vachuska484ae542017-08-29 15:21:57 -0700526 if (app != null) { // FIXME: Once ONOS-6977 is fixed
527 String appName = app.id().name();
528 installAppIfNeeded(app);
529 setActive(appName);
530 notifyDelegate(new ApplicationEvent(APP_ACTIVATED, app));
531 }
Madan Jampaniae538ff2016-08-19 12:42:43 -0700532 }
533 }
534
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800535 /**
536 * Listener to application state distributed map changes.
537 */
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700538 private final class InternalAppsListener
539 implements MapEventListener<ApplicationId, InternalApplicationHolder> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800540 @Override
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700541 public void event(MapEvent<ApplicationId, InternalApplicationHolder> event) {
Thomas Vachuska4fcdae72015-03-24 10:22:54 -0700542 if (delegate == null) {
543 return;
544 }
545
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700546 ApplicationId appId = event.key();
547 InternalApplicationHolder newApp = event.newValue() == null ? null : event.newValue().value();
548 InternalApplicationHolder oldApp = event.oldValue() == null ? null : event.oldValue().value();
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100549 if (event.type() == MapEvent.Type.UPDATE && (newApp == null || oldApp == null ||
550 newApp.state() == oldApp.state())) {
551 log.warn("Can't update the application {}", event.key());
Ray Milkey74e59132018-01-17 15:24:52 -0800552 return;
553 }
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100554 if ((event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) && newApp != null) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700555 setupApplicationAndNotify(appId, newApp.app(), newApp.state());
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100556 } else if (event.type() == MapEvent.Type.REMOVE && oldApp != null) {
Deepa Vadireddy9719eb02017-11-23 10:12:30 +0000557 notifyDelegate(new ApplicationEvent(APP_UNINSTALLED, oldApp.app()));
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100558 purgeApplication(appId.name());
559 } else {
560 log.warn("Can't perform {} on application {}", event.type(), event.key());
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800561 }
562 }
563 }
564
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700565 private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
Madan Jampaniae538ff2016-08-19 12:42:43 -0700566 // ACTIVATED state is handled separately in NextAppToActivateValueListener
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700567 if (state == INSTALLED) {
568 fetchBitsIfNeeded(app);
Ray Milkey2f6f9072016-08-23 13:18:50 -0700569 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700570 } else if (state == DEACTIVATED) {
571 clearActive(appId.name());
Ray Milkey2f6f9072016-08-23 13:18:50 -0700572 notifyDelegate(new ApplicationEvent(APP_DEACTIVATED, app));
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700573 }
574 }
575
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800576 /**
577 * Determines if the application bits are available locally.
578 */
579 private boolean appBitsAvailable(Application app) {
580 try {
581 ApplicationDescription appDesc = getApplicationDescription(app.id().name());
582 return appDesc.version().equals(app.version());
583 } catch (ApplicationException e) {
584 return false;
585 }
586 }
587
588 /**
589 * Fetches the bits from the cluster peers if necessary.
590 */
591 private void fetchBitsIfNeeded(Application app) {
592 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700593 fetchBits(app, false);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800594 }
595 }
596
597 /**
598 * Installs the application if necessary from the application peers.
599 */
600 private void installAppIfNeeded(Application app) {
601 if (!appBitsAvailable(app)) {
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700602 fetchBits(app, true);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800603 }
604 }
605
606 /**
607 * Fetches the bits from the cluster peers.
608 */
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700609 private void fetchBits(Application app, boolean delegateInstallation) {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800610 ControllerNode localNode = clusterService.getLocalNode();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800611 CountDownLatch latch = new CountDownLatch(1);
612
613 // FIXME: send message with name & version to make sure we don't get served old bits
614
615 log.info("Downloading bits for application {}", app.id().name());
616 for (ControllerNode node : clusterService.getNodes()) {
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700617 if (latch.getCount() == 0) {
618 break;
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800619 }
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700620 if (node.equals(localNode)) {
621 continue;
622 }
623 clusterCommunicator.sendAndReceive(app.id().name(),
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100624 APP_BITS_REQUEST,
625 s -> s.getBytes(Charsets.UTF_8),
626 Function.identity(),
627 node.id())
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700628 .whenCompleteAsync((bits, error) -> {
629 if (error == null && latch.getCount() > 0) {
630 saveApplication(new ByteArrayInputStream(bits));
631 log.info("Downloaded bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100632 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700633 latch.countDown();
Thomas Vachuska62ba45f2017-10-02 13:17:28 -0700634 if (delegateInstallation) {
635 notifyDelegate(new ApplicationEvent(APP_INSTALLED, app));
636 }
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700637 } else if (error != null) {
638 log.warn("Unable to fetch bits for application {} from node {}",
Andrea Campanellaaaa9f112018-01-26 12:45:24 +0100639 app.id().name(), node.id());
Thomas Vachuskad0d58542015-06-03 12:38:44 -0700640 }
Jordan Halterman85f560d2017-09-20 11:57:58 -0700641 }, messageHandlingExecutor);
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800642 }
643
644 try {
645 if (!latch.await(FETCH_TIMEOUT_MS, MILLISECONDS)) {
646 log.warn("Unable to fetch bits for application {}", app.id().name());
647 }
648 } catch (InterruptedException e) {
649 log.warn("Interrupted while fetching bits for application {}", app.id().name());
Ray Milkey5c7d4882018-02-05 14:50:39 -0800650 Thread.currentThread().interrupt();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800651 }
652 }
653
654 /**
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800655 * Produces a registered application from the supplied description.
656 */
657 private Application registerApp(ApplicationDescription appDesc) {
658 ApplicationId appId = idStore.registerApplication(appDesc.name());
Ray Milkey47c95412017-09-15 10:40:48 -0700659 return DefaultApplication
660 .builder(appDesc)
661 .withAppId(appId)
662 .build();
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800663 }
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700664
665 /**
666 * Internal class for holding app information.
667 */
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700668 private static final class InternalApplicationHolder {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700669 private final Application app;
670 private final InternalState state;
671 private final Set<Permission> permissions;
672
673 @SuppressWarnings("unused")
674 private InternalApplicationHolder() {
675 app = null;
676 state = null;
677 permissions = null;
678 }
679
Thomas Vachuskabddbb252016-08-08 14:25:05 -0700680 private InternalApplicationHolder(Application app, InternalState state, Set<Permission> permissions) {
Madan Jampani6c02d9e2016-05-24 15:09:47 -0700681 this.app = Preconditions.checkNotNull(app);
682 this.state = state;
683 this.permissions = permissions == null ? null : ImmutableSet.copyOf(permissions);
684 }
685
686 public Application app() {
687 return app;
688 }
689
690 public InternalState state() {
691 return state;
692 }
693
694 public Set<Permission> permissions() {
695 return permissions;
696 }
697
698 @Override
699 public String toString() {
700 return MoreObjects.toStringHelper(getClass())
701 .add("app", app.id())
702 .add("state", state)
703 .toString();
704 }
705 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800706}