Java tutorial
/* * Copyright 2016-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.net.intent.impl; import com.google.common.collect.ImmutableSet; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.lang3.tuple.Pair; import org.onosproject.net.DeviceId; import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription; import org.onosproject.net.behaviour.protection.ProtectionConfig; import org.onosproject.net.config.NetworkConfigService; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.FlowRuleOperations; import org.onosproject.net.flow.FlowRuleOperationsContext; import org.onosproject.net.flow.FlowRuleService; import org.onosproject.net.flowobjective.FilteringObjective; import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.flowobjective.ForwardingObjective; import org.onosproject.net.flowobjective.NextObjective; import org.onosproject.net.flowobjective.Objective; import org.onosproject.net.flowobjective.ObjectiveContext; import org.onosproject.net.flowobjective.ObjectiveError; import org.onosproject.net.intent.FlowObjectiveIntent; import org.onosproject.net.intent.FlowRuleIntent; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.IntentData; import org.onosproject.net.intent.IntentStore; import org.onosproject.net.intent.ProtectionEndpointIntent; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED; import static org.onosproject.net.intent.IntentState.*; import static org.slf4j.LoggerFactory.getLogger; /** * Auxiliary entity responsible for installing the intents into the environment. */ class IntentInstaller { private static final Logger log = getLogger(IntentInstaller.class); private static final long OBJECTIVE_RETRY_THRESHOLD = 5; private IntentStore store; private ObjectiveTrackerService trackerService; private FlowRuleService flowRuleService; private FlowObjectiveService flowObjectiveService; private NetworkConfigService networkConfigService; private enum Direction { ADD, REMOVE } /** * Initializes the installer with references to required services. * * @param intentStore intent store * @param trackerService objective tracking service * @param flowRuleService flow rule service * @param flowObjectiveService flow objective service * @param networkConfigService network configuration service */ void init(IntentStore intentStore, ObjectiveTrackerService trackerService, FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService, NetworkConfigService networkConfigService) { this.store = intentStore; this.trackerService = trackerService; //TODO Various services should be plugged to the intent installer instead of being hardcoded this.flowRuleService = flowRuleService; this.flowObjectiveService = flowObjectiveService; this.networkConfigService = networkConfigService; } // FIXME: Intent Manager should have never become dependent on a specific intent type(s). // This will be addressed in intent domains work; not now. /** * Applies the specified intent updates to the environment by uninstalling * and installing the intents and updating the store references appropriately. * * @param toUninstall optional intent to uninstall * @param toInstall optional intent to install */ void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { // Hook for handling success at intent installation level. Consumer<IntentInstallationContext> successConsumer = (ctx) -> { if (toInstall.isPresent()) { IntentData installData = toInstall.get(); log.debug("Completed installing: {}", installData.key()); installData.setState(INSTALLED); store.write(installData); } else if (toUninstall.isPresent()) { IntentData uninstallData = toUninstall.get(); log.debug("Completed withdrawing: {}", uninstallData.key()); switch (uninstallData.request()) { case INSTALL_REQ: uninstallData.setState(FAILED); break; case WITHDRAW_REQ: default: //TODO "default" case should not happen uninstallData.setState(WITHDRAWN); break; } // Intent has been withdrawn; we can clear the installables store.write(new IntentData(uninstallData, Collections.emptyList())); } }; // Hook for handling errors at intent installation level Consumer<IntentInstallationContext> errorConsumer = (ctx) -> { // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT) if (toInstall.isPresent()) { IntentData installData = toInstall.get(); installData.setState(CORRUPT); installData.incrementErrorCount(); store.write(installData); } // if toUninstall was cause of error, then CORRUPT (another job will clean this up) if (toUninstall.isPresent()) { IntentData uninstallData = toUninstall.get(); uninstallData.setState(CORRUPT); uninstallData.incrementErrorCount(); store.write(uninstallData); } }; // Hooks at operation level Consumer<OperationContext> successOperationConsumer = (ctx) -> { ctx.intentContext.finishContext(ctx); }; Consumer<OperationContext> errorOperationConsumer = (ctx) -> { if (ctx.toInstall.isPresent()) { IntentData installData = toInstall.get(); log.warn("Failed installation operation for: {} {} due to {}", installData.key(), installData.intent(), ctx.error()); } if (ctx.toUninstall.isPresent()) { IntentData uninstallData = toUninstall.get(); log.warn("Failed withdrawal operation for: {} {} due to {}", uninstallData.key(), uninstallData.intent(), ctx.error()); } ctx.intentContext.handleError(ctx); }; // Create a context for tracking the backing operations for applying // the intents to the environment. IntentInstallationContext intentContext = new IntentInstallationContext(successConsumer, errorConsumer); Set<OperationContext> contexts = createContext(intentContext, toUninstall, toInstall); intentContext.pendingContexts = contexts; contexts.forEach(ctx -> { ctx.prepare(toUninstall, toInstall, successOperationConsumer, errorOperationConsumer); ctx.apply(); }); } // Context for applying and tracking multiple kinds of operation contexts // related to specific intent data. private final class IntentInstallationContext { private Set<OperationContext> pendingContexts = Sets.newHashSet(); private Set<OperationContext> errorContexts = Sets.newHashSet(); private Consumer<IntentInstallationContext> successConsumer; private Consumer<IntentInstallationContext> errorConsumer; private IntentInstallationContext(Consumer<IntentInstallationContext> succesConsumer, Consumer<IntentInstallationContext> errorConsumer) { this.successConsumer = succesConsumer; this.errorConsumer = errorConsumer; } private void handleError(OperationContext ctx) { errorContexts.add(ctx); finishContext(ctx); } private void finishContext(OperationContext ctx) { synchronized (pendingContexts) { pendingContexts.remove(ctx); if (pendingContexts.isEmpty()) { if (errorContexts.isEmpty()) { successConsumer.accept(IntentInstallationContext.this); } else { errorConsumer.accept(IntentInstallationContext.this); } } } } @Override public String toString() { return MoreObjects.toStringHelper(this).add("pendingContexts", pendingContexts) .add("errorContexts", errorContexts).toString(); } } // --- Utilities to support various installable Intent ---- // Creates the set of contexts appropriate for tracking operations of the // the specified intents. private Set<OperationContext> createContext(IntentInstallationContext intentContext, Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { Set<OperationContext> contexts = Sets.newConcurrentHashSet(); if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) { contexts.add(new FlowRuleOperationContext(intentContext)); } if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) { contexts.add(new FlowObjectiveOperationContext(intentContext)); } if (isInstallable(toUninstall, toInstall, ProtectionEndpointIntent.class)) { contexts.add(new ProtectionConfigOperationContext(intentContext)); } if (contexts.isEmpty()) { log.warn("{} did not contain installable Intents", intentContext); return ImmutableSet.of(new ErrorContext(intentContext)); } return contexts; } /** * Tests if one of {@code toUninstall} or {@code toInstall} contains * installable Intent of type specified by {@code intentClass}. * * @param toUninstall IntentData to test * @param toInstall IntentData to test * @param intentClass installable Intent class * @return true if at least one of IntentData contains installable specified. */ private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall, Class<? extends Intent> intentClass) { return Stream .concat(toInstall.map(IntentData::installables).map(Collection::stream).orElse(Stream.empty()), toUninstall.map(IntentData::installables).map(Collection::stream).orElse(Stream.empty())) .anyMatch(i -> intentClass.isAssignableFrom(i.getClass())); } // Base context for applying and tracking operations related to installable intents. private abstract class OperationContext { protected IntentInstallationContext intentContext; protected Optional<IntentData> toUninstall; protected Optional<IntentData> toInstall; /** * Implementation of {@link OperationContext} should call this on success. */ protected Consumer<OperationContext> successConsumer; /** * Implementation of {@link OperationContext} should call this on error. */ protected Consumer<OperationContext> errorConsumer; protected OperationContext(IntentInstallationContext context) { this.intentContext = context; } /** * Applies the Intents specified by * {@link #prepareIntents(List, Direction)} call(s) prior to this call. */ abstract void apply(); /** * Returns error state of the context. * <p> * Used for error logging purpose. * Returned Object should have reasonable toString() implementation. * @return context state, describing current error state */ abstract Object error(); /** * Prepares Intent(s) to {@link #apply() apply} in this operation. * <p> * Intents specified by {@code intentsToApply} in a single call * can be applied to the Devices in arbitrary order. * But group of Intents specified in consecutive {@link #prepareIntents(List, Direction)} * calls must be applied in order. (e.g., guarded by barrier) * * @param intentsToApply {@link Intent}s to apply * @param direction of operation */ abstract void prepareIntents(List<Intent> intentsToApply, Direction direction); void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall, Consumer<OperationContext> successConsumer, Consumer<OperationContext> errorConsumer) { this.toUninstall = toUninstall; this.toInstall = toInstall; this.successConsumer = successConsumer; this.errorConsumer = errorConsumer; prepareIntentData(toUninstall, toInstall); } private void prepareIntentData(Optional<IntentData> uninstallData, Optional<IntentData> installData) { if (!installData.isPresent() && !uninstallData.isPresent()) { return; } else if (!installData.isPresent()) { prepareIntentData(uninstallData, Direction.REMOVE); } else if (!uninstallData.isPresent()) { prepareIntentData(installData, Direction.ADD); } else { IntentData uninstall = uninstallData.get(); IntentData install = installData.get(); List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables()); List<Intent> installIntents = Lists.newArrayList(install.installables()); checkState(uninstallIntents.stream().allMatch(this::isSupported), "Unsupported installable intents detected: %s", uninstallIntents); checkState(installIntents.stream().allMatch(this::isSupported), "Unsupported installable intents detected: %s", installIntents); //TODO: Filter FlowObjective intents // Filter out same intents and intents with same flow rules Iterator<Intent> iterator = installIntents.iterator(); while (iterator.hasNext()) { Intent installIntent = iterator.next(); uninstallIntents.stream().filter(uIntent -> { if (uIntent.equals(installIntent)) { return true; } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) { //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis // (direction can be implied from intent state) return !flowRuleIntentChanged(((FlowRuleIntent) uIntent), ((FlowRuleIntent) installIntent)); } else { return false; } }).findFirst().ifPresent(common -> { uninstallIntents.remove(common); if (INSTALLED.equals(uninstall.state())) { // only remove the install intent if the existing // intent (i.e. the uninstall one) is already // installed or installing iterator.remove(); } }); } final IntentData newUninstall = new IntentData(uninstall, uninstallIntents); final IntentData newInstall = new IntentData(install, installIntents); trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources()); uninstallIntents.forEach(installable -> trackerService .removeTrackedResources(newUninstall.intent().key(), installable.resources())); trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources()); installIntents.forEach(installable -> trackerService.addTrackedResources(newInstall.key(), installable.resources())); prepareIntents(uninstallIntents, Direction.REMOVE); prepareIntents(installIntents, Direction.ADD); } } /** * Determines whether there is any flow rule changed * (i.e., different set of flow rules or different treatments) * between FlowRuleIntents to be uninstalled and to be installed. * * @param uninstallIntent FlowRuleIntent to uninstall * @param installIntent FlowRuleIntent to install * @return true if flow rules which to be uninstalled * contains all flow rules which to be installed. */ private boolean flowRuleIntentChanged(FlowRuleIntent uninstallIntent, FlowRuleIntent installIntent) { Collection<FlowRule> flowRulesToUninstall = uninstallIntent.flowRules(); Collection<FlowRule> flowRulesToInstall = installIntent.flowRules(); // Check if any flow rule changed for (FlowRule flowRuleToInstall : flowRulesToInstall) { if (flowRulesToUninstall.stream().noneMatch(flowRuleToInstall::exactMatch)) { return true; } } return false; } /** * Applies the specified intent data, if present, to the network using the * specified context. * * @param intentData optional intent data; no-op if not present * @param direction indicates adding or removal */ private void prepareIntentData(Optional<IntentData> intentData, Direction direction) { if (!intentData.isPresent()) { return; } IntentData data = intentData.get(); List<Intent> intentsToApply = data.installables(); checkState(intentsToApply.stream().allMatch(this::isSupported), "Unsupported installable intents detected: %s", intentsToApply); if (direction == Direction.ADD) { trackerService.addTrackedResources(data.key(), data.intent().resources()); intentsToApply.forEach( installable -> trackerService.addTrackedResources(data.key(), installable.resources())); } else { trackerService.removeTrackedResources(data.key(), data.intent().resources()); intentsToApply.forEach(installable -> trackerService.removeTrackedResources(data.intent().key(), installable.resources())); } prepareIntents(intentsToApply, direction); } private boolean isSupported(Intent intent) { return intent instanceof FlowRuleIntent || intent instanceof FlowObjectiveIntent || intent instanceof ProtectionEndpointIntent; } protected ToStringHelper toStringHelper() { return MoreObjects.toStringHelper(this).add("intentContext", intentContext) .add("toUninstall", toUninstall).add("toInstall", toInstall); } @Override public String toString() { return toStringHelper().toString(); } } // Context for applying and tracking operations related to flow rule intents. private class FlowRuleOperationContext extends OperationContext { FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); FlowRuleOperationsContext flowRuleOperationsContext; FlowRuleOperationContext(IntentInstallationContext context) { super(context); } @Override void apply() { flowRuleOperationsContext = new FlowRuleOperationsContext() { @Override public void onSuccess(FlowRuleOperations ops) { successConsumer.accept(FlowRuleOperationContext.this); } @Override public void onError(FlowRuleOperations ops) { errorConsumer.accept(FlowRuleOperationContext.this); } }; FlowRuleOperations operations = builder.build(flowRuleOperationsContext); if (log.isTraceEnabled()) { log.trace("applying intent {} -> {} with {} rules: {}", toUninstall.map(x -> x.key().toString()).orElse("<empty>"), toInstall.map(x -> x.key().toString()).orElse("<empty>"), operations.stages().stream().mapToLong(Set::size).sum(), operations.stages()); } flowRuleService.apply(operations); } @Override public void prepareIntents(List<Intent> intentsToApply, Direction direction) { // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so. builder.newStage(); List<Collection<FlowRule>> stages = intentsToApply.stream().filter(x -> x instanceof FlowRuleIntent) .map(x -> (FlowRuleIntent) x).map(FlowRuleIntent::flowRules).collect(Collectors.toList()); for (Collection<FlowRule> rules : stages) { if (direction == Direction.ADD) { rules.forEach(builder::add); } else { rules.forEach(builder::remove); } } } @Override public Object error() { return flowRuleOperationsContext; } @Override protected ToStringHelper toStringHelper() { return super.toStringHelper().omitNullValues().add("flowRuleOperationsContext", flowRuleOperationsContext); } } // Context for applying and tracking operations related to flow objective intents. private class FlowObjectiveOperationContext extends OperationContext { private static final String UNSUPPORT_OBJ = "unsupported objective {}"; final List<ObjectiveContext> contexts = Lists.newArrayList(); final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet(); // Second stage of pending contexts final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet(); final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet(); FlowObjectiveOperationContext(IntentInstallationContext context) { super(context); } @Override public void prepareIntents(List<Intent> intentsToApply, Direction direction) { intentsToApply.stream().filter(intent -> intent instanceof FlowObjectiveIntent) .map(intent -> buildObjectiveContexts((FlowObjectiveIntent) intent, direction)) .flatMap(Collection::stream).forEach(contexts::add); // Two stage for different direction context // We will apply REMOVE context first, and apply ADD context. contexts.forEach(context -> { switch (direction) { case REMOVE: pendingContexts.add(context); break; case ADD: nextPendingContexts.add(context); break; default: break; } }); } // Builds the specified objective in the appropriate direction private Set<? extends ObjectiveContext> buildObjectiveContexts(FlowObjectiveIntent intent, Direction direction) { Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet(); int size = intent.objectives().size(); List<Objective> objectives = intent.objectives(); List<DeviceId> deviceIds = intent.devices(); if (direction == Direction.ADD) { for (int i = 0; i < size; i++) { Objective objective = objectives.get(i); DeviceId deviceId = deviceIds.get(i); FlowObjectiveInstallationContext ctx = buildObjectiveContext(objective, deviceId, direction); contexts.add(ctx); } return contexts; } else { // we need to care about ordering here // basic idea is to chain objective contexts for (int i = 0; i < size; i++) { Objective objective = intent.objectives().get(i); DeviceId deviceId = intent.devices().get(i); if (objective instanceof FilteringObjective) { // don't need to care ordering of filtering objective FlowObjectiveInstallationContext ctx = buildObjectiveContext(objective, deviceId, direction); contexts.add(ctx); } else if (objective instanceof NextObjective) { // need to removed after forwarding objective // nothing to do here } else if (objective instanceof ForwardingObjective) { // forwarding objective, also find next objective if // exist FlowObjectiveInstallationContext fwdCtx = buildObjectiveContext(objective, deviceId, direction); ForwardingObjective fwd = (ForwardingObjective) objective; NextObjective nxt = null; Integer nextId = fwd.nextId(); if (nextId != null) { for (int j = 0; j < size; j++) { if (objectives.get(j).id() == nextId) { nxt = (NextObjective) objectives.get(j); break; } } // if a next objective exists in the Intent if (nxt != null) { FlowObjectiveInstallationContext nxtCtx = buildObjectiveContext(nxt, deviceId, direction); fwdCtx.nextContext(nxtCtx); } } contexts.add(fwdCtx); } else { // possible here? log.warn(UNSUPPORT_OBJ, objective); } } } return contexts; } private FlowObjectiveInstallationContext buildObjectiveContext(Objective objective, DeviceId deviceId, Direction direction) { Objective.Builder builder = objective.copy(); FlowObjectiveInstallationContext ctx = new FlowObjectiveInstallationContext(); switch (direction) { case ADD: objective = builder.add(ctx); break; case REMOVE: objective = builder.remove(ctx); break; default: break; } ctx.setObjective(objective, deviceId); return ctx; } @Override void apply() { // If there is no pending contexts, try apply second stage // pending contexts if (pendingContexts.isEmpty()) { pendingContexts.addAll(nextPendingContexts); nextPendingContexts.clear(); } final Set<ObjectiveContext> contextsToApply = Sets.newHashSet(pendingContexts); contextsToApply.forEach(ctx -> { FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx; flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective); }); } @Override public Object error() { return errorContexts; } @Override protected ToStringHelper toStringHelper() { return super.toStringHelper().add("contexts", contexts).add("pendingContexts", pendingContexts) .add("errorContexts", errorContexts); } private class FlowObjectiveInstallationContext implements ObjectiveContext { Objective objective; DeviceId deviceId; ObjectiveError error; AtomicInteger retry; FlowObjectiveInstallationContext nextContext; void setObjective(Objective objective, DeviceId deviceId) { // init function this.objective = objective; this.deviceId = deviceId; this.error = null; this.retry = new AtomicInteger(0); this.nextContext = null; } int retryTimes() { return this.retry.get(); } void increaseRetryValue() { this.retry.incrementAndGet(); } private void finished(ObjectiveError error) { synchronized (pendingContexts) { if (error != null) { this.error = error; handleObjectiveError(this, error); } else { // apply next context if exist if (nextContext != null) { pendingContexts.add(nextContext); flowObjectiveService.apply(nextContext.deviceId, nextContext.objective); pendingContexts.remove(this); } else { pendingContexts.remove(this); } } if (!pendingContexts.isEmpty()) { return; } // Apply second stage pending contexts if it is not empty if (!nextPendingContexts.isEmpty()) { pendingContexts.addAll(nextPendingContexts); nextPendingContexts.clear(); final Set<ObjectiveContext> contextsToApply = Sets.newHashSet(pendingContexts); contextsToApply.forEach(ctx -> { FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx; flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective); }); return; } if (errorContexts.isEmpty()) { successConsumer.accept(FlowObjectiveOperationContext.this); } else { errorConsumer.accept(FlowObjectiveOperationContext.this); } } } @Override public void onSuccess(Objective objective) { finished(null); } @Override public void onError(Objective objective, ObjectiveError error) { finished(error); } @Override public String toString() { return String.format("(%s on %s for %s)", error, deviceId, objective); } public void nextContext(FlowObjectiveInstallationContext nextContext) { this.nextContext = nextContext; } } private void handleObjectiveError(FlowObjectiveInstallationContext ctx, ObjectiveError error) { log.debug("Got error(s) when install objective: {}, error: {}, retry: {}", ctx.objective, ctx.error, ctx.retry); if (ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD) { ctx.error = INSTALLATIONTHRESHOLDEXCEEDED; errorContexts.add(ctx); return; } // reset error ctx.error = null; // strategies for errors switch (error) { case GROUPEXISTS: if (ctx.objective.op() == Objective.Operation.ADD) { // Next group exists // build new objective with new op ADD_TO_EXIST NextObjective newObj = ((NextObjective.Builder) ctx.objective.copy()).addToExisting(ctx); ctx.setObjective(newObj, ctx.deviceId); ctx.increaseRetryValue(); flowObjectiveService.apply(ctx.deviceId, ctx.objective); } else { pendingContexts.remove(ctx); errorContexts.add(ctx); } break; case GROUPINSTALLATIONFAILED: // Group install failed, retry again ctx.increaseRetryValue(); flowObjectiveService.apply(ctx.deviceId, ctx.objective); break; case GROUPMISSING: if (ctx.objective.op() == Objective.Operation.ADD_TO_EXISTING) { // Next group not exist, but we want to add new buckets // build new objective with new op ADD NextObjective newObj = (NextObjective) ctx.objective.copy().add(ctx); ctx.setObjective(newObj, ctx.deviceId); ctx.increaseRetryValue(); flowObjectiveService.apply(ctx.deviceId, ctx.objective); } else if (ctx.objective.op() == Objective.Operation.REMOVE || ctx.objective.op() == Objective.Operation.REMOVE_FROM_EXISTING) { // Already removed, no need to do anything ctx.error = null; pendingContexts.remove(ctx); return; } else { // Next chaining group missing, try again. ctx.increaseRetryValue(); flowObjectiveService.apply(ctx.deviceId, ctx.objective); } break; case FLOWINSTALLATIONFAILED: case GROUPREMOVALFAILED: case INSTALLATIONTIMEOUT: // Retry ctx.increaseRetryValue(); flowObjectiveService.apply(ctx.deviceId, ctx.objective); break; default: pendingContexts.remove(ctx); errorContexts.add(ctx); break; } } } private class ErrorContext extends OperationContext { ErrorContext(IntentInstallationContext context) { super(context); } @Override void apply() { throw new UnsupportedOperationException("Unsupported installable intent"); } @Override Object error() { return null; } @Override void prepareIntents(List<Intent> intentsToApply, Direction direction) { } } /** * Context for applying and tracking operations related to * {@link ProtectionEndpointIntent}. */ @Beta private class ProtectionConfigOperationContext extends OperationContext { ProtectionConfigOperationContext(IntentInstallationContext context) { super(context); } /** * Stage of installable Intents which can be processed in parallel. */ private final class Stage { // should it have progress state, how far it went? private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops; Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) { this.ops = checkNotNull(ops); } CompletableFuture<Void> apply() { return ops.stream().map(op -> applyOp(op.getRight(), op.getLeft())) .reduce(CompletableFuture.completedFuture(null), (l, r) -> { l.join(); return r; }); } private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) { log.trace("applying {}: {}", dir, intent); if (dir == Direction.REMOVE) { networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class); } else if (dir == Direction.ADD) { ProtectedTransportEndpointDescription description = intent.description(); // Can't do following. Will trigger empty CONFIG_ADDED //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(), // ProtectionConfig.class); ProtectionConfig cfg = new ProtectionConfig(intent.deviceId()); cfg.fingerprint(description.fingerprint()); cfg.peer(description.peer()); cfg.paths(description.paths()); //cfg.apply(); networkConfigService.applyConfig(intent.deviceId(), ProtectionConfig.class, cfg.node()); } // TODO Should monitor progress and complete only after it's // actually done. return CompletableFuture.completedFuture(null); } @Override public String toString() { return ops.toString(); } } /** * List of Stages which must be executed in order. */ private final List<Stage> stages = new ArrayList<>(); private final List<Stage> failed = new CopyOnWriteArrayList<>(); @Override synchronized void apply() { for (Stage stage : stages) { log.trace("applying Stage {}", stage); CompletableFuture<Void> result = stage.apply(); // wait for stage completion result.join(); if (result.isCompletedExceptionally()) { log.error("Stage {} failed", stage); failed.add(stage); errorConsumer.accept(ProtectionConfigOperationContext.this); return; } } successConsumer.accept(ProtectionConfigOperationContext.this); } @Override Object error() { // Something to represent error state return failed; } @Override synchronized void prepareIntents(List<Intent> intentsToApply, Direction direction) { stages.add(new Stage(intentsToApply.stream().filter(i -> i instanceof ProtectionEndpointIntent) .map(i -> Pair.of((ProtectionEndpointIntent) i, direction)).collect(Collectors.toList()))); } @Override protected ToStringHelper toStringHelper() { return super.toStringHelper().add("stages", stages).add("failed", failed); } } }