Thomas Vachuska
Committed by Gerrit Code Review

Initial work for adding flow-objective backed intents.

Split installation work from IntentManager into IntentInstaller helper class.

Change-Id: If926ce975d005abee4f22f2b05404de328d94203
/*
* Copyright 2016 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;
import com.google.common.base.MoreObjects;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.flowobjective.Objective;
import java.util.Collection;
/**
* Intent expressed as (and backed by) a collection of flow objectives through
* which the intent is to be accomplished.
*/
public class FlowObjectiveIntent extends Intent {
private final Collection<Objective> objectives;
/**
* Constructor for serialization.
*/
protected FlowObjectiveIntent() {
super();
this.objectives = null;
}
/**
* Creates a flow objective intent with the specified objectives and
* resources.
*
* @param appId application id
* @param objectives backing flow objectives
* @param resources backing network resources
*/
public FlowObjectiveIntent(ApplicationId appId,
Collection<Objective> objectives,
Collection<NetworkResource> resources) {
this(appId, null, objectives, resources);
}
/**
* Creates a flow objective intent with the specified objectives and
* resources.
*
* @param appId application id
* @param key intent key
* @param objectives backing flow objectives
* @param resources backing network resources
*/
public FlowObjectiveIntent(ApplicationId appId, Key key,
Collection<Objective> objectives,
Collection<NetworkResource> resources) {
super(appId, key, resources, DEFAULT_INTENT_PRIORITY);
this.objectives = objectives;
}
/**
* Returns the collection of backing flow objectives.
*
* @return flow objectives
*/
Collection<Objective> objectives() {
return objectives;
}
@Override
public boolean isInstallable() {
return true;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id())
.add("key", key())
.add("appId", appId())
.add("resources", resources())
.add("objectives", objectives)
.toString();
}
}
......@@ -25,6 +25,7 @@ public abstract class AbstractIntentTest {
@Before
public void setUp() throws Exception {
Intent.unbindIdGenerator(idGenerator);
Intent.bindIdGenerator(idGenerator);
}
......
/*
* Copyright 2016 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;
import com.google.common.collect.ImmutableSet;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.Objective;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
/**
* Tests of the flow objective intent.
*/
public class FlowObjectiveIntentTest extends IntentTest {
private static final ApplicationId APP_ID = new DefaultApplicationId(1, "foo");
private static final Key KEY = Key.of("bar", APP_ID);
private static final Objective FO1 = DefaultFilteringObjective.builder()
.fromApp(APP_ID).addCondition(Criteria.matchEthType(123))
.permit().add();
private static final Objective FO2 = DefaultForwardingObjective.builder()
.fromApp(APP_ID)
.withSelector(DefaultTrafficSelector.builder().matchEthType((short) 123).build())
.withTreatment(DefaultTrafficTreatment.emptyTreatment())
.withFlag(ForwardingObjective.Flag.VERSATILE).add();
private static final Collection<Objective> OBJECTIVES = ImmutableSet.of(FO1, FO2);
private static final Collection<NetworkResource> RESOURCES = ImmutableSet.of();
/**
* Tests basics of construction and getters.
*/
@Test
public void basics() {
FlowObjectiveIntent intent =
new FlowObjectiveIntent(APP_ID, KEY, OBJECTIVES, RESOURCES);
assertEquals("incorrect app id", APP_ID, intent.appId());
assertEquals("incorrect key", KEY, intent.key());
assertEquals("incorrect objectives", OBJECTIVES, intent.objectives());
assertEquals("incorrect resources", RESOURCES, intent.resources());
assertTrue("should be installable", intent.isInstallable());
}
/**
* Tests equality.
*/
@Test
public void equality() {
Intent a = createOne();
Intent b = createAnother();
new EqualsTester().addEqualityGroup(a).addEqualityGroup(b).testEquals();
}
/**
* Tests that instance is immutable.
*/
@Test
public void testImmutability() {
assertThatClassIsImmutable(HostToHostIntent.class);
}
@Override
protected Intent createOne() {
return new FlowObjectiveIntent(APP_ID, OBJECTIVES, RESOURCES);
}
@Override
protected Intent createAnother() {
return new FlowObjectiveIntent(APP_ID, OBJECTIVES, RESOURCES);
}
}
\ No newline at end of file
/*
* Copyright 2016 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 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.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.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
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(IntentManager.class);
private IntentStore store;
private ObjectiveTrackerService trackerService;
private FlowRuleService flowRuleService;
private FlowObjectiveService flowObjectiveService;
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
*/
void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService) {
this.store = intentStore;
this.trackerService = trackerService;
this.flowRuleService = flowRuleService;
this.flowObjectiveService = flowObjectiveService;
}
private void applyIntentData(Optional<IntentData> intentData,
FlowRuleOperations.Builder builder,
Direction direction) {
if (!intentData.isPresent()) {
return;
}
IntentData data = intentData.get();
List<Intent> intentsToApply = data.installables();
if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) {
throw new IllegalStateException("installable intents must be FlowRuleIntent");
}
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()));
}
// 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);
}
}
}
// 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
FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
applyIntentData(toUninstall, builder, Direction.REMOVE);
applyIntentData(toInstall, builder, Direction.ADD);
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;
}
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);
}
}
});
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);
}
}
......@@ -25,11 +25,8 @@ import org.onlab.util.Tools;
import org.onosproject.core.CoreService;
import org.onosproject.core.IdGenerator;
import org.onosproject.event.AbstractListenerManager;
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.intent.FlowRuleIntent;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentBatchDelegate;
import org.onosproject.net.intent.IntentCompiler;
......@@ -61,13 +58,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.intent.IntentState.CORRUPT;
import static org.onosproject.net.intent.IntentState.FAILED;
import static org.onosproject.net.intent.IntentState.INSTALLED;
import static org.onosproject.net.intent.IntentState.INSTALL_REQ;
import static org.onosproject.net.intent.IntentState.WITHDRAWING;
import static org.onosproject.net.intent.IntentState.WITHDRAWN;
import static org.onosproject.net.intent.IntentState.WITHDRAW_REQ;
import static org.onosproject.net.intent.IntentState.*;
import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure;
import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.newInitialPhase;
import static org.onosproject.security.AppGuard.checkPermission;
......@@ -75,7 +66,6 @@ import static org.onosproject.security.AppPermission.Type.INTENT_READ;
import static org.onosproject.security.AppPermission.Type.INTENT_WRITE;
import static org.slf4j.LoggerFactory.getLogger;
/**
* An implementation of intent service.
*/
......@@ -110,11 +100,15 @@ public class IntentManager
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ResourceService resourceService;
private ExecutorService batchExecutor;
private ExecutorService workerExecutor;
private final IntentInstaller intentInstaller = new IntentInstaller();
private final CompilerRegistry compilerRegistry = new CompilerRegistry();
private final InternalIntentProcessor processor = new InternalIntentProcessor();
private final IntentStoreDelegate delegate = new InternalStoreDelegate();
......@@ -126,6 +120,7 @@ public class IntentManager
@Activate
public void activate() {
intentInstaller.init(store, trackerService, flowRuleService, flowObjectiveService);
store.setDelegate(delegate);
trackerService.setDelegate(topoDelegate);
eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
......@@ -138,6 +133,7 @@ public class IntentManager
@Deactivate
public void deactivate() {
intentInstaller.init(null, null, null, null);
store.unsetDelegate(delegate);
trackerService.unsetDelegate(topoDelegate);
eventDispatcher.removeSink(IntentEvent.class);
......@@ -322,8 +318,8 @@ public class IntentManager
// write multiple data to store in order
store.batchWrite(Tools.allOf(futures).join().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList()));
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}, batchExecutor).exceptionally(e -> {
log.error("Error submitting batches:", e);
// FIXME incomplete Intents should be cleaned up
......@@ -351,120 +347,8 @@ public class IntentManager
@Override
public void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
IntentManager.this.apply(toUninstall, toInstall);
}
}
private enum Direction {
ADD,
REMOVE
}
private void applyIntentData(Optional<IntentData> intentData,
FlowRuleOperations.Builder builder,
Direction direction) {
if (!intentData.isPresent()) {
return;
intentInstaller.apply(toUninstall, toInstall);
}
IntentData data = intentData.get();
List<Intent> intentsToApply = data.installables();
if (!intentsToApply.stream().allMatch(x -> x instanceof FlowRuleIntent)) {
throw new IllegalStateException("installable intents must be FlowRuleIntent");
}
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()));
}
// 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);
}
}
}
private void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
// need to consider if FlowRuleIntent is only one as installable intent or not
FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
applyIntentData(toUninstall, builder, Direction.REMOVE);
applyIntentData(toInstall, builder, Direction.ADD);
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;
}
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);
}
}
});
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(i -> i.size()).sum(),
operations.stages());
}
flowRuleService.apply(operations);
}
}
......