| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * 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.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; |
| } |
| } |
| } |
| } |