blob: 2f8875a2ea13fa3fdc2616deb9961a3a7ef3ec4e [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
Brian O'Connorfa81eae2014-10-30 13:20:05 -070018import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
Brian O'Connor66630c82014-10-02 21:08:19 -070022import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
Brian O'Connor520c0522014-11-23 23:50:47 -080028import org.onlab.onos.core.CoreService;
29import org.onlab.onos.core.IdGenerator;
Brian O'Connor66630c82014-10-02 21:08:19 -070030import org.onlab.onos.event.AbstractListenerRegistry;
31import org.onlab.onos.event.EventDeliveryService;
Brian O'Connorcb900f42014-10-07 21:55:33 -070032import org.onlab.onos.net.flow.CompletedBatchOperation;
Brian O'Connorf2dbde52014-10-10 16:20:24 -070033import org.onlab.onos.net.flow.FlowRuleBatchOperation;
34import org.onlab.onos.net.flow.FlowRuleService;
Brian O'Connor66630c82014-10-02 21:08:19 -070035import org.onlab.onos.net.intent.Intent;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070036import org.onlab.onos.net.intent.IntentBatchDelegate;
37import org.onlab.onos.net.intent.IntentBatchService;
Brian O'Connor66630c82014-10-02 21:08:19 -070038import org.onlab.onos.net.intent.IntentCompiler;
39import org.onlab.onos.net.intent.IntentEvent;
40import org.onlab.onos.net.intent.IntentException;
41import org.onlab.onos.net.intent.IntentExtensionService;
42import org.onlab.onos.net.intent.IntentId;
43import org.onlab.onos.net.intent.IntentInstaller;
44import org.onlab.onos.net.intent.IntentListener;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070045import org.onlab.onos.net.intent.IntentOperation;
Brian O'Connor66630c82014-10-02 21:08:19 -070046import org.onlab.onos.net.intent.IntentOperations;
47import org.onlab.onos.net.intent.IntentService;
48import org.onlab.onos.net.intent.IntentState;
49import org.onlab.onos.net.intent.IntentStore;
50import org.onlab.onos.net.intent.IntentStoreDelegate;
51import org.slf4j.Logger;
52
Brian O'Connorfa81eae2014-10-30 13:20:05 -070053import java.util.ArrayList;
Brian O'Connor427a1762014-11-19 18:40:32 -080054import java.util.Collections;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070055import java.util.List;
56import java.util.Map;
Brian O'Connorfa81eae2014-10-30 13:20:05 -070057import java.util.concurrent.ConcurrentHashMap;
58import java.util.concurrent.ConcurrentMap;
59import java.util.concurrent.ExecutionException;
60import java.util.concurrent.ExecutorService;
61import java.util.concurrent.Future;
62import java.util.concurrent.TimeUnit;
63import java.util.concurrent.TimeoutException;
64
65import static com.google.common.base.Preconditions.checkArgument;
66import static com.google.common.base.Preconditions.checkNotNull;
67import static java.util.concurrent.Executors.newSingleThreadExecutor;
68import static org.onlab.onos.net.intent.IntentState.*;
69import static org.onlab.util.Tools.namedThreads;
70import static org.slf4j.LoggerFactory.getLogger;
Brian O'Connor66630c82014-10-02 21:08:19 -070071
72/**
73 * An implementation of Intent Manager.
74 */
75@Component(immediate = true)
76@Service
77public class IntentManager
78 implements IntentService, IntentExtensionService {
Sho SHIMIZU8b5051d2014-11-05 11:24:13 -080079 private static final Logger log = getLogger(IntentManager.class);
Brian O'Connor66630c82014-10-02 21:08:19 -070080
81 public static final String INTENT_NULL = "Intent cannot be null";
82 public static final String INTENT_ID_NULL = "Intent ID cannot be null";
83
84 // Collections for compiler, installer, and listener are ONOS instance local
85 private final ConcurrentMap<Class<? extends Intent>,
86 IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
Thomas Vachuskab97cf282014-10-20 23:31:12 -070087 private final ConcurrentMap<Class<? extends Intent>,
88 IntentInstaller<? extends Intent>> installers = new ConcurrentHashMap<>();
Brian O'Connor66630c82014-10-02 21:08:19 -070089
90 private final AbstractListenerRegistry<IntentEvent, IntentListener>
tom95329eb2014-10-06 08:40:06 -070091 listenerRegistry = new AbstractListenerRegistry<>();
Brian O'Connor66630c82014-10-02 21:08:19 -070092
Brian O'Connor520c0522014-11-23 23:50:47 -080093 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected CoreService coreService;
Brian O'Connor66630c82014-10-02 21:08:19 -070095
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected IntentStore store;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700100 protected IntentBatchService batchService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
tom85258ee2014-10-07 00:10:02 -0700103 protected ObjectiveTrackerService trackerService;
tom95329eb2014-10-06 08:40:06 -0700104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Brian O'Connor66630c82014-10-02 21:08:19 -0700106 protected EventDeliveryService eventDispatcher;
107
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected FlowRuleService flowRuleService;
110
Brian O'Connor520c0522014-11-23 23:50:47 -0800111
112 private ExecutorService executor;
113 private ExecutorService monitorExecutor;
114
115 private final IntentStoreDelegate delegate = new InternalStoreDelegate();
116 private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
117 private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
118 private IdGenerator idGenerator;
119
Brian O'Connor66630c82014-10-02 21:08:19 -0700120 @Activate
121 public void activate() {
122 store.setDelegate(delegate);
tom95329eb2014-10-06 08:40:06 -0700123 trackerService.setDelegate(topoDelegate);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700124 batchService.setDelegate(batchDelegate);
Brian O'Connor66630c82014-10-02 21:08:19 -0700125 eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700126 executor = newSingleThreadExecutor(namedThreads("onos-intents"));
127 monitorExecutor = newSingleThreadExecutor(namedThreads("onos-intent-monitor"));
Brian O'Connor520c0522014-11-23 23:50:47 -0800128 idGenerator = coreService.getIdGenerator("intent-ids");
129 Intent.bindIdGenerator(idGenerator);
Brian O'Connor66630c82014-10-02 21:08:19 -0700130 log.info("Started");
131 }
132
133 @Deactivate
134 public void deactivate() {
135 store.unsetDelegate(delegate);
tom95329eb2014-10-06 08:40:06 -0700136 trackerService.unsetDelegate(topoDelegate);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700137 batchService.unsetDelegate(batchDelegate);
Brian O'Connor66630c82014-10-02 21:08:19 -0700138 eventDispatcher.removeSink(IntentEvent.class);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700139 executor.shutdown();
140 monitorExecutor.shutdown();
Brian O'Connor520c0522014-11-23 23:50:47 -0800141 Intent.unbindIdGenerator(idGenerator);
Brian O'Connor66630c82014-10-02 21:08:19 -0700142 log.info("Stopped");
143 }
144
145 @Override
146 public void submit(Intent intent) {
147 checkNotNull(intent, INTENT_NULL);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700148 execute(IntentOperations.builder().addSubmitOperation(intent).build());
Brian O'Connor66630c82014-10-02 21:08:19 -0700149 }
150
151 @Override
152 public void withdraw(Intent intent) {
153 checkNotNull(intent, INTENT_NULL);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700154 execute(IntentOperations.builder().addWithdrawOperation(intent.id()).build());
Brian O'Connor66630c82014-10-02 21:08:19 -0700155 }
156
Brian O'Connor66630c82014-10-02 21:08:19 -0700157 @Override
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700158 public void replace(IntentId oldIntentId, Intent newIntent) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700159 checkNotNull(oldIntentId, INTENT_ID_NULL);
160 checkNotNull(newIntent, INTENT_NULL);
161 execute(IntentOperations.builder()
162 .addReplaceOperation(oldIntentId, newIntent)
163 .build());
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700164 }
165
Thomas Vachuska83e090e2014-10-22 14:25:35 -0700166 @Override
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700167 public void execute(IntentOperations operations) {
Brian O'Connore2ff25a2014-11-18 19:25:43 -0800168 if (operations.operations().isEmpty()) {
169 return;
170 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700171 batchService.addIntentOperations(operations);
Brian O'Connor66630c82014-10-02 21:08:19 -0700172 }
173
174 @Override
175 public Iterable<Intent> getIntents() {
176 return store.getIntents();
177 }
178
179 @Override
180 public long getIntentCount() {
181 return store.getIntentCount();
182 }
183
184 @Override
185 public Intent getIntent(IntentId id) {
186 checkNotNull(id, INTENT_ID_NULL);
187 return store.getIntent(id);
188 }
189
190 @Override
191 public IntentState getIntentState(IntentId id) {
192 checkNotNull(id, INTENT_ID_NULL);
193 return store.getIntentState(id);
194 }
195
196 @Override
Thomas Vachuska10d4abc2014-10-21 12:47:26 -0700197 public List<Intent> getInstallableIntents(IntentId intentId) {
198 checkNotNull(intentId, INTENT_ID_NULL);
199 return store.getInstallableIntents(intentId);
200 }
201
202 @Override
Brian O'Connor66630c82014-10-02 21:08:19 -0700203 public void addListener(IntentListener listener) {
204 listenerRegistry.addListener(listener);
205 }
206
207 @Override
208 public void removeListener(IntentListener listener) {
209 listenerRegistry.removeListener(listener);
210 }
211
212 @Override
213 public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
214 compilers.put(cls, compiler);
215 }
216
217 @Override
218 public <T extends Intent> void unregisterCompiler(Class<T> cls) {
219 compilers.remove(cls);
220 }
221
222 @Override
223 public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
224 return ImmutableMap.copyOf(compilers);
225 }
226
227 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700228 public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700229 installers.put(cls, installer);
230 }
231
232 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700233 public <T extends Intent> void unregisterInstaller(Class<T> cls) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700234 installers.remove(cls);
235 }
236
237 @Override
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700238 public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
Brian O'Connor66630c82014-10-02 21:08:19 -0700239 return ImmutableMap.copyOf(installers);
240 }
241
242 /**
Brian O'Connor66630c82014-10-02 21:08:19 -0700243 * Returns the corresponding intent compiler to the specified intent.
244 *
245 * @param intent intent
tom95329eb2014-10-06 08:40:06 -0700246 * @param <T> the type of intent
Brian O'Connor66630c82014-10-02 21:08:19 -0700247 * @return intent compiler corresponding to the specified intent
248 */
249 private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
250 @SuppressWarnings("unchecked")
251 IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
252 if (compiler == null) {
253 throw new IntentException("no compiler for class " + intent.getClass());
254 }
255 return compiler;
256 }
257
258 /**
259 * Returns the corresponding intent installer to the specified installable intent.
tom95329eb2014-10-06 08:40:06 -0700260 *
Brian O'Connor66630c82014-10-02 21:08:19 -0700261 * @param intent intent
tom95329eb2014-10-06 08:40:06 -0700262 * @param <T> the type of installable intent
Brian O'Connor66630c82014-10-02 21:08:19 -0700263 * @return intent installer corresponding to the specified installable intent
264 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700265 private <T extends Intent> IntentInstaller<T> getInstaller(T intent) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700266 @SuppressWarnings("unchecked")
267 IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
268 if (installer == null) {
269 throw new IntentException("no installer for class " + intent.getClass());
270 }
271 return installer;
272 }
273
274 /**
tom85258ee2014-10-07 00:10:02 -0700275 * Compiles the specified intent.
Brian O'Connor66630c82014-10-02 21:08:19 -0700276 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700277 * @param update intent update
Brian O'Connor66630c82014-10-02 21:08:19 -0700278 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700279 private void executeCompilingPhase(IntentUpdate update) {
280 Intent intent = update.newIntent();
tom85258ee2014-10-07 00:10:02 -0700281 // Indicate that the intent is entering the compiling phase.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700282 update.setState(intent, COMPILING);
tom85258ee2014-10-07 00:10:02 -0700283
284 try {
285 // Compile the intent into installable derivatives.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700286 List<Intent> installables = compileIntent(intent, update);
tom85258ee2014-10-07 00:10:02 -0700287
288 // If all went well, associate the resulting list of installable
289 // intents with the top-level intent and proceed to install.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700290 update.setInstallables(installables);
291 } catch (IntentException e) {
Jonathan Hart11096402014-10-20 17:31:49 -0700292 log.warn("Unable to compile intent {} due to:", intent.id(), e);
tom53945d52014-10-07 11:01:36 -0700293
tom85258ee2014-10-07 00:10:02 -0700294 // If compilation failed, mark the intent as failed.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700295 update.setState(intent, FAILED);
tom85258ee2014-10-07 00:10:02 -0700296 }
297 }
298
Brian O'Connorcb900f42014-10-07 21:55:33 -0700299 /**
300 * Compiles an intent recursively.
301 *
302 * @param intent intent
303 * @return result of compilation
304 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700305 private List<Intent> compileIntent(Intent intent, IntentUpdate update) {
Thomas Vachuska4926c1b2014-10-21 00:44:10 -0700306 if (intent.isInstallable()) {
307 return ImmutableList.of(intent);
Brian O'Connor66630c82014-10-02 21:08:19 -0700308 }
Brian O'Connorcb900f42014-10-07 21:55:33 -0700309
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700310 registerSubclassCompilerIfNeeded(intent);
311 List<Intent> previous = update.oldInstallables();
312 // FIXME: get previous resources
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700313 List<Intent> installable = new ArrayList<>();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700314 for (Intent compiled : getCompiler(intent).compile(intent, previous, null)) {
315 installable.addAll(compileIntent(compiled, update));
Brian O'Connorcb900f42014-10-07 21:55:33 -0700316 }
tom85258ee2014-10-07 00:10:02 -0700317 return installable;
Brian O'Connor66630c82014-10-02 21:08:19 -0700318 }
319
320 /**
tom85258ee2014-10-07 00:10:02 -0700321 * Installs all installable intents associated with the specified top-level
322 * intent.
Brian O'Connor66630c82014-10-02 21:08:19 -0700323 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700324 * @param update intent update
Brian O'Connor66630c82014-10-02 21:08:19 -0700325 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700326 private void executeInstallingPhase(IntentUpdate update) {
327 if (update.newInstallables() == null) {
328 //no failed intents allowed past this point...
329 return;
tom85258ee2014-10-07 00:10:02 -0700330 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700331 // Indicate that the intent is entering the installing phase.
332 update.setState(update.newIntent(), INSTALLING);
333
334 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
335 for (Intent installable : update.newInstallables()) {
336 registerSubclassInstallerIfNeeded(installable);
337 trackerService.addTrackedResources(update.newIntent().id(),
338 installable.resources());
339 try {
340 batches.addAll(getInstaller(installable).install(installable));
341 } catch (IntentException e) {
342 log.warn("Unable to install intent {} due to:", update.newIntent().id(), e);
Brian O'Connor427a1762014-11-19 18:40:32 -0800343 trackerService.removeTrackedResources(update.newIntent().id(),
344 installable.resources());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700345 //FIXME we failed... intent should be recompiled
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700346 }
347 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800348 update.addBatches(batches);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700349 }
350
351 /**
352 * Uninstalls the specified intent by uninstalling all of its associated
353 * installable derivatives.
354 *
355 * @param update intent update
356 */
357 private void executeWithdrawingPhase(IntentUpdate update) {
358 if (!update.oldIntent().equals(update.newIntent())) {
359 update.setState(update.oldIntent(), WITHDRAWING);
360 } // else newIntent is FAILED
Brian O'Connor427a1762014-11-19 18:40:32 -0800361 update.addBatches(uninstallIntent(update.oldIntent(), update.oldInstallables()));
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700362 }
363
364 /**
365 * Uninstalls all installable intents associated with the given intent.
366 *
Brian O'Connor427a1762014-11-19 18:40:32 -0800367 * @param intent intent
368 * @param installables installable intents
369 * @return list of batches to uninstall intent
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700370 */
Brian O'Connor427a1762014-11-19 18:40:32 -0800371 private List<FlowRuleBatchOperation> uninstallIntent(Intent intent, List<Intent> installables) {
372 if (installables == null) {
373 return Collections.emptyList();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700374 }
375 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
Brian O'Connor427a1762014-11-19 18:40:32 -0800376 for (Intent installable : installables) {
377 trackerService.removeTrackedResources(intent.id(),
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700378 installable.resources());
379 try {
380 batches.addAll(getInstaller(installable).uninstall(installable));
381 } catch (IntentException e) {
Brian O'Connor427a1762014-11-19 18:40:32 -0800382 log.warn("Unable to uninstall intent {} due to:", intent.id(), e);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700383 // TODO: this should never happen. but what if it does?
384 }
385 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800386 return batches;
tom85258ee2014-10-07 00:10:02 -0700387 }
388
389 /**
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700390 * Withdraws the old intent and installs the new intent as one operation.
tom85258ee2014-10-07 00:10:02 -0700391 *
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700392 * @param update intent update
tom85258ee2014-10-07 00:10:02 -0700393 */
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700394 private void executeReplacementPhase(IntentUpdate update) {
395 checkArgument(update.oldInstallables().size() == update.newInstallables().size(),
396 "Old and New Intent must have equivalent installable intents.");
397 if (!update.oldIntent().equals(update.newIntent())) {
398 // only set the old intent's state if it is different
399 update.setState(update.oldIntent(), WITHDRAWING);
tom53945d52014-10-07 11:01:36 -0700400 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700401 update.setState(update.newIntent(), INSTALLING);
402
403 List<FlowRuleBatchOperation> batches = Lists.newArrayList();
404 for (int i = 0; i < update.oldInstallables().size(); i++) {
405 Intent oldInstallable = update.oldInstallables().get(i);
406 Intent newInstallable = update.newInstallables().get(i);
Brian O'Connor427a1762014-11-19 18:40:32 -0800407 //FIXME revisit this
408// if (oldInstallable.equals(newInstallable)) {
409// continue;
410// }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700411 checkArgument(oldInstallable.getClass().equals(newInstallable.getClass()),
412 "Installable Intent type mismatch.");
413 trackerService.removeTrackedResources(update.oldIntent().id(), oldInstallable.resources());
414 trackerService.addTrackedResources(update.newIntent().id(), newInstallable.resources());
415 try {
416 batches.addAll(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
417 } catch (IntentException e) {
418 log.warn("Unable to update intent {} due to:", update.oldIntent().id(), e);
419 //FIXME... we failed. need to uninstall (if same) or revert (if different)
Brian O'Connor427a1762014-11-19 18:40:32 -0800420 trackerService.removeTrackedResources(update.newIntent().id(), newInstallable.resources());
421 update.setState(update.newIntent(), FAILED);
422 batches = uninstallIntent(update.oldIntent(), update.oldInstallables());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700423 }
424 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800425 update.addBatches(batches);
tom53945d52014-10-07 11:01:36 -0700426 }
427
428 /**
Brian O'Connor66630c82014-10-02 21:08:19 -0700429 * Registers an intent compiler of the specified intent if an intent compiler
430 * for the intent is not registered. This method traverses the class hierarchy of
431 * the intent. Once an intent compiler for a parent type is found, this method
432 * registers the found intent compiler.
433 *
434 * @param intent intent
435 */
436 private void registerSubclassCompilerIfNeeded(Intent intent) {
437 if (!compilers.containsKey(intent.getClass())) {
438 Class<?> cls = intent.getClass();
439 while (cls != Object.class) {
440 // As long as we're within the Intent class descendants
441 if (Intent.class.isAssignableFrom(cls)) {
442 IntentCompiler<?> compiler = compilers.get(cls);
443 if (compiler != null) {
444 compilers.put(intent.getClass(), compiler);
445 return;
446 }
447 }
448 cls = cls.getSuperclass();
449 }
450 }
451 }
452
453 /**
454 * Registers an intent installer of the specified intent if an intent installer
455 * for the intent is not registered. This method traverses the class hierarchy of
456 * the intent. Once an intent installer for a parent type is found, this method
457 * registers the found intent installer.
458 *
459 * @param intent intent
460 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700461 private void registerSubclassInstallerIfNeeded(Intent intent) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700462 if (!installers.containsKey(intent.getClass())) {
463 Class<?> cls = intent.getClass();
464 while (cls != Object.class) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700465 // As long as we're within the Intent class descendants
466 if (Intent.class.isAssignableFrom(cls)) {
Brian O'Connor66630c82014-10-02 21:08:19 -0700467 IntentInstaller<?> installer = installers.get(cls);
468 if (installer != null) {
469 installers.put(intent.getClass(), installer);
470 return;
471 }
472 }
473 cls = cls.getSuperclass();
474 }
475 }
476 }
477
Brian O'Connor66630c82014-10-02 21:08:19 -0700478 // Store delegate to re-post events emitted from the store.
479 private class InternalStoreDelegate implements IntentStoreDelegate {
480 @Override
481 public void notify(IntentEvent event) {
tom85258ee2014-10-07 00:10:02 -0700482 eventDispatcher.post(event);
Brian O'Connor66630c82014-10-02 21:08:19 -0700483 }
484 }
485
tom95329eb2014-10-06 08:40:06 -0700486 // Topology change delegate
487 private class InternalTopoChangeDelegate implements TopologyChangeDelegate {
488 @Override
tom85258ee2014-10-07 00:10:02 -0700489 public void triggerCompile(Iterable<IntentId> intentIds,
490 boolean compileAllFailed) {
491 // Attempt recompilation of the specified intents first.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700492 IntentOperations.Builder builder = IntentOperations.builder();
493 for (IntentId id : intentIds) {
494 builder.addUpdateOperation(id);
tom85258ee2014-10-07 00:10:02 -0700495 }
496
497 if (compileAllFailed) {
498 // If required, compile all currently failed intents.
499 for (Intent intent : getIntents()) {
500 if (getIntentState(intent.id()) == FAILED) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700501 builder.addUpdateOperation(intent.id());
tom85258ee2014-10-07 00:10:02 -0700502 }
503 }
tom95329eb2014-10-06 08:40:06 -0700504 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700505 execute(builder.build());
tom95329eb2014-10-06 08:40:06 -0700506 }
tom95329eb2014-10-06 08:40:06 -0700507 }
tom85258ee2014-10-07 00:10:02 -0700508
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700509 /**
Brian O'Connor427a1762014-11-19 18:40:32 -0800510 * TODO. rename this...
511 * @param update intent update
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700512 */
Brian O'Connor427a1762014-11-19 18:40:32 -0800513 private void processIntentUpdate(IntentUpdate update) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700514
Brian O'Connor427a1762014-11-19 18:40:32 -0800515 // check to see if the intent needs to be compiled or recompiled
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700516 if (update.newIntent() != null) {
517 executeCompilingPhase(update);
518 }
519
520 if (update.oldInstallables() != null && update.newInstallables() != null) {
521 executeReplacementPhase(update);
522 } else if (update.newInstallables() != null) {
523 executeInstallingPhase(update);
524 } else if (update.oldInstallables() != null) {
525 executeWithdrawingPhase(update);
526 } else {
Brian O'Connor427a1762014-11-19 18:40:32 -0800527 if (update.oldIntent() != null &&
528 !update.oldIntent().equals(update.newIntent())) {
529 // removing failed intent
530 update.setState(update.oldIntent(), WITHDRAWING);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700531 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800532// if (update.newIntent() != null) {
533// // TODO assert that next state is failed
534// }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700535 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700536 }
537
538 // TODO comments...
539 private class IntentUpdate {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700540 private final Intent oldIntent;
541 private final Intent newIntent;
542 private final Map<Intent, IntentState> stateMap = Maps.newHashMap();
543
544 private final List<Intent> oldInstallables;
545 private List<Intent> newInstallables;
Brian O'Connor427a1762014-11-19 18:40:32 -0800546 private final List<FlowRuleBatchOperation> batches = Lists.newLinkedList();
547 private int currentBatch = 0; // TODO: maybe replace with an iterator
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700548
549 IntentUpdate(IntentOperation op) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700550 switch (op.type()) {
551 case SUBMIT:
552 newIntent = op.intent();
553 oldIntent = null;
554 break;
555 case WITHDRAW:
556 newIntent = null;
557 oldIntent = store.getIntent(op.intentId());
558 break;
559 case REPLACE:
560 newIntent = op.intent();
561 oldIntent = store.getIntent(op.intentId());
562 break;
563 case UPDATE:
564 oldIntent = store.getIntent(op.intentId());
Brian O'Connor427a1762014-11-19 18:40:32 -0800565 newIntent = oldIntent;
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700566 break;
567 default:
568 oldIntent = null;
569 newIntent = null;
570 break;
571 }
572 // add new intent to store (if required)
573 if (newIntent != null) {
574 IntentEvent event = store.createIntent(newIntent);
575 if (event != null) {
576 eventDispatcher.post(event);
577 }
578 }
579 // fetch the old intent's installables from the store
580 if (oldIntent != null) {
581 oldInstallables = store.getInstallableIntents(oldIntent.id());
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700582 } else {
583 oldInstallables = null;
584 }
585 }
586
587 Intent oldIntent() {
588 return oldIntent;
589 }
590
591 Intent newIntent() {
592 return newIntent;
593 }
594
595 List<Intent> oldInstallables() {
596 return oldInstallables;
597 }
598
599 List<Intent> newInstallables() {
600 return newInstallables;
601 }
602
603 void setInstallables(List<Intent> installables) {
604 newInstallables = installables;
605 store.setInstallableIntents(newIntent.id(), installables);
606 }
607
Brian O'Connor427a1762014-11-19 18:40:32 -0800608 boolean isComplete() {
609 return currentBatch >= batches.size();
610 }
611
612 FlowRuleBatchOperation currentBatch() {
613 return !isComplete() ? batches.get(currentBatch) : null;
614 }
615
616 void incrementBatch(boolean success) {
617 if (success) { // actually increment
618 if (++currentBatch == batches.size()) {
619 finalizeStates();
620 }
621 } else { // the current batch has failed, so recompile
622 // remove the current batch and all remaining
623 for (int i = currentBatch; i < batches.size(); i++) {
624 batches.remove(i);
625 }
626 if (oldIntent != null) {
627 executeWithdrawingPhase(this); // remove the old intent
628 }
629 if (newIntent != null) {
630 setState(newIntent, FAILED);
631 batches.addAll(uninstallIntent(newIntent, newInstallables()));
632 }
633
634 // FIXME: should we try to recompile?
635 }
636 }
637
638 // FIXME make sure this is called!!!
639 private void finalizeStates() {
640 for (Intent intent : stateMap.keySet()) {
641 switch (getState(intent)) {
642 case INSTALLING:
643 setState(intent, INSTALLED);
644 break;
645 case WITHDRAWING:
646 setState(intent, WITHDRAWN);
647 store.removeInstalledIntents(intent.id());
648 //store.removeIntent(intent.id()); // FIXME we die a horrible death here
649 break;
650 case FAILED:
651 store.removeInstalledIntents(intent.id());
652 break;
653
654 // FALLTHROUGH to default from here
655 case SUBMITTED:
656 case COMPILING:
657 case RECOMPILING:
658 case WITHDRAWN:
659 case INSTALLED:
660 default:
661 //FIXME clean this up (we shouldn't ever get here)
662 log.warn("Bad state: {} for {}", getState(intent), intent);
663 break;
664 }
665 }
666 }
667
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700668 List<FlowRuleBatchOperation> batches() {
669 return batches;
670 }
671
Brian O'Connor427a1762014-11-19 18:40:32 -0800672 void addBatches(List<FlowRuleBatchOperation> batches) {
673 this.batches.addAll(batches);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700674 }
675
676 IntentState getState(Intent intent) {
677 return stateMap.get(intent);
678 }
679
680 void setState(Intent intent, IntentState newState) {
681 // TODO: clean this up, or set to debug
682 IntentState oldState = stateMap.get(intent);
Brian O'Connor427a1762014-11-19 18:40:32 -0800683 log.debug("intent id: {}, old state: {}, new state: {}",
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700684 intent.id(), oldState, newState);
685
686 stateMap.put(intent, newState);
687 IntentEvent event = store.setState(intent, newState);
688 if (event != null) {
689 eventDispatcher.post(event);
690 }
691 }
692
693 Map<Intent, IntentState> stateMap() {
694 return stateMap;
695 }
696 }
697
Brian O'Connorcb900f42014-10-07 21:55:33 -0700698 private class IntentInstallMonitor implements Runnable {
699
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700700 private static final long TIMEOUT = 5000; // ms
Brian O'Connor427a1762014-11-19 18:40:32 -0800701 private static final int MAX_ATTEMPTS = 3;
Brian O'Connorcb900f42014-10-07 21:55:33 -0700702
Brian O'Connor427a1762014-11-19 18:40:32 -0800703 private final IntentOperations ops;
704 private final List<IntentUpdate> intentUpdates = Lists.newArrayList();
705
706 private Future<CompletedBatchOperation> future;
707 private long startTime = System.currentTimeMillis();
708 private long endTime = startTime + TIMEOUT;
709 private int installAttempt;
710
711 public IntentInstallMonitor(IntentOperations ops) {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700712 this.ops = ops;
Brian O'Connor427a1762014-11-19 18:40:32 -0800713 }
714
715 private void buildIntentUpdates() {
716 for (IntentOperation op : ops.operations()) {
717 IntentUpdate update = new IntentUpdate(op);
718 intentUpdates.add(update);
719 processIntentUpdate(update);
720 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700721 future = applyNextBatch();
Brian O'Connorcb900f42014-10-07 21:55:33 -0700722 }
723
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700724 /**
Brian O'Connor427a1762014-11-19 18:40:32 -0800725 * Builds and applies the next batch, and returns the future.
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700726 *
727 * @return Future for next batch
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700728 */
729 private Future<CompletedBatchOperation> applyNextBatch() {
Brian O'Connor427a1762014-11-19 18:40:32 -0800730 //TODO test this. (also, maybe save this batch)
731 FlowRuleBatchOperation batch = new FlowRuleBatchOperation(Collections.emptyList());
732 for (IntentUpdate update : intentUpdates) {
733 if (!update.isComplete()) {
734 batch.addAll(update.currentBatch());
735 }
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700736 }
Brian O'Connor427a1762014-11-19 18:40:32 -0800737 return (batch.size() > 0) ? flowRuleService.applyBatch(batch) : null;
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700738 }
739
Brian O'Connor427a1762014-11-19 18:40:32 -0800740 private void updateBatches(CompletedBatchOperation completed) {
741 if (completed.isSuccess()) {
742 for (IntentUpdate update : intentUpdates) {
743 update.incrementBatch(true);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700744 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700745 } else {
Brian O'Connor427a1762014-11-19 18:40:32 -0800746 // entire batch has been reverted...
747 log.warn("Failed items: {}", completed.failedItems());
748
749 for (Long id : completed.failedIds()) {
750 IntentId targetId = IntentId.valueOf(id);
751 for (IntentUpdate update : intentUpdates) {
752 List<Intent> installables = Lists.newArrayList(update.newInstallables());
753 installables.addAll(update.oldInstallables());
754 for (Intent intent : installables) {
755 if (intent.id().equals(targetId)) {
756 update.incrementBatch(false);
757 break;
758 }
759 }
760 }
761 // don't increment the non-failed items, as they have been reverted.
762 }
763 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700764 }
765
766 /**
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700767 * Iterate through the pending futures, and remove them when they have completed.
768 */
769 private void processFutures() {
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700770 if (future == null) {
771 return; //FIXME look at this
Brian O'Connorcb900f42014-10-07 21:55:33 -0700772 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700773 try {
774 CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
Brian O'Connor427a1762014-11-19 18:40:32 -0800775 updateBatches(completed);
776 future = applyNextBatch();
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700777 } catch (TimeoutException | InterruptedException | ExecutionException te) {
778 //TODO look into error message
Brian O'Connor427a1762014-11-19 18:40:32 -0800779 log.debug("Installation of intents are still pending: {}", ops);
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700780 }
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700781 }
782
Brian O'Connor427a1762014-11-19 18:40:32 -0800783 private void retry() {
784 if (future.cancel(true)) { // cancel success; batch is reverted
785 // reset the timer
786 endTime = System.currentTimeMillis() + TIMEOUT;
787 if (installAttempt++ >= MAX_ATTEMPTS) {
788 log.warn("Install request timed out: {}", ops);
789 for (IntentUpdate update : intentUpdates) {
790 update.incrementBatch(false);
791 }
792 } // else just resubmit the work
793 future = applyNextBatch();
794 monitorExecutor.submit(this);
795 } else {
796 // FIXME
797 // cancel failed... batch is broken; shouldn't happen!
798 // we could manually reverse everything
799 // ... or just core dump and send email to Ali
800 batchService.removeIntentOperations(ops);
801 }
802 }
803
804 boolean isComplete() {
805 // TODO: actually check with the intent update?
806 return future == null;
807 }
808
Brian O'Connorf2dbde52014-10-10 16:20:24 -0700809 @Override
810 public void run() {
Brian O'Connor427a1762014-11-19 18:40:32 -0800811 try {
812 if (intentUpdates.isEmpty()) {
813 // this should only be called on the first iteration
814 // note: this a "expensive", so it is not done in the constructor
815 buildIntentUpdates();
816 }
817 processFutures();
818 if (isComplete()) {
819 // there are no outstanding batches; we are done
820 batchService.removeIntentOperations(ops);
821 } else if (endTime < System.currentTimeMillis()) {
822 retry();
823 } else {
824 // we are not done yet, yield the thread by resubmitting ourselves
825 monitorExecutor.submit(this);
826 }
827 } catch (Exception e) {
828 log.error("Error submitting batches:", e);
Brian O'Connorcb900f42014-10-07 21:55:33 -0700829 }
830 }
831 }
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700832
833 private class InternalBatchDelegate implements IntentBatchDelegate {
834 @Override
835 public void execute(IntentOperations operations) {
836 log.info("Execute operations: {}", operations);
837 //FIXME: perhaps we want to track this task so that we can cancel it.
Brian O'Connor427a1762014-11-19 18:40:32 -0800838 monitorExecutor.execute(new IntentInstallMonitor(operations));
Brian O'Connorfa81eae2014-10-30 13:20:05 -0700839 }
840
841 @Override
842 public void cancel(IntentOperations operations) {
843 //FIXME: implement this
844 log.warn("NOT IMPLEMENTED -- Cancel operations: {}", operations);
845 }
846 }
Brian O'Connor66630c82014-10-02 21:08:19 -0700847}