blob: bb89ced1a19d2ec0eb0ce71661b93d30664422df [file] [log] [blame]
Charles Chana7903c82018-03-15 20:14:16 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.net.flowobjective.impl;
18
19import com.google.common.collect.Lists;
20import com.google.common.collect.Sets;
21import org.junit.Before;
22import org.junit.Ignore;
23import org.junit.Test;
24import org.onlab.packet.Ethernet;
25import org.onlab.packet.IpPrefix;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.MplsLabel;
28import org.onlab.packet.VlanId;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.DefaultApplicationId;
31import org.onosproject.net.DeviceId;
32import org.onosproject.net.PortNumber;
33import org.onosproject.net.behaviour.NextGroup;
34import org.onosproject.net.behaviour.Pipeliner;
35import org.onosproject.net.behaviour.PipelinerAdapter;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.Criteria;
41import org.onosproject.net.flowobjective.DefaultFilteringObjective;
42import org.onosproject.net.flowobjective.DefaultForwardingObjective;
43import org.onosproject.net.flowobjective.DefaultNextObjective;
44import org.onosproject.net.flowobjective.FilteringObjective;
45import org.onosproject.net.flowobjective.FlowObjectiveStore;
46import org.onosproject.net.flowobjective.ForwardingObjective;
47import org.onosproject.net.flowobjective.NextObjective;
48import org.onosproject.net.flowobjective.Objective;
49import org.onosproject.net.flowobjective.ObjectiveError;
50import org.onosproject.net.flowobjective.ObjectiveEvent;
51
52import static java.util.concurrent.Executors.newFixedThreadPool;
53import static org.easymock.EasyMock.createMock;
54import static org.easymock.EasyMock.expect;
55import static org.easymock.EasyMock.replay;
56import static org.easymock.EasyMock.verify;
57import static org.junit.Assert.assertTrue;
58import static org.junit.Assert.assertEquals;
59import static org.onlab.junit.TestTools.assertAfter;
60import static org.onlab.util.Tools.groupedThreads;
61
62import java.util.Collection;
63import java.util.List;
64import java.util.Random;
65
66public class InOrderFlowObjectiveManagerTest {
67 private InOrderFlowObjectiveManager mgr;
68
69 private static final int PRIORITY = 1000;
70 private static final ApplicationId APP_ID = new DefaultApplicationId(1, "org.onosproject.test");
71 private static final DeviceId DEV1 = DeviceId.deviceId("of:1");
72 private static final PortNumber P1 = PortNumber.portNumber(1);
73 private static final PortNumber P2 = PortNumber.portNumber(2);
74 private static final PortNumber P3 = PortNumber.portNumber(3);
75 private static final PortNumber P4 = PortNumber.portNumber(4);
76 private static final MacAddress M1 = MacAddress.valueOf("00:00:00:00:00:01");
77 private static final MacAddress M2 = MacAddress.valueOf("00:00:00:00:00:02");
78 private static final MacAddress M3 = MacAddress.valueOf("00:00:00:00:00:03");
79 private static final VlanId V1 = VlanId.vlanId((short) 10);
80 private static final VlanId V2 = VlanId.vlanId((short) 20);
81 private static final VlanId V3 = VlanId.vlanId((short) 30);
82 private static final TrafficSelector S1 = DefaultTrafficSelector.builder()
83 .matchEthType(Ethernet.TYPE_IPV4).matchIPDst(IpPrefix.valueOf("10.0.0.1/32")).build();
84 private static final TrafficSelector S2 = DefaultTrafficSelector.builder()
85 .matchEthType(Ethernet.TYPE_IPV4).matchIPDst(IpPrefix.valueOf("10.0.0.2/32")).build();
86 private static final int NID1 = 1;
87 private static final int NID2 = 2;
88 private static final NextGroup NGRP1 = () -> new byte[] {0x00, 0x01};
89 private static final NextGroup NGRP2 = () -> new byte[] {0x02, 0x03};
90
91 // Delay flow objectives OFFSET + rand(0, BOUND) millis
92 private static final int OFFSET = 10; // ms
93 private static final int BOUND = 40; // ms
94
95 private static final FilteringObjective FILT1 = buildFilteringObjective(P2, V3, M3, 1).add();
96 private static final FilteringObjective FILT2 = buildFilteringObjective(P2, V2, M2, 2).add();
97 private static final FilteringObjective FILT3 = buildFilteringObjective(P2, V3, M3, 3).remove();
98 private static final FilteringObjective FILT4 = buildFilteringObjective(P1, V1, M1, 4).add();
99 private static final FilteringObjective FILT5 = buildFilteringObjective(P2, V2, M2, 5).remove();
100 private static final FilteringObjective FILT6 = buildFilteringObjective(P1, V1, M1, 6).remove();
101 private static final FilteringObjective FILT7 = buildFilteringObjective(P2, V3, M3, 7).add();
102 private List<FilteringObjective> expectFiltObjs = Lists.newCopyOnWriteArrayList(
103 Lists.newArrayList(FILT1, FILT2, FILT3, FILT4, FILT5, FILT6, FILT7));
104
105 private static final NextObjective NEXT1 = buildNextObjective(NID1, V1, Sets.newHashSet(P1)).add();
106 private static final NextObjective NEXT2 = buildNextObjective(NID2, V2, Sets.newHashSet(P3)).add();
107 private static final NextObjective NEXT3 = buildNextObjective(NID1, V1, Sets.newHashSet(P1, P2)).addToExisting();
108 private static final NextObjective NEXT4 = buildNextObjective(NID2, V2, Sets.newHashSet(P3, P4)).addToExisting();
109 private static final NextObjective NEXT5 = buildNextObjective(NID1, V1, Sets.newHashSet(P1)).removeFromExisting();
110 private static final NextObjective NEXT6 = buildNextObjective(NID2, V2, Sets.newHashSet(P3)).removeFromExisting();
111 private static final NextObjective NEXT7 = buildNextObjective(NID1, V1, Sets.newHashSet()).remove();
112 private static final NextObjective NEXT8 = buildNextObjective(NID2, V2, Sets.newHashSet()).remove();
113 private List<NextObjective> expectNextObjs = Lists.newCopyOnWriteArrayList(
114 Lists.newArrayList(NEXT1, NEXT2, NEXT3, NEXT4, NEXT5, NEXT6, NEXT7, NEXT8));
115 private List<NextObjective> expectNextObjsPending = Lists.newCopyOnWriteArrayList(
116 Lists.newArrayList(NEXT5, NEXT6, NEXT1, NEXT2, NEXT3, NEXT4, NEXT7, NEXT8));
117
118 private static final ForwardingObjective FWD1 = buildFwdObjective(S1, NID1).add();
119 private static final ForwardingObjective FWD2 = buildFwdObjective(S2, NID2).add();
120 private static final ForwardingObjective FWD3 = buildFwdObjective(S1, NID2).add();
121 private static final ForwardingObjective FWD4 = buildFwdObjective(S2, NID1).add();
122 private static final ForwardingObjective FWD5 = buildFwdObjective(S1, NID2).remove();
123 private static final ForwardingObjective FWD6 = buildFwdObjective(S2, NID1).remove();
124 private List<ForwardingObjective> expectFwdObjs = Lists.newCopyOnWriteArrayList(
125 Lists.newArrayList(FWD1, FWD2, FWD3, FWD4, FWD5, FWD6));
126
127 private List<Objective> actualObjs = Lists.newCopyOnWriteArrayList();
128
129 private Pipeliner pipeliner = new PipelinerAdapter() {
130 @Override
131 public void filter(FilteringObjective filterObjective) {
132 recordObjective(filterObjective);
133 }
134
135 @Override
136 public void forward(ForwardingObjective forwardObjective) {
137 recordObjective(forwardObjective);
138 }
139
140 @Override
141 public void next(NextObjective nextObjective) {
142 recordObjective(nextObjective);
143
144 // Notify delegate when the next obj is completed
145 ObjectiveEvent.Type type;
146 if (nextObjective.op() == Objective.Operation.ADD ||
147 nextObjective.op() == Objective.Operation.ADD_TO_EXISTING) {
148 type = ObjectiveEvent.Type.ADD;
149 } else if (nextObjective.op() == Objective.Operation.REMOVE ||
150 nextObjective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
151 type = ObjectiveEvent.Type.REMOVE;
152 } else {
153 return;
154 }
155 mgr.delegate.notify(new ObjectiveEvent(type, nextObjective.id()));
156 }
157
158 /**
159 * Record the objectives.
160 * The random delay is introduced in order to mimic pipeline and flow operation behavior.
161 *
162 * @param obj Flow objective
163 */
164 private void recordObjective(Objective obj) {
165 try {
166 Thread.sleep(new Random().nextInt(BOUND) + OFFSET);
167 actualObjs.add(obj);
168 obj.context().ifPresent(c -> c.onSuccess(obj));
169 } catch (Exception e) {
170 obj.context().ifPresent(c -> c.onError(obj, ObjectiveError.UNKNOWN));
171 }
172 }
173 };
174
175 @Before
176 public void setUp() {
177 mgr = new InOrderFlowObjectiveManager();
178 mgr.pipeliners.put(DEV1, pipeliner);
179 mgr.executorService = newFixedThreadPool(4, groupedThreads("foo", "bar"));
180 mgr.flowObjectiveStore = createMock(FlowObjectiveStore.class);
181
182 actualObjs.clear();
183 }
184
185 @Test
186 public void filter() throws Exception {
187 expectFiltObjs.forEach(filtObj -> mgr.filter(DEV1, filtObj));
188
189 // Wait for the pipeline operation to complete
190 int expectedTime = (BOUND + OFFSET) * 7;
191 assertAfter(expectedTime, expectedTime * 5, () -> assertEquals(expectFiltObjs.size(), actualObjs.size()));
192
193 assertTrue(actualObjs.indexOf(FILT1) < actualObjs.indexOf(FILT2));
194 assertTrue(actualObjs.indexOf(FILT2) < actualObjs.indexOf(FILT3));
195 assertTrue(actualObjs.indexOf(FILT3) < actualObjs.indexOf(FILT5));
196 assertTrue(actualObjs.indexOf(FILT5) < actualObjs.indexOf(FILT7));
197 assertTrue(actualObjs.indexOf(FILT4) < actualObjs.indexOf(FILT6));
198 }
199
200 @Test
201 public void forward() throws Exception {
202 expect(mgr.flowObjectiveStore.getNextGroup(NID1)).andReturn(NGRP1).times(3);
203 expect(mgr.flowObjectiveStore.getNextGroup(NID2)).andReturn(NGRP2).times(3);
204 replay(mgr.flowObjectiveStore);
205
206 expectFwdObjs.forEach(fwdObj -> mgr.forward(DEV1, fwdObj));
207
208 // Wait for the pipeline operation to complete
209 int expectedTime = (BOUND + OFFSET) * 6;
210 assertAfter(expectedTime, expectedTime * 5, () -> assertEquals(expectFwdObjs.size(), actualObjs.size()));
211
212 assertTrue(actualObjs.indexOf(FWD1) < actualObjs.indexOf(FWD3));
213 assertTrue(actualObjs.indexOf(FWD3) < actualObjs.indexOf(FWD5));
214 assertTrue(actualObjs.indexOf(FWD2) < actualObjs.indexOf(FWD4));
215 assertTrue(actualObjs.indexOf(FWD4) < actualObjs.indexOf(FWD6));
216
217 verify(mgr.flowObjectiveStore);
218 }
219
220 @Test
221 public void forwardPending() throws Exception {
222 // Note: current logic will double check if the next obj need to be queued
223 // it does not check when resubmitting pending next back to the queue
224 expect(mgr.flowObjectiveStore.getNextGroup(NID1)).andReturn(null).times(2);
225 expect(mgr.flowObjectiveStore.getNextGroup(NID2)).andReturn(null).times(2);
226 expect(mgr.flowObjectiveStore.getNextGroup(NID1)).andReturn(NGRP1).times(3);
227 expect(mgr.flowObjectiveStore.getNextGroup(NID2)).andReturn(NGRP2).times(3);
228 replay(mgr.flowObjectiveStore);
229
230 expectFwdObjs.forEach(fwdObj -> mgr.forward(DEV1, fwdObj));
231
232 // Trigger the next objectives
233 mgr.next(DEV1, NEXT1);
234 mgr.next(DEV1, NEXT2);
235
236 // Wait for the pipeline operation to complete
237 int expectedTime = (BOUND + OFFSET) * 8;
238 assertAfter(expectedTime, expectedTime * 5, () -> assertEquals(expectFwdObjs.size() + 2, actualObjs.size()));
239
240 assertTrue(actualObjs.indexOf(NEXT1) < actualObjs.indexOf(FWD1));
241 assertTrue(actualObjs.indexOf(FWD1) < actualObjs.indexOf(FWD3));
242 assertTrue(actualObjs.indexOf(FWD3) < actualObjs.indexOf(FWD5));
243 assertTrue(actualObjs.indexOf(NEXT2) < actualObjs.indexOf(FWD2));
244 assertTrue(actualObjs.indexOf(FWD2) < actualObjs.indexOf(FWD4));
245 assertTrue(actualObjs.indexOf(FWD4) < actualObjs.indexOf(FWD6));
246
247 verify(mgr.flowObjectiveStore);
248 }
249
250 @Test
251 public void next() throws Exception {
252 // Note: ADD operation won't query this
253 expect(mgr.flowObjectiveStore.getNextGroup(NID1)).andReturn(NGRP1).times(3);
254 expect(mgr.flowObjectiveStore.getNextGroup(NID2)).andReturn(NGRP2).times(3);
255 replay(mgr.flowObjectiveStore);
256
257 expectNextObjs.forEach(nextObj -> mgr.next(DEV1, nextObj));
258
259 // Wait for the pipeline operation to complete
260 int expectedTime = (BOUND + OFFSET) * 8;
261 assertAfter(expectedTime, expectedTime * 5, () -> assertEquals(expectNextObjs.size(), actualObjs.size()));
262
263 assertTrue(actualObjs.indexOf(NEXT1) < actualObjs.indexOf(NEXT3));
264 assertTrue(actualObjs.indexOf(NEXT3) < actualObjs.indexOf(NEXT5));
265 assertTrue(actualObjs.indexOf(NEXT5) < actualObjs.indexOf(NEXT7));
266 assertTrue(actualObjs.indexOf(NEXT2) < actualObjs.indexOf(NEXT4));
267 assertTrue(actualObjs.indexOf(NEXT4) < actualObjs.indexOf(NEXT6));
268 assertTrue(actualObjs.indexOf(NEXT6) < actualObjs.indexOf(NEXT8));
269
270 verify(mgr.flowObjectiveStore);
271 }
272
273 // FIXME We currently do not handle the case when an app sends edit/remove of a next id before add.
274 // The edit/remove operation will be queued by pendingNext, and the add operation will be
275 // queued by the ordering queue forever due to the deadlock. This can be improved by making
276 // pendingForwards, pendingNexts and ordering queue caches.
277 @Test
278 @Ignore("Not supported")
279 public void nextPending() throws Exception {
280 // Note: current logic will double check if the next obj need to be queued
281 // it does not check when resubmitting pending next back to the queue
282 expect(mgr.flowObjectiveStore.getNextGroup(NID1)).andReturn(null).times(6);
283 expect(mgr.flowObjectiveStore.getNextGroup(NID2)).andReturn(null).times(6);
284 replay(mgr.flowObjectiveStore);
285
286 expectNextObjsPending.forEach(nextObj -> mgr.next(DEV1, nextObj));
287
288 // Wait for the pipeline operation to complete
289 int expectedTime = (BOUND + OFFSET) * 8;
290 assertAfter(expectedTime, expectedTime * 5, () -> assertEquals(expectNextObjs.size(), actualObjs.size()));
291
292 assertTrue(actualObjs.indexOf(NEXT1) < actualObjs.indexOf(NEXT5));
293 assertTrue(actualObjs.indexOf(NEXT5) < actualObjs.indexOf(NEXT3));
294 assertTrue(actualObjs.indexOf(NEXT3) < actualObjs.indexOf(NEXT7));
295 assertTrue(actualObjs.indexOf(NEXT2) < actualObjs.indexOf(NEXT6));
296 assertTrue(actualObjs.indexOf(NEXT6) < actualObjs.indexOf(NEXT4));
297 assertTrue(actualObjs.indexOf(NEXT4) < actualObjs.indexOf(NEXT8));
298
299 verify(mgr.flowObjectiveStore);
300 }
301
302 /**
303 * Creates filtering objective builder with a serial number encoded in MPLS label.
304 * The serial number is used to identify same objective that occurs multiple times.
305 *
306 * @param portnum Port number
307 * @param vlanId VLAN Id
308 * @param mac MAC address
309 * @param serial Serial number
310 * @return Filtering objective builder
311 */
312 private static FilteringObjective.Builder buildFilteringObjective(PortNumber portnum, VlanId vlanId,
313 MacAddress mac, int serial) {
314 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
315 fob.withKey(Criteria.matchInPort(portnum))
316 .addCondition(Criteria.matchEthDst(mac))
317 .addCondition(Criteria.matchVlanId(VlanId.NONE))
318 .addCondition(Criteria.matchMplsLabel(MplsLabel.mplsLabel(serial)))
319 .withPriority(PRIORITY);
320
321 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
322 tBuilder.pushVlan().setVlanId(vlanId);
323 fob.withMeta(tBuilder.build());
324
325 fob.permit().fromApp(APP_ID);
326 return fob;
327 }
328
329 /**
330 * Creates next objective builder.
331 *
332 * @param nextId next ID
333 * @param vlanId VLAN ID
334 * @param ports Set of ports that is in the given VLAN ID
335 *
336 * @return Next objective builder
337 */
338 private static NextObjective.Builder buildNextObjective(int nextId, VlanId vlanId, Collection<PortNumber> ports) {
339 TrafficSelector metadata =
340 DefaultTrafficSelector.builder().matchVlanId(vlanId).build();
341
342 NextObjective.Builder nextObjBuilder = DefaultNextObjective
343 .builder().withId(nextId)
344 .withType(NextObjective.Type.BROADCAST).fromApp(APP_ID)
345 .withMeta(metadata);
346
347 ports.forEach(port -> {
348 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
349 tBuilder.popVlan();
350 tBuilder.setOutput(port);
351 nextObjBuilder.addTreatment(tBuilder.build());
352 });
353
354 return nextObjBuilder;
355 }
356
357 /**
358 * Creates forwarding objective builder.
359 *
360 * @param selector Traffic selector
361 * @param nextId next ID
362 * @return Forwarding objective builder
363 */
364 private static ForwardingObjective.Builder buildFwdObjective(TrafficSelector selector, int nextId) {
365 return DefaultForwardingObjective.builder()
366 .makePermanent()
367 .withSelector(selector)
368 .nextStep(nextId)
369 .fromApp(APP_ID)
370 .withPriority(PRIORITY)
371 .withFlag(ForwardingObjective.Flag.SPECIFIC);
372 }
373}