blob: 31e0c4a7384cc4afb5285882de806668de9792e4 [file] [log] [blame]
/*
* Copyright 2014 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 java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.impl.TestCoreManager;
import org.onosproject.event.impl.TestEventDispatcher;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperation;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentCompiler;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentEvent.Type;
import org.onosproject.net.intent.IntentExtensionService;
import org.onosproject.net.intent.IntentId;
import org.onosproject.net.intent.IntentInstaller;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
import org.onosproject.net.resource.LinkResourceAllocations;
import org.onosproject.store.trivial.impl.SimpleIntentStore;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.onlab.junit.TestTools.assertAfter;
import static org.onlab.util.Tools.delay;
import static org.onosproject.net.intent.IntentState.FAILED;
import static org.onosproject.net.intent.IntentState.INSTALLED;
import static org.onosproject.net.intent.IntentState.WITHDRAWN;
import static org.onosproject.net.intent.IntentTestsMocks.MockFlowRule;
import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
/**
* Test intent manager and transitions.
*
* TODO implement the following tests:
* - {submit, withdraw, update, replace} intent
* - {submit, update, recompiling} intent with failed compilation
* - failed reservation
* - push timeout recovery
* - failed items recovery
*
* in general, verify intents store, flow store, and work queue
*/
public class IntentManagerTest {
private static final ApplicationId APPID = new TestApplicationId("manager-test");
private IntentManager manager;
private MockFlowRuleService flowRuleService;
protected IntentService service;
protected IntentExtensionService extensionService;
protected TestListener listener = new TestListener();
protected TestIntentCompiler compiler = new TestIntentCompiler();
protected TestIntentInstaller installer = new TestIntentInstaller();
private static class TestListener implements IntentListener {
final Multimap<IntentEvent.Type, IntentEvent> events = HashMultimap.create();
Map<IntentEvent.Type, CountDownLatch> latchMap = Maps.newHashMap();
@Override
public void event(IntentEvent event) {
events.put(event.type(), event);
if (latchMap.containsKey(event.type())) {
latchMap.get(event.type()).countDown();
}
}
public int getCounts(IntentEvent.Type type) {
return events.get(type).size();
}
public void setLatch(int count, IntentEvent.Type type) {
latchMap.put(type, new CountDownLatch(count));
}
public void await(IntentEvent.Type type) {
try {
assertTrue("Timed out waiting for: " + type,
latchMap.get(type).await(5, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class TestIntentTracker implements ObjectiveTrackerService {
private TopologyChangeDelegate delegate;
@Override
public void setDelegate(TopologyChangeDelegate delegate) {
this.delegate = delegate;
}
@Override
public void unsetDelegate(TopologyChangeDelegate delegate) {
if (delegate.equals(this.delegate)) {
this.delegate = null;
}
}
@Override
public void addTrackedResources(Key key, Collection<NetworkResource> resources) {
//TODO
}
@Override
public void removeTrackedResources(Key key, Collection<NetworkResource> resources) {
//TODO
}
}
private static class MockInstallableIntent extends MockIntent {
public MockInstallableIntent(Long number) {
super(number);
}
@Override
public boolean isInstallable() {
return true;
}
}
private static class TestIntentCompiler implements IntentCompiler<MockIntent> {
@Override
public List<Intent> compile(MockIntent intent, List<Intent> installable,
Set<LinkResourceAllocations> resources) {
return Lists.newArrayList(new MockInstallableIntent(intent.number()));
}
}
private static class TestIntentCompilerError implements IntentCompiler<MockIntent> {
@Override
public List<Intent> compile(MockIntent intent, List<Intent> installable,
Set<LinkResourceAllocations> resources) {
throw new IntentCompilationException("Compilation always fails");
}
}
private static class TestIntentInstaller implements IntentInstaller<MockInstallableIntent> {
@Override
public List<Collection<org.onosproject.net.flow.FlowRuleOperation>> install(MockInstallableIntent intent) {
FlowRule fr = new MockFlowRule(intent.number().intValue());
Set<FlowRuleOperation> rules = ImmutableSet.of(
new FlowRuleOperation(fr, FlowRuleOperation.Type.ADD));
return Lists.newArrayList(ImmutableSet.of(rules));
}
@Override
public List<Collection<FlowRuleOperation>> uninstall(MockInstallableIntent intent) {
FlowRule fr = new MockFlowRule(intent.number().intValue());
Set<FlowRuleOperation> rules = ImmutableSet.of(
new FlowRuleOperation(fr, FlowRuleOperation.Type.REMOVE));
return Lists.newArrayList(ImmutableSet.of(rules));
}
@Override
public List<Collection<FlowRuleOperation>> replace(MockInstallableIntent oldIntent,
MockInstallableIntent newIntent) {
FlowRule fr = new MockFlowRule(oldIntent.number().intValue());
FlowRule fr2 = new MockFlowRule(newIntent.number().intValue());
Set<FlowRuleOperation> rules = ImmutableSet.of(
new FlowRuleOperation(fr, FlowRuleOperation.Type.REMOVE),
new FlowRuleOperation(fr2, FlowRuleOperation.Type.ADD));
return Lists.newArrayList(ImmutableSet.of(rules));
}
}
private static class TestIntentErrorInstaller implements IntentInstaller<MockInstallableIntent> {
@Override
public List<Collection<FlowRuleOperation>> install(MockInstallableIntent intent) {
throw new IntentInstallationException("install() always fails");
}
@Override
public List<Collection<FlowRuleOperation>> uninstall(MockInstallableIntent intent) {
throw new IntentRemovalException("uninstall() always fails");
}
@Override
public List<Collection<FlowRuleOperation>> replace(MockInstallableIntent oldIntent,
MockInstallableIntent newIntent) {
throw new IntentInstallationException("replace() always fails");
}
}
/**
* Hamcrest matcher to check that a conllection of Intents contains an
* Intent with the specified Intent Id.
*/
public static class EntryForIntentMatcher extends TypeSafeMatcher<Collection<Intent>> {
private final IntentId id;
public EntryForIntentMatcher(IntentId idValue) {
id = idValue;
}
@Override
public boolean matchesSafely(Collection<Intent> intents) {
for (Intent intent : intents) {
if (intent.id().equals(id)) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("an intent with id \" ").
appendText(id.toString()).
appendText("\"");
}
}
private static EntryForIntentMatcher hasIntentWithId(IntentId id) {
return new EntryForIntentMatcher(id);
}
@Before
public void setUp() {
manager = new IntentManager();
flowRuleService = new MockFlowRuleService();
manager.store = new SimpleIntentStore();
manager.eventDispatcher = new TestEventDispatcher();
manager.trackerService = new TestIntentTracker();
manager.flowRuleService = flowRuleService;
manager.coreService = new TestCoreManager();
service = manager;
extensionService = manager;
manager.activate();
service.addListener(listener);
extensionService.registerCompiler(MockIntent.class, compiler);
extensionService.registerInstaller(MockInstallableIntent.class, installer);
assertTrue("store should be empty",
Sets.newHashSet(service.getIntents()).isEmpty());
assertEquals(0L, flowRuleService.getFlowRuleCount());
}
public void verifyState() {
// verify that all intents are parked and the batch operation is unblocked
Set<IntentState> parked = Sets.newHashSet(INSTALLED, WITHDRAWN, FAILED);
for (Intent i : service.getIntents()) {
IntentState state = service.getIntentState(i.key());
assertTrue("Intent " + i.id() + " is in invalid state " + state,
parked.contains(state));
}
//the batch has not yet been removed when we receive the last event
// FIXME: this doesn't guarantee to avoid the race
//FIXME
// for (int tries = 0; tries < 10; tries++) {
// if (manager.batchService.getPendingOperations().isEmpty()) {
// break;
// }
// delay(10);
// }
// assertTrue("There are still pending batch operations.",
// manager.batchService.getPendingOperations().isEmpty());
}
@After
public void tearDown() {
extensionService.unregisterCompiler(MockIntent.class);
extensionService.unregisterInstaller(MockInstallableIntent.class);
service.removeListener(listener);
manager.deactivate();
// TODO null the other refs?
}
@Test
public void submitIntent() {
flowRuleService.setFuture(true);
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.INSTALLED);
Intent intent = new MockIntent(MockIntent.nextId());
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.INSTALLED);
assertEquals(1L, service.getIntentCount());
assertEquals(1L, flowRuleService.getFlowRuleCount());
verifyState();
}
@Test
public void withdrawIntent() {
flowRuleService.setFuture(true);
listener.setLatch(1, Type.INSTALLED);
Intent intent = new MockIntent(MockIntent.nextId());
service.submit(intent);
listener.await(Type.INSTALLED);
assertEquals(1L, service.getIntentCount());
assertEquals(1L, flowRuleService.getFlowRuleCount());
listener.setLatch(1, Type.WITHDRAWN);
service.withdraw(intent);
listener.await(Type.WITHDRAWN);
assertEquals(0L, flowRuleService.getFlowRuleCount());
verifyState();
}
@Test
public void stressSubmitWithdrawUnique() {
flowRuleService.setFuture(true);
int count = 500;
Intent[] intents = new Intent[count];
listener.setLatch(count, Type.WITHDRAWN);
for (int i = 0; i < count; i++) {
intents[i] = new MockIntent(MockIntent.nextId());
service.submit(intents[i]);
}
for (int i = 0; i < count; i++) {
service.withdraw(intents[i]);
}
listener.await(Type.WITHDRAWN);
assertEquals(0L, flowRuleService.getFlowRuleCount());
verifyState();
}
@Test
public void stressSubmitWithdrawSame() {
flowRuleService.setFuture(true);
int count = 50;
Intent intent = new MockIntent(MockIntent.nextId());
for (int i = 0; i < count; i++) {
service.submit(intent);
service.withdraw(intent);
}
assertAfter(100, () -> {
assertEquals(1L, service.getIntentCount());
assertEquals(0L, flowRuleService.getFlowRuleCount());
});
verifyState();
}
/**
* Tests for proper behavior of installation of an intent that triggers
* a compilation error.
*/
@Test
public void errorIntentCompile() {
final TestIntentCompilerError errorCompiler = new TestIntentCompilerError();
extensionService.registerCompiler(MockIntent.class, errorCompiler);
MockIntent intent = new MockIntent(MockIntent.nextId());
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.FAILED);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.FAILED);
verifyState();
}
/**
* Tests handling a future that contains an error as a result of
* installing an intent.
*/
@Ignore("skipping until we fix update ordering problem")
@Test
public void errorIntentInstallFromFlows() {
final Long id = MockIntent.nextId();
flowRuleService.setFuture(false);
MockIntent intent = new MockIntent(id);
listener.setLatch(1, Type.FAILED);
listener.setLatch(1, Type.INSTALL_REQ);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.FAILED);
// FIXME the intent will be moved into INSTALLED immediately which overrides FAILED
// ... the updates come out of order
verifyState();
}
/**
* Tests handling of an error that is generated by the intent installer.
*/
@Test
public void errorIntentInstallFromInstaller() {
final TestIntentErrorInstaller errorInstaller = new TestIntentErrorInstaller();
extensionService.registerInstaller(MockInstallableIntent.class, errorInstaller);
MockIntent intent = new MockIntent(MockIntent.nextId());
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.FAILED);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.FAILED);
verifyState();
}
/**
* Tests handling a future that contains an unresolvable error as a result of
* installing an intent.
*/
@Ignore("test needs to be rewritten, so that the intent is resubmitted")
@Test
public void errorIntentInstallNeverTrue() {
final Long id = MockIntent.nextId();
flowRuleService.setFuture(false);
MockIntent intent = new MockIntent(id);
listener.setLatch(1, Type.FAILED);
listener.setLatch(1, Type.INSTALL_REQ);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
// The delay here forces the retry loop in the intent manager to time out
delay(100);
flowRuleService.setFuture(false);
service.withdraw(intent);
listener.await(Type.FAILED);
verifyState();
}
/**
* Tests that a compiler for a subclass of an intent that already has a
* compiler is automatically added.
*/
@Test
public void intentSubclassCompile() {
class MockIntentSubclass extends MockIntent {
public MockIntentSubclass(Long number) {
super(number);
}
}
flowRuleService.setFuture(true);
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.INSTALLED);
Intent intent = new MockIntentSubclass(MockIntent.nextId());
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.INSTALLED);
assertEquals(1L, service.getIntentCount());
assertEquals(1L, flowRuleService.getFlowRuleCount());
final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers =
extensionService.getCompilers();
assertEquals(2, compilers.size());
assertNotNull(compilers.get(MockIntentSubclass.class));
assertNotNull(compilers.get(MockIntent.class));
verifyState();
}
/**
* Tests an intent with no compiler.
*/
@Test
public void intentWithoutCompiler() {
class IntentNoCompiler extends Intent {
IntentNoCompiler() {
super(APPID, Collections.emptyList());
}
}
Intent intent = new IntentNoCompiler();
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.FAILED);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.FAILED);
verifyState();
}
/**
* Tests an intent with no installer.
*/
@Test
public void intentWithoutInstaller() {
extensionService.unregisterInstaller(MockInstallableIntent.class);
MockIntent intent = new MockIntent(MockIntent.nextId());
listener.setLatch(1, Type.INSTALL_REQ);
listener.setLatch(1, Type.FAILED);
service.submit(intent);
listener.await(Type.INSTALL_REQ);
listener.await(Type.FAILED);
verifyState();
}
/**
* Tests that the intent fetching methods are correct.
*/
@Test
public void testIntentFetching() {
List<Intent> intents;
flowRuleService.setFuture(true);
intents = Lists.newArrayList(service.getIntents());
assertThat(intents, hasSize(0));
final MockIntent intent1 = new MockIntent(MockIntent.nextId());
final MockIntent intent2 = new MockIntent(MockIntent.nextId());
listener.setLatch(2, Type.INSTALL_REQ);
listener.setLatch(2, Type.INSTALLED);
service.submit(intent1);
service.submit(intent2);
listener.await(Type.INSTALL_REQ);
listener.await(Type.INSTALL_REQ);
listener.await(Type.INSTALLED);
listener.await(Type.INSTALLED);
intents = Lists.newArrayList(service.getIntents());
assertThat(intents, hasSize(2));
assertThat(intents, hasIntentWithId(intent1.id()));
assertThat(intents, hasIntentWithId(intent2.id()));
verifyState();
}
}