blob: 8a01f17f6728304958979cd535d46a1be4b67955 [file] [log] [blame]
Brian O'Connor7cbbbb72016-04-09 02:13:23 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
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 */
Charles Chan188ebf52015-12-23 00:15:11 -080016package org.onosproject.driver.pipeline;
17
18import com.google.common.cache.Cache;
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.RemovalCause;
21import com.google.common.cache.RemovalNotification;
Charles Chan5b9df8d2016-03-28 22:21:40 -070022import com.google.common.collect.ImmutableList;
23import com.google.common.collect.Lists;
Charles Chan188ebf52015-12-23 00:15:11 -080024import org.onlab.osgi.ServiceDirectory;
Charles Chan5b9df8d2016-03-28 22:21:40 -070025import org.onlab.packet.IpPrefix;
Charles Chan5270ed02016-01-30 23:22:37 -080026import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -080027import org.onlab.packet.MplsLabel;
28import org.onlab.packet.VlanId;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.DefaultGroupId;
Charles Chan32562522016-04-07 14:37:14 -070031import org.onosproject.driver.extensions.OfdpaSetVlanVid;
Charles Chan188ebf52015-12-23 00:15:11 -080032import org.onosproject.net.DeviceId;
33import org.onosproject.net.PortNumber;
34import org.onosproject.net.behaviour.NextGroup;
35import org.onosproject.net.behaviour.PipelinerContext;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.TrafficSelector;
38import org.onosproject.net.flow.TrafficTreatment;
39import org.onosproject.net.flow.criteria.Criterion;
40import org.onosproject.net.flow.criteria.VlanIdCriterion;
41import org.onosproject.net.flow.instructions.Instruction;
42import org.onosproject.net.flow.instructions.Instructions;
43import org.onosproject.net.flow.instructions.L2ModificationInstruction;
44import org.onosproject.net.flowobjective.FlowObjectiveStore;
45import org.onosproject.net.flowobjective.NextObjective;
46import org.onosproject.net.flowobjective.ObjectiveError;
47import org.onosproject.net.group.DefaultGroupBucket;
48import org.onosproject.net.group.DefaultGroupDescription;
49import org.onosproject.net.group.DefaultGroupKey;
50import org.onosproject.net.group.Group;
51import org.onosproject.net.group.GroupBucket;
52import org.onosproject.net.group.GroupBuckets;
53import org.onosproject.net.group.GroupDescription;
54import org.onosproject.net.group.GroupEvent;
55import org.onosproject.net.group.GroupKey;
56import org.onosproject.net.group.GroupListener;
57import org.onosproject.net.group.GroupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080058import org.onosproject.store.service.AtomicCounter;
59import org.onosproject.store.service.StorageService;
Charles Chan188ebf52015-12-23 00:15:11 -080060import org.slf4j.Logger;
61
62import java.util.ArrayDeque;
63import java.util.ArrayList;
64import java.util.Collection;
65import java.util.Collections;
66import java.util.Deque;
67import java.util.List;
68import java.util.Map;
Charles Chand0fd5dc2016-02-16 23:14:49 -080069import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080070import java.util.Set;
71import java.util.concurrent.ConcurrentHashMap;
72import java.util.concurrent.CopyOnWriteArrayList;
73import java.util.concurrent.Executors;
74import java.util.concurrent.ScheduledExecutorService;
75import java.util.concurrent.TimeUnit;
76import java.util.concurrent.atomic.AtomicInteger;
77import java.util.stream.Collectors;
78
79import static org.onlab.util.Tools.groupedThreads;
80import static org.slf4j.LoggerFactory.getLogger;
81
82/**
83 * Group handler for OFDPA2 pipeline.
84 */
Charles Chan361154b2016-03-24 10:23:39 -070085public class Ofdpa2GroupHandler {
Charles Chan188ebf52015-12-23 00:15:11 -080086 /*
87 * OFDPA requires group-id's to have a certain form.
88 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
89 * L3 Unicast Groups have <4bits-2><28bits-index>
90 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
91 * L3 ECMP Groups have <4bits-7><28bits-index>
92 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
93 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
94 */
Charles Chane849c192016-01-11 18:28:54 -080095 private static final int L2_INTERFACE_TYPE = 0x00000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070096 private static final int L3_INTERFACE_TYPE = 0x50000000;
Charles Chane849c192016-01-11 18:28:54 -080097 private static final int L3_UNICAST_TYPE = 0x20000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070098 private static final int L3_MULTICAST_TYPE = 0x60000000;
Charles Chane849c192016-01-11 18:28:54 -080099 private static final int MPLS_INTERFACE_TYPE = 0x90000000;
100 private static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
101 private static final int L3_ECMP_TYPE = 0x70000000;
102 private static final int L2_FLOOD_TYPE = 0x40000000;
103
104 private static final int TYPE_MASK = 0x0fffffff;
105 private static final int SUBTYPE_MASK = 0x00ffffff;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700106 private static final int TYPE_VLAN_MASK = 0x0000ffff;
Charles Chane849c192016-01-11 18:28:54 -0800107
108 private static final int PORT_LOWER_BITS_MASK = 0x3f;
109 private static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -0800110
111 private final Logger log = getLogger(getClass());
112 private ServiceDirectory serviceDirectory;
113 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800114 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -0800115
116 private DeviceId deviceId;
117 private FlowObjectiveStore flowObjectiveStore;
118 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
119 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
120 private ScheduledExecutorService groupChecker =
121 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
122
123 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800124 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800125
126 // local stores for port-vlan mapping
127 protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
128 protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
129
130 // local store for pending bucketAdds - by design there can only be one
131 // pending bucket for a group
132 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
133
134 protected void init(DeviceId deviceId, PipelinerContext context) {
135 this.deviceId = deviceId;
136 this.flowObjectiveStore = context.store();
137 this.serviceDirectory = context.directory();
138 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800139 this.storageService = serviceDirectory.get(StorageService.class);
140 this.nextIndex = storageService.atomicCounterBuilder()
141 .withName("group-id-index-counter")
142 .build()
143 .asAtomicCounter();
Charles Chan188ebf52015-12-23 00:15:11 -0800144
145 pendingNextObjectives = CacheBuilder.newBuilder()
146 .expireAfterWrite(20, TimeUnit.SECONDS)
147 .removalListener((
148 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
149 if (notification.getCause() == RemovalCause.EXPIRED) {
150 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700151 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800152 ObjectiveError.GROUPINSTALLATIONFAILED));
153
154 }
155 }).build();
156 pendingGroups = new ConcurrentHashMap<>();
157 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
158
159 groupService.addListener(new InnerGroupListener());
160 }
161
Saurav Das8be4e3a2016-03-11 17:19:07 -0800162 //////////////////////////////////////
163 // Group Creation
164 //////////////////////////////////////
165
Charles Chan188ebf52015-12-23 00:15:11 -0800166 protected void addGroup(NextObjective nextObjective) {
167 switch (nextObjective.type()) {
168 case SIMPLE:
169 Collection<TrafficTreatment> treatments = nextObjective.next();
170 if (treatments.size() != 1) {
171 log.error("Next Objectives of type Simple should only have a "
172 + "single Traffic Treatment. Next Objective Id:{}",
173 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700174 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800175 return;
176 }
177 processSimpleNextObjective(nextObjective);
178 break;
179 case BROADCAST:
180 processBroadcastNextObjective(nextObjective);
181 break;
182 case HASHED:
183 processHashedNextObjective(nextObjective);
184 break;
185 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700186 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800187 log.warn("Unsupported next objective type {}", nextObjective.type());
188 break;
189 default:
Charles Chan361154b2016-03-24 10:23:39 -0700190 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800191 log.warn("Unknown next objective type {}", nextObjective.type());
192 }
193 }
194
195 /**
196 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
197 * a chain of groups. The simple Next Objective passed
198 * in by the application has to be broken up into a group chain
199 * comprising of an L3 Unicast Group that points to an L2 Interface
200 * Group which in-turn points to an output port. In some cases, the simple
201 * next Objective can just be an L2 interface without the need for chaining.
202 *
203 * @param nextObj the nextObjective of type SIMPLE
204 */
205 private void processSimpleNextObjective(NextObjective nextObj) {
206 TrafficTreatment treatment = nextObj.next().iterator().next();
207 // determine if plain L2 or L3->L2
208 boolean plainL2 = true;
209 for (Instruction ins : treatment.allInstructions()) {
210 if (ins.type() == Instruction.Type.L2MODIFICATION) {
211 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
212 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
213 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
214 plainL2 = false;
215 break;
216 }
217 }
218 }
219
220 if (plainL2) {
221 createL2InterfaceGroup(nextObj);
222 return;
223 }
224
225 // break up simple next objective to GroupChain objects
226 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800227 nextObj.appId(), false,
228 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800229 if (groupInfo == null) {
230 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
231 return;
232 }
233 // create object for local and distributed storage
234 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700235 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
236 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800237 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
238 Collections.singletonList(gkeyChain),
239 nextObj);
240
241 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700242 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800243
244 // now we are ready to send the l2 groupDescription (inner), as all the stores
245 // that will get async replies have been updated. By waiting to update
246 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700247 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800248 }
249
Charles Chan188ebf52015-12-23 00:15:11 -0800250 /**
251 * Creates a simple L2 Interface Group.
252 *
253 * @param nextObj the next Objective
254 */
255 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700256 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
257 if (assignedVlan == null) {
258 log.warn("VLAN ID required by simple next obj is missing. Abort.");
259 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800260 return;
261 }
262
Charles Chan5b9df8d2016-03-28 22:21:40 -0700263 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800264
Charles Chan5b9df8d2016-03-28 22:21:40 -0700265 // There is only one L2 interface group in this case
266 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800267
Charles Chan5b9df8d2016-03-28 22:21:40 -0700268 // Put all dependency information into allGroupKeys
269 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
270 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
271 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
272 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800273
Charles Chan5b9df8d2016-03-28 22:21:40 -0700274 // Point the next objective to this group
275 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
276 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
277
278 // Start installing the inner-most group
279 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800280 }
281
282 /**
283 * Creates one of two possible group-chains from the treatment
284 * passed in. Depending on the MPLS boolean, this method either creates
285 * an L3Unicast Group --> L2Interface Group, if mpls is false;
286 * or MPLSInterface Group --> L2Interface Group, if mpls is true;
287 * The returned 'inner' group description is always the L2 Interface group.
288 *
289 * @param treatment that needs to be broken up to create the group chain
290 * @param nextId of the next objective that needs this group chain
291 * @param appId of the application that sent this next objective
292 * @param mpls determines if L3Unicast or MPLSInterface group is created
293 * @param meta metadata passed in by the application as part of the nextObjective
294 * @return GroupInfo containing the GroupDescription of the
295 * L2Interface group(inner) and the GroupDescription of the (outer)
296 * L3Unicast/MPLSInterface group. May return null if there is an
297 * error in processing the chain
298 */
299 private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800300 ApplicationId appId, boolean mpls,
301 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800302 // for the l2interface group, get vlan and port info
303 // for the outer group, get the src/dst mac, and vlan info
304 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
305 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
306 VlanId vlanid = null;
307 long portNum = 0;
308 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800309 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800310 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800311 for (Instruction ins : treatment.allInstructions()) {
312 if (ins.type() == Instruction.Type.L2MODIFICATION) {
313 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
314 switch (l2ins.subtype()) {
315 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800316 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
317 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800318 break;
319 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800320 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
321 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800322 break;
323 case VLAN_ID:
324 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
Charles Chan32562522016-04-07 14:37:14 -0700325 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
326 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800327 setVlan = true;
328 break;
329 case VLAN_POP:
330 innerTtb.popVlan();
331 popVlan = true;
332 break;
333 case DEC_MPLS_TTL:
334 case MPLS_LABEL:
335 case MPLS_POP:
336 case MPLS_PUSH:
337 case VLAN_PCP:
338 case VLAN_PUSH:
339 default:
340 break;
341 }
342 } else if (ins.type() == Instruction.Type.OUTPUT) {
343 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
344 innerTtb.add(ins);
345 } else {
346 log.warn("Driver does not handle this type of TrafficTreatment"
347 + " instruction in nextObjectives: {}", ins.type());
348 }
349 }
350
351 if (vlanid == null && meta != null) {
352 // use metadata if available
353 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
354 if (vidCriterion != null) {
355 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
356 }
357 // if vlan is not set, use the vlan in metadata for outerTtb
358 if (vlanid != null && !setVlan) {
Charles Chan32562522016-04-07 14:37:14 -0700359 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
360 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800361 }
362 }
363
364 if (vlanid == null) {
365 log.error("Driver cannot process an L2/L3 group chain without "
366 + "egress vlan information for dev: {} port:{}",
367 deviceId, portNum);
368 return null;
369 }
370
371 if (!setVlan && !popVlan) {
372 // untagged outgoing port
373 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
374 temp.popVlan();
375 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
376 innerTtb = temp;
377 }
378
379 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800380 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800381 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800382 // but different for the same portnumber on different devices. Also different
383 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800384 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700385 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800386
387 // assemble information for outer group
388 GroupDescription outerGrpDesc = null;
389 if (mpls) {
390 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800391 int mplsInterfaceIndex = getNextAvailableIndex();
392 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
393 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700394 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800395 outerTtb.group(new DefaultGroupId(l2groupId));
396 // create the mpls-interface group description to wait for the
397 // l2 interface group to be processed
398 GroupBucket mplsinterfaceGroupBucket =
399 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
400 outerGrpDesc = new DefaultGroupDescription(
401 deviceId,
402 GroupDescription.Type.INDIRECT,
403 new GroupBuckets(Collections.singletonList(
404 mplsinterfaceGroupBucket)),
405 mplsgroupkey,
406 mplsgroupId,
407 appId);
408 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
409 deviceId, Integer.toHexString(mplsgroupId),
410 mplsgroupkey, nextId);
411 } else {
412 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800413 int l3unicastIndex = getNextAvailableIndex();
414 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
415 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700416 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800417 outerTtb.group(new DefaultGroupId(l2groupId));
418 // create the l3unicast group description to wait for the
419 // l2 interface group to be processed
420 GroupBucket l3unicastGroupBucket =
421 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
422 outerGrpDesc = new DefaultGroupDescription(
423 deviceId,
424 GroupDescription.Type.INDIRECT,
425 new GroupBuckets(Collections.singletonList(
426 l3unicastGroupBucket)),
427 l3groupkey,
428 l3groupId,
429 appId);
430 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
431 deviceId, Integer.toHexString(l3groupId),
432 l3groupkey, nextId);
433 }
434
435 // store l2groupkey with the groupChainElem for the outer-group that depends on it
436 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
437 updatePendingGroups(l2groupkey, gce);
438
439 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700440 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800441 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
442 GroupDescription l2groupDescription =
443 new DefaultGroupDescription(
444 deviceId,
445 GroupDescription.Type.INDIRECT,
446 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700447 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800448 l2groupkey,
449 l2groupId,
450 appId);
451 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
452 deviceId, Integer.toHexString(l2groupId),
453 l2groupkey, nextId);
454 return new GroupInfo(l2groupDescription, outerGrpDesc);
455
456 }
457
458 /**
459 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
460 * a chain of groups. The broadcast Next Objective passed in by the application
461 * has to be broken up into a group chain comprising of an
462 * L2 Flood group whose buckets point to L2 Interface groups.
463 *
464 * @param nextObj the nextObjective of type BROADCAST
465 */
466 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700467 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
468 if (assignedVlan == null) {
469 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
470 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800471 return;
472 }
Charles Chan188ebf52015-12-23 00:15:11 -0800473
Charles Chan5b9df8d2016-03-28 22:21:40 -0700474 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
475
476 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
477 if (ipDst != null) {
478 if (ipDst.isMulticast()) {
479 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
480 } else {
481 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
482 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
483 return;
484 }
485 } else {
486 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
487 }
488 }
489
490 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
491 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
492
493 // break up broadcast next objective to multiple groups
494 Collection<TrafficTreatment> buckets = nextObj.next();
495
Charles Chan188ebf52015-12-23 00:15:11 -0800496 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800497 for (TrafficTreatment treatment : buckets) {
498 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
499 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700500 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800501 // ensure that the only allowed treatments are pop-vlan and output
502 for (Instruction ins : treatment.allInstructions()) {
503 if (ins.type() == Instruction.Type.L2MODIFICATION) {
504 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
505 switch (l2ins.subtype()) {
506 case VLAN_POP:
507 newTreatment.add(l2ins);
508 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700509 case VLAN_ID:
510 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
511 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800512 default:
513 log.debug("action {} not permitted for broadcast nextObj",
514 l2ins.subtype());
515 break;
516 }
517 } else if (ins.type() == Instruction.Type.OUTPUT) {
518 portNum = ((Instructions.OutputInstruction) ins).port();
519 newTreatment.add(ins);
520 } else {
Charles Chane849c192016-01-11 18:28:54 -0800521 log.debug("TrafficTreatment of type {} not permitted in " +
522 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800523 }
524 }
525
Charles Chan188ebf52015-12-23 00:15:11 -0800526 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700527 VlanId l2InterfaceGroupVlan =
528 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
529 egressVlan : assignedVlan;
530 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
531 final GroupKey l2InterfaceGroupKey =
532 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
533 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800534 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700535 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800536 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700537 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800538 new DefaultGroupDescription(
539 deviceId,
540 GroupDescription.Type.INDIRECT,
541 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700542 l2InterfaceGroupBucket)),
543 l2InterfaceGroupKey,
544 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800545 nextObj.appId());
546 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700547 deviceId, Integer.toHexString(l2InterfaceGroupId),
548 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800549
Charles Chan5b9df8d2016-03-28 22:21:40 -0700550 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
551 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800552 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700553 return groupInfoBuilder.build();
554 }
Charles Chan188ebf52015-12-23 00:15:11 -0800555
Charles Chan5b9df8d2016-03-28 22:21:40 -0700556 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800557 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800558 // since there can be only one flood group for a vlan, its index is always the same - 0
559 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800560 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700561 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700562
Charles Chan188ebf52015-12-23 00:15:11 -0800563 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700564 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
565 groupInfos.forEach(groupInfo -> {
566 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800567 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
568 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
569 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
570 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700571 });
Charles Chan188ebf52015-12-23 00:15:11 -0800572 // create the l2flood group-description to wait for all the
573 // l2interface groups to be processed
574 GroupDescription l2floodGroupDescription =
575 new DefaultGroupDescription(
576 deviceId,
577 GroupDescription.Type.ALL,
578 new GroupBuckets(l2floodBuckets),
579 l2floodgroupkey,
580 l2floodgroupId,
581 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800582 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
583 deviceId, Integer.toHexString(l2floodgroupId),
584 l2floodgroupkey, nextObj.id());
585
Charles Chan5b9df8d2016-03-28 22:21:40 -0700586 // Put all dependency information into allGroupKeys
587 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
588 groupInfos.forEach(groupInfo -> {
589 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
590 // In this case we should have L2 interface group only
591 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
592 gkeyChain.addFirst(l2floodgroupkey);
593 allGroupKeys.add(gkeyChain);
594 });
Charles Chan188ebf52015-12-23 00:15:11 -0800595
Charles Chan5b9df8d2016-03-28 22:21:40 -0700596 // Point the next objective to this group
597 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800598 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
599
Charles Chan5b9df8d2016-03-28 22:21:40 -0700600 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
601 groupInfos.size(), false);
602 groupInfos.forEach(groupInfo -> {
603 // Point this group to the next group
604 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
605 // Start installing the inner-most group
606 groupService.addGroup(groupInfo.innerMostGroupDesc);
607 });
608 }
609
610 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
611 List<GroupBucket> l3McastBuckets = new ArrayList<>();
612 groupInfos.forEach(groupInfo -> {
613 // Points to L3 interface group if there is one.
614 // Otherwise points to L2 interface group directly.
615 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
616 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
617 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
618 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
619 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
620 l3McastBuckets.add(abucket);
621 });
622
623 int l3MulticastIndex = getNextAvailableIndex();
624 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
625 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
626
627 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
628 GroupDescription.Type.ALL,
629 new GroupBuckets(l3McastBuckets),
630 l3MulticastGroupKey,
631 l3MulticastGroupId,
632 nextObj.appId());
633
634 // Put all dependency information into allGroupKeys
635 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
636 groupInfos.forEach(groupInfo -> {
637 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
638 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
639 // Add L3 interface group to the chain if there is one.
640 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
641 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
642 }
643 gkeyChain.addFirst(l3MulticastGroupKey);
644 allGroupKeys.add(gkeyChain);
645 });
646
647 // Point the next objective to this group
648 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
649 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
650
651 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
652 groupInfos.size(), false);
653 groupInfos.forEach(groupInfo -> {
654 // Point this group (L3 multicast) to the next group
655 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
656
657 // Point next group to inner-most group, if any
658 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
659 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
660 1, false);
661 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
662 }
663
664 // Start installing the inner-most group
665 groupService.addGroup(groupInfo.innerMostGroupDesc);
666 });
Charles Chan188ebf52015-12-23 00:15:11 -0800667 }
668
Charles Chan188ebf52015-12-23 00:15:11 -0800669 /**
670 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
671 * a chain of groups. The hashed Next Objective passed in by the application
672 * has to be broken up into a group chain comprising of an
673 * L3 ECMP group as the top level group. Buckets of this group can point
674 * to a variety of groups in a group chain, depending on the whether
675 * MPLS labels are being pushed or not.
676 * <p>
677 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
678 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
679 * check the nextObjective meta to see what is matching before being
680 * sent to this nextObjective.
681 *
682 * @param nextObj the nextObjective of type HASHED
683 */
684 private void processHashedNextObjective(NextObjective nextObj) {
685 // storage for all group keys in the chain of groups created
686 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
687 List<GroupInfo> unsentGroups = new ArrayList<>();
688 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
689
690 // now we can create the outermost L3 ECMP group
691 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
692 for (GroupInfo gi : unsentGroups) {
693 // create ECMP bucket to point to the outer group
694 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700695 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800696 GroupBucket sbucket = DefaultGroupBucket
697 .createSelectGroupBucket(ttb.build());
698 l3ecmpGroupBuckets.add(sbucket);
699 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800700 int l3ecmpIndex = getNextAvailableIndex();
701 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
702 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700703 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800704 GroupDescription l3ecmpGroupDesc =
705 new DefaultGroupDescription(
706 deviceId,
707 GroupDescription.Type.SELECT,
708 new GroupBuckets(l3ecmpGroupBuckets),
709 l3ecmpGroupKey,
710 l3ecmpGroupId,
711 nextObj.appId());
712 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
713 l3ecmpGroupBuckets.size(),
714 false);
715
716 // create objects for local and distributed storage
717 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
718 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
719
720 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
721 // that depends on it
722 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
723
724 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
725 deviceId, Integer.toHexString(l3ecmpGroupId),
726 l3ecmpGroupKey, nextObj.id());
727 // finally we are ready to send the innermost groups
728 for (GroupInfo gi : unsentGroups) {
729 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700730 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
731 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
732 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800733 }
734
735 }
736
737 /**
738 * Creates group chains for all buckets in a hashed group, and stores the
739 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
740 * should be empty.
741 * <p>
742 * Does not create the top level ECMP group. Does not actually send the
743 * groups to the groupService.
744 *
745 * @param nextObj the Next Objective with buckets that need to be converted
746 * to group chains
747 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
748 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
749 */
750 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800751 List<Deque<GroupKey>> allGroupKeys,
752 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800753 // break up hashed next objective to multiple groups
754 Collection<TrafficTreatment> buckets = nextObj.next();
755
756 for (TrafficTreatment bucket : buckets) {
757 //figure out how many labels are pushed in each bucket
758 int labelsPushed = 0;
759 MplsLabel innermostLabel = null;
760 for (Instruction ins : bucket.allInstructions()) {
761 if (ins.type() == Instruction.Type.L2MODIFICATION) {
762 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
763 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
764 labelsPushed++;
765 }
766 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
767 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800768 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800769 }
770 }
771 }
772 }
773
774 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
775 // XXX we only deal with 0 and 1 label push right now
776 if (labelsPushed == 0) {
777 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
778 nextObj.appId(), false,
779 nextObj.meta());
780 if (nolabelGroupInfo == null) {
781 log.error("Could not process nextObj={} in dev:{}",
782 nextObj.id(), deviceId);
783 return;
784 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700785 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
786 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800787
788 // we can't send the inner group description yet, as we have to
789 // create the dependent ECMP group first. So we store..
790 unsentGroups.add(nolabelGroupInfo);
791
792 } else if (labelsPushed == 1) {
793 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
794 nextObj.appId(), true,
795 nextObj.meta());
796 if (onelabelGroupInfo == null) {
797 log.error("Could not process nextObj={} in dev:{}",
798 nextObj.id(), deviceId);
799 return;
800 }
801 // we need to add another group to this chain - the L3VPN group
802 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
803 l3vpnTtb.pushMpls()
804 .setMpls(innermostLabel)
805 .setMplsBos(true)
806 .copyTtlOut()
807 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700808 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800809 GroupBucket l3vpnGrpBkt =
810 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800811 int l3vpnIndex = getNextAvailableIndex();
812 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
813 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700814 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800815 GroupDescription l3vpnGroupDesc =
816 new DefaultGroupDescription(
817 deviceId,
818 GroupDescription.Type.INDIRECT,
819 new GroupBuckets(Collections.singletonList(
820 l3vpnGrpBkt)),
821 l3vpngroupkey,
822 l3vpngroupId,
823 nextObj.appId());
824 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700825 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800826
Charles Chan5b9df8d2016-03-28 22:21:40 -0700827 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
828 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800829 gkeyChain.addFirst(l3vpngroupkey);
830
831 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700832 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800833
834 // we can't send the innermost group yet, as we have to create
835 // the dependent ECMP group first. So we store ...
836 unsentGroups.add(onelabelGroupInfo);
837
838 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
839 deviceId, Integer.toHexString(l3vpngroupId),
840 l3vpngroupkey, nextObj.id());
841
842 } else {
843 log.warn("Driver currently does not handle more than 1 MPLS "
844 + "labels. Not processing nextObjective {}", nextObj.id());
845 return;
846 }
847
848 // all groups in this chain
849 allGroupKeys.add(gkeyChain);
850 }
851 }
852
Saurav Das8be4e3a2016-03-11 17:19:07 -0800853 //////////////////////////////////////
854 // Group Editing
855 //////////////////////////////////////
856
Charles Chan188ebf52015-12-23 00:15:11 -0800857 /**
858 * Adds a bucket to the top level group of a group-chain, and creates the chain.
859 *
860 * @param nextObjective the next group to add a bucket to
861 * @param next the representation of the existing group-chain for this next objective
862 */
863 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
864 if (nextObjective.type() != NextObjective.Type.HASHED) {
865 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
866 nextObjective.type(), deviceId, nextObjective.id());
867 return;
868 }
869 if (nextObjective.next().size() > 1) {
870 log.warn("Only one bucket can be added at a time");
871 return;
872 }
873 // storage for all group keys in the chain of groups created
874 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
875 List<GroupInfo> unsentGroups = new ArrayList<>();
876 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
877
878 // now we can create the outermost L3 ECMP group bucket to add
879 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
880 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700881 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800882 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
883
884 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800885 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700886 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800887
888 // Although GroupDescriptions are not necessary for adding buckets to
889 // existing groups, we use one in the GroupChainElem. When the latter is
890 // processed, the info will be extracted for the bucketAdd call to groupService
891 GroupDescription l3ecmpGroupDesc =
892 new DefaultGroupDescription(
893 deviceId,
894 GroupDescription.Type.SELECT,
895 new GroupBuckets(Collections.singletonList(sbucket)),
896 l3ecmpGroupKey,
897 l3ecmpGroupId,
898 nextObjective.appId());
899 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
900
901 // update original NextGroup with new bucket-chain
902 // don't need to update pendingNextObjectives -- group already exists
903 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
904 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700905 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800906 allOriginalKeys.add(newBucketChain);
907 flowObjectiveStore.putNextGroup(nextObjective.id(),
908 new OfdpaNextGroup(allOriginalKeys, nextObjective));
909
910 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
911 deviceId, Integer.toHexString(l3ecmpGroupId),
912 l3ecmpGroupKey, nextObjective.id());
913 // send the innermost group
914 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700915 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
916 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
917 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800918
919 }
920
921 /**
922 * Removes the bucket in the top level group of a possible group-chain. Does
923 * not remove the groups in a group-chain pointed to by this bucket, as they
924 * may be in use (referenced by other groups) elsewhere.
925 *
926 * @param nextObjective the next group to remove a bucket from
927 * @param next the representation of the existing group-chain for this next objective
928 */
929 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
930 if (nextObjective.type() != NextObjective.Type.HASHED) {
931 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
932 nextObjective.type(), deviceId, nextObjective.id());
933 return;
934 }
935 Collection<TrafficTreatment> treatments = nextObjective.next();
936 TrafficTreatment treatment = treatments.iterator().next();
937 // find the bucket to remove by noting the outport, and figuring out the
938 // top-level group in the group-chain that indirectly references the port
939 PortNumber outport = null;
940 for (Instruction ins : treatment.allInstructions()) {
941 if (ins instanceof Instructions.OutputInstruction) {
942 outport = ((Instructions.OutputInstruction) ins).port();
943 break;
944 }
945 }
946 if (outport == null) {
947 log.error("next objective {} has no outport", nextObjective.id());
948 return;
949 }
950
Charles Chan361154b2016-03-24 10:23:39 -0700951 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800952 Deque<GroupKey> foundChain = null;
953 int index = 0;
954 for (Deque<GroupKey> gkeys : allgkeys) {
955 GroupKey groupWithPort = gkeys.peekLast();
956 Group group = groupService.getGroup(deviceId, groupWithPort);
957 if (group == null) {
958 log.warn("Inconsistent group chain");
959 continue;
960 }
961 // last group in group chain should have a single bucket pointing to port
962 List<Instruction> lastIns = group.buckets().buckets().iterator()
963 .next().treatment().allInstructions();
964 for (Instruction i : lastIns) {
965 if (i instanceof Instructions.OutputInstruction) {
966 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
967 if (lastport.equals(outport)) {
968 foundChain = gkeys;
969 break;
970 }
971 }
972 }
973 if (foundChain != null) {
974 break;
975 }
976 index++;
977 }
978 if (foundChain != null) {
979 //first groupkey is the one we want to modify
980 GroupKey modGroupKey = foundChain.peekFirst();
981 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
982 //second groupkey is the one we wish to remove the reference to
983 GroupKey pointedGroupKey = null;
984 int i = 0;
985 for (GroupKey gk : foundChain) {
986 if (i++ == 1) {
987 pointedGroupKey = gk;
988 break;
989 }
990 }
991 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
992 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
993 DefaultTrafficTreatment.builder()
994 .group(pointedGroup.id())
995 .build());
996 GroupBuckets removeBuckets = new GroupBuckets(Collections
997 .singletonList(bucket));
998 log.debug("Removing buckets from group id {} for next id {} in device {}",
999 modGroup.id(), nextObjective.id(), deviceId);
1000 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
1001 removeBuckets, modGroupKey,
1002 nextObjective.appId());
1003 //update store
1004 allgkeys.remove(index);
1005 flowObjectiveStore.putNextGroup(nextObjective.id(),
1006 new OfdpaNextGroup(allgkeys, nextObjective));
1007 } else {
1008 log.warn("Could not find appropriate group-chain for removing bucket"
1009 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
1010 }
1011 }
1012
1013 /**
1014 * Removes all groups in multiple possible group-chains that represent the next
1015 * objective.
1016 *
1017 * @param nextObjective the next objective to remove
1018 * @param next the NextGroup that represents the existing group-chain for
1019 * this next objective
1020 */
1021 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001022 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -08001023 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1024 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1025 flowObjectiveStore.removeNextGroup(nextObjective.id());
1026 }
1027
Saurav Das8be4e3a2016-03-11 17:19:07 -08001028 //////////////////////////////////////
1029 // Helper Methods and Classes
1030 //////////////////////////////////////
1031
1032 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1033 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1034 nextList.add(value);
1035 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
1036 .putIfAbsent(key, nextList);
1037 if (ret != null) {
1038 ret.add(value);
1039 }
1040 }
1041
1042 private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
1043 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1044 new ConcurrentHashMap<GroupChainElem, Boolean>());
1045 gceSet.add(gce);
1046 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1047 if (retval != null) {
1048 retval.add(gce);
1049 }
1050 }
1051
Charles Chan188ebf52015-12-23 00:15:11 -08001052 /**
1053 * Processes next element of a group chain. Assumption is that if this
1054 * group points to another group, the latter has already been created
1055 * and this driver has received notification for it. A second assumption is
1056 * that if there is another group waiting for this group then the appropriate
1057 * stores already have the information to act upon the notification for the
1058 * creation of this group.
1059 * <p>
1060 * The processing of the GroupChainElement depends on the number of groups
1061 * this element is waiting on. For all group types other than SIMPLE, a
1062 * GroupChainElement could be waiting on multiple groups.
1063 *
1064 * @param gce the group chain element to be processed next
1065 */
1066 private void processGroupChain(GroupChainElem gce) {
1067 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1068 if (waitOnGroups != 0) {
1069 log.debug("GCE: {} not ready to be processed", gce);
1070 return;
1071 }
1072 log.debug("GCE: {} ready to be processed", gce);
1073 if (gce.addBucketToGroup) {
1074 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1075 gce.groupDescription.appCookie(),
1076 gce.groupDescription.buckets(),
1077 gce.groupDescription.appCookie(),
1078 gce.groupDescription.appId());
1079 } else {
1080 groupService.addGroup(gce.groupDescription);
1081 }
1082 }
1083
1084 private class GroupChecker implements Runnable {
1085 @Override
1086 public void run() {
1087 Set<GroupKey> keys = pendingGroups.keySet().stream()
1088 .filter(key -> groupService.getGroup(deviceId, key) != null)
1089 .collect(Collectors.toSet());
1090 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1091 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1092 .collect(Collectors.toSet());
1093 keys.addAll(otherkeys);
1094
1095 keys.stream().forEach(key ->
1096 processPendingGroupsOrNextObjectives(key, false));
1097 }
1098 }
1099
Saurav Das8be4e3a2016-03-11 17:19:07 -08001100 private class InnerGroupListener implements GroupListener {
1101 @Override
1102 public void event(GroupEvent event) {
1103 log.trace("received group event of type {}", event.type());
1104 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1105 GroupKey key = event.subject().appCookie();
1106 processPendingGroupsOrNextObjectives(key, true);
1107 }
1108 }
1109 }
1110
Charles Chan188ebf52015-12-23 00:15:11 -08001111 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1112 //first check for group chain
1113 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1114 if (gceSet != null) {
1115 for (GroupChainElem gce : gceSet) {
1116 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001117 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001118 (added) ? "ADDED" : "processed",
1119 key, deviceId,
1120 Integer.toHexString(gce.groupDescription.givenGroupId()));
1121 processGroupChain(gce);
1122 }
1123 } else {
1124 // otherwise chain complete - check for waiting nextObjectives
1125 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1126 if (nextGrpList != null) {
1127 pendingNextObjectives.invalidate(key);
1128 nextGrpList.forEach(nextGrp -> {
1129 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001130 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001131 (added) ? "ADDED" : "processed",
1132 key, deviceId, nextGrp.nextObjective().id(),
1133 Integer.toHexString(groupService.getGroup(deviceId, key)
1134 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001135 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001136 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1137 // check if addBuckets waiting for this completion
1138 NextObjective pendBkt = pendingBuckets
1139 .remove(nextGrp.nextObjective().id());
1140 if (pendBkt != null) {
1141 addBucketToGroup(pendBkt, nextGrp);
1142 }
1143 });
1144 }
1145 }
1146 }
1147
Saurav Das8be4e3a2016-03-11 17:19:07 -08001148 private int getNextAvailableIndex() {
1149 return (int) nextIndex.incrementAndGet();
1150 }
1151
Charles Chane849c192016-01-11 18:28:54 -08001152 /**
1153 * Returns a hash as the L2 Interface Group Key.
1154 *
1155 * Keep the lower 6-bit for port since port number usually smaller than 64.
1156 * Hash other information into remaining 28 bits.
1157 *
1158 * @param deviceId Device ID
1159 * @param vlanId VLAN ID
1160 * @param portNumber Port number
1161 * @return L2 interface group key
1162 */
1163 private int l2InterfaceGroupKey(
1164 DeviceId deviceId, VlanId vlanId, long portNumber) {
1165 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1166 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001167 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001168 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1169 }
1170
Charles Chan188ebf52015-12-23 00:15:11 -08001171 /**
1172 * Utility class for moving group information around.
1173 */
1174 private class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001175 /**
1176 * Description of the inner-most group of the group chain.
1177 * It is always an L2 interface group.
1178 */
1179 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001180
Charles Chan5b9df8d2016-03-28 22:21:40 -07001181 /**
1182 * Description of the next group in the group chain.
1183 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1184 * It is possible that nextGroup is the same as the innerMostGroup.
1185 */
1186 private GroupDescription nextGroupDesc;
1187
1188 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1189 this.innerMostGroupDesc = innerMostGroupDesc;
1190 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001191 }
1192 }
1193
1194 /**
1195 * Represents an entire group-chain that implements a Next-Objective from
1196 * the application. The objective is represented as a list of deques, where
1197 * each deque is a separate chain of groups.
1198 * <p>
1199 * For example, an ECMP group with 3 buckets, where each bucket points to
1200 * a group chain of L3 Unicast and L2 interface groups will look like this:
1201 * <ul>
1202 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1203 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1204 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1205 * </ul>
1206 * where the first element of each deque is the same, representing the
1207 * top level ECMP group, while every other element represents a unique groupKey.
1208 * <p>
1209 * Also includes information about the next objective that
1210 * resulted in this group-chain.
1211 *
1212 */
1213 protected class OfdpaNextGroup implements NextGroup {
1214 private final NextObjective nextObj;
1215 private final List<Deque<GroupKey>> gkeys;
1216
1217 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1218 this.gkeys = gkeys;
1219 this.nextObj = nextObj;
1220 }
1221
Charles Chan188ebf52015-12-23 00:15:11 -08001222 public List<Deque<GroupKey>> groupKey() {
1223 return gkeys;
1224 }
1225
1226 public NextObjective nextObjective() {
1227 return nextObj;
1228 }
1229
1230 @Override
1231 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001232 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001233 }
1234 }
1235
1236 /**
1237 * Represents a group element that is part of a chain of groups.
1238 * Stores enough information to create a Group Description to add the group
1239 * to the switch by requesting the Group Service. Objects instantiating this
1240 * class are meant to be temporary and live as long as it is needed to wait for
1241 * preceding groups in the group chain to be created.
1242 */
1243 private class GroupChainElem {
1244 private GroupDescription groupDescription;
1245 private AtomicInteger waitOnGroups;
1246 private boolean addBucketToGroup;
1247
1248 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1249 boolean addBucketToGroup) {
1250 this.groupDescription = groupDescription;
1251 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1252 this.addBucketToGroup = addBucketToGroup;
1253 }
1254
1255 /**
1256 * This methods atomically decrements the counter for the number of
1257 * groups this GroupChainElement is waiting on, for notifications from
1258 * the Group Service. When this method returns a value of 0, this
1259 * GroupChainElement is ready to be processed.
1260 *
1261 * @return integer indication of the number of notifications being waited on
1262 */
1263 int decrementAndGetGroupsWaitedOn() {
1264 return waitOnGroups.decrementAndGet();
1265 }
1266
1267 @Override
1268 public String toString() {
1269 return (Integer.toHexString(groupDescription.givenGroupId()) +
1270 " groupKey: " + groupDescription.appCookie() +
1271 " waiting-on-groups: " + waitOnGroups.get() +
1272 " addBucketToGroup: " + addBucketToGroup +
1273 " device: " + deviceId);
1274 }
1275 }
1276}