blob: 0334bba5388c2da26db295d184e770aa07792c68 [file] [log] [blame]
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -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 */
16
17package org.onosproject.net.intent.impl;
18
Andreas Papazois05548962016-11-23 11:36:55 +020019import com.google.common.collect.ImmutableSet;
20import com.google.common.annotations.Beta;
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -080021import com.google.common.base.MoreObjects;
22import com.google.common.base.MoreObjects.ToStringHelper;
Ray Milkeyfd724402016-05-26 14:45:46 -070023import com.google.common.collect.Lists;
Thomas Vachuska2980c972016-02-23 20:58:49 -080024import com.google.common.collect.Sets;
Andreas Papazois05548962016-11-23 11:36:55 +020025
26import org.apache.commons.lang3.tuple.Pair;
Thomas Vachuska2980c972016-02-23 20:58:49 -080027import org.onosproject.net.DeviceId;
Andreas Papazois05548962016-11-23 11:36:55 +020028import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription;
29import org.onosproject.net.behaviour.protection.ProtectionConfig;
30import org.onosproject.net.config.NetworkConfigService;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080031import org.onosproject.net.flow.FlowRule;
32import org.onosproject.net.flow.FlowRuleOperations;
33import org.onosproject.net.flow.FlowRuleOperationsContext;
34import org.onosproject.net.flow.FlowRuleService;
Yi Tseng38fc71e2017-02-03 14:50:47 -080035import org.onosproject.net.flowobjective.FilteringObjective;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080036import org.onosproject.net.flowobjective.FlowObjectiveService;
Yi Tseng38fc71e2017-02-03 14:50:47 -080037import org.onosproject.net.flowobjective.ForwardingObjective;
38import org.onosproject.net.flowobjective.NextObjective;
Thomas Vachuska2980c972016-02-23 20:58:49 -080039import org.onosproject.net.flowobjective.Objective;
40import org.onosproject.net.flowobjective.ObjectiveContext;
41import org.onosproject.net.flowobjective.ObjectiveError;
42import org.onosproject.net.intent.FlowObjectiveIntent;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080043import org.onosproject.net.intent.FlowRuleIntent;
44import org.onosproject.net.intent.Intent;
45import org.onosproject.net.intent.IntentData;
46import org.onosproject.net.intent.IntentStore;
Andreas Papazois05548962016-11-23 11:36:55 +020047import org.onosproject.net.intent.ProtectionEndpointIntent;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080048import org.slf4j.Logger;
49
Thomas Vachuska2980c972016-02-23 20:58:49 -080050import java.util.ArrayList;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080051import java.util.Collection;
Brian O'Connora78f0602016-09-22 10:56:08 -070052import java.util.Collections;
helenyrwue6aaa332016-08-05 15:41:42 -070053import java.util.Iterator;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080054import java.util.List;
55import java.util.Optional;
56import java.util.Set;
Andreas Papazois05548962016-11-23 11:36:55 +020057import java.util.concurrent.CompletableFuture;
58import java.util.concurrent.CopyOnWriteArrayList;
Yi Tseng38fc71e2017-02-03 14:50:47 -080059import java.util.concurrent.atomic.AtomicInteger;
Thomas Vachuska2980c972016-02-23 20:58:49 -080060import java.util.function.Consumer;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080061import java.util.stream.Collectors;
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -080062import java.util.stream.Stream;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080063
Andreas Papazois05548962016-11-23 11:36:55 +020064import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska2980c972016-02-23 20:58:49 -080065import static com.google.common.base.Preconditions.checkState;
Yi Tseng38fc71e2017-02-03 14:50:47 -080066import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080067import static org.onosproject.net.intent.IntentState.*;
68import static org.slf4j.LoggerFactory.getLogger;
69
70/**
71 * Auxiliary entity responsible for installing the intents into the environment.
72 */
73class IntentInstaller {
74
Brian O'Connorc590ebb2016-12-08 18:16:41 -080075 private static final Logger log = getLogger(IntentInstaller.class);
Yi Tseng38fc71e2017-02-03 14:50:47 -080076 private static final long OBJECTIVE_RETRY_THRESHOLD = 5;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080077
78 private IntentStore store;
79 private ObjectiveTrackerService trackerService;
80 private FlowRuleService flowRuleService;
81 private FlowObjectiveService flowObjectiveService;
Andreas Papazois05548962016-11-23 11:36:55 +020082 private NetworkConfigService networkConfigService;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080083
84 private enum Direction {
85 ADD,
86 REMOVE
87 }
88
89 /**
90 * Initializes the installer with references to required services.
91 *
92 * @param intentStore intent store
93 * @param trackerService objective tracking service
94 * @param flowRuleService flow rule service
95 * @param flowObjectiveService flow objective service
Andreas Papazois05548962016-11-23 11:36:55 +020096 * @param networkConfigService network configuration service
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080097 */
98 void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
Andreas Papazois05548962016-11-23 11:36:55 +020099 FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService,
100 NetworkConfigService networkConfigService) {
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800101 this.store = intentStore;
102 this.trackerService = trackerService;
Andreas Papazois05548962016-11-23 11:36:55 +0200103 //TODO Various services should be plugged to the intent installer instead of being hardcoded
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800104 this.flowRuleService = flowRuleService;
105 this.flowObjectiveService = flowObjectiveService;
Andreas Papazois05548962016-11-23 11:36:55 +0200106 this.networkConfigService = networkConfigService;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800107 }
108
Thomas Vachuska2980c972016-02-23 20:58:49 -0800109 // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
110 // This will be addressed in intent domains work; not now.
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800111
112 /**
113 * Applies the specified intent updates to the environment by uninstalling
114 * and installing the intents and updating the store references appropriately.
115 *
116 * @param toUninstall optional intent to uninstall
117 * @param toInstall optional intent to install
118 */
119 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
Andreas Papazois05548962016-11-23 11:36:55 +0200120 // Hook for handling success at intent installation level.
121 Consumer<IntentInstallationContext> successConsumer = (ctx) -> {
Thomas Vachuska2980c972016-02-23 20:58:49 -0800122 if (toInstall.isPresent()) {
123 IntentData installData = toInstall.get();
124 log.debug("Completed installing: {}", installData.key());
125 installData.setState(INSTALLED);
126 store.write(installData);
127 } else if (toUninstall.isPresent()) {
128 IntentData uninstallData = toUninstall.get();
129 log.debug("Completed withdrawing: {}", uninstallData.key());
130 switch (uninstallData.request()) {
131 case INSTALL_REQ:
132 uninstallData.setState(FAILED);
133 break;
134 case WITHDRAW_REQ:
135 default: //TODO "default" case should not happen
136 uninstallData.setState(WITHDRAWN);
137 break;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800138 }
Brian O'Connora78f0602016-09-22 10:56:08 -0700139 // Intent has been withdrawn; we can clear the installables
140 store.write(new IntentData(uninstallData, Collections.emptyList()));
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800141 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800142 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800143
Andreas Papazois05548962016-11-23 11:36:55 +0200144 // Hook for handling errors at intent installation level
145 Consumer<IntentInstallationContext> errorConsumer = (ctx) -> {
Thomas Vachuska2980c972016-02-23 20:58:49 -0800146 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
147 if (toInstall.isPresent()) {
148 IntentData installData = toInstall.get();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800149 installData.setState(CORRUPT);
150 installData.incrementErrorCount();
151 store.write(installData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800152 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800153 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
154 if (toUninstall.isPresent()) {
155 IntentData uninstallData = toUninstall.get();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800156 uninstallData.setState(CORRUPT);
157 uninstallData.incrementErrorCount();
158 store.write(uninstallData);
159 }
160 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800161
Andreas Papazois05548962016-11-23 11:36:55 +0200162 // Hooks at operation level
163 Consumer<OperationContext> successOperationConsumer = (ctx) -> {
164 ctx.intentContext.finishContext(ctx);
165 };
166 Consumer<OperationContext> errorOperationConsumer = (ctx) -> {
167 if (ctx.toInstall.isPresent()) {
168 IntentData installData = toInstall.get();
169 log.warn("Failed installation operation for: {} {} due to {}",
170 installData.key(), installData.intent(), ctx.error());
171 }
172 if (ctx.toUninstall.isPresent()) {
173 IntentData uninstallData = toUninstall.get();
174 log.warn("Failed withdrawal operation for: {} {} due to {}",
175 uninstallData.key(), uninstallData.intent(), ctx.error());
176 }
177 ctx.intentContext.handleError(ctx);
178 };
179
Thomas Vachuska2980c972016-02-23 20:58:49 -0800180 // Create a context for tracking the backing operations for applying
181 // the intents to the environment.
Andreas Papazois05548962016-11-23 11:36:55 +0200182 IntentInstallationContext intentContext =
183 new IntentInstallationContext(successConsumer, errorConsumer);
184 Set<OperationContext> contexts = createContext(intentContext, toUninstall, toInstall);
185 intentContext.pendingContexts = contexts;
186 contexts.forEach(ctx -> {
187 ctx.prepare(toUninstall, toInstall, successOperationConsumer, errorOperationConsumer);
188 ctx.apply();
189 });
Thomas Vachuska2980c972016-02-23 20:58:49 -0800190 }
191
Andreas Papazois05548962016-11-23 11:36:55 +0200192 // Context for applying and tracking multiple kinds of operation contexts
193 // related to specific intent data.
194 private final class IntentInstallationContext {
195 private Set<OperationContext> pendingContexts = Sets.newHashSet();
196 private Set<OperationContext> errorContexts = Sets.newHashSet();
197 private Consumer<IntentInstallationContext> successConsumer;
198 private Consumer<IntentInstallationContext> errorConsumer;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800199
Andreas Papazois05548962016-11-23 11:36:55 +0200200 private IntentInstallationContext(Consumer<IntentInstallationContext> succesConsumer,
201 Consumer<IntentInstallationContext> errorConsumer) {
202 this.successConsumer = succesConsumer;
203 this.errorConsumer = errorConsumer;
204 }
205
206 private void handleError(OperationContext ctx) {
207 errorContexts.add(ctx);
208 finishContext(ctx);
209 }
210
211 private void finishContext(OperationContext ctx) {
212 synchronized (pendingContexts) {
213 pendingContexts.remove(ctx);
214 if (pendingContexts.isEmpty()) {
215 if (errorContexts.isEmpty()) {
216 successConsumer.accept(IntentInstallationContext.this);
217 } else {
218 errorConsumer.accept(IntentInstallationContext.this);
219 }
220 }
221 }
222 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800223
224 @Override
225 public String toString() {
226 return MoreObjects.toStringHelper(this)
227 .add("pendingContexts", pendingContexts)
228 .add("errorContexts", errorContexts)
229 .toString();
230 }
Andreas Papazois05548962016-11-23 11:36:55 +0200231 }
232
233 // --- Utilities to support various installable Intent ----
234
235 // Creates the set of contexts appropriate for tracking operations of the
Thomas Vachuska2980c972016-02-23 20:58:49 -0800236 // the specified intents.
Andreas Papazois05548962016-11-23 11:36:55 +0200237 private Set<OperationContext> createContext(IntentInstallationContext intentContext,
238 Optional<IntentData> toUninstall,
239 Optional<IntentData> toInstall) {
240
241 Set<OperationContext> contexts = Sets.newConcurrentHashSet();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800242 if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
Andreas Papazois05548962016-11-23 11:36:55 +0200243 contexts.add(new FlowRuleOperationContext(intentContext));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800244 }
245 if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
Andreas Papazois05548962016-11-23 11:36:55 +0200246 contexts.add(new FlowObjectiveOperationContext(intentContext));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800247 }
Andreas Papazois05548962016-11-23 11:36:55 +0200248 if (isInstallable(toUninstall, toInstall, ProtectionEndpointIntent.class)) {
249 contexts.add(new ProtectionConfigOperationContext(intentContext));
250 }
251
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800252 if (contexts.isEmpty()) {
253 log.warn("{} did not contain installable Intents", intentContext);
254 return ImmutableSet.of(new ErrorContext(intentContext));
255 }
256
257 return contexts;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800258 }
259
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -0800260 /**
261 * Tests if one of {@code toUninstall} or {@code toInstall} contains
262 * installable Intent of type specified by {@code intentClass}.
263 *
264 * @param toUninstall IntentData to test
265 * @param toInstall IntentData to test
266 * @param intentClass installable Intent class
267 * @return true if at least one of IntentData contains installable specified.
268 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800269 private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
270 Class<? extends Intent> intentClass) {
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -0800271
272 return Stream.concat(toInstall
273 .map(IntentData::installables)
274 .map(Collection::stream)
275 .orElse(Stream.empty()),
276 toUninstall
277 .map(IntentData::installables)
278 .map(Collection::stream)
279 .orElse(Stream.empty()))
280 .anyMatch(i -> intentClass.isAssignableFrom(i.getClass()));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800281 }
282
283 // Base context for applying and tracking operations related to installable intents.
284 private abstract class OperationContext {
Andreas Papazois05548962016-11-23 11:36:55 +0200285 protected IntentInstallationContext intentContext;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800286 protected Optional<IntentData> toUninstall;
287 protected Optional<IntentData> toInstall;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700288 /**
289 * Implementation of {@link OperationContext} should call this on success.
290 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800291 protected Consumer<OperationContext> successConsumer;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700292 /**
293 * Implementation of {@link OperationContext} should call this on error.
294 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800295 protected Consumer<OperationContext> errorConsumer;
296
Andreas Papazois05548962016-11-23 11:36:55 +0200297 protected OperationContext(IntentInstallationContext context) {
298 this.intentContext = context;
299 }
300
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700301 /**
302 * Applies the Intents specified by
303 * {@link #prepareIntents(List, Direction)} call(s) prior to this call.
304 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800305 abstract void apply();
306
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700307 /**
308 * Returns error state of the context.
309 * <p>
310 * Used for error logging purpose.
311 * Returned Object should have reasonable toString() implementation.
312 * @return context state, describing current error state
313 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800314 abstract Object error();
315
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700316 /**
317 * Prepares Intent(s) to {@link #apply() apply} in this operation.
318 * <p>
319 * Intents specified by {@code intentsToApply} in a single call
320 * can be applied to the Devices in arbitrary order.
321 * But group of Intents specified in consecutive {@link #prepareIntents(List, Direction)}
322 * calls must be applied in order. (e.g., guarded by barrier)
323 *
324 * @param intentsToApply {@link Intent}s to apply
325 * @param direction of operation
326 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800327 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
328
329 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
330 Consumer<OperationContext> successConsumer,
331 Consumer<OperationContext> errorConsumer) {
332 this.toUninstall = toUninstall;
333 this.toInstall = toInstall;
334 this.successConsumer = successConsumer;
335 this.errorConsumer = errorConsumer;
helenyrwue6aaa332016-08-05 15:41:42 -0700336 prepareIntentData(toUninstall, toInstall);
337 }
338
339 private void prepareIntentData(Optional<IntentData> uninstallData,
340 Optional<IntentData> installData) {
341 if (!installData.isPresent() && !uninstallData.isPresent()) {
342 return;
343 } else if (!installData.isPresent()) {
344 prepareIntentData(uninstallData, Direction.REMOVE);
345 } else if (!uninstallData.isPresent()) {
346 prepareIntentData(installData, Direction.ADD);
347 } else {
348 IntentData uninstall = uninstallData.get();
349 IntentData install = installData.get();
Brian O'Connor09d90f02016-09-13 11:06:14 -0700350 List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
351 List<Intent> installIntents = Lists.newArrayList(install.installables());
helenyrwue6aaa332016-08-05 15:41:42 -0700352
353 checkState(uninstallIntents.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800354 "Unsupported installable intents detected: %s", uninstallIntents);
helenyrwue6aaa332016-08-05 15:41:42 -0700355 checkState(installIntents.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800356 "Unsupported installable intents detected: %s", installIntents);
helenyrwue6aaa332016-08-05 15:41:42 -0700357
358 //TODO: Filter FlowObjective intents
359 // Filter out same intents and intents with same flow rules
360 Iterator<Intent> iterator = installIntents.iterator();
361 while (iterator.hasNext()) {
362 Intent installIntent = iterator.next();
363 uninstallIntents.stream().filter(uIntent -> {
364 if (uIntent.equals(installIntent)) {
365 return true;
366 } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
Brian O'Connor09d90f02016-09-13 11:06:14 -0700367 //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
368 // (direction can be implied from intent state)
Yi Tseng424bfa72017-04-14 13:43:26 -0700369 return !flowRuleIntentChanged(((FlowRuleIntent) uIntent),
370 ((FlowRuleIntent) installIntent));
helenyrwue6aaa332016-08-05 15:41:42 -0700371 } else {
372 return false;
373 }
374 }).findFirst().ifPresent(common -> {
375 uninstallIntents.remove(common);
Brian O'Connor09d90f02016-09-13 11:06:14 -0700376 if (INSTALLED.equals(uninstall.state())) {
377 // only remove the install intent if the existing
378 // intent (i.e. the uninstall one) is already
379 // installed or installing
380 iterator.remove();
381 }
helenyrwue6aaa332016-08-05 15:41:42 -0700382 });
383 }
384
385 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
386 final IntentData newInstall = new IntentData(install, installIntents);
387
388 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
389 uninstallIntents.forEach(installable ->
390 trackerService.removeTrackedResources(newUninstall.intent().key(),
391 installable.resources()));
392 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
393 installIntents.forEach(installable ->
394 trackerService.addTrackedResources(newInstall.key(),
395 installable.resources()));
396 prepareIntents(uninstallIntents, Direction.REMOVE);
397 prepareIntents(installIntents, Direction.ADD);
398 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800399 }
400
Thomas Vachuska2980c972016-02-23 20:58:49 -0800401 /**
Yi Tseng424bfa72017-04-14 13:43:26 -0700402 * Determines whether there is any flow rule changed
403 * (i.e., different set of flow rules or different treatments)
404 * between FlowRuleIntents to be uninstalled and to be installed.
405 *
406 * @param uninstallIntent FlowRuleIntent to uninstall
407 * @param installIntent FlowRuleIntent to install
408 * @return true if flow rules which to be uninstalled
409 * contains all flow rules which to be installed.
410 */
411 private boolean flowRuleIntentChanged(FlowRuleIntent uninstallIntent,
412 FlowRuleIntent installIntent) {
413 Collection<FlowRule> flowRulesToUninstall = uninstallIntent.flowRules();
414 Collection<FlowRule> flowRulesToInstall = installIntent.flowRules();
415
416 // Check if any flow rule changed
417 for (FlowRule flowRuleToInstall : flowRulesToInstall) {
418 if (flowRulesToUninstall.stream().noneMatch(flowRuleToInstall::exactMatch)) {
419 return true;
420 }
421 }
422 return false;
423 }
424
425 /**
Thomas Vachuska2980c972016-02-23 20:58:49 -0800426 * Applies the specified intent data, if present, to the network using the
427 * specified context.
428 *
429 * @param intentData optional intent data; no-op if not present
430 * @param direction indicates adding or removal
431 */
432 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
433 if (!intentData.isPresent()) {
434 return;
435 }
436
437 IntentData data = intentData.get();
438 List<Intent> intentsToApply = data.installables();
439 checkState(intentsToApply.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800440 "Unsupported installable intents detected: %s", intentsToApply);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800441
442 if (direction == Direction.ADD) {
443 trackerService.addTrackedResources(data.key(), data.intent().resources());
444 intentsToApply.forEach(installable ->
445 trackerService.addTrackedResources(data.key(),
446 installable.resources()));
447 } else {
448 trackerService.removeTrackedResources(data.key(), data.intent().resources());
449 intentsToApply.forEach(installable ->
450 trackerService.removeTrackedResources(data.intent().key(),
451 installable.resources()));
452 }
453
454 prepareIntents(intentsToApply, direction);
455 }
456
457 private boolean isSupported(Intent intent) {
Andreas Papazois05548962016-11-23 11:36:55 +0200458 return intent instanceof FlowRuleIntent ||
459 intent instanceof FlowObjectiveIntent ||
460 intent instanceof ProtectionEndpointIntent;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800461 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800462
463 protected ToStringHelper toStringHelper() {
464 return MoreObjects.toStringHelper(this)
465 .add("intentContext", intentContext)
466 .add("toUninstall", toUninstall)
467 .add("toInstall", toInstall);
468 }
469
470 @Override
471 public String toString() {
472 return toStringHelper()
473 .toString();
474 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800475 }
476
477
Andreas Papazois05548962016-11-23 11:36:55 +0200478 // Context for applying and tracking operations related to flow rule intents.
Thomas Vachuska2980c972016-02-23 20:58:49 -0800479 private class FlowRuleOperationContext extends OperationContext {
480 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
481 FlowRuleOperationsContext flowRuleOperationsContext;
482
Andreas Papazois05548962016-11-23 11:36:55 +0200483 FlowRuleOperationContext(IntentInstallationContext context) {
484 super(context);
485 }
486
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700487 @Override
Thomas Vachuska2980c972016-02-23 20:58:49 -0800488 void apply() {
489 flowRuleOperationsContext = new FlowRuleOperationsContext() {
490 @Override
491 public void onSuccess(FlowRuleOperations ops) {
492 successConsumer.accept(FlowRuleOperationContext.this);
493 }
494
495 @Override
496 public void onError(FlowRuleOperations ops) {
497 errorConsumer.accept(FlowRuleOperationContext.this);
498 }
499 };
500 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
501
502 if (log.isTraceEnabled()) {
503 log.trace("applying intent {} -> {} with {} rules: {}",
504 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
505 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
506 operations.stages().stream().mapToLong(Set::size).sum(),
507 operations.stages());
508 }
509
510 flowRuleService.apply(operations);
511 }
512
513 @Override
514 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
515 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
516 builder.newStage();
517
518 List<Collection<FlowRule>> stages = intentsToApply.stream()
Andreas Papazois05548962016-11-23 11:36:55 +0200519 .filter(x -> x instanceof FlowRuleIntent)
Thomas Vachuska2980c972016-02-23 20:58:49 -0800520 .map(x -> (FlowRuleIntent) x)
521 .map(FlowRuleIntent::flowRules)
522 .collect(Collectors.toList());
523
524 for (Collection<FlowRule> rules : stages) {
525 if (direction == Direction.ADD) {
526 rules.forEach(builder::add);
527 } else {
528 rules.forEach(builder::remove);
529 }
530 }
531
532 }
533
534 @Override
535 public Object error() {
536 return flowRuleOperationsContext;
537 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800538
539 @Override
540 protected ToStringHelper toStringHelper() {
541 return super.toStringHelper()
542 .omitNullValues()
543 .add("flowRuleOperationsContext", flowRuleOperationsContext);
544 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800545 }
546
547 // Context for applying and tracking operations related to flow objective intents.
548 private class FlowObjectiveOperationContext extends OperationContext {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800549 private static final String UNSUPPORT_OBJ = "unsupported objective {}";
550 final List<ObjectiveContext> contexts = Lists.newArrayList();
551
552 final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet();
553
554 // Second stage of pending contexts
555 final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800556 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
557
Andreas Papazois05548962016-11-23 11:36:55 +0200558 FlowObjectiveOperationContext(IntentInstallationContext context) {
559 super(context);
560 }
561
Thomas Vachuska2980c972016-02-23 20:58:49 -0800562 @Override
563 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800564 intentsToApply
565 .stream()
566 .filter(intent -> intent instanceof FlowObjectiveIntent)
567 .map(intent -> buildObjectiveContexts((FlowObjectiveIntent) intent, direction))
568 .flatMap(Collection::stream)
569 .forEach(contexts::add);
570
571 // Two stage for different direction context
572 // We will apply REMOVE context first, and apply ADD context.
573 contexts.forEach(context -> {
574 switch (direction) {
575 case REMOVE:
576 pendingContexts.add(context);
577 break;
578 case ADD:
579 nextPendingContexts.add(context);
580 break;
581 default:
582 break;
583 }
584 });
Thomas Vachuska2980c972016-02-23 20:58:49 -0800585 }
586
587 // Builds the specified objective in the appropriate direction
Yi Tseng38fc71e2017-02-03 14:50:47 -0800588 private Set<? extends ObjectiveContext> buildObjectiveContexts(FlowObjectiveIntent intent,
589 Direction direction) {
590 Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800591 int size = intent.objectives().size();
Yi Tseng38fc71e2017-02-03 14:50:47 -0800592 List<Objective> objectives = intent.objectives();
593 List<DeviceId> deviceIds = intent.devices();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800594
Yi Tseng38fc71e2017-02-03 14:50:47 -0800595 if (direction == Direction.ADD) {
596 for (int i = 0; i < size; i++) {
597 Objective objective = objectives.get(i);
598 DeviceId deviceId = deviceIds.get(i);
599 FlowObjectiveInstallationContext ctx =
600 buildObjectiveContext(objective, deviceId, direction);
601 contexts.add(ctx);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800602 }
Yi Tseng38fc71e2017-02-03 14:50:47 -0800603 return contexts;
604 } else {
605 // we need to care about ordering here
606 // basic idea is to chain objective contexts
607 for (int i = 0; i < size; i++) {
608 Objective objective = intent.objectives().get(i);
609 DeviceId deviceId = intent.devices().get(i);
610
611 if (objective instanceof FilteringObjective) {
612 // don't need to care ordering of filtering objective
613 FlowObjectiveInstallationContext ctx =
614 buildObjectiveContext(objective, deviceId, direction);
615 contexts.add(ctx);
616 } else if (objective instanceof NextObjective) {
617 // need to removed after forwarding objective
618 // nothing to do here
619 } else if (objective instanceof ForwardingObjective) {
620 // forwarding objective, also find next objective if
621 // exist
622 FlowObjectiveInstallationContext fwdCtx =
623 buildObjectiveContext(objective, deviceId, direction);
624 ForwardingObjective fwd = (ForwardingObjective) objective;
625 NextObjective nxt = null;
626 Integer nextId = fwd.nextId();
627 if (nextId != null) {
628 for (int j = 0; j < size; j++) {
629 if (objectives.get(j).id() == nextId) {
630 nxt = (NextObjective) objectives.get(j);
631 break;
632 }
633 }
634 // if a next objective exists in the Intent
635 if (nxt != null) {
636 FlowObjectiveInstallationContext nxtCtx =
637 buildObjectiveContext(nxt, deviceId, direction);
638 fwdCtx.nextContext(nxtCtx);
639 }
640 }
641 contexts.add(fwdCtx);
642 } else {
643 // possible here?
644 log.warn(UNSUPPORT_OBJ, objective);
645 }
646 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800647 }
648 return contexts;
649 }
650
Yi Tseng38fc71e2017-02-03 14:50:47 -0800651 private FlowObjectiveInstallationContext buildObjectiveContext(Objective objective,
652 DeviceId deviceId,
653 Direction direction) {
654 Objective.Builder builder = objective.copy();
655 FlowObjectiveInstallationContext ctx = new FlowObjectiveInstallationContext();
656 switch (direction) {
657 case ADD:
658 objective = builder.add(ctx);
659 break;
660 case REMOVE:
661 objective = builder.remove(ctx);
662 break;
663 default:
664 break;
665 }
666 ctx.setObjective(objective, deviceId);
667 return ctx;
668 }
669
Thomas Vachuska2980c972016-02-23 20:58:49 -0800670 @Override
671 void apply() {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800672 // If there is no pending contexts, try apply second stage
673 // pending contexts
674 if (pendingContexts.isEmpty()) {
675 pendingContexts.addAll(nextPendingContexts);
676 nextPendingContexts.clear();
677 }
678 final Set<ObjectiveContext> contextsToApply = Sets.newHashSet(pendingContexts);
679 contextsToApply.forEach(ctx -> {
680 FlowObjectiveInstallationContext foiCtx =
681 (FlowObjectiveInstallationContext) ctx;
682
683 flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective);
684 });
Thomas Vachuska2980c972016-02-23 20:58:49 -0800685 }
686
687 @Override
688 public Object error() {
689 return errorContexts;
690 }
691
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800692 @Override
693 protected ToStringHelper toStringHelper() {
694 return super.toStringHelper()
695 .add("contexts", contexts)
696 .add("pendingContexts", pendingContexts)
697 .add("errorContexts", errorContexts);
698 }
699
Thomas Vachuska2980c972016-02-23 20:58:49 -0800700 private class FlowObjectiveInstallationContext implements ObjectiveContext {
701 Objective objective;
702 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700703 ObjectiveError error;
Yi Tseng38fc71e2017-02-03 14:50:47 -0800704 AtomicInteger retry;
705 FlowObjectiveInstallationContext nextContext;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800706
707 void setObjective(Objective objective, DeviceId deviceId) {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800708 // init function
Thomas Vachuska2980c972016-02-23 20:58:49 -0800709 this.objective = objective;
710 this.deviceId = deviceId;
Yi Tseng38fc71e2017-02-03 14:50:47 -0800711 this.error = null;
712 this.retry = new AtomicInteger(0);
713 this.nextContext = null;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800714 }
715
Yi Tseng38fc71e2017-02-03 14:50:47 -0800716 int retryTimes() {
717 return this.retry.get();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800718 }
719
Yi Tseng38fc71e2017-02-03 14:50:47 -0800720 void increaseRetryValue() {
721 this.retry.incrementAndGet();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800722 }
723
Yi Tseng38fc71e2017-02-03 14:50:47 -0800724 private void finished(ObjectiveError error) {
725
Thomas Vachuska2980c972016-02-23 20:58:49 -0800726 synchronized (pendingContexts) {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800727 if (error != null) {
728 this.error = error;
729 handleObjectiveError(this, error);
730 } else {
731 // apply next context if exist
732 if (nextContext != null) {
733 pendingContexts.add(nextContext);
734 flowObjectiveService.apply(nextContext.deviceId,
735 nextContext.objective);
736 pendingContexts.remove(this);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800737 } else {
Yi Tseng38fc71e2017-02-03 14:50:47 -0800738 pendingContexts.remove(this);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800739 }
740 }
Yi Tseng38fc71e2017-02-03 14:50:47 -0800741 if (!pendingContexts.isEmpty()) {
742 return;
743 }
744 // Apply second stage pending contexts if it is not empty
745 if (!nextPendingContexts.isEmpty()) {
746 pendingContexts.addAll(nextPendingContexts);
747 nextPendingContexts.clear();
748 final Set<ObjectiveContext> contextsToApply =
749 Sets.newHashSet(pendingContexts);
750 contextsToApply.forEach(ctx -> {
751 FlowObjectiveInstallationContext foiCtx =
752 (FlowObjectiveInstallationContext) ctx;
753 flowObjectiveService.apply(foiCtx.deviceId,
754 foiCtx.objective);
755 });
756 return;
757 }
758 if (errorContexts.isEmpty()) {
759 successConsumer.accept(FlowObjectiveOperationContext.this);
760 } else {
761 errorConsumer.accept(FlowObjectiveOperationContext.this);
762 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800763 }
764 }
765
766 @Override
Yi Tseng38fc71e2017-02-03 14:50:47 -0800767 public void onSuccess(Objective objective) {
768 finished(null);
769 }
770
771 @Override
772 public void onError(Objective objective, ObjectiveError error) {
773 finished(error);
774 }
775
776 @Override
Thomas Vachuska2980c972016-02-23 20:58:49 -0800777 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700778 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800779 }
Yi Tseng38fc71e2017-02-03 14:50:47 -0800780
781 public void nextContext(FlowObjectiveInstallationContext nextContext) {
782 this.nextContext = nextContext;
783 }
784 }
785
786 private void handleObjectiveError(FlowObjectiveInstallationContext ctx,
787 ObjectiveError error) {
788 log.debug("Got error(s) when install objective: {}, error: {}, retry: {}",
789 ctx.objective, ctx.error, ctx.retry);
790 if (ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD) {
791 ctx.error = INSTALLATIONTHRESHOLDEXCEEDED;
792 errorContexts.add(ctx);
793 return;
794 }
795 // reset error
796 ctx.error = null;
797 // strategies for errors
798 switch (error) {
799 case GROUPEXISTS:
800 if (ctx.objective.op() == Objective.Operation.ADD) {
801 // Next group exists
802 // build new objective with new op ADD_TO_EXIST
803 NextObjective newObj =
804 ((NextObjective.Builder) ctx.objective.copy()).addToExisting(ctx);
805 ctx.setObjective(newObj, ctx.deviceId);
806 ctx.increaseRetryValue();
807 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
808 } else {
809 pendingContexts.remove(ctx);
810 errorContexts.add(ctx);
811 }
812 break;
813 case GROUPINSTALLATIONFAILED:
814 // Group install failed, retry again
815 ctx.increaseRetryValue();
816 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
817 break;
818 case GROUPMISSING:
819 if (ctx.objective.op() == Objective.Operation.ADD_TO_EXISTING) {
820 // Next group not exist, but we want to add new buckets
821 // build new objective with new op ADD
822 NextObjective newObj = (NextObjective) ctx.objective.copy().add(ctx);
823 ctx.setObjective(newObj, ctx.deviceId);
824 ctx.increaseRetryValue();
825 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
826 } else if (ctx.objective.op() == Objective.Operation.REMOVE ||
827 ctx.objective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
828 // Already removed, no need to do anything
829 ctx.error = null;
830 pendingContexts.remove(ctx);
831 return;
832 } else {
833 // Next chaining group missing, try again.
834 ctx.increaseRetryValue();
835 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
836 }
837 break;
838 case FLOWINSTALLATIONFAILED:
839 case GROUPREMOVALFAILED:
840 case INSTALLATIONTIMEOUT:
841 // Retry
842 ctx.increaseRetryValue();
843 flowObjectiveService.apply(ctx.deviceId, ctx.objective);
844 break;
845 default:
846 pendingContexts.remove(ctx);
847 errorContexts.add(ctx);
848 break;
849 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800850 }
851 }
852
853 private class ErrorContext extends OperationContext {
Andreas Papazois05548962016-11-23 11:36:55 +0200854 ErrorContext(IntentInstallationContext context) {
855 super(context);
856 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800857 @Override
858 void apply() {
859 throw new UnsupportedOperationException("Unsupported installable intent");
860 }
861
862 @Override
863 Object error() {
864 return null;
865 }
866
867 @Override
868 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
869 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800870 }
Andreas Papazois05548962016-11-23 11:36:55 +0200871
872
873 /**
874 * Context for applying and tracking operations related to
875 * {@link ProtectionEndpointIntent}.
876 */
877 @Beta
878 private class ProtectionConfigOperationContext extends OperationContext {
879
880 ProtectionConfigOperationContext(IntentInstallationContext context) {
881 super(context);
882 }
883
884 /**
885 * Stage of installable Intents which can be processed in parallel.
886 */
887 private final class Stage {
888 // should it have progress state, how far it went?
889 private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops;
890
891 Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) {
892 this.ops = checkNotNull(ops);
893 }
894
895 CompletableFuture<Void> apply() {
896 return ops.stream()
897 .map(op -> applyOp(op.getRight(), op.getLeft()))
898 .reduce(CompletableFuture.completedFuture(null),
899 (l, r) -> {
900 l.join();
901 return r;
902 });
903 }
904
905 private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) {
906 log.trace("applying {}: {}", dir, intent);
907 if (dir == Direction.REMOVE) {
908 networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class);
909 } else if (dir == Direction.ADD) {
910 ProtectedTransportEndpointDescription description = intent.description();
911
912 // Can't do following. Will trigger empty CONFIG_ADDED
913 //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(),
914 // ProtectionConfig.class);
915 ProtectionConfig cfg = new ProtectionConfig(intent.deviceId());
916 cfg.fingerprint(description.fingerprint());
917 cfg.peer(description.peer());
918 cfg.paths(description.paths());
919 //cfg.apply();
920
921 networkConfigService.applyConfig(intent.deviceId(),
922 ProtectionConfig.class,
923 cfg.node());
924 }
925 // TODO Should monitor progress and complete only after it's
926 // actually done.
927 return CompletableFuture.completedFuture(null);
928 }
929
930 @Override
931 public String toString() {
932 return ops.toString();
933 }
934 }
935
936 /**
937 * List of Stages which must be executed in order.
938 */
939 private final List<Stage> stages = new ArrayList<>();
940
941 private final List<Stage> failed = new CopyOnWriteArrayList<>();
942
943 @Override
944 synchronized void apply() {
945 for (Stage stage : stages) {
946 log.trace("applying Stage {}", stage);
947 CompletableFuture<Void> result = stage.apply();
948 // wait for stage completion
949 result.join();
950 if (result.isCompletedExceptionally()) {
951 log.error("Stage {} failed", stage);
952 failed.add(stage);
953 errorConsumer.accept(ProtectionConfigOperationContext.this);
954 return;
955 }
956 }
957 successConsumer.accept(ProtectionConfigOperationContext.this);
958 }
959
960 @Override
961 Object error() {
962 // Something to represent error state
963 return failed;
964 }
965
966 @Override
967 synchronized void prepareIntents(List<Intent> intentsToApply,
968 Direction direction) {
969
970 stages.add(new Stage(intentsToApply.stream()
971 .filter(i -> i instanceof ProtectionEndpointIntent)
972 .map(i -> Pair.of((ProtectionEndpointIntent) i, direction))
973 .collect(Collectors.toList())));
974 }
975
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800976 @Override
977 protected ToStringHelper toStringHelper() {
978 return super.toStringHelper()
979 .add("stages", stages)
980 .add("failed", failed);
981 }
Andreas Papazois05548962016-11-23 11:36:55 +0200982 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800983}