Brian O'Connor

Adding Intent API tests

Showing 23 changed files with 1348 additions and 0 deletions
1 package org.onlab.onos.net.intent; 1 package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
2 3
3 import static com.google.common.base.Preconditions.checkNotNull; 4 import static com.google.common.base.Preconditions.checkNotNull;
4 5
......
1 package org.onlab.onos.net.intent; 1 package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
2 3
3 import java.util.Objects; 4 import java.util.Objects;
4 5
......
1 package org.onlab.onos.net.intent; 1 package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
2 3
3 /** 4 /**
4 * An interface of the class which is assigned to BatchOperation. 5 * An interface of the class which is assigned to BatchOperation.
......
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +/**
5 + * A generalized interface for ID generation
6 + *
7 + * {@link #getNewId()} generates a globally unique ID instance on
8 + * each invocation.
9 + *
10 + * @param <T> the type of ID
11 + */
12 +// TODO: do we need to define a base marker interface for ID,
13 +// then changed the type parameter to <T extends BaseId> something
14 +// like that?
15 +public interface IdGenerator<T> {
16 + /**
17 + * Returns a globally unique ID instance.
18 + *
19 + * @return globally unique ID instance
20 + */
21 + T getNewId();
22 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import java.util.Set;
4 +
5 +import org.onlab.onos.net.ConnectPoint;
6 +import org.onlab.onos.net.DeviceId;
7 +import org.onlab.onos.net.PortNumber;
8 +import org.onlab.onos.net.flow.DefaultTrafficSelector;
9 +import org.onlab.onos.net.flow.DefaultTrafficTreatment;
10 +import org.onlab.onos.net.flow.TrafficSelector;
11 +import org.onlab.onos.net.flow.TrafficTreatment;
12 +
13 +/**
14 + * Base facilities to test various connectivity tests.
15 + */
16 +public abstract class ConnectivityIntentTest extends IntentTest {
17 +
18 + public static final IntentId IID = new IntentId(123);
19 + public static final TrafficSelector MATCH = (new DefaultTrafficSelector.Builder()).build();
20 + public static final TrafficTreatment NOP = (new DefaultTrafficTreatment.Builder()).build();
21 +
22 + public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
23 + public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
24 + public static final ConnectPoint P3 = new ConnectPoint(DeviceId.deviceId("333"), PortNumber.portNumber(0x3));
25 +
26 + public static final Set<ConnectPoint> PS1 = itemSet(new ConnectPoint[]{P1, P3});
27 + public static final Set<ConnectPoint> PS2 = itemSet(new ConnectPoint[]{P2, P3});
28 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import java.util.ArrayList;
4 +import java.util.Collections;
5 +import java.util.HashMap;
6 +import java.util.HashSet;
7 +import java.util.List;
8 +import java.util.Map;
9 +import java.util.Set;
10 +import java.util.concurrent.ExecutorService;
11 +import java.util.concurrent.Executors;
12 +
13 +/**
14 + * Fake implementation of the intent service to assist in developing tests
15 + * of the interface contract.
16 + */
17 +public class FakeIntentManager implements TestableIntentService {
18 +
19 + private final Map<IntentId, Intent> intents = new HashMap<>();
20 + private final Map<IntentId, IntentState> intentStates = new HashMap<>();
21 + private final Map<IntentId, List<InstallableIntent>> installables = new HashMap<>();
22 + private final Set<IntentEventListener> listeners = new HashSet<>();
23 +
24 + private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>();
25 + private final Map<Class<? extends InstallableIntent>,
26 + IntentInstaller<? extends InstallableIntent>> installers = new HashMap<>();
27 +
28 + private final ExecutorService executor = Executors.newSingleThreadExecutor();
29 + private final List<IntentException> exceptions = new ArrayList<>();
30 +
31 + @Override
32 + public List<IntentException> getExceptions() {
33 + return exceptions;
34 + }
35 +
36 + // Provides an out-of-thread simulation of intent submit life-cycle
37 + private void executeSubmit(final Intent intent) {
38 + registerSubclassCompilerIfNeeded(intent);
39 + executor.execute(new Runnable() {
40 + @Override
41 + public void run() {
42 + try {
43 + List<InstallableIntent> installable = compileIntent(intent);
44 + installIntents(intent, installable);
45 + } catch (IntentException e) {
46 + exceptions.add(e);
47 + }
48 + }
49 + });
50 + }
51 +
52 + // Provides an out-of-thread simulation of intent withdraw life-cycle
53 + private void executeWithdraw(final Intent intent) {
54 + executor.execute(new Runnable() {
55 + @Override
56 + public void run() {
57 + try {
58 + List<InstallableIntent> installable = getInstallable(intent.getId());
59 + uninstallIntents(intent, installable);
60 + } catch (IntentException e) {
61 + exceptions.add(e);
62 + }
63 +
64 + }
65 + });
66 + }
67 +
68 + private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
69 + @SuppressWarnings("unchecked")
70 + IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
71 + if (compiler == null) {
72 + throw new IntentException("no compiler for class " + intent.getClass());
73 + }
74 + return compiler;
75 + }
76 +
77 + private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) {
78 + @SuppressWarnings("unchecked")
79 + IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
80 + if (installer == null) {
81 + throw new IntentException("no installer for class " + intent.getClass());
82 + }
83 + return installer;
84 + }
85 +
86 + private <T extends Intent> List<InstallableIntent> compileIntent(T intent) {
87 + try {
88 + // For the fake, we compile using a single level pass
89 + List<InstallableIntent> installable = new ArrayList<>();
90 + for (Intent compiled : getCompiler(intent).compile(intent)) {
91 + installable.add((InstallableIntent) compiled);
92 + }
93 + setState(intent, IntentState.COMPILED);
94 + return installable;
95 + } catch (IntentException e) {
96 + setState(intent, IntentState.FAILED);
97 + throw e;
98 + }
99 + }
100 +
101 + private void installIntents(Intent intent, List<InstallableIntent> installable) {
102 + try {
103 + for (InstallableIntent ii : installable) {
104 + registerSubclassInstallerIfNeeded(ii);
105 + getInstaller(ii).install(ii);
106 + }
107 + setState(intent, IntentState.INSTALLED);
108 + putInstallable(intent.getId(), installable);
109 + } catch (IntentException e) {
110 + setState(intent, IntentState.FAILED);
111 + throw e;
112 + }
113 + }
114 +
115 + private void uninstallIntents(Intent intent, List<InstallableIntent> installable) {
116 + try {
117 + for (InstallableIntent ii : installable) {
118 + getInstaller(ii).uninstall(ii);
119 + }
120 + setState(intent, IntentState.WITHDRAWN);
121 + removeInstallable(intent.getId());
122 + } catch (IntentException e) {
123 + setState(intent, IntentState.FAILED);
124 + throw e;
125 + }
126 + }
127 +
128 +
129 + // Sets the internal state for the given intent and dispatches an event
130 + private void setState(Intent intent, IntentState state) {
131 + IntentState previous = intentStates.get(intent.getId());
132 + intentStates.put(intent.getId(), state);
133 + dispatch(new IntentEvent(intent, state, previous, System.currentTimeMillis()));
134 + }
135 +
136 + private void putInstallable(IntentId id, List<InstallableIntent> installable) {
137 + installables.put(id, installable);
138 + }
139 +
140 + private void removeInstallable(IntentId id) {
141 + installables.remove(id);
142 + }
143 +
144 + private List<InstallableIntent> getInstallable(IntentId id) {
145 + List<InstallableIntent> installable = installables.get(id);
146 + if (installable != null) {
147 + return installable;
148 + } else {
149 + return Collections.emptyList();
150 + }
151 + }
152 +
153 + @Override
154 + public void submit(Intent intent) {
155 + intents.put(intent.getId(), intent);
156 + setState(intent, IntentState.SUBMITTED);
157 + executeSubmit(intent);
158 + }
159 +
160 + @Override
161 + public void withdraw(Intent intent) {
162 + intents.remove(intent.getId());
163 + setState(intent, IntentState.WITHDRAWING);
164 + executeWithdraw(intent);
165 + }
166 +
167 + @Override
168 + public void execute(IntentOperations operations) {
169 + // TODO: implement later
170 + }
171 +
172 + @Override
173 + public Set<Intent> getIntents() {
174 + return Collections.unmodifiableSet(new HashSet<>(intents.values()));
175 + }
176 +
177 + @Override
178 + public Intent getIntent(IntentId id) {
179 + return intents.get(id);
180 + }
181 +
182 + @Override
183 + public IntentState getIntentState(IntentId id) {
184 + return intentStates.get(id);
185 + }
186 +
187 + @Override
188 + public void addListener(IntentEventListener listener) {
189 + listeners.add(listener);
190 + }
191 +
192 + @Override
193 + public void removeListener(IntentEventListener listener) {
194 + listeners.remove(listener);
195 + }
196 +
197 + private void dispatch(IntentEvent event) {
198 + for (IntentEventListener listener : listeners) {
199 + listener.event(event);
200 + }
201 + }
202 +
203 + @Override
204 + public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
205 + compilers.put(cls, compiler);
206 + }
207 +
208 + @Override
209 + public <T extends Intent> void unregisterCompiler(Class<T> cls) {
210 + compilers.remove(cls);
211 + }
212 +
213 + @Override
214 + public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
215 + return Collections.unmodifiableMap(compilers);
216 + }
217 +
218 + @Override
219 + public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
220 + installers.put(cls, installer);
221 + }
222 +
223 + @Override
224 + public <T extends InstallableIntent> void unregisterInstaller(Class<T> cls) {
225 + installers.remove(cls);
226 + }
227 +
228 + @Override
229 + public Map<Class<? extends InstallableIntent>,
230 + IntentInstaller<? extends InstallableIntent>> getInstallers() {
231 + return Collections.unmodifiableMap(installers);
232 + }
233 +
234 + private void registerSubclassCompilerIfNeeded(Intent intent) {
235 + if (!compilers.containsKey(intent.getClass())) {
236 + Class<?> cls = intent.getClass();
237 + while (cls != Object.class) {
238 + // As long as we're within the Intent class descendants
239 + if (Intent.class.isAssignableFrom(cls)) {
240 + IntentCompiler<?> compiler = compilers.get(cls);
241 + if (compiler != null) {
242 + compilers.put(intent.getClass(), compiler);
243 + return;
244 + }
245 + }
246 + cls = cls.getSuperclass();
247 + }
248 + }
249 + }
250 +
251 + private void registerSubclassInstallerIfNeeded(InstallableIntent intent) {
252 + if (!installers.containsKey(intent.getClass())) {
253 + Class<?> cls = intent.getClass();
254 + while (cls != Object.class) {
255 + // As long as we're within the InstallableIntent class descendants
256 + if (InstallableIntent.class.isAssignableFrom(cls)) {
257 + IntentInstaller<?> installer = installers.get(cls);
258 + if (installer != null) {
259 + installers.put(intent.getClass(), installer);
260 + return;
261 + }
262 + }
263 + cls = cls.getSuperclass();
264 + }
265 + }
266 + }
267 +
268 +}
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +import org.hamcrest.Description;
5 +import org.hamcrest.StringDescription;
6 +
7 +import java.lang.reflect.Field;
8 +import java.lang.reflect.Method;
9 +import java.lang.reflect.Modifier;
10 +
11 +/**
12 + * Hamcrest style class for verifying that a class follows the
13 + * accepted rules for immutable classes.
14 + *
15 + * The rules that are enforced for immutable classes:
16 + * - the class must be declared final
17 + * - all data members of the class must be declared private and final
18 + * - the class must not define any setter methods
19 + */
20 +
21 +public class ImmutableClassChecker {
22 +
23 + private String failureReason = "";
24 +
25 + /**
26 + * Method to determine if a given class is a properly specified
27 + * immutable class.
28 + *
29 + * @param clazz the class to check
30 + * @return true if the given class is a properly specified immutable class.
31 + */
32 + private boolean isImmutableClass(Class<?> clazz) {
33 + // class must be declared final
34 + if (!Modifier.isFinal(clazz.getModifiers())) {
35 + failureReason = "a class that is not final";
36 + return false;
37 + }
38 +
39 + // class must have only final and private data members
40 + for (final Field field : clazz.getDeclaredFields()) {
41 + if (field.getName().startsWith("__cobertura")) {
42 + // cobertura sticks these fields into classes - ignore them
43 + continue;
44 + }
45 + if (!Modifier.isFinal(field.getModifiers())) {
46 + failureReason = "a field named '" + field.getName() +
47 + "' that is not final";
48 + return false;
49 + }
50 + if (!Modifier.isPrivate(field.getModifiers())) {
51 + //
52 + // NOTE: We relax the recommended rules for defining immutable
53 + // objects and allow "static final" fields that are not
54 + // private. The "final" check was already done above so we
55 + // don't repeat it here.
56 + //
57 + if (!Modifier.isStatic(field.getModifiers())) {
58 + failureReason = "a field named '" + field.getName() +
59 + "' that is not private and is not static";
60 + return false;
61 + }
62 + }
63 + }
64 +
65 + // class must not define any setters
66 + for (final Method method : clazz.getMethods()) {
67 + if (method.getDeclaringClass().equals(clazz)) {
68 + if (method.getName().startsWith("set")) {
69 + failureReason = "a class with a setter named '" + method.getName() + "'";
70 + return false;
71 + }
72 + }
73 + }
74 +
75 + return true;
76 + }
77 +
78 + /**
79 + * Describe why an error was reported. Uses Hamcrest style Description
80 + * interfaces.
81 + *
82 + * @param description the Description object to use for reporting the
83 + * mismatch
84 + */
85 + public void describeMismatch(Description description) {
86 + description.appendText(failureReason);
87 + }
88 +
89 + /**
90 + * Describe the source object that caused an error, using a Hamcrest
91 + * Matcher style interface. In this case, it always returns
92 + * that we are looking for a properly defined utility class.
93 + *
94 + * @param description the Description object to use to report the "to"
95 + * object
96 + */
97 + public void describeTo(Description description) {
98 + description.appendText("a properly defined immutable class");
99 + }
100 +
101 + /**
102 + * Assert that the given class adheres to the utility class rules.
103 + *
104 + * @param clazz the class to check
105 + *
106 + * @throws java.lang.AssertionError if the class is not a valid
107 + * utility class
108 + */
109 + public static void assertThatClassIsImmutable(Class<?> clazz) {
110 + final ImmutableClassChecker checker = new ImmutableClassChecker();
111 + if (!checker.isImmutableClass(clazz)) {
112 + final Description toDescription = new StringDescription();
113 + final Description mismatchDescription = new StringDescription();
114 +
115 + checker.describeTo(toDescription);
116 + checker.describeMismatch(mismatchDescription);
117 + final String reason =
118 + "\n" +
119 + "Expected: is \"" + toDescription.toString() + "\"\n" +
120 + " but : was \"" + mismatchDescription.toString() + "\"";
121 +
122 + throw new AssertionError(reason);
123 + }
124 + }
125 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.junit.Assert.assertEquals;
6 +
7 +/**
8 + * Test of the intent exception.
9 + */
10 +public class IntentExceptionTest {
11 +
12 + @Test
13 + public void basics() {
14 + validate(new IntentException(), null, null);
15 + validate(new IntentException("foo"), "foo", null);
16 +
17 + Throwable cause = new NullPointerException("bar");
18 + validate(new IntentException("foo", cause), "foo", cause);
19 + }
20 +
21 + /**
22 + * Validates that the specified exception has the correct message and cause.
23 + *
24 + * @param e exception to test
25 + * @param message expected message
26 + * @param cause expected cause
27 + */
28 + protected void validate(RuntimeException e, String message, Throwable cause) {
29 + assertEquals("incorrect message", message, e.getMessage());
30 + assertEquals("incorrect cause", cause, e.getCause());
31 + }
32 +
33 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +/**
4 + * This interface is for generator of IntentId. It is defined only for
5 + * testing purpose to keep type safety on mock creation.
6 + *
7 + * <p>
8 + * {@link #getNewId()} generates a globally unique {@link IntentId} instance
9 + * on each invocation. Application developers should not generate IntentId
10 + * by themselves. Instead use an implementation of this interface.
11 + * </p>
12 + */
13 +public interface IntentIdGenerator extends IdGenerator<IntentId> {
14 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.hamcrest.Matchers.is;
6 +import static org.hamcrest.Matchers.not;
7 +import static org.junit.Assert.assertEquals;
8 +import static org.junit.Assert.assertThat;
9 +
10 +/**
11 + * This class tests the immutability, equality, and non-equality of
12 + * {@link IntentId}.
13 + */
14 +public class IntentIdTest {
15 + /**
16 + * Tests the immutability of {@link IntentId}.
17 + */
18 + @Test
19 + public void intentIdFollowsGuidelineForImmutableObject() {
20 + ImmutableClassChecker.assertThatClassIsImmutable(IntentId.class);
21 + }
22 +
23 + /**
24 + * Tests equality of {@link IntentId}.
25 + */
26 + @Test
27 + public void testEquality() {
28 + IntentId id1 = new IntentId(1L);
29 + IntentId id2 = new IntentId(1L);
30 +
31 + assertThat(id1, is(id2));
32 + }
33 +
34 + /**
35 + * Tests non-equality of {@link IntentId}.
36 + */
37 + @Test
38 + public void testNonEquality() {
39 + IntentId id1 = new IntentId(1L);
40 + IntentId id2 = new IntentId(2L);
41 +
42 + assertThat(id1, is(not(id2)));
43 + }
44 +
45 + @Test
46 + public void valueOf() {
47 + IntentId id = new IntentId(12345);
48 + assertEquals("incorrect valueOf", id, IntentId.valueOf("12345"));
49 + }
50 +
51 + @Test
52 + public void valueOfHex() {
53 + IntentId id = new IntentId(0xdeadbeefL);
54 + assertEquals("incorrect valueOf", id, IntentId.valueOf(id.toString()));
55 + }
56 +
57 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.After;
4 +import org.junit.Before;
5 +import org.junit.Test;
6 +
7 +import java.util.ArrayList;
8 +import java.util.Arrays;
9 +import java.util.Collections;
10 +import java.util.Iterator;
11 +import java.util.List;
12 +
13 +import static org.onlab.onos.net.intent.IntentState.*;
14 +import static org.junit.Assert.*;
15 +
16 +// TODO: consider make it categorized as integration test when it become
17 +// slow test or fragile test
18 +/**
19 + * Suite of tests for the intent service contract.
20 + */
21 +public class IntentServiceTest {
22 +
23 + public static final IntentId IID = new IntentId(123);
24 + public static final IntentId INSTALLABLE_IID = new IntentId(234);
25 +
26 + protected static final int GRACE_MS = 500; // millis
27 +
28 + protected TestableIntentService service;
29 + protected TestListener listener = new TestListener();
30 +
31 + @Before
32 + public void setUp() {
33 + service = createIntentService();
34 + service.addListener(listener);
35 + }
36 +
37 + @After
38 + public void tearDown() {
39 + service.removeListener(listener);
40 + }
41 +
42 + /**
43 + * Creates a service instance appropriately instrumented for testing.
44 + *
45 + * @return testable intent service
46 + */
47 + protected TestableIntentService createIntentService() {
48 + return new FakeIntentManager();
49 + }
50 +
51 + @Test
52 + public void basics() {
53 + // Make sure there are no intents
54 + assertEquals("incorrect intent count", 0, service.getIntents().size());
55 +
56 + // Register a compiler and an installer both setup for success.
57 + service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
58 + service.registerInstaller(TestInstallableIntent.class, new TestInstaller(false));
59 +
60 + final Intent intent = new TestIntent(IID);
61 + service.submit(intent);
62 +
63 + // Allow a small window of time until the intent is in the expected state
64 + TestTools.assertAfter(GRACE_MS, new Runnable() {
65 + @Override
66 + public void run() {
67 + assertEquals("incorrect intent state", INSTALLED,
68 + service.getIntentState(intent.getId()));
69 + }
70 + });
71 +
72 + // Make sure that all expected events have been emitted
73 + validateEvents(intent, SUBMITTED, COMPILED, INSTALLED);
74 +
75 + // Make sure there is just one intent (and is ours)
76 + assertEquals("incorrect intent count", 1, service.getIntents().size());
77 + assertEquals("incorrect intent", intent, service.getIntent(intent.getId()));
78 +
79 + // Reset the listener events
80 + listener.events.clear();
81 +
82 + // Now withdraw the intent
83 + service.withdraw(intent);
84 +
85 + // Allow a small window of time until the event is in the expected state
86 + TestTools.assertAfter(GRACE_MS, new Runnable() {
87 + @Override
88 + public void run() {
89 + assertEquals("incorrect intent state", WITHDRAWN,
90 + service.getIntentState(intent.getId()));
91 + }
92 + });
93 +
94 + // Make sure that all expected events have been emitted
95 + validateEvents(intent, WITHDRAWING, WITHDRAWN);
96 +
97 + // TODO: discuss what is the fate of intents after they have been withdrawn
98 + // Make sure that the intent is no longer in the system
99 +// assertEquals("incorrect intent count", 0, service.getIntents().size());
100 +// assertNull("intent should not be found", service.getIntent(intent.getId()));
101 +// assertNull("intent state should not be found", service.getIntentState(intent.getId()));
102 + }
103 +
104 + @Test
105 + public void failedCompilation() {
106 + // Register a compiler programmed for success
107 + service.registerCompiler(TestIntent.class, new TestCompiler(true));
108 +
109 + // Submit an intent
110 + final Intent intent = new TestIntent(IID);
111 + service.submit(intent);
112 +
113 + // Allow a small window of time until the intent is in the expected state
114 + TestTools.assertAfter(GRACE_MS, new Runnable() {
115 + @Override
116 + public void run() {
117 + assertEquals("incorrect intent state", FAILED,
118 + service.getIntentState(intent.getId()));
119 + }
120 + });
121 +
122 + // Make sure that all expected events have been emitted
123 + validateEvents(intent, SUBMITTED, FAILED);
124 + }
125 +
126 + @Test
127 + public void failedInstallation() {
128 + // Register a compiler programmed for success and installer for failure
129 + service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
130 + service.registerInstaller(TestInstallableIntent.class, new TestInstaller(true));
131 +
132 + // Submit an intent
133 + final Intent intent = new TestIntent(IID);
134 + service.submit(intent);
135 +
136 + // Allow a small window of time until the intent is in the expected state
137 + TestTools.assertAfter(GRACE_MS, new Runnable() {
138 + @Override
139 + public void run() {
140 + assertEquals("incorrect intent state", FAILED,
141 + service.getIntentState(intent.getId()));
142 + }
143 + });
144 +
145 + // Make sure that all expected events have been emitted
146 + validateEvents(intent, SUBMITTED, COMPILED, FAILED);
147 + }
148 +
149 + /**
150 + * Validates that the test event listener has received the following events
151 + * for the specified intent. Events received for other intents will not be
152 + * considered.
153 + *
154 + * @param intent intent subject
155 + * @param states list of states for which events are expected
156 + */
157 + protected void validateEvents(Intent intent, IntentState... states) {
158 + Iterator<IntentEvent> events = listener.events.iterator();
159 + for (IntentState state : states) {
160 + IntentEvent event = events.hasNext() ? events.next() : null;
161 + if (event == null) {
162 + fail("expected event not found: " + state);
163 + } else if (intent.equals(event.getIntent())) {
164 + assertEquals("incorrect state", state, event.getState());
165 + }
166 + }
167 +
168 + // Remainder of events should not apply to this intent; make sure.
169 + while (events.hasNext()) {
170 + assertFalse("unexpected event for intent",
171 + intent.equals(events.next().getIntent()));
172 + }
173 + }
174 +
175 + @Test
176 + public void compilerBasics() {
177 + // Make sure there are no compilers
178 + assertEquals("incorrect compiler count", 0, service.getCompilers().size());
179 +
180 + // Add a compiler and make sure that it appears in the map
181 + IntentCompiler<TestIntent> compiler = new TestCompiler(false);
182 + service.registerCompiler(TestIntent.class, compiler);
183 + assertEquals("incorrect compiler", compiler,
184 + service.getCompilers().get(TestIntent.class));
185 +
186 + // Remove the same and make sure that it no longer appears in the map
187 + service.unregisterCompiler(TestIntent.class);
188 + assertNull("compiler should not be registered",
189 + service.getCompilers().get(TestIntent.class));
190 + }
191 +
192 + @Test
193 + public void installerBasics() {
194 + // Make sure there are no installers
195 + assertEquals("incorrect installer count", 0, service.getInstallers().size());
196 +
197 + // Add an installer and make sure that it appears in the map
198 + IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
199 + service.registerInstaller(TestInstallableIntent.class, installer);
200 + assertEquals("incorrect installer", installer,
201 + service.getInstallers().get(TestInstallableIntent.class));
202 +
203 + // Remove the same and make sure that it no longer appears in the map
204 + service.unregisterInstaller(TestInstallableIntent.class);
205 + assertNull("installer should not be registered",
206 + service.getInstallers().get(TestInstallableIntent.class));
207 + }
208 +
209 + @Test
210 + public void implicitRegistration() {
211 + // Add a compiler and make sure that it appears in the map
212 + IntentCompiler<TestIntent> compiler = new TestCompiler(new TestSubclassInstallableIntent(INSTALLABLE_IID));
213 + service.registerCompiler(TestIntent.class, compiler);
214 + assertEquals("incorrect compiler", compiler,
215 + service.getCompilers().get(TestIntent.class));
216 +
217 + // Add a installer and make sure that it appears in the map
218 + IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
219 + service.registerInstaller(TestInstallableIntent.class, installer);
220 + assertEquals("incorrect installer", installer,
221 + service.getInstallers().get(TestInstallableIntent.class));
222 +
223 +
224 + // Submit an intent which is a subclass of the one we registered
225 + final Intent intent = new TestSubclassIntent(IID);
226 + service.submit(intent);
227 +
228 + // Allow some time for the intent to be compiled and installed
229 + TestTools.assertAfter(GRACE_MS, new Runnable() {
230 + @Override
231 + public void run() {
232 + assertEquals("incorrect intent state", INSTALLED,
233 + service.getIntentState(intent.getId()));
234 + }
235 + });
236 +
237 + // Make sure that now we have an implicit registration of the compiler
238 + // under the intent subclass
239 + assertEquals("incorrect compiler", compiler,
240 + service.getCompilers().get(TestSubclassIntent.class));
241 +
242 + // Make sure that now we have an implicit registration of the installer
243 + // under the intent subclass
244 + assertEquals("incorrect installer", installer,
245 + service.getInstallers().get(TestSubclassInstallableIntent.class));
246 +
247 + // TODO: discuss whether or if implicit registration should require implicit unregistration
248 + // perhaps unregister by compiler or installer itself, rather than by class would be better
249 + }
250 +
251 +
252 + // Fixture to track emitted intent events
253 + protected class TestListener implements IntentEventListener {
254 + final List<IntentEvent> events = new ArrayList<>();
255 +
256 + @Override
257 + public void event(IntentEvent event) {
258 + events.add(event);
259 + }
260 + }
261 +
262 + // Controllable compiler
263 + private class TestCompiler implements IntentCompiler<TestIntent> {
264 + private final boolean fail;
265 + private final List<Intent> result;
266 +
267 + TestCompiler(boolean fail) {
268 + this.fail = fail;
269 + this.result = Collections.emptyList();
270 + }
271 +
272 + TestCompiler(Intent... result) {
273 + this.fail = false;
274 + this.result = Arrays.asList(result);
275 + }
276 +
277 + @Override
278 + public List<Intent> compile(TestIntent intent) {
279 + if (fail) {
280 + throw new IntentException("compile failed by design");
281 + }
282 + List<Intent> compiled = new ArrayList<>(result);
283 + return compiled;
284 + }
285 + }
286 +
287 + // Controllable installer
288 + private class TestInstaller implements IntentInstaller<TestInstallableIntent> {
289 + private final boolean fail;
290 +
291 + TestInstaller(boolean fail) {
292 + this.fail = fail;
293 + }
294 +
295 + @Override
296 + public void install(TestInstallableIntent intent) {
297 + if (fail) {
298 + throw new IntentException("install failed by design");
299 + }
300 + }
301 +
302 + @Override
303 + public void uninstall(TestInstallableIntent intent) {
304 + if (fail) {
305 + throw new IntentException("remove failed by design");
306 + }
307 + }
308 + }
309 +
310 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import static org.junit.Assert.assertEquals;
4 +import static org.junit.Assert.assertFalse;
5 +import static org.junit.Assert.assertTrue;
6 +
7 +import java.util.Arrays;
8 +import java.util.HashSet;
9 +import java.util.Set;
10 +
11 +import org.junit.Test;
12 +
13 +/**
14 + * Base facilities to test various intent tests.
15 + */
16 +public abstract class IntentTest {
17 + /**
18 + * Produces a set of items from the supplied items.
19 + *
20 + * @param items items to be placed in set
21 + * @param <T> item type
22 + * @return set of items
23 + */
24 + protected static <T> Set<T> itemSet(T[] items) {
25 + return new HashSet<>(Arrays.asList(items));
26 + }
27 +
28 + @Test
29 + public void equalsAndHashCode() {
30 + Intent one = createOne();
31 + Intent like = createOne();
32 + Intent another = createAnother();
33 +
34 + assertTrue("should be equal", one.equals(like));
35 + assertEquals("incorrect hashCode", one.hashCode(), like.hashCode());
36 +
37 + assertFalse("should not be equal", one.equals(another));
38 +
39 + assertFalse("should not be equal", one.equals(null));
40 + assertFalse("should not be equal", one.equals("foo"));
41 + }
42 +
43 + @Test
44 + public void testToString() {
45 + Intent one = createOne();
46 + Intent like = createOne();
47 + assertEquals("incorrect toString", one.toString(), like.toString());
48 + }
49 +
50 + /**
51 + * Creates a new intent, but always a like intent, i.e. all instances will
52 + * be equal, but should not be the same.
53 + *
54 + * @return intent
55 + */
56 + protected abstract Intent createOne();
57 +
58 + /**
59 + * Creates another intent, not equals to the one created by
60 + * {@link #createOne()} and with a different hash code.
61 + *
62 + * @return another intent
63 + */
64 + protected abstract Intent createAnother();
65 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.junit.Assert.assertEquals;
6 +
7 +/**
8 + * Suite of tests of the multi-to-single point intent descriptor.
9 + */
10 +public class MultiPointToSinglePointIntentTest extends ConnectivityIntentTest {
11 +
12 + @Test
13 + public void basics() {
14 + MultiPointToSinglePointIntent intent = createOne();
15 + assertEquals("incorrect id", IID, intent.getId());
16 + assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
17 + assertEquals("incorrect ingress", PS1, intent.getIngressPorts());
18 + assertEquals("incorrect egress", P2, intent.getEgressPort());
19 + }
20 +
21 + @Override
22 + protected MultiPointToSinglePointIntent createOne() {
23 + return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS1, P2);
24 + }
25 +
26 + @Override
27 + protected MultiPointToSinglePointIntent createAnother() {
28 + return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS2, P1);
29 + }
30 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import static org.junit.Assert.assertEquals;
4 +
5 +import org.junit.Test;
6 +import org.onlab.onos.net.NetTestTools;
7 +import org.onlab.onos.net.Path;
8 +
9 +public class PathIntentTest extends ConnectivityIntentTest {
10 + // 111:11 --> 222:22
11 + private static final Path PATH1 = NetTestTools.createPath("111", "222");
12 +
13 + // 111:11 --> 333:33
14 + private static final Path PATH2 = NetTestTools.createPath("222", "333");
15 +
16 + @Test
17 + public void basics() {
18 + PathIntent intent = createOne();
19 + assertEquals("incorrect id", IID, intent.getId());
20 + assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
21 + assertEquals("incorrect action", NOP, intent.getTrafficTreatment());
22 + assertEquals("incorrect ingress", P1, intent.getIngressPort());
23 + assertEquals("incorrect egress", P2, intent.getEgressPort());
24 + assertEquals("incorrect path", PATH1, intent.getPath());
25 + }
26 +
27 + @Override
28 + protected PathIntent createOne() {
29 + return new PathIntent(IID, MATCH, NOP, P1, P2, PATH1);
30 + }
31 +
32 + @Override
33 + protected PathIntent createAnother() {
34 + return new PathIntent(IID, MATCH, NOP, P1, P3, PATH2);
35 + }
36 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.junit.Assert.assertEquals;
6 +
7 +/**
8 + * Suite of tests of the point-to-point intent descriptor.
9 + */
10 +public class PointToPointIntentTest extends ConnectivityIntentTest {
11 +
12 + @Test
13 + public void basics() {
14 + PointToPointIntent intent = createOne();
15 + assertEquals("incorrect id", IID, intent.getId());
16 + assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
17 + assertEquals("incorrect ingress", P1, intent.getIngressPort());
18 + assertEquals("incorrect egress", P2, intent.getEgressPort());
19 + }
20 +
21 + @Override
22 + protected PointToPointIntent createOne() {
23 + return new PointToPointIntent(IID, MATCH, NOP, P1, P2);
24 + }
25 +
26 + @Override
27 + protected PointToPointIntent createAnother() {
28 + return new PointToPointIntent(IID, MATCH, NOP, P2, P1);
29 + }
30 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.junit.Assert.assertEquals;
6 +
7 +/**
8 + * Suite of tests of the single-to-multi point intent descriptor.
9 + */
10 +public class SinglePointToMultiPointIntentTest extends ConnectivityIntentTest {
11 +
12 + @Test
13 + public void basics() {
14 + SinglePointToMultiPointIntent intent = createOne();
15 + assertEquals("incorrect id", IID, intent.getId());
16 + assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
17 + assertEquals("incorrect ingress", P1, intent.getIngressPort());
18 + assertEquals("incorrect egress", PS2, intent.getEgressPorts());
19 + }
20 +
21 + @Override
22 + protected SinglePointToMultiPointIntent createOne() {
23 + return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P1, PS2);
24 + }
25 +
26 + @Override
27 + protected SinglePointToMultiPointIntent createAnother() {
28 + return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P2, PS1);
29 + }
30 +}
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +/**
5 + * An installable intent used in the unit test.
6 + *
7 + * FIXME: we don't want to expose this class publicly, but the current Kryo
8 + * serialization mechanism does not allow this class to be private and placed
9 + * on testing directory.
10 + */
11 +public class TestInstallableIntent extends AbstractIntent implements InstallableIntent {
12 + /**
13 + * Constructs an instance with the specified intent ID.
14 + *
15 + * @param id intent ID
16 + */
17 + public TestInstallableIntent(IntentId id) {
18 + super(id);
19 + }
20 +
21 + /**
22 + * Constructor for serializer.
23 + */
24 + protected TestInstallableIntent() {
25 + super();
26 + }
27 +
28 +}
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +/**
5 + * An intent used in the unit test.
6 + *
7 + * FIXME: we don't want to expose this class publicly, but the current Kryo
8 + * serialization mechanism does not allow this class to be private and placed
9 + * on testing directory.
10 + */
11 +public class TestIntent extends AbstractIntent {
12 + /**
13 + * Constructs an instance with the specified intent ID.
14 + *
15 + * @param id intent ID
16 + */
17 + public TestIntent(IntentId id) {
18 + super(id);
19 + }
20 +
21 + /**
22 + * Constructor for serializer.
23 + */
24 + protected TestIntent() {
25 + super();
26 + }
27 +}
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +/**
5 + * An intent used in the unit test.
6 + *
7 + * FIXME: we don't want to expose this class publicly, but the current Kryo
8 + * serialization mechanism does not allow this class to be private and placed
9 + * on testing directory.
10 + */
11 +public class TestSubclassInstallableIntent extends TestInstallableIntent implements InstallableIntent {
12 + /**
13 + * Constructs an instance with the specified intent ID.
14 + *
15 + * @param id intent ID
16 + */
17 + public TestSubclassInstallableIntent(IntentId id) {
18 + super(id);
19 + }
20 +
21 + /**
22 + * Constructor for serializer.
23 + */
24 + protected TestSubclassInstallableIntent() {
25 + super();
26 + }
27 +}
1 +package org.onlab.onos.net.intent;
2 +//TODO is this the right package?
3 +
4 +/**
5 + * An intent used in the unit test.
6 + *
7 + * FIXME: we don't want to expose this class publicly, but the current Kryo
8 + * serialization mechanism does not allow this class to be private and placed
9 + * on testing directory.
10 + */
11 +public class TestSubclassIntent extends TestIntent {
12 + /**
13 + * Constructs an instance with the specified intent ID.
14 + *
15 + * @param id intent ID
16 + */
17 + public TestSubclassIntent(IntentId id) {
18 + super(id);
19 + }
20 +
21 + /**
22 + * Constructor for serializer.
23 + */
24 + protected TestSubclassIntent() {
25 + super();
26 + }
27 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import static org.junit.Assert.fail;
4 +
5 +/**
6 + * Set of test tools.
7 + */
8 +public final class TestTools {
9 +
10 + // Disallow construction
11 + private TestTools() {
12 + }
13 +
14 + /**
15 + * Utility method to pause the current thread for the specified number of
16 + * milliseconds.
17 + *
18 + * @param ms number of milliseconds to pause
19 + */
20 + public static void delay(int ms) {
21 + try {
22 + Thread.sleep(ms);
23 + } catch (InterruptedException e) {
24 + fail("unexpected interrupt");
25 + }
26 + }
27 +
28 + /**
29 + * Periodically runs the given runnable, which should contain a series of
30 + * test assertions until all the assertions succeed, in which case it will
31 + * return, or until the the time expires, in which case it will throw the
32 + * first failed assertion error.
33 + *
34 + * @param start start time, in millis since start of epoch from which the
35 + * duration will be measured
36 + * @param delay initial delay (in milliseconds) before the first assertion
37 + * attempt
38 + * @param step delay (in milliseconds) between successive assertion
39 + * attempts
40 + * @param duration number of milliseconds beyond the given start time,
41 + * after which the failed assertions will be propagated and allowed
42 + * to fail the test
43 + * @param assertions runnable housing the test assertions
44 + */
45 + public static void assertAfter(long start, int delay, int step,
46 + int duration, Runnable assertions) {
47 + delay(delay);
48 + while (true) {
49 + try {
50 + assertions.run();
51 + break;
52 + } catch (AssertionError e) {
53 + if (System.currentTimeMillis() - start > duration) {
54 + throw e;
55 + }
56 + }
57 + delay(step);
58 + }
59 + }
60 +
61 + /**
62 + * Periodically runs the given runnable, which should contain a series of
63 + * test assertions until all the assertions succeed, in which case it will
64 + * return, or until the the time expires, in which case it will throw the
65 + * first failed assertion error.
66 + * <p>
67 + * The start of the period is the current time.
68 + *
69 + * @param delay initial delay (in milliseconds) before the first assertion
70 + * attempt
71 + * @param step delay (in milliseconds) between successive assertion
72 + * attempts
73 + * @param duration number of milliseconds beyond the current time time,
74 + * after which the failed assertions will be propagated and allowed
75 + * to fail the test
76 + * @param assertions runnable housing the test assertions
77 + */
78 + public static void assertAfter(int delay, int step, int duration,
79 + Runnable assertions) {
80 + assertAfter(System.currentTimeMillis(), delay, step, duration,
81 + assertions);
82 + }
83 +
84 + /**
85 + * Periodically runs the given runnable, which should contain a series of
86 + * test assertions until all the assertions succeed, in which case it will
87 + * return, or until the the time expires, in which case it will throw the
88 + * first failed assertion error.
89 + * <p>
90 + * The start of the period is the current time and the first assertion
91 + * attempt is delayed by the value of {@code step} parameter.
92 + *
93 + * @param step delay (in milliseconds) between successive assertion
94 + * attempts
95 + * @param duration number of milliseconds beyond the current time time,
96 + * after which the failed assertions will be propagated and allowed
97 + * to fail the test
98 + * @param assertions runnable housing the test assertions
99 + */
100 + public static void assertAfter(int step, int duration,
101 + Runnable assertions) {
102 + assertAfter(step, step, duration, assertions);
103 + }
104 +
105 + /**
106 + * Periodically runs the given runnable, which should contain a series of
107 + * test assertions until all the assertions succeed, in which case it will
108 + * return, or until the the time expires, in which case it will throw the
109 + * first failed assertion error.
110 + * <p>
111 + * The start of the period is the current time and each successive
112 + * assertion attempt is delayed by at least 10 milliseconds unless the
113 + * {@code duration} is less than that, in which case the one and only
114 + * assertion is made after that delay.
115 + *
116 + * @param duration number of milliseconds beyond the current time,
117 + * after which the failed assertions will be propagated and allowed
118 + * to fail the test
119 + * @param assertions runnable housing the test assertions
120 + */
121 + public static void assertAfter(int duration, Runnable assertions) {
122 + int step = Math.min(duration, Math.max(10, duration / 10));
123 + assertAfter(step, duration, assertions);
124 + }
125 +
126 +}
1 +package org.onlab.onos.net.intent;
2 +
3 +import java.util.List;
4 +
5 +/**
6 + * Abstraction of an extensible intent service enabled for unit tests.
7 + */
8 +public interface TestableIntentService extends IntentService, IntentExtensionService {
9 +
10 + List<IntentException> getExceptions();
11 +
12 +}
...@@ -48,6 +48,19 @@ ...@@ -48,6 +48,19 @@
48 </dependency> 48 </dependency>
49 49
50 <dependency> 50 <dependency>
51 + <groupId>org.hamcrest</groupId>
52 + <artifactId>hamcrest-core</artifactId>
53 + <version>1.3</version>
54 + <scope>test</scope>
55 + </dependency>
56 + <dependency>
57 + <groupId>org.hamcrest</groupId>
58 + <artifactId>hamcrest-library</artifactId>
59 + <version>1.3</version>
60 + <scope>test</scope>
61 + </dependency>
62 +
63 + <dependency>
51 <groupId>org.slf4j</groupId> 64 <groupId>org.slf4j</groupId>
52 <artifactId>slf4j-api</artifactId> 65 <artifactId>slf4j-api</artifactId>
53 <version>1.7.6</version> 66 <version>1.7.6</version>
...@@ -244,6 +257,14 @@ ...@@ -244,6 +257,14 @@
244 <artifactId>junit</artifactId> 257 <artifactId>junit</artifactId>
245 </dependency> 258 </dependency>
246 <dependency> 259 <dependency>
260 + <groupId>org.hamcrest</groupId>
261 + <artifactId>hamcrest-core</artifactId>
262 + </dependency>
263 + <dependency>
264 + <groupId>org.hamcrest</groupId>
265 + <artifactId>hamcrest-library</artifactId>
266 + </dependency>
267 + <dependency>
247 <groupId>org.slf4j</groupId> 268 <groupId>org.slf4j</groupId>
248 <artifactId>slf4j-jdk14</artifactId> 269 <artifactId>slf4j-jdk14</artifactId>
249 </dependency> 270 </dependency>
...@@ -320,6 +341,35 @@ ...@@ -320,6 +341,35 @@
320 </plugin> 341 </plugin>
321 342
322 <!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only --> 343 <!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only -->
344 + <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
345 + <plugin>
346 + <groupId>org.eclipse.m2e</groupId>
347 + <artifactId>lifecycle-mapping</artifactId>
348 + <version>1.0.0</version>
349 + <configuration>
350 + <lifecycleMappingMetadata>
351 + <pluginExecutions>
352 + <pluginExecution>
353 + <pluginExecutionFilter>
354 + <groupId>org.jacoco</groupId>
355 + <artifactId>
356 + jacoco-maven-plugin
357 + </artifactId>
358 + <versionRange>
359 + [0.7.1.201405082137,)
360 + </versionRange>
361 + <goals>
362 + <goal>prepare-agent</goal>
363 + </goals>
364 + </pluginExecutionFilter>
365 + <action>
366 + <ignore></ignore>
367 + </action>
368 + </pluginExecution>
369 + </pluginExecutions>
370 + </lifecycleMappingMetadata>
371 + </configuration>
372 + </plugin>
323 </plugins> 373 </plugins>
324 </pluginManagement> 374 </pluginManagement>
325 375
......