blob: 0fd57a986e8e165d4886d66a8c991e61cf6790bf [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;
35import org.onosproject.net.flowobjective.FlowObjectiveService;
Thomas Vachuska2980c972016-02-23 20:58:49 -080036import org.onosproject.net.flowobjective.Objective;
37import org.onosproject.net.flowobjective.ObjectiveContext;
38import org.onosproject.net.flowobjective.ObjectiveError;
39import org.onosproject.net.intent.FlowObjectiveIntent;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080040import org.onosproject.net.intent.FlowRuleIntent;
41import org.onosproject.net.intent.Intent;
42import org.onosproject.net.intent.IntentData;
43import org.onosproject.net.intent.IntentStore;
Andreas Papazois05548962016-11-23 11:36:55 +020044import org.onosproject.net.intent.ProtectionEndpointIntent;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080045import org.slf4j.Logger;
46
Thomas Vachuska2980c972016-02-23 20:58:49 -080047import java.util.ArrayList;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080048import java.util.Collection;
Brian O'Connora78f0602016-09-22 10:56:08 -070049import java.util.Collections;
helenyrwue6aaa332016-08-05 15:41:42 -070050import java.util.Iterator;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080051import java.util.List;
52import java.util.Optional;
53import java.util.Set;
Andreas Papazois05548962016-11-23 11:36:55 +020054import java.util.concurrent.CompletableFuture;
55import java.util.concurrent.CopyOnWriteArrayList;
Thomas Vachuska2980c972016-02-23 20:58:49 -080056import java.util.function.Consumer;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080057import java.util.stream.Collectors;
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -080058import java.util.stream.Stream;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080059
Andreas Papazois05548962016-11-23 11:36:55 +020060import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuska2980c972016-02-23 20:58:49 -080061import static com.google.common.base.Preconditions.checkState;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080062import static org.onosproject.net.intent.IntentState.*;
63import static org.slf4j.LoggerFactory.getLogger;
64
65/**
66 * Auxiliary entity responsible for installing the intents into the environment.
67 */
68class IntentInstaller {
69
Brian O'Connorc590ebb2016-12-08 18:16:41 -080070 private static final Logger log = getLogger(IntentInstaller.class);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080071
72 private IntentStore store;
73 private ObjectiveTrackerService trackerService;
74 private FlowRuleService flowRuleService;
75 private FlowObjectiveService flowObjectiveService;
Andreas Papazois05548962016-11-23 11:36:55 +020076 private NetworkConfigService networkConfigService;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080077
78 private enum Direction {
79 ADD,
80 REMOVE
81 }
82
83 /**
84 * Initializes the installer with references to required services.
85 *
86 * @param intentStore intent store
87 * @param trackerService objective tracking service
88 * @param flowRuleService flow rule service
89 * @param flowObjectiveService flow objective service
Andreas Papazois05548962016-11-23 11:36:55 +020090 * @param networkConfigService network configuration service
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080091 */
92 void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
Andreas Papazois05548962016-11-23 11:36:55 +020093 FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService,
94 NetworkConfigService networkConfigService) {
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080095 this.store = intentStore;
96 this.trackerService = trackerService;
Andreas Papazois05548962016-11-23 11:36:55 +020097 //TODO Various services should be plugged to the intent installer instead of being hardcoded
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -080098 this.flowRuleService = flowRuleService;
99 this.flowObjectiveService = flowObjectiveService;
Andreas Papazois05548962016-11-23 11:36:55 +0200100 this.networkConfigService = networkConfigService;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800101 }
102
Thomas Vachuska2980c972016-02-23 20:58:49 -0800103 // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
104 // This will be addressed in intent domains work; not now.
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800105
106 /**
107 * Applies the specified intent updates to the environment by uninstalling
108 * and installing the intents and updating the store references appropriately.
109 *
110 * @param toUninstall optional intent to uninstall
111 * @param toInstall optional intent to install
112 */
113 void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
Andreas Papazois05548962016-11-23 11:36:55 +0200114 // Hook for handling success at intent installation level.
115 Consumer<IntentInstallationContext> successConsumer = (ctx) -> {
Thomas Vachuska2980c972016-02-23 20:58:49 -0800116 if (toInstall.isPresent()) {
117 IntentData installData = toInstall.get();
118 log.debug("Completed installing: {}", installData.key());
119 installData.setState(INSTALLED);
120 store.write(installData);
121 } else if (toUninstall.isPresent()) {
122 IntentData uninstallData = toUninstall.get();
123 log.debug("Completed withdrawing: {}", uninstallData.key());
124 switch (uninstallData.request()) {
125 case INSTALL_REQ:
126 uninstallData.setState(FAILED);
127 break;
128 case WITHDRAW_REQ:
129 default: //TODO "default" case should not happen
130 uninstallData.setState(WITHDRAWN);
131 break;
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800132 }
Brian O'Connora78f0602016-09-22 10:56:08 -0700133 // Intent has been withdrawn; we can clear the installables
134 store.write(new IntentData(uninstallData, Collections.emptyList()));
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800135 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800136 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800137
Andreas Papazois05548962016-11-23 11:36:55 +0200138 // Hook for handling errors at intent installation level
139 Consumer<IntentInstallationContext> errorConsumer = (ctx) -> {
Thomas Vachuska2980c972016-02-23 20:58:49 -0800140 // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
141 if (toInstall.isPresent()) {
142 IntentData installData = toInstall.get();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800143 installData.setState(CORRUPT);
144 installData.incrementErrorCount();
145 store.write(installData);
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800146 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800147 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
148 if (toUninstall.isPresent()) {
149 IntentData uninstallData = toUninstall.get();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800150 uninstallData.setState(CORRUPT);
151 uninstallData.incrementErrorCount();
152 store.write(uninstallData);
153 }
154 };
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800155
Andreas Papazois05548962016-11-23 11:36:55 +0200156 // Hooks at operation level
157 Consumer<OperationContext> successOperationConsumer = (ctx) -> {
158 ctx.intentContext.finishContext(ctx);
159 };
160 Consumer<OperationContext> errorOperationConsumer = (ctx) -> {
161 if (ctx.toInstall.isPresent()) {
162 IntentData installData = toInstall.get();
163 log.warn("Failed installation operation for: {} {} due to {}",
164 installData.key(), installData.intent(), ctx.error());
165 }
166 if (ctx.toUninstall.isPresent()) {
167 IntentData uninstallData = toUninstall.get();
168 log.warn("Failed withdrawal operation for: {} {} due to {}",
169 uninstallData.key(), uninstallData.intent(), ctx.error());
170 }
171 ctx.intentContext.handleError(ctx);
172 };
173
Thomas Vachuska2980c972016-02-23 20:58:49 -0800174 // Create a context for tracking the backing operations for applying
175 // the intents to the environment.
Andreas Papazois05548962016-11-23 11:36:55 +0200176 IntentInstallationContext intentContext =
177 new IntentInstallationContext(successConsumer, errorConsumer);
178 Set<OperationContext> contexts = createContext(intentContext, toUninstall, toInstall);
179 intentContext.pendingContexts = contexts;
180 contexts.forEach(ctx -> {
181 ctx.prepare(toUninstall, toInstall, successOperationConsumer, errorOperationConsumer);
182 ctx.apply();
183 });
Thomas Vachuska2980c972016-02-23 20:58:49 -0800184 }
185
Andreas Papazois05548962016-11-23 11:36:55 +0200186 // Context for applying and tracking multiple kinds of operation contexts
187 // related to specific intent data.
188 private final class IntentInstallationContext {
189 private Set<OperationContext> pendingContexts = Sets.newHashSet();
190 private Set<OperationContext> errorContexts = Sets.newHashSet();
191 private Consumer<IntentInstallationContext> successConsumer;
192 private Consumer<IntentInstallationContext> errorConsumer;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800193
Andreas Papazois05548962016-11-23 11:36:55 +0200194 private IntentInstallationContext(Consumer<IntentInstallationContext> succesConsumer,
195 Consumer<IntentInstallationContext> errorConsumer) {
196 this.successConsumer = succesConsumer;
197 this.errorConsumer = errorConsumer;
198 }
199
200 private void handleError(OperationContext ctx) {
201 errorContexts.add(ctx);
202 finishContext(ctx);
203 }
204
205 private void finishContext(OperationContext ctx) {
206 synchronized (pendingContexts) {
207 pendingContexts.remove(ctx);
208 if (pendingContexts.isEmpty()) {
209 if (errorContexts.isEmpty()) {
210 successConsumer.accept(IntentInstallationContext.this);
211 } else {
212 errorConsumer.accept(IntentInstallationContext.this);
213 }
214 }
215 }
216 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800217
218 @Override
219 public String toString() {
220 return MoreObjects.toStringHelper(this)
221 .add("pendingContexts", pendingContexts)
222 .add("errorContexts", errorContexts)
223 .toString();
224 }
Andreas Papazois05548962016-11-23 11:36:55 +0200225 }
226
227 // --- Utilities to support various installable Intent ----
228
229 // Creates the set of contexts appropriate for tracking operations of the
Thomas Vachuska2980c972016-02-23 20:58:49 -0800230 // the specified intents.
Andreas Papazois05548962016-11-23 11:36:55 +0200231 private Set<OperationContext> createContext(IntentInstallationContext intentContext,
232 Optional<IntentData> toUninstall,
233 Optional<IntentData> toInstall) {
234
235 Set<OperationContext> contexts = Sets.newConcurrentHashSet();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800236 if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
Andreas Papazois05548962016-11-23 11:36:55 +0200237 contexts.add(new FlowRuleOperationContext(intentContext));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800238 }
239 if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
Andreas Papazois05548962016-11-23 11:36:55 +0200240 contexts.add(new FlowObjectiveOperationContext(intentContext));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800241 }
Andreas Papazois05548962016-11-23 11:36:55 +0200242 if (isInstallable(toUninstall, toInstall, ProtectionEndpointIntent.class)) {
243 contexts.add(new ProtectionConfigOperationContext(intentContext));
244 }
245
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800246 if (contexts.isEmpty()) {
247 log.warn("{} did not contain installable Intents", intentContext);
248 return ImmutableSet.of(new ErrorContext(intentContext));
249 }
250
251 return contexts;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800252 }
253
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -0800254 /**
255 * Tests if one of {@code toUninstall} or {@code toInstall} contains
256 * installable Intent of type specified by {@code intentClass}.
257 *
258 * @param toUninstall IntentData to test
259 * @param toInstall IntentData to test
260 * @param intentClass installable Intent class
261 * @return true if at least one of IntentData contains installable specified.
262 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800263 private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
264 Class<? extends Intent> intentClass) {
Yuta HIGUCHI3026c9b2017-01-30 13:28:49 -0800265
266 return Stream.concat(toInstall
267 .map(IntentData::installables)
268 .map(Collection::stream)
269 .orElse(Stream.empty()),
270 toUninstall
271 .map(IntentData::installables)
272 .map(Collection::stream)
273 .orElse(Stream.empty()))
274 .anyMatch(i -> intentClass.isAssignableFrom(i.getClass()));
Thomas Vachuska2980c972016-02-23 20:58:49 -0800275 }
276
277 // Base context for applying and tracking operations related to installable intents.
278 private abstract class OperationContext {
Andreas Papazois05548962016-11-23 11:36:55 +0200279 protected IntentInstallationContext intentContext;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800280 protected Optional<IntentData> toUninstall;
281 protected Optional<IntentData> toInstall;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700282 /**
283 * Implementation of {@link OperationContext} should call this on success.
284 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800285 protected Consumer<OperationContext> successConsumer;
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700286 /**
287 * Implementation of {@link OperationContext} should call this on error.
288 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800289 protected Consumer<OperationContext> errorConsumer;
290
Andreas Papazois05548962016-11-23 11:36:55 +0200291 protected OperationContext(IntentInstallationContext context) {
292 this.intentContext = context;
293 }
294
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700295 /**
296 * Applies the Intents specified by
297 * {@link #prepareIntents(List, Direction)} call(s) prior to this call.
298 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800299 abstract void apply();
300
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700301 /**
302 * Returns error state of the context.
303 * <p>
304 * Used for error logging purpose.
305 * Returned Object should have reasonable toString() implementation.
306 * @return context state, describing current error state
307 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800308 abstract Object error();
309
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700310 /**
311 * Prepares Intent(s) to {@link #apply() apply} in this operation.
312 * <p>
313 * Intents specified by {@code intentsToApply} in a single call
314 * can be applied to the Devices in arbitrary order.
315 * But group of Intents specified in consecutive {@link #prepareIntents(List, Direction)}
316 * calls must be applied in order. (e.g., guarded by barrier)
317 *
318 * @param intentsToApply {@link Intent}s to apply
319 * @param direction of operation
320 */
Thomas Vachuska2980c972016-02-23 20:58:49 -0800321 abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
322
323 void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
324 Consumer<OperationContext> successConsumer,
325 Consumer<OperationContext> errorConsumer) {
326 this.toUninstall = toUninstall;
327 this.toInstall = toInstall;
328 this.successConsumer = successConsumer;
329 this.errorConsumer = errorConsumer;
helenyrwue6aaa332016-08-05 15:41:42 -0700330 prepareIntentData(toUninstall, toInstall);
331 }
332
333 private void prepareIntentData(Optional<IntentData> uninstallData,
334 Optional<IntentData> installData) {
335 if (!installData.isPresent() && !uninstallData.isPresent()) {
336 return;
337 } else if (!installData.isPresent()) {
338 prepareIntentData(uninstallData, Direction.REMOVE);
339 } else if (!uninstallData.isPresent()) {
340 prepareIntentData(installData, Direction.ADD);
341 } else {
342 IntentData uninstall = uninstallData.get();
343 IntentData install = installData.get();
Brian O'Connor09d90f02016-09-13 11:06:14 -0700344 List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
345 List<Intent> installIntents = Lists.newArrayList(install.installables());
helenyrwue6aaa332016-08-05 15:41:42 -0700346
347 checkState(uninstallIntents.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800348 "Unsupported installable intents detected: %s", uninstallIntents);
helenyrwue6aaa332016-08-05 15:41:42 -0700349 checkState(installIntents.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800350 "Unsupported installable intents detected: %s", installIntents);
helenyrwue6aaa332016-08-05 15:41:42 -0700351
352 //TODO: Filter FlowObjective intents
353 // Filter out same intents and intents with same flow rules
354 Iterator<Intent> iterator = installIntents.iterator();
355 while (iterator.hasNext()) {
356 Intent installIntent = iterator.next();
357 uninstallIntents.stream().filter(uIntent -> {
358 if (uIntent.equals(installIntent)) {
359 return true;
360 } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
Brian O'Connor09d90f02016-09-13 11:06:14 -0700361 //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
362 // (direction can be implied from intent state)
helenyrwue6aaa332016-08-05 15:41:42 -0700363 return ((FlowRuleIntent) uIntent).flowRules()
364 .containsAll(((FlowRuleIntent) installIntent).flowRules());
365 } else {
366 return false;
367 }
368 }).findFirst().ifPresent(common -> {
369 uninstallIntents.remove(common);
Brian O'Connor09d90f02016-09-13 11:06:14 -0700370 if (INSTALLED.equals(uninstall.state())) {
371 // only remove the install intent if the existing
372 // intent (i.e. the uninstall one) is already
373 // installed or installing
374 iterator.remove();
375 }
helenyrwue6aaa332016-08-05 15:41:42 -0700376 });
377 }
378
379 final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
380 final IntentData newInstall = new IntentData(install, installIntents);
381
382 trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
383 uninstallIntents.forEach(installable ->
384 trackerService.removeTrackedResources(newUninstall.intent().key(),
385 installable.resources()));
386 trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
387 installIntents.forEach(installable ->
388 trackerService.addTrackedResources(newInstall.key(),
389 installable.resources()));
390 prepareIntents(uninstallIntents, Direction.REMOVE);
391 prepareIntents(installIntents, Direction.ADD);
392 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800393 }
394
Thomas Vachuska2980c972016-02-23 20:58:49 -0800395 /**
396 * Applies the specified intent data, if present, to the network using the
397 * specified context.
398 *
399 * @param intentData optional intent data; no-op if not present
400 * @param direction indicates adding or removal
401 */
402 private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
403 if (!intentData.isPresent()) {
404 return;
405 }
406
407 IntentData data = intentData.get();
408 List<Intent> intentsToApply = data.installables();
409 checkState(intentsToApply.stream().allMatch(this::isSupported),
Yuta HIGUCHId5324f32017-01-27 14:35:13 -0800410 "Unsupported installable intents detected: %s", intentsToApply);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800411
412 if (direction == Direction.ADD) {
413 trackerService.addTrackedResources(data.key(), data.intent().resources());
414 intentsToApply.forEach(installable ->
415 trackerService.addTrackedResources(data.key(),
416 installable.resources()));
417 } else {
418 trackerService.removeTrackedResources(data.key(), data.intent().resources());
419 intentsToApply.forEach(installable ->
420 trackerService.removeTrackedResources(data.intent().key(),
421 installable.resources()));
422 }
423
424 prepareIntents(intentsToApply, direction);
425 }
426
427 private boolean isSupported(Intent intent) {
Andreas Papazois05548962016-11-23 11:36:55 +0200428 return intent instanceof FlowRuleIntent ||
429 intent instanceof FlowObjectiveIntent ||
430 intent instanceof ProtectionEndpointIntent;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800431 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800432
433 protected ToStringHelper toStringHelper() {
434 return MoreObjects.toStringHelper(this)
435 .add("intentContext", intentContext)
436 .add("toUninstall", toUninstall)
437 .add("toInstall", toInstall);
438 }
439
440 @Override
441 public String toString() {
442 return toStringHelper()
443 .toString();
444 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800445 }
446
447
Andreas Papazois05548962016-11-23 11:36:55 +0200448 // Context for applying and tracking operations related to flow rule intents.
Thomas Vachuska2980c972016-02-23 20:58:49 -0800449 private class FlowRuleOperationContext extends OperationContext {
450 FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
451 FlowRuleOperationsContext flowRuleOperationsContext;
452
Andreas Papazois05548962016-11-23 11:36:55 +0200453 FlowRuleOperationContext(IntentInstallationContext context) {
454 super(context);
455 }
456
Yuta HIGUCHI4d19ab92016-10-27 16:27:15 -0700457 @Override
Thomas Vachuska2980c972016-02-23 20:58:49 -0800458 void apply() {
459 flowRuleOperationsContext = new FlowRuleOperationsContext() {
460 @Override
461 public void onSuccess(FlowRuleOperations ops) {
462 successConsumer.accept(FlowRuleOperationContext.this);
463 }
464
465 @Override
466 public void onError(FlowRuleOperations ops) {
467 errorConsumer.accept(FlowRuleOperationContext.this);
468 }
469 };
470 FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
471
472 if (log.isTraceEnabled()) {
473 log.trace("applying intent {} -> {} with {} rules: {}",
474 toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
475 toInstall.map(x -> x.key().toString()).orElse("<empty>"),
476 operations.stages().stream().mapToLong(Set::size).sum(),
477 operations.stages());
478 }
479
480 flowRuleService.apply(operations);
481 }
482
483 @Override
484 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
485 // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
486 builder.newStage();
487
488 List<Collection<FlowRule>> stages = intentsToApply.stream()
Andreas Papazois05548962016-11-23 11:36:55 +0200489 .filter(x -> x instanceof FlowRuleIntent)
Thomas Vachuska2980c972016-02-23 20:58:49 -0800490 .map(x -> (FlowRuleIntent) x)
491 .map(FlowRuleIntent::flowRules)
492 .collect(Collectors.toList());
493
494 for (Collection<FlowRule> rules : stages) {
495 if (direction == Direction.ADD) {
496 rules.forEach(builder::add);
497 } else {
498 rules.forEach(builder::remove);
499 }
500 }
501
502 }
503
504 @Override
505 public Object error() {
506 return flowRuleOperationsContext;
507 }
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800508
509 @Override
510 protected ToStringHelper toStringHelper() {
511 return super.toStringHelper()
512 .omitNullValues()
513 .add("flowRuleOperationsContext", flowRuleOperationsContext);
514 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800515 }
516
517 // Context for applying and tracking operations related to flow objective intents.
518 private class FlowObjectiveOperationContext extends OperationContext {
Ray Milkeyfd724402016-05-26 14:45:46 -0700519 List<FlowObjectiveInstallationContext> contexts = Lists.newLinkedList();
Thomas Vachuska2980c972016-02-23 20:58:49 -0800520 final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
521 final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
522
Andreas Papazois05548962016-11-23 11:36:55 +0200523 FlowObjectiveOperationContext(IntentInstallationContext context) {
524 super(context);
525 }
526
Thomas Vachuska2980c972016-02-23 20:58:49 -0800527 @Override
528 public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
Ray Milkeyfd724402016-05-26 14:45:46 -0700529 intentsToApply.stream()
Andreas Papazois05548962016-11-23 11:36:55 +0200530 .filter(x -> x instanceof FlowObjectiveIntent)
Thomas Vachuska2980c972016-02-23 20:58:49 -0800531 .flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
Ray Milkeyfd724402016-05-26 14:45:46 -0700532 .forEach(contexts::add);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800533 }
534
535 // Builds the specified objective in the appropriate direction
536 private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
537 Direction direction) {
538 int size = intent.objectives().size();
539 List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
540 for (int i = 0; i < size; i++) {
541 DeviceId deviceId = intent.devices().get(i);
542 Objective.Builder builder = intent.objectives().get(i).copy();
543 FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
544
545 final Objective objective;
546 switch (direction) {
547 case ADD:
548 objective = builder.add(context);
549 break;
550 case REMOVE:
551 objective = builder.remove(context);
552 break;
553 default:
554 throw new UnsupportedOperationException("Unsupported direction " + direction);
555 }
556 context.setObjective(objective, deviceId);
557 contexts.add(context);
558 }
559 return contexts;
560 }
561
562 @Override
563 void apply() {
Pier Ventre2c433ce2016-12-13 13:23:02 -0800564 pendingContexts.addAll(contexts);
565 contexts.forEach(objectiveContext ->
Thomas Vachuska2980c972016-02-23 20:58:49 -0800566 flowObjectiveService.apply(objectiveContext.deviceId,
Pier Ventre2c433ce2016-12-13 13:23:02 -0800567 objectiveContext.objective)
568 );
Thomas Vachuska2980c972016-02-23 20:58:49 -0800569 }
570
571 @Override
572 public Object error() {
573 return errorContexts;
574 }
575
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800576 @Override
577 protected ToStringHelper toStringHelper() {
578 return super.toStringHelper()
579 .add("contexts", contexts)
580 .add("pendingContexts", pendingContexts)
581 .add("errorContexts", errorContexts);
582 }
583
584
Thomas Vachuska2980c972016-02-23 20:58:49 -0800585 private class FlowObjectiveInstallationContext implements ObjectiveContext {
586 Objective objective;
587 DeviceId deviceId;
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700588 ObjectiveError error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800589
590 void setObjective(Objective objective, DeviceId deviceId) {
591 this.objective = objective;
592 this.deviceId = deviceId;
593 }
594
595 @Override
596 public void onSuccess(Objective objective) {
597 finish();
598 }
599
600 @Override
601 public void onError(Objective objective, ObjectiveError error) {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700602 this.error = error;
Thomas Vachuska2980c972016-02-23 20:58:49 -0800603 errorContexts.add(this);
604 finish();
605 }
606
607 private void finish() {
608 synchronized (pendingContexts) {
609 pendingContexts.remove(this);
610 if (pendingContexts.isEmpty()) {
611 if (errorContexts.isEmpty()) {
612 successConsumer.accept(FlowObjectiveOperationContext.this);
613 } else {
614 errorConsumer.accept(FlowObjectiveOperationContext.this);
615 }
616 }
617 }
618 }
619
620 @Override
621 public String toString() {
Thomas Vachuskad27097c2016-06-14 19:10:41 -0700622 return String.format("(%s on %s for %s)", error, deviceId, objective);
Thomas Vachuska2980c972016-02-23 20:58:49 -0800623 }
624 }
625 }
626
627 private class ErrorContext extends OperationContext {
Andreas Papazois05548962016-11-23 11:36:55 +0200628 ErrorContext(IntentInstallationContext context) {
629 super(context);
630 }
Thomas Vachuska2980c972016-02-23 20:58:49 -0800631 @Override
632 void apply() {
633 throw new UnsupportedOperationException("Unsupported installable intent");
634 }
635
636 @Override
637 Object error() {
638 return null;
639 }
640
641 @Override
642 void prepareIntents(List<Intent> intentsToApply, Direction direction) {
643 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800644 }
Andreas Papazois05548962016-11-23 11:36:55 +0200645
646
647 /**
648 * Context for applying and tracking operations related to
649 * {@link ProtectionEndpointIntent}.
650 */
651 @Beta
652 private class ProtectionConfigOperationContext extends OperationContext {
653
654 ProtectionConfigOperationContext(IntentInstallationContext context) {
655 super(context);
656 }
657
658 /**
659 * Stage of installable Intents which can be processed in parallel.
660 */
661 private final class Stage {
662 // should it have progress state, how far it went?
663 private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops;
664
665 Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) {
666 this.ops = checkNotNull(ops);
667 }
668
669 CompletableFuture<Void> apply() {
670 return ops.stream()
671 .map(op -> applyOp(op.getRight(), op.getLeft()))
672 .reduce(CompletableFuture.completedFuture(null),
673 (l, r) -> {
674 l.join();
675 return r;
676 });
677 }
678
679 private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) {
680 log.trace("applying {}: {}", dir, intent);
681 if (dir == Direction.REMOVE) {
682 networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class);
683 } else if (dir == Direction.ADD) {
684 ProtectedTransportEndpointDescription description = intent.description();
685
686 // Can't do following. Will trigger empty CONFIG_ADDED
687 //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(),
688 // ProtectionConfig.class);
689 ProtectionConfig cfg = new ProtectionConfig(intent.deviceId());
690 cfg.fingerprint(description.fingerprint());
691 cfg.peer(description.peer());
692 cfg.paths(description.paths());
693 //cfg.apply();
694
695 networkConfigService.applyConfig(intent.deviceId(),
696 ProtectionConfig.class,
697 cfg.node());
698 }
699 // TODO Should monitor progress and complete only after it's
700 // actually done.
701 return CompletableFuture.completedFuture(null);
702 }
703
704 @Override
705 public String toString() {
706 return ops.toString();
707 }
708 }
709
710 /**
711 * List of Stages which must be executed in order.
712 */
713 private final List<Stage> stages = new ArrayList<>();
714
715 private final List<Stage> failed = new CopyOnWriteArrayList<>();
716
717 @Override
718 synchronized void apply() {
719 for (Stage stage : stages) {
720 log.trace("applying Stage {}", stage);
721 CompletableFuture<Void> result = stage.apply();
722 // wait for stage completion
723 result.join();
724 if (result.isCompletedExceptionally()) {
725 log.error("Stage {} failed", stage);
726 failed.add(stage);
727 errorConsumer.accept(ProtectionConfigOperationContext.this);
728 return;
729 }
730 }
731 successConsumer.accept(ProtectionConfigOperationContext.this);
732 }
733
734 @Override
735 Object error() {
736 // Something to represent error state
737 return failed;
738 }
739
740 @Override
741 synchronized void prepareIntents(List<Intent> intentsToApply,
742 Direction direction) {
743
744 stages.add(new Stage(intentsToApply.stream()
745 .filter(i -> i instanceof ProtectionEndpointIntent)
746 .map(i -> Pair.of((ProtectionEndpointIntent) i, direction))
747 .collect(Collectors.toList())));
748 }
749
Yuta HIGUCHI63bbedd2017-01-30 10:49:29 -0800750 @Override
751 protected ToStringHelper toStringHelper() {
752 return super.toStringHelper()
753 .add("stages", stages)
754 .add("failed", failed);
755 }
Andreas Papazois05548962016-11-23 11:36:55 +0200756 }
Thomas Vachuskaf6ec97b2016-02-22 10:59:23 -0800757}