Pier Luigi Ventre
Committed by Yuta HIGUCHI

Fix ONOS-4570

Changes:
- Adds RandomVLAN behavior as described in ONOS-4570;
- Adds a unit test for RandomVLAN;
- Fixes the VLAN rewriting;
- Updates the unit tests relative to VLAN encapsulation;

Change-Id: I52ab2f40a30f3be617606b2b0bb7a89d48414138
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
16 package org.onosproject.net.intent.impl.compiler; 16 package org.onosproject.net.intent.impl.compiler;
17 17
18 import com.google.common.collect.ImmutableList; 18 import com.google.common.collect.ImmutableList;
19 +import com.google.common.collect.Iterables;
19 import com.google.common.collect.Sets; 20 import com.google.common.collect.Sets;
21 +import org.apache.commons.lang.math.RandomUtils;
20 import org.onlab.packet.EthType; 22 import org.onlab.packet.EthType;
21 import org.onlab.packet.Ethernet; 23 import org.onlab.packet.Ethernet;
22 import org.onlab.packet.MplsLabel; 24 import org.onlab.packet.MplsLabel;
...@@ -62,6 +64,8 @@ import static org.onosproject.net.LinkKey.linkKey; ...@@ -62,6 +64,8 @@ import static org.onosproject.net.LinkKey.linkKey;
62 64
63 public class PathCompiler<T> { 65 public class PathCompiler<T> {
64 66
67 + public static final boolean RANDOM_SELECTION = true;
68 +
65 /** 69 /**
66 * Defines methods used to create objects representing flows. 70 * Defines methods used to create objects representing flows.
67 */ 71 */
...@@ -120,6 +124,44 @@ public class PathCompiler<T> { ...@@ -120,6 +124,44 @@ public class PathCompiler<T> {
120 return vlanIds; 124 return vlanIds;
121 } 125 }
122 126
127 + /**
128 + * Implements the first fit selection behavior.
129 + *
130 + * @param available the set of available VLAN ids.
131 + * @return the chosen VLAN id.
132 + */
133 + private VlanId firsFitSelection(Set<VlanId> available) {
134 + if (!available.isEmpty()) {
135 + return available.iterator().next();
136 + }
137 + return VlanId.vlanId(VlanId.NO_VID);
138 + }
139 +
140 + /**
141 + * Implements the random selection behavior.
142 + *
143 + * @param available the set of available VLAN ids.
144 + * @return the chosen VLAN id.
145 + */
146 + private VlanId randomSelection(Set<VlanId> available) {
147 + if (!available.isEmpty()) {
148 + int size = available.size();
149 + int index = RandomUtils.nextInt(size);
150 + return Iterables.get(available, index);
151 + }
152 + return VlanId.vlanId(VlanId.NO_VID);
153 + }
154 +
155 + /**
156 + * Select a VLAN id from the set of available VLAN ids.
157 + *
158 + * @param available the set of available VLAN ids.
159 + * @return the chosen VLAN id.
160 + */
161 + private VlanId selectVlanId(Set<VlanId> available) {
162 + return RANDOM_SELECTION ? randomSelection(available) : firsFitSelection(available);
163 + }
164 +
123 private Map<LinkKey, VlanId> findVlanIds(PathCompilerCreateFlow creator, Set<LinkKey> links) { 165 private Map<LinkKey, VlanId> findVlanIds(PathCompilerCreateFlow creator, Set<LinkKey> links) {
124 Map<LinkKey, VlanId> vlanIds = new HashMap<>(); 166 Map<LinkKey, VlanId> vlanIds = new HashMap<>();
125 for (LinkKey link : links) { 167 for (LinkKey link : links) {
...@@ -129,7 +171,11 @@ public class PathCompiler<T> { ...@@ -129,7 +171,11 @@ public class PathCompiler<T> {
129 if (common.isEmpty()) { 171 if (common.isEmpty()) {
130 continue; 172 continue;
131 } 173 }
132 - vlanIds.put(link, common.iterator().next()); 174 + VlanId selected = selectVlanId(common);
175 + if (selected.toShort() == VlanId.NO_VID) {
176 + continue;
177 + }
178 + vlanIds.put(link, selected);
133 } 179 }
134 return vlanIds; 180 return vlanIds;
135 } 181 }
...@@ -185,7 +231,6 @@ public class PathCompiler<T> { ...@@ -185,7 +231,6 @@ public class PathCompiler<T> {
185 if (egressVlanId == null) { 231 if (egressVlanId == null) {
186 throw new IntentCompilationException("No available VLAN ID for " + link); 232 throw new IntentCompilationException("No available VLAN ID for " + link);
187 } 233 }
188 - prevVlanId = egressVlanId;
189 234
190 TrafficSelector transitSelector = DefaultTrafficSelector.builder() 235 TrafficSelector transitSelector = DefaultTrafficSelector.builder()
191 .matchInPort(prev.port()) 236 .matchInPort(prev.port())
...@@ -200,6 +245,11 @@ public class PathCompiler<T> { ...@@ -200,6 +245,11 @@ public class PathCompiler<T> {
200 creator.createFlow(transitSelector, 245 creator.createFlow(transitSelector,
201 transitTreat.build(), prev, link.src(), 246 transitTreat.build(), prev, link.src(),
202 intent.priority(), true, flows, devices); 247 intent.priority(), true, flows, devices);
248 + /* For the next hop we have to remember
249 + * the previous egress VLAN id and the egress
250 + * node
251 + */
252 + prevVlanId = egressVlanId;
203 prev = link.dst(); 253 prev = link.dst();
204 } else { 254 } else {
205 // Egress traffic 255 // Egress traffic
......
...@@ -106,10 +106,25 @@ class MockResourceService implements ResourceService { ...@@ -106,10 +106,25 @@ class MockResourceService implements ResourceService {
106 .collect(Collectors.toList()); 106 .collect(Collectors.toList());
107 } 107 }
108 108
109 +
110 + /**
111 + * It adds a number of VLAN ids in order to test the random behavior.
112 + *
113 + * @param parent the parent resource
114 + * @return a set of VLAN ids
115 + */
116 + private Collection<Resource> addVlanIds(DiscreteResourceId parent) {
117 + Collection<Resource> resources = new HashSet<>();
118 + for (int i = VlanId.NO_VID + 1; i < VlanId.MAX_VLAN; i++) {
119 + resources.add(Resources.discrete(parent).resource().child(VlanId.vlanId((short) i)));
120 + }
121 + return resources;
122 + }
123 +
109 @Override 124 @Override
110 public Set<Resource> getAvailableResources(DiscreteResourceId parent) { 125 public Set<Resource> getAvailableResources(DiscreteResourceId parent) {
111 Collection<Resource> resources = new HashSet<>(); 126 Collection<Resource> resources = new HashSet<>();
112 - resources.add(Resources.discrete(parent).resource().child(VlanId.vlanId((short) 10))); 127 + resources.addAll(addVlanIds(parent));
113 resources.add(Resources.discrete(parent).resource().child(MplsLabel.mplsLabel(10))); 128 resources.add(Resources.discrete(parent).resource().child(MplsLabel.mplsLabel(10)));
114 resources.add(Resources.discrete(parent).resource().child(TributarySlot.of(1))); 129 resources.add(Resources.discrete(parent).resource().child(TributarySlot.of(1)));
115 resources.add(Resources.discrete(parent).resource().child(TributarySlot.of(2))); 130 resources.add(Resources.discrete(parent).resource().child(TributarySlot.of(2)));
......
...@@ -61,7 +61,10 @@ import static org.easymock.EasyMock.replay; ...@@ -61,7 +61,10 @@ import static org.easymock.EasyMock.replay;
61 import static org.hamcrest.MatcherAssert.assertThat; 61 import static org.hamcrest.MatcherAssert.assertThat;
62 import static org.hamcrest.Matchers.hasSize; 62 import static org.hamcrest.Matchers.hasSize;
63 import static org.hamcrest.Matchers.is; 63 import static org.hamcrest.Matchers.is;
64 +import static org.hamcrest.Matchers.lessThan;
64 import static org.hamcrest.number.OrderingComparison.greaterThan; 65 import static org.hamcrest.number.OrderingComparison.greaterThan;
66 +import static org.junit.Assert.assertNotEquals;
67 +import static org.junit.Assert.assertTrue;
65 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; 68 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
66 import static org.onosproject.net.Link.Type.DIRECT; 69 import static org.onosproject.net.Link.Type.DIRECT;
67 import static org.onosproject.net.NetTestTools.APP_ID; 70 import static org.onosproject.net.NetTestTools.APP_ID;
...@@ -257,7 +260,7 @@ public class PathIntentCompilerTest { ...@@ -257,7 +260,7 @@ public class PathIntentCompilerTest {
257 .get(); 260 .get();
258 verifyIdAndPriority(rule2, d2p0.deviceId()); 261 verifyIdAndPriority(rule2, d2p0.deviceId());
259 verifyVlanEncapSelector(rule2.selector(), d2p0, vlanToEncap); 262 verifyVlanEncapSelector(rule2.selector(), d2p0, vlanToEncap);
260 - verifyVlanEncapTreatment(rule2.treatment(), d2p1, false, false); 263 + vlanToEncap = verifyVlanEncapTreatment(rule2.treatment(), d2p1, false, false);
261 264
262 FlowRule rule3 = rules.stream() 265 FlowRule rule3 = rules.stream()
263 .filter(x -> x.deviceId().equals(d3p0.deviceId())) 266 .filter(x -> x.deviceId().equals(d3p0.deviceId()))
...@@ -300,7 +303,7 @@ public class PathIntentCompilerTest { ...@@ -300,7 +303,7 @@ public class PathIntentCompilerTest {
300 .get(); 303 .get();
301 verifyIdAndPriority(rule2, d2p0.deviceId()); 304 verifyIdAndPriority(rule2, d2p0.deviceId());
302 verifyVlanEncapSelector(rule2.selector(), d2p0, vlanToEncap); 305 verifyVlanEncapSelector(rule2.selector(), d2p0, vlanToEncap);
303 - verifyVlanEncapTreatment(rule2.treatment(), d2p1, false, false); 306 + vlanToEncap = verifyVlanEncapTreatment(rule2.treatment(), d2p1, false, false);
304 307
305 FlowRule rule3 = rules.stream() 308 FlowRule rule3 = rules.stream()
306 .filter(x -> x.deviceId().equals(d3p0.deviceId())) 309 .filter(x -> x.deviceId().equals(d3p0.deviceId()))
...@@ -323,6 +326,66 @@ public class PathIntentCompilerTest { ...@@ -323,6 +326,66 @@ public class PathIntentCompilerTest {
323 sut.deactivate(); 326 sut.deactivate();
324 } 327 }
325 328
329 + /**
330 + * Tests the random selection of VlanIds in the PathCompiler.
331 + * It can fail randomly (it is unlikely)
332 + */
333 + @Test
334 + public void testRandomVlanSelection() {
335 +
336 + if (PathCompiler.RANDOM_SELECTION) {
337 +
338 + sut.activate();
339 +
340 + List<Intent> compiled = sut.compile(constraintVlanIntent, Collections.emptyList());
341 + assertThat(compiled, hasSize(1));
342 +
343 + Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
344 + assertThat(rules, hasSize(3));
345 +
346 + FlowRule rule1 = rules.stream()
347 + .filter(x -> x.deviceId().equals(d1p0.deviceId()))
348 + .findFirst()
349 + .get();
350 + verifyIdAndPriority(rule1, d1p0.deviceId());
351 + assertThat(rule1.selector(), is(DefaultTrafficSelector.builder(selector)
352 + .matchInPort(d1p0.port()).build()));
353 +
354 + VlanId vlanToEncap = verifyVlanEncapTreatment(rule1.treatment(), d1p1, true, false);
355 +
356 + assertTrue(VlanId.NO_VID < vlanToEncap.toShort() && vlanToEncap.toShort() < VlanId.MAX_VLAN);
357 +
358 + /**
359 + * This second part is meant to test if the random selection is working properly.
360 + * We are compiling the same intent in order to verify if the VLAN ID is different
361 + * from the previous one.
362 + */
363 +
364 + List<Intent> compiled2 = sut.compile(constraintVlanIntent, Collections.emptyList());
365 + assertThat(compiled2, hasSize(1));
366 +
367 + Collection<FlowRule> rules2 = ((FlowRuleIntent) compiled2.get(0)).flowRules();
368 + assertThat(rules2, hasSize(3));
369 +
370 + FlowRule rule2 = rules2.stream()
371 + .filter(x -> x.deviceId().equals(d1p0.deviceId()))
372 + .findFirst()
373 + .get();
374 + verifyIdAndPriority(rule2, d1p0.deviceId());
375 + assertThat(rule2.selector(), is(DefaultTrafficSelector.builder(selector)
376 + .matchInPort(d1p0.port()).build()));
377 +
378 + VlanId vlanToEncap2 = verifyVlanEncapTreatment(rule2.treatment(), d1p1, true, false);
379 +
380 + assertTrue(VlanId.NO_VID < vlanToEncap2.toShort() && vlanToEncap2.toShort() < VlanId.MAX_VLAN);
381 + assertNotEquals(vlanToEncap, vlanToEncap2);
382 +
383 + sut.deactivate();
384 +
385 + }
386 +
387 + }
388 +
326 private VlanId verifyVlanEncapTreatment(TrafficTreatment trafficTreatment, 389 private VlanId verifyVlanEncapTreatment(TrafficTreatment trafficTreatment,
327 ConnectPoint egress, boolean isIngress, boolean isEgress) { 390 ConnectPoint egress, boolean isIngress, boolean isEgress) {
328 Set<Instructions.OutputInstruction> ruleOutput = trafficTreatment.allInstructions().stream() 391 Set<Instructions.OutputInstruction> ruleOutput = trafficTreatment.allInstructions().stream()
...@@ -339,12 +402,21 @@ public class PathIntentCompilerTest { ...@@ -339,12 +402,21 @@ public class PathIntentCompilerTest {
339 .collect(Collectors.toSet()); 402 .collect(Collectors.toSet());
340 assertThat(vlanRules, hasSize(1)); 403 assertThat(vlanRules, hasSize(1));
341 L2ModificationInstruction.ModVlanIdInstruction vlanRule = vlanRules.iterator().next(); 404 L2ModificationInstruction.ModVlanIdInstruction vlanRule = vlanRules.iterator().next();
342 - assertThat(vlanRule.vlanId().toShort(), greaterThan((short) 0)); 405 + assertThat(vlanRule.vlanId().toShort(), greaterThan((short) VlanId.NO_VID));
406 + assertThat(vlanRule.vlanId().toShort(), lessThan((short) VlanId.MAX_VLAN));
343 vlanToEncap = vlanRule.vlanId(); 407 vlanToEncap = vlanRule.vlanId();
344 } else if (!isIngress && !isEgress) { 408 } else if (!isIngress && !isEgress) {
345 - assertThat(trafficTreatment.allInstructions().stream() 409 +
346 - .filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction) 410 + Set<L2ModificationInstruction.ModVlanIdInstruction> vlanRules = trafficTreatment.allInstructions().stream()
347 - .collect(Collectors.toSet()), hasSize(0)); 411 + .filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction)
412 + .map(x -> (L2ModificationInstruction.ModVlanIdInstruction) x)
413 + .collect(Collectors.toSet());
414 + assertThat(vlanRules, hasSize(1));
415 + L2ModificationInstruction.ModVlanIdInstruction vlanRule = vlanRules.iterator().next();
416 + assertThat(vlanRule.vlanId().toShort(), greaterThan((short) VlanId.NO_VID));
417 + assertThat(vlanRule.vlanId().toShort(), lessThan((short) VlanId.MAX_VLAN));
418 + vlanToEncap = vlanRule.vlanId();
419 +
348 } else { 420 } else {
349 assertThat(trafficTreatment.allInstructions().stream() 421 assertThat(trafficTreatment.allInstructions().stream()
350 .filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction) 422 .filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction)
...@@ -385,7 +457,7 @@ public class PathIntentCompilerTest { ...@@ -385,7 +457,7 @@ public class PathIntentCompilerTest {
385 .get(); 457 .get();
386 verifyIdAndPriority(rule1, d1p0.deviceId()); 458 verifyIdAndPriority(rule1, d1p0.deviceId());
387 assertThat(rule1.selector(), is(DefaultTrafficSelector.builder(selector) 459 assertThat(rule1.selector(), is(DefaultTrafficSelector.builder(selector)
388 - .matchInPort(d1p0.port()).build())); 460 + .matchInPort(d1p0.port()).build()));
389 MplsLabel mplsLabelToEncap = verifyMplsEncapTreatment(rule1.treatment(), d1p1, true, false); 461 MplsLabel mplsLabelToEncap = verifyMplsEncapTreatment(rule1.treatment(), d1p1, true, false);
390 462
391 FlowRule rule2 = rules.stream() 463 FlowRule rule2 = rules.stream()
......