Thomas Vachuska
Committed by Brian O'Connor

IntentManager refactoring for flow objectives.

Change-Id: I220682dbace03908b25ba86332664238dafaef90
......@@ -16,23 +16,32 @@
package org.onosproject.net.intent.impl;
import com.google.common.collect.Sets;
import org.onosproject.net.DeviceId;
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.FlowObjectiveService;
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.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkState;
import static org.onosproject.net.intent.IntentState.*;
import static org.slf4j.LoggerFactory.getLogger;
......@@ -69,121 +78,332 @@ class IntentInstaller {
this.flowObjectiveService = flowObjectiveService;
}
private void applyIntentData(Optional<IntentData> intentData,
FlowRuleOperations.Builder builder,
Direction direction) {
if (!intentData.isPresent()) {
return;
// FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
// 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
Consumer<OperationContext> 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;
}
store.write(uninstallData);
}
};
// Hook for handling errors
Consumer<OperationContext> errorConsumer = (ctx) -> {
// if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
if (toInstall.isPresent()) {
IntentData installData = toInstall.get();
log.warn("Failed installation: {} {} on {}",
installData.key(), installData.intent(), ctx.error());
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();
log.warn("Failed withdrawal: {} {} on {}",
uninstallData.key(), uninstallData.intent(), ctx.error());
uninstallData.setState(CORRUPT);
uninstallData.incrementErrorCount();
store.write(uninstallData);
}
};
// Create a context for tracking the backing operations for applying
// the intents to the environment.
OperationContext context = createContext(toUninstall, toInstall);
context.prepare(toUninstall, toInstall, successConsumer, errorConsumer);
context.apply();
}
// ------ Utilities to support FlowRule vs. FlowObjective behavior -------
// Creates the context appropriate for tracking operations of the
// the specified intents.
private OperationContext createContext(Optional<IntentData> toUninstall,
Optional<IntentData> toInstall) {
if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
return new FlowRuleOperationContext();
}
IntentData data = intentData.get();
if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
return new FlowObjectiveOperationContext();
}
return new ErrorContext();
}
List<Intent> intentsToApply = data.installables();
if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) {
throw new IllegalStateException("installable intents must be FlowRuleIntent");
private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
Class<? extends Intent> intentClass) {
boolean notBothNull = false;
if (toInstall.isPresent()) {
notBothNull = true;
if (!toInstall.get().installables().stream()
.allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
return false;
}
}
if (toUninstall.isPresent()) {
notBothNull = true;
if (!toUninstall.get().installables().stream()
.allMatch(i -> intentClass.isAssignableFrom(i.getClass()))) {
return false;
}
}
return notBothNull;
}
// Base context for applying and tracking operations related to installable intents.
private abstract class OperationContext {
protected Optional<IntentData> toUninstall;
protected Optional<IntentData> toInstall;
protected Consumer<OperationContext> successConsumer;
protected Consumer<OperationContext> errorConsumer;
abstract void apply();
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()));
abstract Object error();
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, Direction.REMOVE);
prepareIntentData(toInstall, Direction.ADD);
}
// FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
builder.newStage();
/**
* 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;
}
List<Collection<FlowRule>> stages = intentsToApply.stream()
.map(x -> (FlowRuleIntent) x)
.map(FlowRuleIntent::flowRules)
.collect(Collectors.toList());
IntentData data = intentData.get();
List<Intent> intentsToApply = data.installables();
checkState(intentsToApply.stream().allMatch(this::isSupported),
"Unsupported installable intents detected");
for (Collection<FlowRule> rules : stages) {
if (direction == Direction.ADD) {
rules.forEach(builder::add);
trackerService.addTrackedResources(data.key(), data.intent().resources());
intentsToApply.forEach(installable ->
trackerService.addTrackedResources(data.key(),
installable.resources()));
} else {
rules.forEach(builder::remove);
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;
}
}
// FIXME: Refactor to accept both FlowObjectiveIntent and FlowRuleIntents
// Note: Intent Manager should have never become dependent on a specific
// intent type.
/**
* 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) {
// need to consider if FlowRuleIntent is only one as installable intent or not
// Context for applying and tracking operations related to flow rule intent.
private class FlowRuleOperationContext extends OperationContext {
FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
applyIntentData(toUninstall, builder, Direction.REMOVE);
applyIntentData(toInstall, builder, Direction.ADD);
FlowRuleOperationsContext flowRuleOperationsContext;
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()
.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;
}
}
// Context for applying and tracking operations related to flow objective intents.
private class FlowObjectiveOperationContext extends OperationContext {
List<FlowObjectiveInstallationContext> contexts;
final Set<ObjectiveContext> pendingContexts = Sets.newHashSet();
final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
@Override
public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
contexts = intentsToApply.stream()
.flatMap(x -> buildObjectiveContexts((FlowObjectiveIntent) x, direction).stream())
.collect(Collectors.toList());
}
// Builds the specified objective in the appropriate direction
private List<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
Direction direction) {
int size = intent.objectives().size();
List<FlowObjectiveInstallationContext> contexts = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
DeviceId deviceId = intent.devices().get(i);
Objective.Builder builder = intent.objectives().get(i).copy();
FlowObjectiveInstallationContext context = new FlowObjectiveInstallationContext();
final Objective objective;
switch (direction) {
case ADD:
objective = builder.add(context);
break;
case REMOVE:
objective = builder.remove(context);
break;
default:
throw new UnsupportedOperationException("Unsupported direction " + direction);
}
context.setObjective(objective, deviceId);
contexts.add(context);
}
return contexts;
}
@Override
void apply() {
contexts.forEach(objectiveContext -> {
pendingContexts.add(objectiveContext);
flowObjectiveService.apply(objectiveContext.deviceId,
objectiveContext.objective);
});
}
@Override
public Object error() {
return errorContexts;
}
private class FlowObjectiveInstallationContext implements ObjectiveContext {
Objective objective;
DeviceId deviceId;
void setObjective(Objective objective, DeviceId deviceId) {
this.objective = objective;
this.deviceId = deviceId;
}
@Override
public void onSuccess(Objective objective) {
finish();
}
FlowRuleOperations operations = builder.build(new FlowRuleOperationsContext() {
@Override
public void onSuccess(FlowRuleOperations ops) {
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;
public void onError(Objective objective, ObjectiveError error) {
errorContexts.add(this);
finish();
}
private void finish() {
synchronized (pendingContexts) {
pendingContexts.remove(this);
if (pendingContexts.isEmpty()) {
if (errorContexts.isEmpty()) {
successConsumer.accept(FlowObjectiveOperationContext.this);
} else {
errorConsumer.accept(FlowObjectiveOperationContext.this);
}
}
store.write(uninstallData);
}
}
@Override
public void onError(FlowRuleOperations ops) {
// if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
if (toInstall.isPresent()) {
IntentData installData = toInstall.get();
log.warn("Failed installation: {} {} on {}",
installData.key(), installData.intent(), ops);
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();
log.warn("Failed withdrawal: {} {} on {}",
uninstallData.key(), uninstallData.intent(), ops);
uninstallData.setState(CORRUPT);
uninstallData.incrementErrorCount();
store.write(uninstallData);
}
public String toString() {
return String.format("(%s, %s)", deviceId, objective);
}
});
}
}
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());
private class ErrorContext extends OperationContext {
@Override
void apply() {
throw new UnsupportedOperationException("Unsupported installable intent");
}
flowRuleService.apply(operations);
@Override
Object error() {
return null;
}
@Override
void prepareIntents(List<Intent> intentsToApply, Direction direction) {
}
}
}
......