blob: d93432dbf5f1d40241882e8fcf4280f6a9f4b431 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
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 */
Brian O'Connor66630c82014-10-02 21:08:19 -070016package org.onlab.onos.net.intent.impl;
17
Ray Milkeye97ede92014-11-20 10:43:12 -080018import java.util.ArrayList;
19import java.util.Collections;
20import java.util.List;
21import java.util.Map;
22import java.util.concurrent.ConcurrentHashMap;
23import java.util.concurrent.ConcurrentMap;
24import java.util.concurrent.ExecutionException;
25import java.util.concurrent.ExecutorService;
26import java.util.concurrent.Future;
27import java.util.concurrent.TimeUnit;
28import java.util.concurrent.TimeoutException;
29
Brian O'Connor66630c82014-10-02 21:08:19 -070030import org.apache.felix.scr.annotations.Activate;
31import org.apache.felix.scr.annotations.Component;
32import org.apache.felix.scr.annotations.Deactivate;
33import org.apache.felix.scr.annotations.Reference;
34import org.apache.felix.scr.annotations.ReferenceCardinality;
35import org.apache.felix.scr.annotations.Service;
Brian O'Connor72a034c2014-11-26 18:24:23 -080036import org.onlab.onos.core.ApplicationId;
Brian O'Connor520c0522014-11-23 23:50:47 -080037import org.onlab.onos.core.CoreService;
38import org.onlab.onos.core.IdGenerator;
Brian O'Connor66630c82014-10-02 21:08:19 -070039import org.onlab.onos.event.AbstractListenerRegistry;
40import org.onlab.onos.event.EventDeliveryService;
Brian O'Connorcb900f42014-10-07 21:55:33 -070041import org.onlab.onos.net.flow.CompletedBatchOperation;
Brian O'Connorf2dbde52014-10-10 16:20:24 -070042import org.onlab.onos.net.flow.FlowRuleBatchOperation;
43import org.onlab.onos.net.flow.FlowRuleService;
Brian O'Connor66630c82014-10-02 21:08:19 -070044import org.onlab.onos.net.intent.Intent;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070045import org.onlab.onos.net.intent.IntentBatchDelegate;
46import org.onlab.onos.net.intent.IntentBatchService;
Brian O'Connor66630c82014-10-02 21:08:19 -070047import org.onlab.onos.net.intent.IntentCompiler;
48import org.onlab.onos.net.intent.IntentEvent;
49import org.onlab.onos.net.intent.IntentException;
50import org.onlab.onos.net.intent.IntentExtensionService;
51import org.onlab.onos.net.intent.IntentId;
52import org.onlab.onos.net.intent.IntentInstaller;
53import org.onlab.onos.net.intent.IntentListener;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070054import org.onlab.onos.net.intent.IntentOperation;
Brian O'Connor66630c82014-10-02 21:08:19 -070055import org.onlab.onos.net.intent.IntentOperations;
56import org.onlab.onos.net.intent.IntentService;
57import org.onlab.onos.net.intent.IntentState;
58import org.onlab.onos.net.intent.IntentStore;
59import org.onlab.onos.net.intent.IntentStoreDelegate;
60import org.slf4j.Logger;
61
Ray Milkeye97ede92014-11-20 10:43:12 -080062import com.google.common.collect.ImmutableList;
63import com.google.common.collect.ImmutableMap;
64import com.google.common.collect.Lists;
65import com.google.common.collect.Maps;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070066
67import static com.google.common.base.Preconditions.checkArgument;
68import static com.google.common.base.Preconditions.checkNotNull;
69import static java.util.concurrent.Executors.newSingleThreadExecutor;
Ray Milkeye97ede92014-11-20 10:43:12 -080070import static org.onlab.onos.net.intent.IntentState.COMPILING;
71import static org.onlab.onos.net.intent.IntentState.FAILED;
72import static org.onlab.onos.net.intent.IntentState.INSTALLED;
73import static org.onlab.onos.net.intent.IntentState.INSTALLING;
74import static org.onlab.onos.net.intent.IntentState.WITHDRAWING;
75import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070076import static org.onlab.util.Tools.namedThreads;
77import static org.slf4j.LoggerFactory.getLogger;
Brian O'Connor66630c82014-10-02 21:08:19 -070078
79/**
80 * An implementation of Intent Manager.
81 */
82@Component(immediate = true)
83@Service
84public class IntentManager
85 implements IntentService, IntentExtensionService {
Sho SHIMIZU8b5051d2014-11-05 11:24:13 -080086 private static final Logger log = getLogger(IntentManager.class);
Brian O'Connor66630c82014-10-02 21:08:19 -070087
88 public static final String INTENT_NULL = "Intent cannot be null";
89 public static final String INTENT_ID_NULL = "Intent ID cannot be null";
90
91 // Collections for compiler, installer, and listener are ONOS instance local
92 private final ConcurrentMap<Class<? extends Intent>,
93 IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
Thomas Vachuskab97cf282014-10-20 23:31:12 -070094 private final ConcurrentMap<Class<? extends Intent>,
95 IntentInstaller<? extends Intent>> installers = new ConcurrentHashMap<>();
Brian O'Connor66630c82014-10-02 21:08:19 -070096
97 private final AbstractListenerRegistry<IntentEvent, IntentListener>
tom95329eb2014-10-06 08:40:06 -070098 listenerRegistry = new AbstractListenerRegistry<>();
Brian O'Connor66630c82014-10-02 21:08:19 -070099
Brian O'Connor520c0522014-11-23 23:50:47 -0800100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected CoreService coreService;
Brian O'Connor66630c82014-10-02 21:08:19 -0700102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected IntentStore store;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700107 protected IntentBatchService batchService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
tom85258ee2014-10-07 00:10:02 -0700110 protected ObjectiveTrackerService trackerService;
tom95329eb2014-10-06 08:40:06 -0700111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connor66630c82014-10-02 21:08:19 -0700113 protected EventDeliveryService eventDispatcher;
114
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected FlowRuleService flowRuleService;
117
Brian O'Connor520c0522014-11-23 23:50:47 -0800118
119 private ExecutorService executor;
120 private ExecutorService monitorExecutor;
121
122 private final IntentStoreDelegate delegate = new InternalStoreDelegate();
123 private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
124 private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
125 private IdGenerator idGenerator;
126
Brian O'Connor66630c82014-10-02 21:08:19 -0700127 @Activate
128 public void activate() {
129 store.setDelegate(delegate);
tom95329eb2014-10-06 08:40:06 -0700130 trackerService.setDelegate(topoDelegate);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700131 batchService.setDelegate(batchDelegate);
Brian O'Connor66630c82014-10-02 21:08:19 -0700132 eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700133 executor = newSingleThreadExecutor(namedThreads("onos-intents"));
134 monitorExecutor = newSingleThreadExecutor(namedThreads("onos-intent-monitor"));
Brian O'Connor520c0522014-11-23 23:50:47 -0800135 idGenerator = coreService.getIdGenerator("intent-ids");
136 Intent.bindIdGenerator(idGenerator);
Brian O'Connor66630c82014-10-02 21:08:19 -0700137 log.info("Started");
138 }
139
140 @Deactivate
141 public void deactivate() {
142 store.unsetDelegate(delegate);
tom95329eb2014-10-06 08:40:06 -0700143 trackerService.unsetDelegate(topoDelegate);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700144 batchService.unsetDelegate(batchDelegate);
Brian O'Connor66630c82014-10-02 21:08:19 -0700145 eventDispatcher.removeSink(IntentEvent.class);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700146 executor.shutdown();
147 monitorExecutor.shutdown();
Brian O'Connor520c0522014-11-23 23:50:47 -0800148 Intent.unbindIdGenerator(idGenerator);
Brian O'Connor66630c82014-10-02 21:08:19 -0700149 log.info("Stopped");
150 }
151
152 @Override
153 public void submit(Intent intent) {
154 checkNotNull(intent, INTENT_NULL);
Brian O'Connor72a034c2014-11-26 18:24:23 -0800155 execute(IntentOperations.builder(intent.appId())
156 .addSubmitOperation(intent).build());
Brian O'Connor66630c82014-10-02 21:08:19 -0700157 }
158
159 @Override
160 public void withdraw(Intent intent) {
161 checkNotNull(intent, INTENT_NULL);
Brian O'Connor72a034c2014-11-26 18:24:23 -0800162 execute(IntentOperations.builder(intent.appId())
163 .addWithdrawOperation(intent.id()).build());
Brian O'Connor66630c82014-10-02 21:08:19 -0700164 }
165
Brian O'Connor66630c82014-10-02 21:08:19 -0700166 @Override
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700167 public void replace(IntentId oldIntentId, Intent newIntent) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700168 checkNotNull(oldIntentId, INTENT_ID_NULL);
169 checkNotNull(newIntent, INTENT_NULL);
Brian O'Connor72a034c2014-11-26 18:24:23 -0800170 execute(IntentOperations.builder(newIntent.appId())
Ray Milkeye97ede92014-11-20 10:43:12 -0800171 .addReplaceOperation(oldIntentId, newIntent)
172 .build());
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700173 }
174
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700175 @Override
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700176 public void execute(IntentOperations operations) {
Brian O'Connore2ff25a2014-11-18 19:25:43 -0800177 if (operations.operations().isEmpty()) {
178 return;
179 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700180 batchService.addIntentOperations(operations);
Brian O'Connor66630c82014-10-02 21:08:19 -0700181 }
182
183 @Override
184 public Iterable<Intent> getIntents() {
185 return store.getIntents();
186 }
187
188 @Override
189 public long getIntentCount() {
190 return store.getIntentCount();
191 }
192
193 @Override
194 public Intent getIntent(IntentId id) {
195 checkNotNull(id, INTENT_ID_NULL);
196 return store.getIntent(id);
197 }
198
199 @Override
200 public IntentState getIntentState(IntentId id) {
201 checkNotNull(id, INTENT_ID_NULL);
202 return store.getIntentState(id);
203 }
204
205 @Override
Thomas Vachuska10d4abc2014-10-21 12:47:26 -0700206 public List<Intent> getInstallableIntents(IntentId intentId) {
207 checkNotNull(intentId, INTENT_ID_NULL);
208 return store.getInstallableIntents(intentId);
209 }
210
211 @Override
Brian O'Connor66630c82014-10-02 21:08:19 -0700212 public void addListener(IntentListener listener) {
213 listenerRegistry.addListener(listener);
214 }
215
216 @Override
217 public void removeListener(IntentListener listener) {
218 listenerRegistry.removeListener(listener);
219 }
220
221 @Override
222 public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
223 compilers.put(cls, compiler);
224 }
225
226 @Override
227 public <T extends Intent> void unregisterCompiler(Class<T> cls) {
228 compilers.remove(cls);
229 }
230
231 @Override
232 public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
233 return ImmutableMap.copyOf(compilers);
234 }
235
236 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700237 public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700238 installers.put(cls, installer);
239 }
240
241 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700242 public <T extends Intent> void unregisterInstaller(Class<T> cls) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700243 installers.remove(cls);
244 }
245
246 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700247 public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
Brian O'Connor66630c82014-10-02 21:08:19 -0700248 return ImmutableMap.copyOf(installers);
249 }
250
251 /**
Brian O'Connor66630c82014-10-02 21:08:19 -0700252 * Returns the corresponding intent compiler to the specified intent.
253 *
254 * @param intent intent
tom95329eb2014-10-06 08:40:06 -0700255 * @param <T> the type of intent
Brian O'Connor66630c82014-10-02 21:08:19 -0700256 * @return intent compiler corresponding to the specified intent
257 */
258 private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
259 @SuppressWarnings("unchecked")
260 IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
261 if (compiler == null) {
262 throw new IntentException("no compiler for class " + intent.getClass());
263 }
264 return compiler;
265 }
266
267 /**
268 * Returns the corresponding intent installer to the specified installable intent.
tom95329eb2014-10-06 08:40:06 -0700269 *
Brian O'Connor66630c82014-10-02 21:08:19 -0700270 * @param intent intent
tom95329eb2014-10-06 08:40:06 -0700271 * @param <T> the type of installable intent
Brian O'Connor66630c82014-10-02 21:08:19 -0700272 * @return intent installer corresponding to the specified installable intent
273 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700274 private <T extends Intent> IntentInstaller<T> getInstaller(T intent) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700275 @SuppressWarnings("unchecked")
276 IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
277 if (installer == null) {
278 throw new IntentException("no installer for class " + intent.getClass());
279 }
280 return installer;
281 }
282
283 /**
tom85258ee2014-10-07 00:10:02 -0700284 * Compiles the specified intent.
Brian O'Connor66630c82014-10-02 21:08:19 -0700285 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700286 * @param update intent update
Brian O'Connor66630c82014-10-02 21:08:19 -0700287 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700288 private void executeCompilingPhase(IntentUpdate update) {
289 Intent intent = update.newIntent();
tom85258ee2014-10-07 00:10:02 -0700290 // Indicate that the intent is entering the compiling phase.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700291 update.setState(intent, COMPILING);
tom85258ee2014-10-07 00:10:02 -0700292
293 try {
294 // Compile the intent into installable derivatives.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700295 List<Intent> installables = compileIntent(intent, update);
tom85258ee2014-10-07 00:10:02 -0700296
297 // If all went well, associate the resulting list of installable
298 // intents with the top-level intent and proceed to install.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700299 update.setInstallables(installables);
300 } catch (IntentException e) {
Jonathan Hart11096402014-10-20 17:31:49 -0700301 log.warn("Unable to compile intent {} due to:", intent.id(), e);
tom53945d52014-10-07 11:01:36 -0700302
tom85258ee2014-10-07 00:10:02 -0700303 // If compilation failed, mark the intent as failed.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700304 update.setState(intent, FAILED);
tom85258ee2014-10-07 00:10:02 -0700305 }
306 }
307
Brian O'Connorcb900f42014-10-07 21:55:33 -0700308 /**
309 * Compiles an intent recursively.
310 *
311 * @param intent intent
312 * @return result of compilation
313 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700314 private List<Intent> compileIntent(Intent intent, IntentUpdate update) {
Thomas Vachuska4926c1b2014-10-21 00:44:10 -0700315 if (intent.isInstallable()) {
316 return ImmutableList.of(intent);
Brian O'Connor66630c82014-10-02 21:08:19 -0700317 }
Brian O'Connorcb900f42014-10-07 21:55:33 -0700318
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700319 registerSubclassCompilerIfNeeded(intent);
320 List<Intent> previous = update.oldInstallables();
321 // FIXME: get previous resources
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700322 List<Intent> installable = new ArrayList<>();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700323 for (Intent compiled : getCompiler(intent).compile(intent, previous, null)) {
324 installable.addAll(compileIntent(compiled, update));
Brian O'Connorcb900f42014-10-07 21:55:33 -0700325 }
tom85258ee2014-10-07 00:10:02 -0700326 return installable;
Brian O'Connor66630c82014-10-02 21:08:19 -0700327 }
328
329 /**
tom85258ee2014-10-07 00:10:02 -0700330 * Installs all installable intents associated with the specified top-level
331 * intent.
Brian O'Connor66630c82014-10-02 21:08:19 -0700332 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700333 * @param update intent update
Brian O'Connor66630c82014-10-02 21:08:19 -0700334 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700335 private void executeInstallingPhase(IntentUpdate update) {
336 if (update.newInstallables() == null) {
337 //no failed intents allowed past this point...
338 return;
tom85258ee2014-10-07 00:10:02 -0700339 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700340 // Indicate that the intent is entering the installing phase.
341 update.setState(update.newIntent(), INSTALLING);
342
343 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
344 for (Intent installable : update.newInstallables()) {
345 registerSubclassInstallerIfNeeded(installable);
346 trackerService.addTrackedResources(update.newIntent().id(),
347 installable.resources());
348 try {
349 batches.addAll(getInstaller(installable).install(installable));
350 } catch (IntentException e) {
351 log.warn("Unable to install intent {} due to:", update.newIntent().id(), e);
Brian O'Connor427a1762014-11-19 18:40:32 -0800352 trackerService.removeTrackedResources(update.newIntent().id(),
353 installable.resources());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700354 //FIXME we failed... intent should be recompiled
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700355 }
356 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800357 update.addBatches(batches);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700358 }
359
360 /**
361 * Uninstalls the specified intent by uninstalling all of its associated
362 * installable derivatives.
363 *
364 * @param update intent update
365 */
366 private void executeWithdrawingPhase(IntentUpdate update) {
367 if (!update.oldIntent().equals(update.newIntent())) {
368 update.setState(update.oldIntent(), WITHDRAWING);
369 } // else newIntent is FAILED
Brian O'Connor427a1762014-11-19 18:40:32 -0800370 update.addBatches(uninstallIntent(update.oldIntent(), update.oldInstallables()));
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700371 }
372
373 /**
374 * Uninstalls all installable intents associated with the given intent.
375 *
Brian O'Connor427a1762014-11-19 18:40:32 -0800376 * @param intent intent
377 * @param installables installable intents
378 * @return list of batches to uninstall intent
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700379 */
Brian O'Connor427a1762014-11-19 18:40:32 -0800380 private List<FlowRuleBatchOperation> uninstallIntent(Intent intent, List<Intent> installables) {
381 if (installables == null) {
382 return Collections.emptyList();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700383 }
384 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
Brian O'Connor427a1762014-11-19 18:40:32 -0800385 for (Intent installable : installables) {
386 trackerService.removeTrackedResources(intent.id(),
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700387 installable.resources());
388 try {
389 batches.addAll(getInstaller(installable).uninstall(installable));
390 } catch (IntentException e) {
Brian O'Connor427a1762014-11-19 18:40:32 -0800391 log.warn("Unable to uninstall intent {} due to:", intent.id(), e);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700392 // TODO: this should never happen. but what if it does?
393 }
394 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800395 return batches;
tom85258ee2014-10-07 00:10:02 -0700396 }
397
398 /**
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700399 * Withdraws the old intent and installs the new intent as one operation.
tom85258ee2014-10-07 00:10:02 -0700400 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700401 * @param update intent update
tom85258ee2014-10-07 00:10:02 -0700402 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700403 private void executeReplacementPhase(IntentUpdate update) {
404 checkArgument(update.oldInstallables().size() == update.newInstallables().size(),
405 "Old and New Intent must have equivalent installable intents.");
406 if (!update.oldIntent().equals(update.newIntent())) {
407 // only set the old intent's state if it is different
408 update.setState(update.oldIntent(), WITHDRAWING);
tom53945d52014-10-07 11:01:36 -0700409 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700410 update.setState(update.newIntent(), INSTALLING);
411
412 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
413 for (int i = 0; i < update.oldInstallables().size(); i++) {
414 Intent oldInstallable = update.oldInstallables().get(i);
415 Intent newInstallable = update.newInstallables().get(i);
Brian O'Connor427a1762014-11-19 18:40:32 -0800416 //FIXME revisit this
417// if (oldInstallable.equals(newInstallable)) {
418// continue;
419// }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700420 checkArgument(oldInstallable.getClass().equals(newInstallable.getClass()),
421 "Installable Intent type mismatch.");
422 trackerService.removeTrackedResources(update.oldIntent().id(), oldInstallable.resources());
423 trackerService.addTrackedResources(update.newIntent().id(), newInstallable.resources());
424 try {
425 batches.addAll(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
426 } catch (IntentException e) {
427 log.warn("Unable to update intent {} due to:", update.oldIntent().id(), e);
428 //FIXME... we failed. need to uninstall (if same) or revert (if different)
Brian O'Connor427a1762014-11-19 18:40:32 -0800429 trackerService.removeTrackedResources(update.newIntent().id(), newInstallable.resources());
430 update.setState(update.newIntent(), FAILED);
431 batches = uninstallIntent(update.oldIntent(), update.oldInstallables());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700432 }
433 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800434 update.addBatches(batches);
tom53945d52014-10-07 11:01:36 -0700435 }
436
437 /**
Brian O'Connor66630c82014-10-02 21:08:19 -0700438 * Registers an intent compiler of the specified intent if an intent compiler
439 * for the intent is not registered. This method traverses the class hierarchy of
440 * the intent. Once an intent compiler for a parent type is found, this method
441 * registers the found intent compiler.
442 *
443 * @param intent intent
444 */
445 private void registerSubclassCompilerIfNeeded(Intent intent) {
446 if (!compilers.containsKey(intent.getClass())) {
447 Class<?> cls = intent.getClass();
448 while (cls != Object.class) {
449 // As long as we're within the Intent class descendants
450 if (Intent.class.isAssignableFrom(cls)) {
451 IntentCompiler<?> compiler = compilers.get(cls);
452 if (compiler != null) {
453 compilers.put(intent.getClass(), compiler);
454 return;
455 }
456 }
457 cls = cls.getSuperclass();
458 }
459 }
460 }
461
462 /**
463 * Registers an intent installer of the specified intent if an intent installer
464 * for the intent is not registered. This method traverses the class hierarchy of
465 * the intent. Once an intent installer for a parent type is found, this method
466 * registers the found intent installer.
467 *
468 * @param intent intent
469 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700470 private void registerSubclassInstallerIfNeeded(Intent intent) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700471 if (!installers.containsKey(intent.getClass())) {
472 Class<?> cls = intent.getClass();
473 while (cls != Object.class) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700474 // As long as we're within the Intent class descendants
475 if (Intent.class.isAssignableFrom(cls)) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700476 IntentInstaller<?> installer = installers.get(cls);
477 if (installer != null) {
478 installers.put(intent.getClass(), installer);
479 return;
480 }
481 }
482 cls = cls.getSuperclass();
483 }
484 }
485 }
486
Brian O'Connor66630c82014-10-02 21:08:19 -0700487 // Store delegate to re-post events emitted from the store.
488 private class InternalStoreDelegate implements IntentStoreDelegate {
489 @Override
490 public void notify(IntentEvent event) {
tom85258ee2014-10-07 00:10:02 -0700491 eventDispatcher.post(event);
Brian O'Connor66630c82014-10-02 21:08:19 -0700492 }
493 }
494
Brian O'Connor72a034c2014-11-26 18:24:23 -0800495 private void buildAndSubmitBatches(Iterable<IntentId> intentIds,
496 boolean compileAllFailed) {
497 Map<ApplicationId, IntentOperations.Builder> batches = Maps.newHashMap();
498 // Attempt recompilation of the specified intents first.
499 for (IntentId id : intentIds) {
500 Intent intent = store.getIntent(id);
501 if (intent == null) {
502 continue;
503 }
504 IntentOperations.Builder builder = batches.get(intent.appId());
505 if (builder == null) {
506 builder = IntentOperations.builder(intent.appId());
507 batches.put(intent.appId(), builder);
508 }
509 builder.addUpdateOperation(id);
510 }
511
512 if (compileAllFailed) {
513 // If required, compile all currently failed intents.
514 for (Intent intent : getIntents()) {
515 if (getIntentState(intent.id()) == FAILED) {
516 IntentOperations.Builder builder = batches.get(intent.appId());
517 if (builder == null) {
518 builder = IntentOperations.builder(intent.appId());
519 batches.put(intent.appId(), builder);
520 }
521 builder.addUpdateOperation(intent.id());
522 }
523 }
524 }
525
526 for (ApplicationId appId : batches.keySet()) {
527 if (batchService.isLocalLeader(appId)) {
528 execute(batches.get(appId).build());
529 }
530 }
531 }
532
tom95329eb2014-10-06 08:40:06 -0700533 // Topology change delegate
534 private class InternalTopoChangeDelegate implements TopologyChangeDelegate {
535 @Override
tom85258ee2014-10-07 00:10:02 -0700536 public void triggerCompile(Iterable<IntentId> intentIds,
537 boolean compileAllFailed) {
Brian O'Connor72a034c2014-11-26 18:24:23 -0800538 buildAndSubmitBatches(intentIds, compileAllFailed);
tom95329eb2014-10-06 08:40:06 -0700539 }
tom95329eb2014-10-06 08:40:06 -0700540 }
tom85258ee2014-10-07 00:10:02 -0700541
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700542 /**
Brian O'Connor427a1762014-11-19 18:40:32 -0800543 * TODO. rename this...
544 * @param update intent update
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700545 */
Brian O'Connor427a1762014-11-19 18:40:32 -0800546 private void processIntentUpdate(IntentUpdate update) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700547
Brian O'Connor427a1762014-11-19 18:40:32 -0800548 // check to see if the intent needs to be compiled or recompiled
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700549 if (update.newIntent() != null) {
550 executeCompilingPhase(update);
551 }
552
553 if (update.oldInstallables() != null && update.newInstallables() != null) {
554 executeReplacementPhase(update);
555 } else if (update.newInstallables() != null) {
556 executeInstallingPhase(update);
557 } else if (update.oldInstallables() != null) {
558 executeWithdrawingPhase(update);
559 } else {
Brian O'Connor427a1762014-11-19 18:40:32 -0800560 if (update.oldIntent() != null &&
561 !update.oldIntent().equals(update.newIntent())) {
562 // removing failed intent
563 update.setState(update.oldIntent(), WITHDRAWING);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700564 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800565// if (update.newIntent() != null) {
566// // TODO assert that next state is failed
567// }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700568 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700569 }
570
571 // TODO comments...
572 private class IntentUpdate {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700573 private final Intent oldIntent;
574 private final Intent newIntent;
575 private final Map<Intent, IntentState> stateMap = Maps.newHashMap();
576
577 private final List<Intent> oldInstallables;
578 private List<Intent> newInstallables;
Brian O'Connor427a1762014-11-19 18:40:32 -0800579 private final List<FlowRuleBatchOperation> batches = Lists.newLinkedList();
580 private int currentBatch = 0; // TODO: maybe replace with an iterator
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700581
582 IntentUpdate(IntentOperation op) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700583 switch (op.type()) {
584 case SUBMIT:
585 newIntent = op.intent();
586 oldIntent = null;
587 break;
588 case WITHDRAW:
589 newIntent = null;
590 oldIntent = store.getIntent(op.intentId());
591 break;
592 case REPLACE:
593 newIntent = op.intent();
594 oldIntent = store.getIntent(op.intentId());
595 break;
596 case UPDATE:
597 oldIntent = store.getIntent(op.intentId());
Brian O'Connor427a1762014-11-19 18:40:32 -0800598 newIntent = oldIntent;
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700599 break;
600 default:
601 oldIntent = null;
602 newIntent = null;
603 break;
604 }
605 // add new intent to store (if required)
606 if (newIntent != null) {
607 IntentEvent event = store.createIntent(newIntent);
608 if (event != null) {
609 eventDispatcher.post(event);
610 }
611 }
612 // fetch the old intent's installables from the store
613 if (oldIntent != null) {
614 oldInstallables = store.getInstallableIntents(oldIntent.id());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700615 } else {
616 oldInstallables = null;
617 }
618 }
619
620 Intent oldIntent() {
621 return oldIntent;
622 }
623
624 Intent newIntent() {
625 return newIntent;
626 }
627
628 List<Intent> oldInstallables() {
629 return oldInstallables;
630 }
631
632 List<Intent> newInstallables() {
633 return newInstallables;
634 }
635
636 void setInstallables(List<Intent> installables) {
637 newInstallables = installables;
638 store.setInstallableIntents(newIntent.id(), installables);
639 }
640
Brian O'Connor427a1762014-11-19 18:40:32 -0800641 boolean isComplete() {
642 return currentBatch >= batches.size();
643 }
644
645 FlowRuleBatchOperation currentBatch() {
646 return !isComplete() ? batches.get(currentBatch) : null;
647 }
648
649 void incrementBatch(boolean success) {
650 if (success) { // actually increment
651 if (++currentBatch == batches.size()) {
652 finalizeStates();
653 }
654 } else { // the current batch has failed, so recompile
655 // remove the current batch and all remaining
656 for (int i = currentBatch; i < batches.size(); i++) {
657 batches.remove(i);
658 }
659 if (oldIntent != null) {
660 executeWithdrawingPhase(this); // remove the old intent
661 }
662 if (newIntent != null) {
663 setState(newIntent, FAILED);
664 batches.addAll(uninstallIntent(newIntent, newInstallables()));
665 }
666
667 // FIXME: should we try to recompile?
668 }
669 }
670
671 // FIXME make sure this is called!!!
672 private void finalizeStates() {
673 for (Intent intent : stateMap.keySet()) {
674 switch (getState(intent)) {
675 case INSTALLING:
676 setState(intent, INSTALLED);
677 break;
678 case WITHDRAWING:
679 setState(intent, WITHDRAWN);
680 store.removeInstalledIntents(intent.id());
681 //store.removeIntent(intent.id()); // FIXME we die a horrible death here
682 break;
683 case FAILED:
684 store.removeInstalledIntents(intent.id());
685 break;
686
687 // FALLTHROUGH to default from here
688 case SUBMITTED:
689 case COMPILING:
690 case RECOMPILING:
691 case WITHDRAWN:
692 case INSTALLED:
693 default:
694 //FIXME clean this up (we shouldn't ever get here)
695 log.warn("Bad state: {} for {}", getState(intent), intent);
696 break;
697 }
698 }
699 }
700
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700701 List<FlowRuleBatchOperation> batches() {
702 return batches;
703 }
704
Brian O'Connor427a1762014-11-19 18:40:32 -0800705 void addBatches(List<FlowRuleBatchOperation> batches) {
706 this.batches.addAll(batches);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700707 }
708
709 IntentState getState(Intent intent) {
710 return stateMap.get(intent);
711 }
712
713 void setState(Intent intent, IntentState newState) {
714 // TODO: clean this up, or set to debug
715 IntentState oldState = stateMap.get(intent);
Brian O'Connor427a1762014-11-19 18:40:32 -0800716 log.debug("intent id: {}, old state: {}, new state: {}",
Ray Milkeye97ede92014-11-20 10:43:12 -0800717 intent.id(), oldState, newState);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700718
719 stateMap.put(intent, newState);
720 IntentEvent event = store.setState(intent, newState);
721 if (event != null) {
722 eventDispatcher.post(event);
723 }
Thomas Vachuskae4b6bb22014-11-25 17:09:43 -0800724
725 if (newState == WITHDRAWN) {
726 store.removeIntent(intent.id());
727 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700728 }
729
730 Map<Intent, IntentState> stateMap() {
731 return stateMap;
732 }
733 }
734
Brian O'Connorcb900f42014-10-07 21:55:33 -0700735 private class IntentInstallMonitor implements Runnable {
736
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800737 // TODO make this configurable
738 private static final int TIMEOUT_PER_OP = 500; // ms
Brian O'Connor427a1762014-11-19 18:40:32 -0800739 private static final int MAX_ATTEMPTS = 3;
Brian O'Connorcb900f42014-10-07 21:55:33 -0700740
Brian O'Connor427a1762014-11-19 18:40:32 -0800741 private final IntentOperations ops;
742 private final List<IntentUpdate> intentUpdates = Lists.newArrayList();
743
744 private Future<CompletedBatchOperation> future;
745 private long startTime = System.currentTimeMillis();
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800746 private long endTime;
Brian O'Connor427a1762014-11-19 18:40:32 -0800747 private int installAttempt;
748
749 public IntentInstallMonitor(IntentOperations ops) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700750 this.ops = ops;
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800751 resetTimeoutLimit();
752 }
753
754 private void resetTimeoutLimit() {
755 // FIXME compute reasonable timeouts
756 this.endTime = System.currentTimeMillis()
757 + ops.operations().size() * TIMEOUT_PER_OP;
Brian O'Connor427a1762014-11-19 18:40:32 -0800758 }
759
760 private void buildIntentUpdates() {
761 for (IntentOperation op : ops.operations()) {
762 IntentUpdate update = new IntentUpdate(op);
763 intentUpdates.add(update);
764 processIntentUpdate(update);
765 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700766 future = applyNextBatch();
Brian O'Connorcb900f42014-10-07 21:55:33 -0700767 }
768
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700769 /**
Brian O'Connor427a1762014-11-19 18:40:32 -0800770 * Builds and applies the next batch, and returns the future.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700771 *
772 * @return Future for next batch
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700773 */
774 private Future<CompletedBatchOperation> applyNextBatch() {
Brian O'Connor427a1762014-11-19 18:40:32 -0800775 //TODO test this. (also, maybe save this batch)
776 FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList());
777 for (IntentUpdate update : intentUpdates) {
778 if (!update.isComplete()) {
779 batch.addAll(update.currentBatch());
780 }
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700781 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800782 return (batch.size() > 0) ? flowRuleService.applyBatch(batch) : null;
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700783 }
784
Brian O'Connor427a1762014-11-19 18:40:32 -0800785 private void updateBatches(CompletedBatchOperation completed) {
786 if (completed.isSuccess()) {
787 for (IntentUpdate update : intentUpdates) {
788 update.incrementBatch(true);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700789 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700790 } else {
Brian O'Connor427a1762014-11-19 18:40:32 -0800791 // entire batch has been reverted...
792 log.warn("Failed items: {}", completed.failedItems());
793
794 for (Long id : completed.failedIds()) {
795 IntentId targetId = IntentId.valueOf(id);
796 for (IntentUpdate update : intentUpdates) {
797 List<Intent> installables = Lists.newArrayList(update.newInstallables());
798 installables.addAll(update.oldInstallables());
799 for (Intent intent : installables) {
800 if (intent.id().equals(targetId)) {
801 update.incrementBatch(false);
802 break;
803 }
804 }
805 }
806 // don't increment the non-failed items, as they have been reverted.
807 }
808 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700809 }
810
811 /**
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700812 * Iterate through the pending futures, and remove them when they have completed.
813 */
814 private void processFutures() {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700815 if (future == null) {
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800816 log.warn("I have no Future.");
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700817 return; //FIXME look at this
Brian O'Connorcb900f42014-10-07 21:55:33 -0700818 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700819 try {
820 CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
Brian O'Connor427a1762014-11-19 18:40:32 -0800821 updateBatches(completed);
822 future = applyNextBatch();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700823 } catch (TimeoutException | InterruptedException | ExecutionException te) {
824 //TODO look into error message
Brian O'Connor427a1762014-11-19 18:40:32 -0800825 log.debug("Installation of intents are still pending: {}", ops);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700826 }
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700827 }
828
Brian O'Connor427a1762014-11-19 18:40:32 -0800829 private void retry() {
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800830 log.debug("Execution timed out, retrying.");
Brian O'Connor427a1762014-11-19 18:40:32 -0800831 if (future.cancel(true)) { // cancel success; batch is reverted
832 // reset the timer
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800833 resetTimeoutLimit();
Brian O'Connor427a1762014-11-19 18:40:32 -0800834 if (installAttempt++ >= MAX_ATTEMPTS) {
835 log.warn("Install request timed out: {}", ops);
836 for (IntentUpdate update : intentUpdates) {
837 update.incrementBatch(false);
838 }
839 } // else just resubmit the work
840 future = applyNextBatch();
841 monitorExecutor.submit(this);
842 } else {
843 // FIXME
844 // cancel failed... batch is broken; shouldn't happen!
845 // we could manually reverse everything
846 // ... or just core dump and send email to Ali
847 batchService.removeIntentOperations(ops);
848 }
849 }
850
851 boolean isComplete() {
852 // TODO: actually check with the intent update?
853 return future == null;
854 }
855
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700856 @Override
857 public void run() {
Brian O'Connor427a1762014-11-19 18:40:32 -0800858 try {
859 if (intentUpdates.isEmpty()) {
860 // this should only be called on the first iteration
861 // note: this a "expensive", so it is not done in the constructor
862 buildIntentUpdates();
863 }
864 processFutures();
865 if (isComplete()) {
866 // there are no outstanding batches; we are done
867 batchService.removeIntentOperations(ops);
868 } else if (endTime < System.currentTimeMillis()) {
869 retry();
870 } else {
871 // we are not done yet, yield the thread by resubmitting ourselves
872 monitorExecutor.submit(this);
873 }
874 } catch (Exception e) {
875 log.error("Error submitting batches:", e);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700876 }
877 }
878 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700879
880 private class InternalBatchDelegate implements IntentBatchDelegate {
881 @Override
882 public void execute(IntentOperations operations) {
Yuta HIGUCHIfe4367a2014-11-24 19:19:09 -0800883 log.info("Execute {} operation(s).", operations.operations().size());
884 log.debug("Execute operations: {}", operations.operations());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700885 //FIXME: perhaps we want to track this task so that we can cancel it.
Brian O'Connor427a1762014-11-19 18:40:32 -0800886 monitorExecutor.execute(new IntentInstallMonitor(operations));
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700887 }
888
889 @Override
890 public void cancel(IntentOperations operations) {
891 //FIXME: implement this
892 log.warn("NOT IMPLEMENTED -- Cancel operations: {}", operations);
893 }
894 }
Ray Milkeye97ede92014-11-20 10:43:12 -0800895
Brian O'Connor66630c82014-10-02 21:08:19 -0700896}