blob: f21ae4c08b62edf850f7a4efc2c2be632c12c0c8 [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;
Charles Chand0fd5dc2016-02-16 23:14:49 -080068import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080069import java.util.Set;
70import java.util.concurrent.ConcurrentHashMap;
71import java.util.concurrent.CopyOnWriteArrayList;
72import java.util.concurrent.Executors;
73import java.util.concurrent.ScheduledExecutorService;
74import java.util.concurrent.TimeUnit;
75import java.util.concurrent.atomic.AtomicInteger;
76import java.util.stream.Collectors;
77
78import static org.onlab.util.Tools.groupedThreads;
79import static org.slf4j.LoggerFactory.getLogger;
80
81/**
82 * Group handler for OFDPA2 pipeline.
83 */
Charles Chan361154b2016-03-24 10:23:39 -070084public class Ofdpa2GroupHandler {
Charles Chan188ebf52015-12-23 00:15:11 -080085 /*
86 * OFDPA requires group-id's to have a certain form.
87 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
88 * L3 Unicast Groups have <4bits-2><28bits-index>
89 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
90 * L3 ECMP Groups have <4bits-7><28bits-index>
91 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
92 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
93 */
Charles Chan425854b2016-04-11 15:32:12 -070094 protected static final int L2_INTERFACE_TYPE = 0x00000000;
95 protected static final int L3_INTERFACE_TYPE = 0x50000000;
96 protected static final int L3_UNICAST_TYPE = 0x20000000;
97 protected static final int L3_MULTICAST_TYPE = 0x60000000;
98 protected static final int MPLS_INTERFACE_TYPE = 0x90000000;
99 protected static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
100 protected static final int L3_ECMP_TYPE = 0x70000000;
101 protected static final int L2_FLOOD_TYPE = 0x40000000;
Charles Chane849c192016-01-11 18:28:54 -0800102
Charles Chan425854b2016-04-11 15:32:12 -0700103 protected static final int TYPE_MASK = 0x0fffffff;
104 protected static final int SUBTYPE_MASK = 0x00ffffff;
105 protected static final int TYPE_VLAN_MASK = 0x0000ffff;
Charles Chane849c192016-01-11 18:28:54 -0800106
Charles Chan425854b2016-04-11 15:32:12 -0700107 protected static final int PORT_LOWER_BITS_MASK = 0x3f;
108 protected static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -0800109
110 private final Logger log = getLogger(getClass());
111 private ServiceDirectory serviceDirectory;
112 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800113 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -0800114
Charles Chan425854b2016-04-11 15:32:12 -0700115 protected DeviceId deviceId;
Charles Chan188ebf52015-12-23 00:15:11 -0800116 private FlowObjectiveStore flowObjectiveStore;
117 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
118 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
119 private ScheduledExecutorService groupChecker =
HIGUCHI Yutad9e01052016-04-14 09:31:42 -0700120 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d", log));
Charles Chan188ebf52015-12-23 00:15:11 -0800121
122 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800123 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800124
Charles Chan188ebf52015-12-23 00:15:11 -0800125 // local store for pending bucketAdds - by design there can only be one
126 // pending bucket for a group
127 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
128
129 protected void init(DeviceId deviceId, PipelinerContext context) {
130 this.deviceId = deviceId;
131 this.flowObjectiveStore = context.store();
132 this.serviceDirectory = context.directory();
133 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800134 this.storageService = serviceDirectory.get(StorageService.class);
Madan Jampanid5714e02016-04-19 14:15:20 -0700135 this.nextIndex = storageService.getAtomicCounter("group-id-index-counter");
Charles Chan188ebf52015-12-23 00:15:11 -0800136
137 pendingNextObjectives = CacheBuilder.newBuilder()
138 .expireAfterWrite(20, TimeUnit.SECONDS)
139 .removalListener((
140 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
141 if (notification.getCause() == RemovalCause.EXPIRED) {
142 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700143 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800144 ObjectiveError.GROUPINSTALLATIONFAILED));
145
146 }
147 }).build();
148 pendingGroups = new ConcurrentHashMap<>();
149 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
150
151 groupService.addListener(new InnerGroupListener());
152 }
153
Saurav Das8be4e3a2016-03-11 17:19:07 -0800154 //////////////////////////////////////
155 // Group Creation
156 //////////////////////////////////////
157
Charles Chan188ebf52015-12-23 00:15:11 -0800158 protected void addGroup(NextObjective nextObjective) {
159 switch (nextObjective.type()) {
160 case SIMPLE:
161 Collection<TrafficTreatment> treatments = nextObjective.next();
162 if (treatments.size() != 1) {
163 log.error("Next Objectives of type Simple should only have a "
164 + "single Traffic Treatment. Next Objective Id:{}",
165 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700166 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800167 return;
168 }
169 processSimpleNextObjective(nextObjective);
170 break;
171 case BROADCAST:
172 processBroadcastNextObjective(nextObjective);
173 break;
174 case HASHED:
175 processHashedNextObjective(nextObjective);
176 break;
177 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700178 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800179 log.warn("Unsupported next objective type {}", nextObjective.type());
180 break;
181 default:
Charles Chan361154b2016-03-24 10:23:39 -0700182 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800183 log.warn("Unknown next objective type {}", nextObjective.type());
184 }
185 }
186
187 /**
188 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
189 * a chain of groups. The simple Next Objective passed
190 * in by the application has to be broken up into a group chain
191 * comprising of an L3 Unicast Group that points to an L2 Interface
192 * Group which in-turn points to an output port. In some cases, the simple
193 * next Objective can just be an L2 interface without the need for chaining.
194 *
195 * @param nextObj the nextObjective of type SIMPLE
196 */
197 private void processSimpleNextObjective(NextObjective nextObj) {
198 TrafficTreatment treatment = nextObj.next().iterator().next();
199 // determine if plain L2 or L3->L2
200 boolean plainL2 = true;
201 for (Instruction ins : treatment.allInstructions()) {
202 if (ins.type() == Instruction.Type.L2MODIFICATION) {
203 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
204 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
205 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
206 plainL2 = false;
207 break;
208 }
209 }
210 }
211
212 if (plainL2) {
213 createL2InterfaceGroup(nextObj);
214 return;
215 }
216
217 // break up simple next objective to GroupChain objects
218 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800219 nextObj.appId(), false,
220 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800221 if (groupInfo == null) {
222 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
223 return;
224 }
225 // create object for local and distributed storage
226 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700227 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
228 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800229 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
230 Collections.singletonList(gkeyChain),
231 nextObj);
232
233 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700234 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800235
236 // now we are ready to send the l2 groupDescription (inner), as all the stores
237 // that will get async replies have been updated. By waiting to update
238 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700239 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800240 }
241
Charles Chan188ebf52015-12-23 00:15:11 -0800242 /**
243 * Creates a simple L2 Interface Group.
244 *
245 * @param nextObj the next Objective
246 */
247 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700248 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
249 if (assignedVlan == null) {
250 log.warn("VLAN ID required by simple next obj is missing. Abort.");
251 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800252 return;
253 }
254
Charles Chan5b9df8d2016-03-28 22:21:40 -0700255 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800256
Charles Chan5b9df8d2016-03-28 22:21:40 -0700257 // There is only one L2 interface group in this case
258 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800259
Charles Chan5b9df8d2016-03-28 22:21:40 -0700260 // Put all dependency information into allGroupKeys
261 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
262 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
263 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
264 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800265
Charles Chan5b9df8d2016-03-28 22:21:40 -0700266 // Point the next objective to this group
267 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
268 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
269
270 // Start installing the inner-most group
271 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800272 }
273
274 /**
275 * Creates one of two possible group-chains from the treatment
276 * passed in. Depending on the MPLS boolean, this method either creates
Charles Chan425854b2016-04-11 15:32:12 -0700277 * an L3Unicast Group --&gt; L2Interface Group, if mpls is false;
278 * or MPLSInterface Group --&gt; L2Interface Group, if mpls is true;
Charles Chan188ebf52015-12-23 00:15:11 -0800279 * The returned 'inner' group description is always the L2 Interface group.
280 *
281 * @param treatment that needs to be broken up to create the group chain
282 * @param nextId of the next objective that needs this group chain
283 * @param appId of the application that sent this next objective
284 * @param mpls determines if L3Unicast or MPLSInterface group is created
285 * @param meta metadata passed in by the application as part of the nextObjective
286 * @return GroupInfo containing the GroupDescription of the
287 * L2Interface group(inner) and the GroupDescription of the (outer)
288 * L3Unicast/MPLSInterface group. May return null if there is an
289 * error in processing the chain
290 */
Charles Chan425854b2016-04-11 15:32:12 -0700291 protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800292 ApplicationId appId, boolean mpls,
293 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800294 // for the l2interface group, get vlan and port info
295 // for the outer group, get the src/dst mac, and vlan info
296 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
297 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
298 VlanId vlanid = null;
299 long portNum = 0;
300 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800301 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800302 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800303 for (Instruction ins : treatment.allInstructions()) {
304 if (ins.type() == Instruction.Type.L2MODIFICATION) {
305 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
306 switch (l2ins.subtype()) {
307 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800308 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
309 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800310 break;
311 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800312 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
313 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800314 break;
315 case VLAN_ID:
316 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
Charles Chan32562522016-04-07 14:37:14 -0700317 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
318 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800319 setVlan = true;
320 break;
321 case VLAN_POP:
322 innerTtb.popVlan();
323 popVlan = true;
324 break;
325 case DEC_MPLS_TTL:
326 case MPLS_LABEL:
327 case MPLS_POP:
328 case MPLS_PUSH:
329 case VLAN_PCP:
330 case VLAN_PUSH:
331 default:
332 break;
333 }
334 } else if (ins.type() == Instruction.Type.OUTPUT) {
335 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
336 innerTtb.add(ins);
337 } else {
338 log.warn("Driver does not handle this type of TrafficTreatment"
339 + " instruction in nextObjectives: {}", ins.type());
340 }
341 }
342
343 if (vlanid == null && meta != null) {
344 // use metadata if available
345 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
346 if (vidCriterion != null) {
347 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
348 }
349 // if vlan is not set, use the vlan in metadata for outerTtb
350 if (vlanid != null && !setVlan) {
Charles Chan32562522016-04-07 14:37:14 -0700351 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
352 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800353 }
354 }
355
356 if (vlanid == null) {
357 log.error("Driver cannot process an L2/L3 group chain without "
358 + "egress vlan information for dev: {} port:{}",
359 deviceId, portNum);
360 return null;
361 }
362
363 if (!setVlan && !popVlan) {
364 // untagged outgoing port
365 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
366 temp.popVlan();
367 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
368 innerTtb = temp;
369 }
370
371 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800372 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800373 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800374 // but different for the same portnumber on different devices. Also different
375 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800376 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700377 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800378
379 // assemble information for outer group
380 GroupDescription outerGrpDesc = null;
381 if (mpls) {
382 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800383 int mplsInterfaceIndex = getNextAvailableIndex();
384 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
385 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700386 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800387 outerTtb.group(new DefaultGroupId(l2groupId));
388 // create the mpls-interface group description to wait for the
389 // l2 interface group to be processed
390 GroupBucket mplsinterfaceGroupBucket =
391 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
392 outerGrpDesc = new DefaultGroupDescription(
393 deviceId,
394 GroupDescription.Type.INDIRECT,
395 new GroupBuckets(Collections.singletonList(
396 mplsinterfaceGroupBucket)),
397 mplsgroupkey,
398 mplsgroupId,
399 appId);
400 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
401 deviceId, Integer.toHexString(mplsgroupId),
402 mplsgroupkey, nextId);
403 } else {
404 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800405 int l3unicastIndex = getNextAvailableIndex();
406 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
407 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700408 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800409 outerTtb.group(new DefaultGroupId(l2groupId));
410 // create the l3unicast group description to wait for the
411 // l2 interface group to be processed
412 GroupBucket l3unicastGroupBucket =
413 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
414 outerGrpDesc = new DefaultGroupDescription(
415 deviceId,
416 GroupDescription.Type.INDIRECT,
417 new GroupBuckets(Collections.singletonList(
418 l3unicastGroupBucket)),
419 l3groupkey,
420 l3groupId,
421 appId);
422 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
423 deviceId, Integer.toHexString(l3groupId),
424 l3groupkey, nextId);
425 }
426
427 // store l2groupkey with the groupChainElem for the outer-group that depends on it
428 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
429 updatePendingGroups(l2groupkey, gce);
430
431 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700432 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800433 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
434 GroupDescription l2groupDescription =
435 new DefaultGroupDescription(
436 deviceId,
437 GroupDescription.Type.INDIRECT,
438 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700439 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800440 l2groupkey,
441 l2groupId,
442 appId);
443 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
444 deviceId, Integer.toHexString(l2groupId),
445 l2groupkey, nextId);
446 return new GroupInfo(l2groupDescription, outerGrpDesc);
447
448 }
449
450 /**
451 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
452 * a chain of groups. The broadcast Next Objective passed in by the application
453 * has to be broken up into a group chain comprising of an
454 * L2 Flood group whose buckets point to L2 Interface groups.
455 *
456 * @param nextObj the nextObjective of type BROADCAST
457 */
458 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700459 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
460 if (assignedVlan == null) {
461 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
462 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800463 return;
464 }
Charles Chan188ebf52015-12-23 00:15:11 -0800465
Charles Chan5b9df8d2016-03-28 22:21:40 -0700466 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
467
468 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
469 if (ipDst != null) {
470 if (ipDst.isMulticast()) {
471 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
472 } else {
473 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
474 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
475 return;
476 }
477 } else {
478 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
479 }
480 }
481
482 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
483 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
484
485 // break up broadcast next objective to multiple groups
486 Collection<TrafficTreatment> buckets = nextObj.next();
487
Charles Chan188ebf52015-12-23 00:15:11 -0800488 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800489 for (TrafficTreatment treatment : buckets) {
490 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
491 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700492 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800493 // ensure that the only allowed treatments are pop-vlan and output
494 for (Instruction ins : treatment.allInstructions()) {
495 if (ins.type() == Instruction.Type.L2MODIFICATION) {
496 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
497 switch (l2ins.subtype()) {
498 case VLAN_POP:
499 newTreatment.add(l2ins);
500 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700501 case VLAN_ID:
502 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
503 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800504 default:
505 log.debug("action {} not permitted for broadcast nextObj",
506 l2ins.subtype());
507 break;
508 }
509 } else if (ins.type() == Instruction.Type.OUTPUT) {
510 portNum = ((Instructions.OutputInstruction) ins).port();
511 newTreatment.add(ins);
512 } else {
Charles Chane849c192016-01-11 18:28:54 -0800513 log.debug("TrafficTreatment of type {} not permitted in " +
514 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800515 }
516 }
517
Charles Chan188ebf52015-12-23 00:15:11 -0800518 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700519 VlanId l2InterfaceGroupVlan =
520 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
521 egressVlan : assignedVlan;
522 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
523 final GroupKey l2InterfaceGroupKey =
524 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
525 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800526 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700527 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800528 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700529 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800530 new DefaultGroupDescription(
531 deviceId,
532 GroupDescription.Type.INDIRECT,
533 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700534 l2InterfaceGroupBucket)),
535 l2InterfaceGroupKey,
536 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800537 nextObj.appId());
538 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700539 deviceId, Integer.toHexString(l2InterfaceGroupId),
540 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800541
Charles Chan5b9df8d2016-03-28 22:21:40 -0700542 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
543 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800544 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700545 return groupInfoBuilder.build();
546 }
Charles Chan188ebf52015-12-23 00:15:11 -0800547
Charles Chan5b9df8d2016-03-28 22:21:40 -0700548 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800549 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800550 // since there can be only one flood group for a vlan, its index is always the same - 0
551 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800552 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700553 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700554
Charles Chan188ebf52015-12-23 00:15:11 -0800555 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700556 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
557 groupInfos.forEach(groupInfo -> {
558 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800559 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
560 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
561 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
562 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700563 });
Charles Chan188ebf52015-12-23 00:15:11 -0800564 // create the l2flood group-description to wait for all the
565 // l2interface groups to be processed
566 GroupDescription l2floodGroupDescription =
567 new DefaultGroupDescription(
568 deviceId,
569 GroupDescription.Type.ALL,
570 new GroupBuckets(l2floodBuckets),
571 l2floodgroupkey,
572 l2floodgroupId,
573 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800574 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
575 deviceId, Integer.toHexString(l2floodgroupId),
576 l2floodgroupkey, nextObj.id());
577
Charles Chan5b9df8d2016-03-28 22:21:40 -0700578 // Put all dependency information into allGroupKeys
579 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
580 groupInfos.forEach(groupInfo -> {
581 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
582 // In this case we should have L2 interface group only
583 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
584 gkeyChain.addFirst(l2floodgroupkey);
585 allGroupKeys.add(gkeyChain);
586 });
Charles Chan188ebf52015-12-23 00:15:11 -0800587
Charles Chan5b9df8d2016-03-28 22:21:40 -0700588 // Point the next objective to this group
589 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800590 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
591
Charles Chan5b9df8d2016-03-28 22:21:40 -0700592 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
593 groupInfos.size(), false);
594 groupInfos.forEach(groupInfo -> {
595 // Point this group to the next group
596 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
597 // Start installing the inner-most group
598 groupService.addGroup(groupInfo.innerMostGroupDesc);
599 });
600 }
601
602 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
603 List<GroupBucket> l3McastBuckets = new ArrayList<>();
604 groupInfos.forEach(groupInfo -> {
605 // Points to L3 interface group if there is one.
606 // Otherwise points to L2 interface group directly.
607 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
608 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
609 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
610 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
611 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
612 l3McastBuckets.add(abucket);
613 });
614
615 int l3MulticastIndex = getNextAvailableIndex();
616 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
617 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
618
619 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
620 GroupDescription.Type.ALL,
621 new GroupBuckets(l3McastBuckets),
622 l3MulticastGroupKey,
623 l3MulticastGroupId,
624 nextObj.appId());
625
626 // Put all dependency information into allGroupKeys
627 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
628 groupInfos.forEach(groupInfo -> {
629 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
630 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
631 // Add L3 interface group to the chain if there is one.
632 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
633 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
634 }
635 gkeyChain.addFirst(l3MulticastGroupKey);
636 allGroupKeys.add(gkeyChain);
637 });
638
639 // Point the next objective to this group
640 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
641 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
642
643 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
644 groupInfos.size(), false);
645 groupInfos.forEach(groupInfo -> {
646 // Point this group (L3 multicast) to the next group
647 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
648
649 // Point next group to inner-most group, if any
650 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
651 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
652 1, false);
653 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
654 }
655
656 // Start installing the inner-most group
657 groupService.addGroup(groupInfo.innerMostGroupDesc);
658 });
Charles Chan188ebf52015-12-23 00:15:11 -0800659 }
660
Charles Chan188ebf52015-12-23 00:15:11 -0800661 /**
662 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
663 * a chain of groups. The hashed Next Objective passed in by the application
664 * has to be broken up into a group chain comprising of an
665 * L3 ECMP group as the top level group. Buckets of this group can point
666 * to a variety of groups in a group chain, depending on the whether
667 * MPLS labels are being pushed or not.
668 * <p>
669 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
670 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
671 * check the nextObjective meta to see what is matching before being
672 * sent to this nextObjective.
673 *
674 * @param nextObj the nextObjective of type HASHED
675 */
676 private void processHashedNextObjective(NextObjective nextObj) {
677 // storage for all group keys in the chain of groups created
678 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
679 List<GroupInfo> unsentGroups = new ArrayList<>();
680 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
681
682 // now we can create the outermost L3 ECMP group
683 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
684 for (GroupInfo gi : unsentGroups) {
685 // create ECMP bucket to point to the outer group
686 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700687 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800688 GroupBucket sbucket = DefaultGroupBucket
689 .createSelectGroupBucket(ttb.build());
690 l3ecmpGroupBuckets.add(sbucket);
691 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800692 int l3ecmpIndex = getNextAvailableIndex();
693 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
694 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700695 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800696 GroupDescription l3ecmpGroupDesc =
697 new DefaultGroupDescription(
698 deviceId,
699 GroupDescription.Type.SELECT,
700 new GroupBuckets(l3ecmpGroupBuckets),
701 l3ecmpGroupKey,
702 l3ecmpGroupId,
703 nextObj.appId());
704 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
705 l3ecmpGroupBuckets.size(),
706 false);
707
708 // create objects for local and distributed storage
709 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
710 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
711
712 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
713 // that depends on it
714 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
715
716 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
717 deviceId, Integer.toHexString(l3ecmpGroupId),
718 l3ecmpGroupKey, nextObj.id());
719 // finally we are ready to send the innermost groups
720 for (GroupInfo gi : unsentGroups) {
721 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700722 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
723 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
724 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800725 }
726
727 }
728
729 /**
730 * Creates group chains for all buckets in a hashed group, and stores the
731 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
732 * should be empty.
733 * <p>
734 * Does not create the top level ECMP group. Does not actually send the
735 * groups to the groupService.
736 *
737 * @param nextObj the Next Objective with buckets that need to be converted
738 * to group chains
739 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
740 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
741 */
742 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800743 List<Deque<GroupKey>> allGroupKeys,
744 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800745 // break up hashed next objective to multiple groups
746 Collection<TrafficTreatment> buckets = nextObj.next();
747
748 for (TrafficTreatment bucket : buckets) {
749 //figure out how many labels are pushed in each bucket
750 int labelsPushed = 0;
751 MplsLabel innermostLabel = null;
752 for (Instruction ins : bucket.allInstructions()) {
753 if (ins.type() == Instruction.Type.L2MODIFICATION) {
754 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
755 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
756 labelsPushed++;
757 }
758 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
759 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800760 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800761 }
762 }
763 }
764 }
765
766 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
767 // XXX we only deal with 0 and 1 label push right now
768 if (labelsPushed == 0) {
769 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
770 nextObj.appId(), false,
771 nextObj.meta());
772 if (nolabelGroupInfo == null) {
773 log.error("Could not process nextObj={} in dev:{}",
774 nextObj.id(), deviceId);
775 return;
776 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700777 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
778 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800779
780 // we can't send the inner group description yet, as we have to
781 // create the dependent ECMP group first. So we store..
782 unsentGroups.add(nolabelGroupInfo);
783
784 } else if (labelsPushed == 1) {
785 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
786 nextObj.appId(), true,
787 nextObj.meta());
788 if (onelabelGroupInfo == null) {
789 log.error("Could not process nextObj={} in dev:{}",
790 nextObj.id(), deviceId);
791 return;
792 }
793 // we need to add another group to this chain - the L3VPN group
794 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
795 l3vpnTtb.pushMpls()
796 .setMpls(innermostLabel)
797 .setMplsBos(true)
798 .copyTtlOut()
799 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700800 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800801 GroupBucket l3vpnGrpBkt =
802 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800803 int l3vpnIndex = getNextAvailableIndex();
804 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
805 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700806 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800807 GroupDescription l3vpnGroupDesc =
808 new DefaultGroupDescription(
809 deviceId,
810 GroupDescription.Type.INDIRECT,
811 new GroupBuckets(Collections.singletonList(
812 l3vpnGrpBkt)),
813 l3vpngroupkey,
814 l3vpngroupId,
815 nextObj.appId());
816 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700817 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800818
Charles Chan5b9df8d2016-03-28 22:21:40 -0700819 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
820 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800821 gkeyChain.addFirst(l3vpngroupkey);
822
823 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700824 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800825
826 // we can't send the innermost group yet, as we have to create
827 // the dependent ECMP group first. So we store ...
828 unsentGroups.add(onelabelGroupInfo);
829
830 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
831 deviceId, Integer.toHexString(l3vpngroupId),
832 l3vpngroupkey, nextObj.id());
833
834 } else {
835 log.warn("Driver currently does not handle more than 1 MPLS "
836 + "labels. Not processing nextObjective {}", nextObj.id());
837 return;
838 }
839
840 // all groups in this chain
841 allGroupKeys.add(gkeyChain);
842 }
843 }
844
Saurav Das8be4e3a2016-03-11 17:19:07 -0800845 //////////////////////////////////////
846 // Group Editing
847 //////////////////////////////////////
848
Charles Chan188ebf52015-12-23 00:15:11 -0800849 /**
850 * Adds a bucket to the top level group of a group-chain, and creates the chain.
851 *
852 * @param nextObjective the next group to add a bucket to
853 * @param next the representation of the existing group-chain for this next objective
854 */
855 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
856 if (nextObjective.type() != NextObjective.Type.HASHED) {
857 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
858 nextObjective.type(), deviceId, nextObjective.id());
859 return;
860 }
861 if (nextObjective.next().size() > 1) {
862 log.warn("Only one bucket can be added at a time");
863 return;
864 }
865 // storage for all group keys in the chain of groups created
866 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
867 List<GroupInfo> unsentGroups = new ArrayList<>();
868 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
869
870 // now we can create the outermost L3 ECMP group bucket to add
871 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
872 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700873 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800874 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
875
876 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800877 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700878 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800879
880 // Although GroupDescriptions are not necessary for adding buckets to
881 // existing groups, we use one in the GroupChainElem. When the latter is
882 // processed, the info will be extracted for the bucketAdd call to groupService
883 GroupDescription l3ecmpGroupDesc =
884 new DefaultGroupDescription(
885 deviceId,
886 GroupDescription.Type.SELECT,
887 new GroupBuckets(Collections.singletonList(sbucket)),
888 l3ecmpGroupKey,
889 l3ecmpGroupId,
890 nextObjective.appId());
891 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
892
893 // update original NextGroup with new bucket-chain
894 // don't need to update pendingNextObjectives -- group already exists
895 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
896 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700897 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800898 allOriginalKeys.add(newBucketChain);
899 flowObjectiveStore.putNextGroup(nextObjective.id(),
900 new OfdpaNextGroup(allOriginalKeys, nextObjective));
901
902 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
903 deviceId, Integer.toHexString(l3ecmpGroupId),
904 l3ecmpGroupKey, nextObjective.id());
905 // send the innermost group
906 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700907 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
908 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
909 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800910
911 }
912
913 /**
914 * Removes the bucket in the top level group of a possible group-chain. Does
915 * not remove the groups in a group-chain pointed to by this bucket, as they
916 * may be in use (referenced by other groups) elsewhere.
917 *
918 * @param nextObjective the next group to remove a bucket from
919 * @param next the representation of the existing group-chain for this next objective
920 */
921 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
922 if (nextObjective.type() != NextObjective.Type.HASHED) {
923 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
924 nextObjective.type(), deviceId, nextObjective.id());
925 return;
926 }
927 Collection<TrafficTreatment> treatments = nextObjective.next();
928 TrafficTreatment treatment = treatments.iterator().next();
929 // find the bucket to remove by noting the outport, and figuring out the
930 // top-level group in the group-chain that indirectly references the port
931 PortNumber outport = null;
932 for (Instruction ins : treatment.allInstructions()) {
933 if (ins instanceof Instructions.OutputInstruction) {
934 outport = ((Instructions.OutputInstruction) ins).port();
935 break;
936 }
937 }
938 if (outport == null) {
939 log.error("next objective {} has no outport", nextObjective.id());
940 return;
941 }
942
Charles Chan361154b2016-03-24 10:23:39 -0700943 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800944 Deque<GroupKey> foundChain = null;
945 int index = 0;
946 for (Deque<GroupKey> gkeys : allgkeys) {
947 GroupKey groupWithPort = gkeys.peekLast();
948 Group group = groupService.getGroup(deviceId, groupWithPort);
949 if (group == null) {
950 log.warn("Inconsistent group chain");
951 continue;
952 }
953 // last group in group chain should have a single bucket pointing to port
954 List<Instruction> lastIns = group.buckets().buckets().iterator()
955 .next().treatment().allInstructions();
956 for (Instruction i : lastIns) {
957 if (i instanceof Instructions.OutputInstruction) {
958 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
959 if (lastport.equals(outport)) {
960 foundChain = gkeys;
961 break;
962 }
963 }
964 }
965 if (foundChain != null) {
966 break;
967 }
968 index++;
969 }
970 if (foundChain != null) {
971 //first groupkey is the one we want to modify
972 GroupKey modGroupKey = foundChain.peekFirst();
973 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
974 //second groupkey is the one we wish to remove the reference to
975 GroupKey pointedGroupKey = null;
976 int i = 0;
977 for (GroupKey gk : foundChain) {
978 if (i++ == 1) {
979 pointedGroupKey = gk;
980 break;
981 }
982 }
983 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
984 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
985 DefaultTrafficTreatment.builder()
986 .group(pointedGroup.id())
987 .build());
988 GroupBuckets removeBuckets = new GroupBuckets(Collections
989 .singletonList(bucket));
990 log.debug("Removing buckets from group id {} for next id {} in device {}",
991 modGroup.id(), nextObjective.id(), deviceId);
992 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
993 removeBuckets, modGroupKey,
994 nextObjective.appId());
995 //update store
996 allgkeys.remove(index);
997 flowObjectiveStore.putNextGroup(nextObjective.id(),
998 new OfdpaNextGroup(allgkeys, nextObjective));
999 } else {
1000 log.warn("Could not find appropriate group-chain for removing bucket"
1001 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
1002 }
1003 }
1004
1005 /**
1006 * Removes all groups in multiple possible group-chains that represent the next
1007 * objective.
1008 *
1009 * @param nextObjective the next objective to remove
1010 * @param next the NextGroup that represents the existing group-chain for
1011 * this next objective
1012 */
1013 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001014 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -08001015 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1016 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1017 flowObjectiveStore.removeNextGroup(nextObjective.id());
1018 }
1019
Saurav Das8be4e3a2016-03-11 17:19:07 -08001020 //////////////////////////////////////
1021 // Helper Methods and Classes
1022 //////////////////////////////////////
1023
1024 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1025 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1026 nextList.add(value);
1027 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
1028 .putIfAbsent(key, nextList);
1029 if (ret != null) {
1030 ret.add(value);
1031 }
1032 }
1033
Charles Chan425854b2016-04-11 15:32:12 -07001034 protected void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001035 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1036 new ConcurrentHashMap<GroupChainElem, Boolean>());
1037 gceSet.add(gce);
1038 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1039 if (retval != null) {
1040 retval.add(gce);
1041 }
1042 }
1043
Charles Chan188ebf52015-12-23 00:15:11 -08001044 /**
1045 * Processes next element of a group chain. Assumption is that if this
1046 * group points to another group, the latter has already been created
1047 * and this driver has received notification for it. A second assumption is
1048 * that if there is another group waiting for this group then the appropriate
1049 * stores already have the information to act upon the notification for the
1050 * creation of this group.
1051 * <p>
1052 * The processing of the GroupChainElement depends on the number of groups
1053 * this element is waiting on. For all group types other than SIMPLE, a
1054 * GroupChainElement could be waiting on multiple groups.
1055 *
1056 * @param gce the group chain element to be processed next
1057 */
1058 private void processGroupChain(GroupChainElem gce) {
1059 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1060 if (waitOnGroups != 0) {
1061 log.debug("GCE: {} not ready to be processed", gce);
1062 return;
1063 }
1064 log.debug("GCE: {} ready to be processed", gce);
1065 if (gce.addBucketToGroup) {
1066 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1067 gce.groupDescription.appCookie(),
1068 gce.groupDescription.buckets(),
1069 gce.groupDescription.appCookie(),
1070 gce.groupDescription.appId());
1071 } else {
1072 groupService.addGroup(gce.groupDescription);
1073 }
1074 }
1075
1076 private class GroupChecker implements Runnable {
1077 @Override
1078 public void run() {
1079 Set<GroupKey> keys = pendingGroups.keySet().stream()
1080 .filter(key -> groupService.getGroup(deviceId, key) != null)
1081 .collect(Collectors.toSet());
1082 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1083 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1084 .collect(Collectors.toSet());
1085 keys.addAll(otherkeys);
1086
1087 keys.stream().forEach(key ->
1088 processPendingGroupsOrNextObjectives(key, false));
1089 }
1090 }
1091
Saurav Das8be4e3a2016-03-11 17:19:07 -08001092 private class InnerGroupListener implements GroupListener {
1093 @Override
1094 public void event(GroupEvent event) {
1095 log.trace("received group event of type {}", event.type());
1096 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1097 GroupKey key = event.subject().appCookie();
1098 processPendingGroupsOrNextObjectives(key, true);
1099 }
1100 }
1101 }
1102
Charles Chan188ebf52015-12-23 00:15:11 -08001103 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1104 //first check for group chain
1105 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1106 if (gceSet != null) {
1107 for (GroupChainElem gce : gceSet) {
1108 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001109 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001110 (added) ? "ADDED" : "processed",
1111 key, deviceId,
1112 Integer.toHexString(gce.groupDescription.givenGroupId()));
1113 processGroupChain(gce);
1114 }
1115 } else {
1116 // otherwise chain complete - check for waiting nextObjectives
1117 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1118 if (nextGrpList != null) {
1119 pendingNextObjectives.invalidate(key);
1120 nextGrpList.forEach(nextGrp -> {
1121 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001122 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001123 (added) ? "ADDED" : "processed",
1124 key, deviceId, nextGrp.nextObjective().id(),
1125 Integer.toHexString(groupService.getGroup(deviceId, key)
1126 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001127 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001128 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1129 // check if addBuckets waiting for this completion
1130 NextObjective pendBkt = pendingBuckets
1131 .remove(nextGrp.nextObjective().id());
1132 if (pendBkt != null) {
1133 addBucketToGroup(pendBkt, nextGrp);
1134 }
1135 });
1136 }
1137 }
1138 }
1139
Charles Chan425854b2016-04-11 15:32:12 -07001140 protected int getNextAvailableIndex() {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001141 return (int) nextIndex.incrementAndGet();
1142 }
1143
Charles Chane849c192016-01-11 18:28:54 -08001144 /**
1145 * Returns a hash as the L2 Interface Group Key.
1146 *
1147 * Keep the lower 6-bit for port since port number usually smaller than 64.
1148 * Hash other information into remaining 28 bits.
1149 *
1150 * @param deviceId Device ID
1151 * @param vlanId VLAN ID
1152 * @param portNumber Port number
1153 * @return L2 interface group key
1154 */
Charles Chan425854b2016-04-11 15:32:12 -07001155 protected int l2InterfaceGroupKey(
Charles Chane849c192016-01-11 18:28:54 -08001156 DeviceId deviceId, VlanId vlanId, long portNumber) {
1157 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1158 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001159 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001160 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1161 }
1162
Charles Chan188ebf52015-12-23 00:15:11 -08001163 /**
1164 * Utility class for moving group information around.
1165 */
Charles Chan425854b2016-04-11 15:32:12 -07001166 protected class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001167 /**
1168 * Description of the inner-most group of the group chain.
1169 * It is always an L2 interface group.
1170 */
1171 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001172
Charles Chan5b9df8d2016-03-28 22:21:40 -07001173 /**
1174 * Description of the next group in the group chain.
1175 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1176 * It is possible that nextGroup is the same as the innerMostGroup.
1177 */
1178 private GroupDescription nextGroupDesc;
1179
1180 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1181 this.innerMostGroupDesc = innerMostGroupDesc;
1182 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001183 }
1184 }
1185
1186 /**
1187 * Represents an entire group-chain that implements a Next-Objective from
1188 * the application. The objective is represented as a list of deques, where
1189 * each deque is a separate chain of groups.
1190 * <p>
1191 * For example, an ECMP group with 3 buckets, where each bucket points to
1192 * a group chain of L3 Unicast and L2 interface groups will look like this:
1193 * <ul>
1194 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1195 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1196 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1197 * </ul>
1198 * where the first element of each deque is the same, representing the
1199 * top level ECMP group, while every other element represents a unique groupKey.
1200 * <p>
1201 * Also includes information about the next objective that
1202 * resulted in this group-chain.
1203 *
1204 */
1205 protected class OfdpaNextGroup implements NextGroup {
1206 private final NextObjective nextObj;
1207 private final List<Deque<GroupKey>> gkeys;
1208
1209 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1210 this.gkeys = gkeys;
1211 this.nextObj = nextObj;
1212 }
1213
Charles Chan188ebf52015-12-23 00:15:11 -08001214 public List<Deque<GroupKey>> groupKey() {
1215 return gkeys;
1216 }
1217
1218 public NextObjective nextObjective() {
1219 return nextObj;
1220 }
1221
1222 @Override
1223 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001224 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001225 }
1226 }
1227
1228 /**
1229 * Represents a group element that is part of a chain of groups.
1230 * Stores enough information to create a Group Description to add the group
1231 * to the switch by requesting the Group Service. Objects instantiating this
1232 * class are meant to be temporary and live as long as it is needed to wait for
1233 * preceding groups in the group chain to be created.
1234 */
Charles Chan425854b2016-04-11 15:32:12 -07001235 protected class GroupChainElem {
Charles Chan188ebf52015-12-23 00:15:11 -08001236 private GroupDescription groupDescription;
1237 private AtomicInteger waitOnGroups;
1238 private boolean addBucketToGroup;
1239
1240 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1241 boolean addBucketToGroup) {
1242 this.groupDescription = groupDescription;
1243 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1244 this.addBucketToGroup = addBucketToGroup;
1245 }
1246
1247 /**
1248 * This methods atomically decrements the counter for the number of
1249 * groups this GroupChainElement is waiting on, for notifications from
1250 * the Group Service. When this method returns a value of 0, this
1251 * GroupChainElement is ready to be processed.
1252 *
1253 * @return integer indication of the number of notifications being waited on
1254 */
1255 int decrementAndGetGroupsWaitedOn() {
1256 return waitOnGroups.decrementAndGet();
1257 }
1258
1259 @Override
1260 public String toString() {
1261 return (Integer.toHexString(groupDescription.givenGroupId()) +
1262 " groupKey: " + groupDescription.appCookie() +
1263 " waiting-on-groups: " + waitOnGroups.get() +
1264 " addBucketToGroup: " + addBucketToGroup +
1265 " device: " + deviceId);
1266 }
1267 }
1268}