blob: a237b8e3e760b36aa589b0decfe44cc6581151af [file] [log] [blame]
/*
* 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.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;
}
}
}
}