/* * Copyright 2017-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.installer; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.onosproject.net.DeviceId; 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.IntentData; import org.onosproject.net.intent.IntentExtensionService; import org.onosproject.net.intent.IntentInstallCoordinator; import org.onosproject.net.intent.IntentOperationContext; import org.onosproject.net.intent.IntentInstaller; import org.onosproject.net.intent.impl.IntentManager; import org.onosproject.net.intent.impl.ObjectiveTrackerService; import org.slf4j.Logger; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED; import static org.onosproject.net.intent.IntentInstaller.Direction.ADD; import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE; import static org.slf4j.LoggerFactory.getLogger; /** * Installer for FlowObjectiveIntent. */ @Component(immediate = true) public class FlowObjectiveIntentInstaller implements IntentInstaller<FlowObjectiveIntent> { private static final int OBJECTIVE_RETRY_THRESHOLD = 5; private static final String UNSUPPORT_OBJ = "unsupported objective {}"; private final Logger log = getLogger(IntentManager.class); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentExtensionService intentExtensionService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ObjectiveTrackerService trackerService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentInstallCoordinator intentInstallCoordinator; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected FlowObjectiveService flowObjectiveService; @Activate public void activate() { intentExtensionService.registerInstaller(FlowObjectiveIntent.class, this); } @Deactivate public void deactivated() { intentExtensionService.unregisterInstaller(FlowObjectiveIntent.class); } @Override public void apply(IntentOperationContext<FlowObjectiveIntent> intentOperationContext) { Objects.requireNonNull(intentOperationContext); Optional<IntentData> toUninstall = intentOperationContext.toUninstall(); Optional<IntentData> toInstall = intentOperationContext.toInstall(); List<FlowObjectiveIntent> uninstallIntents = intentOperationContext.intentsToUninstall(); List<FlowObjectiveIntent> installIntents = intentOperationContext.intentsToInstall(); if (!toInstall.isPresent() && !toUninstall.isPresent()) { intentInstallCoordinator.intentInstallSuccess(intentOperationContext); return; } if (toUninstall.isPresent()) { IntentData intentData = toUninstall.get(); trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources()); uninstallIntents.forEach(installable -> trackerService.removeTrackedResources(intentData.intent().key(), installable.resources())); } if (toInstall.isPresent()) { IntentData intentData = toInstall.get(); trackerService.addTrackedResources(intentData.key(), intentData.intent().resources()); installIntents.forEach(installable -> trackerService.addTrackedResources(intentData.key(), installable.resources())); } FlowObjectiveIntentInstallationContext intentInstallationContext = new FlowObjectiveIntentInstallationContext(intentOperationContext); uninstallIntents.stream() .map(intent -> buildObjectiveContexts(intent, REMOVE)) .flatMap(Collection::stream) .forEach(context -> { context.intentInstallationContext(intentInstallationContext); intentInstallationContext.addContext(context); intentInstallationContext.addPendingContext(context); }); installIntents.stream() .map(intent -> buildObjectiveContexts(intent, ADD)) .flatMap(Collection::stream) .forEach(context -> { context.intentInstallationContext(intentInstallationContext); intentInstallationContext.addContext(context); intentInstallationContext.addNextPendingContext(context); }); intentInstallationContext.apply(); } /** * Builds all objective contexts for a given flow objective Intent with given * operation. * * @param intent the flow objective Intent * @param direction the operation of this Intent * @return all objective context of the Intent with given operation */ private Set<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent, Direction direction) { Objects.requireNonNull(intent); Objects.requireNonNull(direction); Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet(); int size = intent.objectives().size(); List<Objective> objectives = intent.objectives(); List<DeviceId> deviceIds = intent.devices(); if (direction == ADD) { // Install objectives // The flow objective system will handle the installation order 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 { // Uninstall objecitves // 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) { Objects.requireNonNull(objective); Objects.requireNonNull(deviceId); Objects.requireNonNull(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; } /** * Installation context for flow objective. * Manages installation state of a flow objective. */ class FlowObjectiveInstallationContext implements ObjectiveContext { private Objective objective; private DeviceId deviceId; private ObjectiveError error; private AtomicInteger retry; private FlowObjectiveInstallationContext nextContext; private FlowObjectiveIntentInstallationContext intentInstallationContext; /** * Set flow objective Intent installation context to this context. * * @param intentInstallationContext the Intent installation context */ public void intentInstallationContext(FlowObjectiveIntentInstallationContext intentInstallationContext) { Objects.requireNonNull(intentInstallationContext); this.intentInstallationContext = intentInstallationContext; // Set Intent installation context to the next context if exists. if (nextContext != null) { nextContext.intentInstallationContext(intentInstallationContext); } } /** * Sets next flow objective installation context. * * @param nextContext the next flow objective installation context */ public void nextContext(FlowObjectiveInstallationContext nextContext) { Objects.requireNonNull(nextContext); this.nextContext = nextContext; } /** * Sets objective and device id to this context; reset error states. * * @param objective the objective * @param deviceId the device id */ void setObjective(Objective objective, DeviceId deviceId) { Objects.requireNonNull(objective); Objects.requireNonNull(deviceId); this.objective = objective; this.deviceId = deviceId; this.error = null; this.retry = new AtomicInteger(0); } /** * Gets the number of retries. * * @return the retry count */ int retryTimes() { return this.retry.get(); } /** * Increases the number of retries. */ void increaseRetryValue() { this.retry.incrementAndGet(); } /** * Completed this context. * * @param error the error of this context if exist; null otherwise */ private void finished(ObjectiveError error) { synchronized (intentInstallationContext) { if (error != null) { this.error = error; intentInstallationContext.handleObjectiveError(this, error); } else { // apply next context if exist if (nextContext != null) { intentInstallationContext.addPendingContext(nextContext); flowObjectiveService.apply(nextContext.deviceId, nextContext.objective); intentInstallationContext.removePendingContext(this); } else { intentInstallationContext.removePendingContext(this); } } if (!intentInstallationContext.pendingContexts().isEmpty()) { return; } // Apply second stage pending contexts if it is not empty if (!intentInstallationContext.nextPendingContexts().isEmpty()) { intentInstallationContext.moveNextPendingToPending(); final Set<ObjectiveContext> contextsToApply = Sets.newHashSet(intentInstallationContext.pendingContexts()); contextsToApply.forEach(ctx -> { FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx; flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective); }); return; } if (intentInstallationContext.errorContexts().isEmpty()) { intentInstallCoordinator.intentInstallSuccess(intentInstallationContext.intentOperationContext()); } else { intentInstallCoordinator.intentInstallFailed(intentInstallationContext.intentOperationContext()); } } } @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); } } /** * Installation context for FlowObjective Intent. * Manages states of pending and error flow objective contexts. */ class FlowObjectiveIntentInstallationContext { private final IntentOperationContext<FlowObjectiveIntent> intentOperationContext; final List<ObjectiveContext> contexts = Lists.newArrayList(); final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet(); final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet(); // Second stage of pending contexts final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet(); /** * Creates a flow objective installation context. * * @param intentOperationContext the flow objective installation context */ public FlowObjectiveIntentInstallationContext( IntentOperationContext<FlowObjectiveIntent> intentOperationContext) { Objects.requireNonNull(intentOperationContext); this.intentOperationContext = intentOperationContext; } /** * Gets Intent operation context of this context. * * @return the Intent operation context */ public IntentOperationContext<FlowObjectiveIntent> intentOperationContext() { return intentOperationContext; } /** * Applies all contexts to flow objective service. */ public void apply() { if (pendingContexts.isEmpty()) { moveNextPendingToPending(); } final Set<ObjectiveContext> contextsToApply = pendingContexts(); contextsToApply.forEach(ctx -> { FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx; flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective); }); } /** * Gets all error contexts. * * @return the error contexts */ public Set<ObjectiveContext> errorContexts() { return ImmutableSet.copyOf(errorContexts); } /** * Gets all pending contexts. * * @return the pending contexts */ public Set<ObjectiveContext> pendingContexts() { return ImmutableSet.copyOf(pendingContexts); } /** * Gets all pending contexts of next stage. * * @return the pending contexts for next stage */ public Set<ObjectiveContext> nextPendingContexts() { return ImmutableSet.copyOf(nextPendingContexts); } /** * Adds a context. * * @param context the context */ public void addContext(ObjectiveContext context) { Objects.requireNonNull(context); contexts.add(context); } /** * Adds a context to pending context of next stage. * * @param context the context */ public void addNextPendingContext(ObjectiveContext context) { Objects.requireNonNull(context); nextPendingContexts.add(context); } /** * Adds a context to pending context. * * @param context the context */ public void addPendingContext(ObjectiveContext context) { Objects.requireNonNull(context); pendingContexts.add(context); } /** * Removes the pending context. * * @param context the context */ public void removePendingContext(ObjectiveContext context) { Objects.requireNonNull(context); pendingContexts.remove(context); } /** * Moves pending context from next stage to current stage. */ public void moveNextPendingToPending() { pendingContexts.addAll(nextPendingContexts); nextPendingContexts.clear(); } /** * Handles error of objective context. * * @param ctx the objective context * @param error the error */ public void handleObjectiveError(FlowObjectiveInstallationContext ctx, ObjectiveError error) { Objects.requireNonNull(ctx); Objects.requireNonNull(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; pendingContexts.remove(ctx); errorContexts.add(ctx); return; } // reset error ctx.error = null; // strategies for errors switch (error) { case GROUPEXISTS: if (ctx.objective.op() == Objective.Operation.ADD && ctx.objective instanceof NextObjective) { // 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; } } } }