blob: a9c2bb17fdc52cf990c6cd70fc6898dc2eb35dbc [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 =
120 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
121
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);
135 this.nextIndex = storageService.atomicCounterBuilder()
136 .withName("group-id-index-counter")
137 .build()
138 .asAtomicCounter();
Charles Chan188ebf52015-12-23 00:15:11 -0800139
140 pendingNextObjectives = CacheBuilder.newBuilder()
141 .expireAfterWrite(20, TimeUnit.SECONDS)
142 .removalListener((
143 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
144 if (notification.getCause() == RemovalCause.EXPIRED) {
145 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700146 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800147 ObjectiveError.GROUPINSTALLATIONFAILED));
148
149 }
150 }).build();
151 pendingGroups = new ConcurrentHashMap<>();
152 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
153
154 groupService.addListener(new InnerGroupListener());
155 }
156
Saurav Das8be4e3a2016-03-11 17:19:07 -0800157 //////////////////////////////////////
158 // Group Creation
159 //////////////////////////////////////
160
Charles Chan188ebf52015-12-23 00:15:11 -0800161 protected void addGroup(NextObjective nextObjective) {
162 switch (nextObjective.type()) {
163 case SIMPLE:
164 Collection<TrafficTreatment> treatments = nextObjective.next();
165 if (treatments.size() != 1) {
166 log.error("Next Objectives of type Simple should only have a "
167 + "single Traffic Treatment. Next Objective Id:{}",
168 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700169 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800170 return;
171 }
172 processSimpleNextObjective(nextObjective);
173 break;
174 case BROADCAST:
175 processBroadcastNextObjective(nextObjective);
176 break;
177 case HASHED:
178 processHashedNextObjective(nextObjective);
179 break;
180 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700181 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800182 log.warn("Unsupported next objective type {}", nextObjective.type());
183 break;
184 default:
Charles Chan361154b2016-03-24 10:23:39 -0700185 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800186 log.warn("Unknown next objective type {}", nextObjective.type());
187 }
188 }
189
190 /**
191 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
192 * a chain of groups. The simple Next Objective passed
193 * in by the application has to be broken up into a group chain
194 * comprising of an L3 Unicast Group that points to an L2 Interface
195 * Group which in-turn points to an output port. In some cases, the simple
196 * next Objective can just be an L2 interface without the need for chaining.
197 *
198 * @param nextObj the nextObjective of type SIMPLE
199 */
200 private void processSimpleNextObjective(NextObjective nextObj) {
201 TrafficTreatment treatment = nextObj.next().iterator().next();
202 // determine if plain L2 or L3->L2
203 boolean plainL2 = true;
204 for (Instruction ins : treatment.allInstructions()) {
205 if (ins.type() == Instruction.Type.L2MODIFICATION) {
206 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
207 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
208 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
209 plainL2 = false;
210 break;
211 }
212 }
213 }
214
215 if (plainL2) {
216 createL2InterfaceGroup(nextObj);
217 return;
218 }
219
220 // break up simple next objective to GroupChain objects
221 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800222 nextObj.appId(), false,
223 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800224 if (groupInfo == null) {
225 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
226 return;
227 }
228 // create object for local and distributed storage
229 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700230 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
231 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800232 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
233 Collections.singletonList(gkeyChain),
234 nextObj);
235
236 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700237 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800238
239 // now we are ready to send the l2 groupDescription (inner), as all the stores
240 // that will get async replies have been updated. By waiting to update
241 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700242 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800243 }
244
Charles Chan188ebf52015-12-23 00:15:11 -0800245 /**
246 * Creates a simple L2 Interface Group.
247 *
248 * @param nextObj the next Objective
249 */
250 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700251 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
252 if (assignedVlan == null) {
253 log.warn("VLAN ID required by simple next obj is missing. Abort.");
254 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800255 return;
256 }
257
Charles Chan5b9df8d2016-03-28 22:21:40 -0700258 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800259
Charles Chan5b9df8d2016-03-28 22:21:40 -0700260 // There is only one L2 interface group in this case
261 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800262
Charles Chan5b9df8d2016-03-28 22:21:40 -0700263 // Put all dependency information into allGroupKeys
264 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
265 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
266 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
267 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800268
Charles Chan5b9df8d2016-03-28 22:21:40 -0700269 // Point the next objective to this group
270 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
271 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
272
273 // Start installing the inner-most group
274 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800275 }
276
277 /**
278 * Creates one of two possible group-chains from the treatment
279 * passed in. Depending on the MPLS boolean, this method either creates
Charles Chan425854b2016-04-11 15:32:12 -0700280 * an L3Unicast Group --&gt; L2Interface Group, if mpls is false;
281 * or MPLSInterface Group --&gt; L2Interface Group, if mpls is true;
Charles Chan188ebf52015-12-23 00:15:11 -0800282 * The returned 'inner' group description is always the L2 Interface group.
283 *
284 * @param treatment that needs to be broken up to create the group chain
285 * @param nextId of the next objective that needs this group chain
286 * @param appId of the application that sent this next objective
287 * @param mpls determines if L3Unicast or MPLSInterface group is created
288 * @param meta metadata passed in by the application as part of the nextObjective
289 * @return GroupInfo containing the GroupDescription of the
290 * L2Interface group(inner) and the GroupDescription of the (outer)
291 * L3Unicast/MPLSInterface group. May return null if there is an
292 * error in processing the chain
293 */
Charles Chan425854b2016-04-11 15:32:12 -0700294 protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800295 ApplicationId appId, boolean mpls,
296 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800297 // for the l2interface group, get vlan and port info
298 // for the outer group, get the src/dst mac, and vlan info
299 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
300 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
301 VlanId vlanid = null;
302 long portNum = 0;
303 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800304 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800305 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800306 for (Instruction ins : treatment.allInstructions()) {
307 if (ins.type() == Instruction.Type.L2MODIFICATION) {
308 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
309 switch (l2ins.subtype()) {
310 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800311 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
312 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800313 break;
314 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800315 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
316 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800317 break;
318 case VLAN_ID:
319 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
Charles Chan32562522016-04-07 14:37:14 -0700320 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
321 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800322 setVlan = true;
323 break;
324 case VLAN_POP:
325 innerTtb.popVlan();
326 popVlan = true;
327 break;
328 case DEC_MPLS_TTL:
329 case MPLS_LABEL:
330 case MPLS_POP:
331 case MPLS_PUSH:
332 case VLAN_PCP:
333 case VLAN_PUSH:
334 default:
335 break;
336 }
337 } else if (ins.type() == Instruction.Type.OUTPUT) {
338 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
339 innerTtb.add(ins);
340 } else {
341 log.warn("Driver does not handle this type of TrafficTreatment"
342 + " instruction in nextObjectives: {}", ins.type());
343 }
344 }
345
346 if (vlanid == null && meta != null) {
347 // use metadata if available
348 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
349 if (vidCriterion != null) {
350 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
351 }
352 // if vlan is not set, use the vlan in metadata for outerTtb
353 if (vlanid != null && !setVlan) {
Charles Chan32562522016-04-07 14:37:14 -0700354 OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanid);
355 outerTtb.extension(ofdpaSetVlanVid, deviceId);
Charles Chan188ebf52015-12-23 00:15:11 -0800356 }
357 }
358
359 if (vlanid == null) {
360 log.error("Driver cannot process an L2/L3 group chain without "
361 + "egress vlan information for dev: {} port:{}",
362 deviceId, portNum);
363 return null;
364 }
365
366 if (!setVlan && !popVlan) {
367 // untagged outgoing port
368 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
369 temp.popVlan();
370 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
371 innerTtb = temp;
372 }
373
374 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800375 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800376 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800377 // but different for the same portnumber on different devices. Also different
378 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800379 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700380 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800381
382 // assemble information for outer group
383 GroupDescription outerGrpDesc = null;
384 if (mpls) {
385 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800386 int mplsInterfaceIndex = getNextAvailableIndex();
387 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
388 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700389 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800390 outerTtb.group(new DefaultGroupId(l2groupId));
391 // create the mpls-interface group description to wait for the
392 // l2 interface group to be processed
393 GroupBucket mplsinterfaceGroupBucket =
394 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
395 outerGrpDesc = new DefaultGroupDescription(
396 deviceId,
397 GroupDescription.Type.INDIRECT,
398 new GroupBuckets(Collections.singletonList(
399 mplsinterfaceGroupBucket)),
400 mplsgroupkey,
401 mplsgroupId,
402 appId);
403 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
404 deviceId, Integer.toHexString(mplsgroupId),
405 mplsgroupkey, nextId);
406 } else {
407 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800408 int l3unicastIndex = getNextAvailableIndex();
409 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
410 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700411 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800412 outerTtb.group(new DefaultGroupId(l2groupId));
413 // create the l3unicast group description to wait for the
414 // l2 interface group to be processed
415 GroupBucket l3unicastGroupBucket =
416 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
417 outerGrpDesc = new DefaultGroupDescription(
418 deviceId,
419 GroupDescription.Type.INDIRECT,
420 new GroupBuckets(Collections.singletonList(
421 l3unicastGroupBucket)),
422 l3groupkey,
423 l3groupId,
424 appId);
425 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
426 deviceId, Integer.toHexString(l3groupId),
427 l3groupkey, nextId);
428 }
429
430 // store l2groupkey with the groupChainElem for the outer-group that depends on it
431 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
432 updatePendingGroups(l2groupkey, gce);
433
434 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700435 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800436 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
437 GroupDescription l2groupDescription =
438 new DefaultGroupDescription(
439 deviceId,
440 GroupDescription.Type.INDIRECT,
441 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700442 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800443 l2groupkey,
444 l2groupId,
445 appId);
446 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
447 deviceId, Integer.toHexString(l2groupId),
448 l2groupkey, nextId);
449 return new GroupInfo(l2groupDescription, outerGrpDesc);
450
451 }
452
453 /**
454 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
455 * a chain of groups. The broadcast Next Objective passed in by the application
456 * has to be broken up into a group chain comprising of an
457 * L2 Flood group whose buckets point to L2 Interface groups.
458 *
459 * @param nextObj the nextObjective of type BROADCAST
460 */
461 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700462 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
463 if (assignedVlan == null) {
464 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
465 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800466 return;
467 }
Charles Chan188ebf52015-12-23 00:15:11 -0800468
Charles Chan5b9df8d2016-03-28 22:21:40 -0700469 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
470
471 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
472 if (ipDst != null) {
473 if (ipDst.isMulticast()) {
474 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
475 } else {
476 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
477 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
478 return;
479 }
480 } else {
481 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
482 }
483 }
484
485 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
486 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
487
488 // break up broadcast next objective to multiple groups
489 Collection<TrafficTreatment> buckets = nextObj.next();
490
Charles Chan188ebf52015-12-23 00:15:11 -0800491 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800492 for (TrafficTreatment treatment : buckets) {
493 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
494 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700495 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800496 // ensure that the only allowed treatments are pop-vlan and output
497 for (Instruction ins : treatment.allInstructions()) {
498 if (ins.type() == Instruction.Type.L2MODIFICATION) {
499 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
500 switch (l2ins.subtype()) {
501 case VLAN_POP:
502 newTreatment.add(l2ins);
503 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700504 case VLAN_ID:
505 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
506 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800507 default:
508 log.debug("action {} not permitted for broadcast nextObj",
509 l2ins.subtype());
510 break;
511 }
512 } else if (ins.type() == Instruction.Type.OUTPUT) {
513 portNum = ((Instructions.OutputInstruction) ins).port();
514 newTreatment.add(ins);
515 } else {
Charles Chane849c192016-01-11 18:28:54 -0800516 log.debug("TrafficTreatment of type {} not permitted in " +
517 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800518 }
519 }
520
Charles Chan188ebf52015-12-23 00:15:11 -0800521 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700522 VlanId l2InterfaceGroupVlan =
523 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
524 egressVlan : assignedVlan;
525 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
526 final GroupKey l2InterfaceGroupKey =
527 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
528 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800529 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700530 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800531 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700532 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800533 new DefaultGroupDescription(
534 deviceId,
535 GroupDescription.Type.INDIRECT,
536 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700537 l2InterfaceGroupBucket)),
538 l2InterfaceGroupKey,
539 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800540 nextObj.appId());
541 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700542 deviceId, Integer.toHexString(l2InterfaceGroupId),
543 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800544
Charles Chan5b9df8d2016-03-28 22:21:40 -0700545 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
546 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800547 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700548 return groupInfoBuilder.build();
549 }
Charles Chan188ebf52015-12-23 00:15:11 -0800550
Charles Chan5b9df8d2016-03-28 22:21:40 -0700551 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800552 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800553 // since there can be only one flood group for a vlan, its index is always the same - 0
554 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800555 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700556 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700557
Charles Chan188ebf52015-12-23 00:15:11 -0800558 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700559 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
560 groupInfos.forEach(groupInfo -> {
561 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800562 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
563 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
564 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
565 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700566 });
Charles Chan188ebf52015-12-23 00:15:11 -0800567 // create the l2flood group-description to wait for all the
568 // l2interface groups to be processed
569 GroupDescription l2floodGroupDescription =
570 new DefaultGroupDescription(
571 deviceId,
572 GroupDescription.Type.ALL,
573 new GroupBuckets(l2floodBuckets),
574 l2floodgroupkey,
575 l2floodgroupId,
576 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800577 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
578 deviceId, Integer.toHexString(l2floodgroupId),
579 l2floodgroupkey, nextObj.id());
580
Charles Chan5b9df8d2016-03-28 22:21:40 -0700581 // Put all dependency information into allGroupKeys
582 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
583 groupInfos.forEach(groupInfo -> {
584 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
585 // In this case we should have L2 interface group only
586 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
587 gkeyChain.addFirst(l2floodgroupkey);
588 allGroupKeys.add(gkeyChain);
589 });
Charles Chan188ebf52015-12-23 00:15:11 -0800590
Charles Chan5b9df8d2016-03-28 22:21:40 -0700591 // Point the next objective to this group
592 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800593 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
594
Charles Chan5b9df8d2016-03-28 22:21:40 -0700595 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
596 groupInfos.size(), false);
597 groupInfos.forEach(groupInfo -> {
598 // Point this group to the next group
599 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
600 // Start installing the inner-most group
601 groupService.addGroup(groupInfo.innerMostGroupDesc);
602 });
603 }
604
605 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
606 List<GroupBucket> l3McastBuckets = new ArrayList<>();
607 groupInfos.forEach(groupInfo -> {
608 // Points to L3 interface group if there is one.
609 // Otherwise points to L2 interface group directly.
610 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
611 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
612 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
613 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
614 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
615 l3McastBuckets.add(abucket);
616 });
617
618 int l3MulticastIndex = getNextAvailableIndex();
619 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
620 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
621
622 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
623 GroupDescription.Type.ALL,
624 new GroupBuckets(l3McastBuckets),
625 l3MulticastGroupKey,
626 l3MulticastGroupId,
627 nextObj.appId());
628
629 // Put all dependency information into allGroupKeys
630 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
631 groupInfos.forEach(groupInfo -> {
632 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
633 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
634 // Add L3 interface group to the chain if there is one.
635 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
636 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
637 }
638 gkeyChain.addFirst(l3MulticastGroupKey);
639 allGroupKeys.add(gkeyChain);
640 });
641
642 // Point the next objective to this group
643 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
644 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
645
646 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
647 groupInfos.size(), false);
648 groupInfos.forEach(groupInfo -> {
649 // Point this group (L3 multicast) to the next group
650 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
651
652 // Point next group to inner-most group, if any
653 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
654 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
655 1, false);
656 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
657 }
658
659 // Start installing the inner-most group
660 groupService.addGroup(groupInfo.innerMostGroupDesc);
661 });
Charles Chan188ebf52015-12-23 00:15:11 -0800662 }
663
Charles Chan188ebf52015-12-23 00:15:11 -0800664 /**
665 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
666 * a chain of groups. The hashed Next Objective passed in by the application
667 * has to be broken up into a group chain comprising of an
668 * L3 ECMP group as the top level group. Buckets of this group can point
669 * to a variety of groups in a group chain, depending on the whether
670 * MPLS labels are being pushed or not.
671 * <p>
672 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
673 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
674 * check the nextObjective meta to see what is matching before being
675 * sent to this nextObjective.
676 *
677 * @param nextObj the nextObjective of type HASHED
678 */
679 private void processHashedNextObjective(NextObjective nextObj) {
680 // storage for all group keys in the chain of groups created
681 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
682 List<GroupInfo> unsentGroups = new ArrayList<>();
683 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
684
685 // now we can create the outermost L3 ECMP group
686 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
687 for (GroupInfo gi : unsentGroups) {
688 // create ECMP bucket to point to the outer group
689 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700690 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800691 GroupBucket sbucket = DefaultGroupBucket
692 .createSelectGroupBucket(ttb.build());
693 l3ecmpGroupBuckets.add(sbucket);
694 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800695 int l3ecmpIndex = getNextAvailableIndex();
696 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
697 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700698 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800699 GroupDescription l3ecmpGroupDesc =
700 new DefaultGroupDescription(
701 deviceId,
702 GroupDescription.Type.SELECT,
703 new GroupBuckets(l3ecmpGroupBuckets),
704 l3ecmpGroupKey,
705 l3ecmpGroupId,
706 nextObj.appId());
707 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
708 l3ecmpGroupBuckets.size(),
709 false);
710
711 // create objects for local and distributed storage
712 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
713 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
714
715 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
716 // that depends on it
717 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
718
719 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
720 deviceId, Integer.toHexString(l3ecmpGroupId),
721 l3ecmpGroupKey, nextObj.id());
722 // finally we are ready to send the innermost groups
723 for (GroupInfo gi : unsentGroups) {
724 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700725 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
726 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
727 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800728 }
729
730 }
731
732 /**
733 * Creates group chains for all buckets in a hashed group, and stores the
734 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
735 * should be empty.
736 * <p>
737 * Does not create the top level ECMP group. Does not actually send the
738 * groups to the groupService.
739 *
740 * @param nextObj the Next Objective with buckets that need to be converted
741 * to group chains
742 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
743 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
744 */
745 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800746 List<Deque<GroupKey>> allGroupKeys,
747 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800748 // break up hashed next objective to multiple groups
749 Collection<TrafficTreatment> buckets = nextObj.next();
750
751 for (TrafficTreatment bucket : buckets) {
752 //figure out how many labels are pushed in each bucket
753 int labelsPushed = 0;
754 MplsLabel innermostLabel = null;
755 for (Instruction ins : bucket.allInstructions()) {
756 if (ins.type() == Instruction.Type.L2MODIFICATION) {
757 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
758 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
759 labelsPushed++;
760 }
761 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
762 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800763 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800764 }
765 }
766 }
767 }
768
769 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
770 // XXX we only deal with 0 and 1 label push right now
771 if (labelsPushed == 0) {
772 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
773 nextObj.appId(), false,
774 nextObj.meta());
775 if (nolabelGroupInfo == null) {
776 log.error("Could not process nextObj={} in dev:{}",
777 nextObj.id(), deviceId);
778 return;
779 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700780 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
781 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800782
783 // we can't send the inner group description yet, as we have to
784 // create the dependent ECMP group first. So we store..
785 unsentGroups.add(nolabelGroupInfo);
786
787 } else if (labelsPushed == 1) {
788 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
789 nextObj.appId(), true,
790 nextObj.meta());
791 if (onelabelGroupInfo == null) {
792 log.error("Could not process nextObj={} in dev:{}",
793 nextObj.id(), deviceId);
794 return;
795 }
796 // we need to add another group to this chain - the L3VPN group
797 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
798 l3vpnTtb.pushMpls()
799 .setMpls(innermostLabel)
800 .setMplsBos(true)
801 .copyTtlOut()
802 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700803 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800804 GroupBucket l3vpnGrpBkt =
805 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800806 int l3vpnIndex = getNextAvailableIndex();
807 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
808 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700809 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800810 GroupDescription l3vpnGroupDesc =
811 new DefaultGroupDescription(
812 deviceId,
813 GroupDescription.Type.INDIRECT,
814 new GroupBuckets(Collections.singletonList(
815 l3vpnGrpBkt)),
816 l3vpngroupkey,
817 l3vpngroupId,
818 nextObj.appId());
819 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700820 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800821
Charles Chan5b9df8d2016-03-28 22:21:40 -0700822 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
823 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800824 gkeyChain.addFirst(l3vpngroupkey);
825
826 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700827 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800828
829 // we can't send the innermost group yet, as we have to create
830 // the dependent ECMP group first. So we store ...
831 unsentGroups.add(onelabelGroupInfo);
832
833 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
834 deviceId, Integer.toHexString(l3vpngroupId),
835 l3vpngroupkey, nextObj.id());
836
837 } else {
838 log.warn("Driver currently does not handle more than 1 MPLS "
839 + "labels. Not processing nextObjective {}", nextObj.id());
840 return;
841 }
842
843 // all groups in this chain
844 allGroupKeys.add(gkeyChain);
845 }
846 }
847
Saurav Das8be4e3a2016-03-11 17:19:07 -0800848 //////////////////////////////////////
849 // Group Editing
850 //////////////////////////////////////
851
Charles Chan188ebf52015-12-23 00:15:11 -0800852 /**
853 * Adds a bucket to the top level group of a group-chain, and creates the chain.
854 *
855 * @param nextObjective the next group to add a bucket to
856 * @param next the representation of the existing group-chain for this next objective
857 */
858 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
859 if (nextObjective.type() != NextObjective.Type.HASHED) {
860 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
861 nextObjective.type(), deviceId, nextObjective.id());
862 return;
863 }
864 if (nextObjective.next().size() > 1) {
865 log.warn("Only one bucket can be added at a time");
866 return;
867 }
868 // storage for all group keys in the chain of groups created
869 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
870 List<GroupInfo> unsentGroups = new ArrayList<>();
871 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
872
873 // now we can create the outermost L3 ECMP group bucket to add
874 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
875 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700876 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800877 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
878
879 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800880 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700881 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800882
883 // Although GroupDescriptions are not necessary for adding buckets to
884 // existing groups, we use one in the GroupChainElem. When the latter is
885 // processed, the info will be extracted for the bucketAdd call to groupService
886 GroupDescription l3ecmpGroupDesc =
887 new DefaultGroupDescription(
888 deviceId,
889 GroupDescription.Type.SELECT,
890 new GroupBuckets(Collections.singletonList(sbucket)),
891 l3ecmpGroupKey,
892 l3ecmpGroupId,
893 nextObjective.appId());
894 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
895
896 // update original NextGroup with new bucket-chain
897 // don't need to update pendingNextObjectives -- group already exists
898 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
899 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700900 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800901 allOriginalKeys.add(newBucketChain);
902 flowObjectiveStore.putNextGroup(nextObjective.id(),
903 new OfdpaNextGroup(allOriginalKeys, nextObjective));
904
905 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
906 deviceId, Integer.toHexString(l3ecmpGroupId),
907 l3ecmpGroupKey, nextObjective.id());
908 // send the innermost group
909 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700910 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
911 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
912 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800913
914 }
915
916 /**
917 * Removes the bucket in the top level group of a possible group-chain. Does
918 * not remove the groups in a group-chain pointed to by this bucket, as they
919 * may be in use (referenced by other groups) elsewhere.
920 *
921 * @param nextObjective the next group to remove a bucket from
922 * @param next the representation of the existing group-chain for this next objective
923 */
924 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
925 if (nextObjective.type() != NextObjective.Type.HASHED) {
926 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
927 nextObjective.type(), deviceId, nextObjective.id());
928 return;
929 }
930 Collection<TrafficTreatment> treatments = nextObjective.next();
931 TrafficTreatment treatment = treatments.iterator().next();
932 // find the bucket to remove by noting the outport, and figuring out the
933 // top-level group in the group-chain that indirectly references the port
934 PortNumber outport = null;
935 for (Instruction ins : treatment.allInstructions()) {
936 if (ins instanceof Instructions.OutputInstruction) {
937 outport = ((Instructions.OutputInstruction) ins).port();
938 break;
939 }
940 }
941 if (outport == null) {
942 log.error("next objective {} has no outport", nextObjective.id());
943 return;
944 }
945
Charles Chan361154b2016-03-24 10:23:39 -0700946 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800947 Deque<GroupKey> foundChain = null;
948 int index = 0;
949 for (Deque<GroupKey> gkeys : allgkeys) {
950 GroupKey groupWithPort = gkeys.peekLast();
951 Group group = groupService.getGroup(deviceId, groupWithPort);
952 if (group == null) {
953 log.warn("Inconsistent group chain");
954 continue;
955 }
956 // last group in group chain should have a single bucket pointing to port
957 List<Instruction> lastIns = group.buckets().buckets().iterator()
958 .next().treatment().allInstructions();
959 for (Instruction i : lastIns) {
960 if (i instanceof Instructions.OutputInstruction) {
961 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
962 if (lastport.equals(outport)) {
963 foundChain = gkeys;
964 break;
965 }
966 }
967 }
968 if (foundChain != null) {
969 break;
970 }
971 index++;
972 }
973 if (foundChain != null) {
974 //first groupkey is the one we want to modify
975 GroupKey modGroupKey = foundChain.peekFirst();
976 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
977 //second groupkey is the one we wish to remove the reference to
978 GroupKey pointedGroupKey = null;
979 int i = 0;
980 for (GroupKey gk : foundChain) {
981 if (i++ == 1) {
982 pointedGroupKey = gk;
983 break;
984 }
985 }
986 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
987 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
988 DefaultTrafficTreatment.builder()
989 .group(pointedGroup.id())
990 .build());
991 GroupBuckets removeBuckets = new GroupBuckets(Collections
992 .singletonList(bucket));
993 log.debug("Removing buckets from group id {} for next id {} in device {}",
994 modGroup.id(), nextObjective.id(), deviceId);
995 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
996 removeBuckets, modGroupKey,
997 nextObjective.appId());
998 //update store
999 allgkeys.remove(index);
1000 flowObjectiveStore.putNextGroup(nextObjective.id(),
1001 new OfdpaNextGroup(allgkeys, nextObjective));
1002 } else {
1003 log.warn("Could not find appropriate group-chain for removing bucket"
1004 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
1005 }
1006 }
1007
1008 /**
1009 * Removes all groups in multiple possible group-chains that represent the next
1010 * objective.
1011 *
1012 * @param nextObjective the next objective to remove
1013 * @param next the NextGroup that represents the existing group-chain for
1014 * this next objective
1015 */
1016 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001017 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -08001018 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1019 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1020 flowObjectiveStore.removeNextGroup(nextObjective.id());
1021 }
1022
Saurav Das8be4e3a2016-03-11 17:19:07 -08001023 //////////////////////////////////////
1024 // Helper Methods and Classes
1025 //////////////////////////////////////
1026
1027 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1028 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1029 nextList.add(value);
1030 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
1031 .putIfAbsent(key, nextList);
1032 if (ret != null) {
1033 ret.add(value);
1034 }
1035 }
1036
Charles Chan425854b2016-04-11 15:32:12 -07001037 protected void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001038 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1039 new ConcurrentHashMap<GroupChainElem, Boolean>());
1040 gceSet.add(gce);
1041 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1042 if (retval != null) {
1043 retval.add(gce);
1044 }
1045 }
1046
Charles Chan188ebf52015-12-23 00:15:11 -08001047 /**
1048 * Processes next element of a group chain. Assumption is that if this
1049 * group points to another group, the latter has already been created
1050 * and this driver has received notification for it. A second assumption is
1051 * that if there is another group waiting for this group then the appropriate
1052 * stores already have the information to act upon the notification for the
1053 * creation of this group.
1054 * <p>
1055 * The processing of the GroupChainElement depends on the number of groups
1056 * this element is waiting on. For all group types other than SIMPLE, a
1057 * GroupChainElement could be waiting on multiple groups.
1058 *
1059 * @param gce the group chain element to be processed next
1060 */
1061 private void processGroupChain(GroupChainElem gce) {
1062 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1063 if (waitOnGroups != 0) {
1064 log.debug("GCE: {} not ready to be processed", gce);
1065 return;
1066 }
1067 log.debug("GCE: {} ready to be processed", gce);
1068 if (gce.addBucketToGroup) {
1069 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1070 gce.groupDescription.appCookie(),
1071 gce.groupDescription.buckets(),
1072 gce.groupDescription.appCookie(),
1073 gce.groupDescription.appId());
1074 } else {
1075 groupService.addGroup(gce.groupDescription);
1076 }
1077 }
1078
1079 private class GroupChecker implements Runnable {
1080 @Override
1081 public void run() {
1082 Set<GroupKey> keys = pendingGroups.keySet().stream()
1083 .filter(key -> groupService.getGroup(deviceId, key) != null)
1084 .collect(Collectors.toSet());
1085 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1086 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1087 .collect(Collectors.toSet());
1088 keys.addAll(otherkeys);
1089
1090 keys.stream().forEach(key ->
1091 processPendingGroupsOrNextObjectives(key, false));
1092 }
1093 }
1094
Saurav Das8be4e3a2016-03-11 17:19:07 -08001095 private class InnerGroupListener implements GroupListener {
1096 @Override
1097 public void event(GroupEvent event) {
1098 log.trace("received group event of type {}", event.type());
1099 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1100 GroupKey key = event.subject().appCookie();
1101 processPendingGroupsOrNextObjectives(key, true);
1102 }
1103 }
1104 }
1105
Charles Chan188ebf52015-12-23 00:15:11 -08001106 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1107 //first check for group chain
1108 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1109 if (gceSet != null) {
1110 for (GroupChainElem gce : gceSet) {
1111 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001112 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001113 (added) ? "ADDED" : "processed",
1114 key, deviceId,
1115 Integer.toHexString(gce.groupDescription.givenGroupId()));
1116 processGroupChain(gce);
1117 }
1118 } else {
1119 // otherwise chain complete - check for waiting nextObjectives
1120 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1121 if (nextGrpList != null) {
1122 pendingNextObjectives.invalidate(key);
1123 nextGrpList.forEach(nextGrp -> {
1124 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001125 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001126 (added) ? "ADDED" : "processed",
1127 key, deviceId, nextGrp.nextObjective().id(),
1128 Integer.toHexString(groupService.getGroup(deviceId, key)
1129 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001130 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001131 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1132 // check if addBuckets waiting for this completion
1133 NextObjective pendBkt = pendingBuckets
1134 .remove(nextGrp.nextObjective().id());
1135 if (pendBkt != null) {
1136 addBucketToGroup(pendBkt, nextGrp);
1137 }
1138 });
1139 }
1140 }
1141 }
1142
Charles Chan425854b2016-04-11 15:32:12 -07001143 protected int getNextAvailableIndex() {
Saurav Das8be4e3a2016-03-11 17:19:07 -08001144 return (int) nextIndex.incrementAndGet();
1145 }
1146
Charles Chane849c192016-01-11 18:28:54 -08001147 /**
1148 * Returns a hash as the L2 Interface Group Key.
1149 *
1150 * Keep the lower 6-bit for port since port number usually smaller than 64.
1151 * Hash other information into remaining 28 bits.
1152 *
1153 * @param deviceId Device ID
1154 * @param vlanId VLAN ID
1155 * @param portNumber Port number
1156 * @return L2 interface group key
1157 */
Charles Chan425854b2016-04-11 15:32:12 -07001158 protected int l2InterfaceGroupKey(
Charles Chane849c192016-01-11 18:28:54 -08001159 DeviceId deviceId, VlanId vlanId, long portNumber) {
1160 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1161 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001162 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001163 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1164 }
1165
Charles Chan188ebf52015-12-23 00:15:11 -08001166 /**
1167 * Utility class for moving group information around.
1168 */
Charles Chan425854b2016-04-11 15:32:12 -07001169 protected class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001170 /**
1171 * Description of the inner-most group of the group chain.
1172 * It is always an L2 interface group.
1173 */
1174 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001175
Charles Chan5b9df8d2016-03-28 22:21:40 -07001176 /**
1177 * Description of the next group in the group chain.
1178 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1179 * It is possible that nextGroup is the same as the innerMostGroup.
1180 */
1181 private GroupDescription nextGroupDesc;
1182
1183 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1184 this.innerMostGroupDesc = innerMostGroupDesc;
1185 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001186 }
1187 }
1188
1189 /**
1190 * Represents an entire group-chain that implements a Next-Objective from
1191 * the application. The objective is represented as a list of deques, where
1192 * each deque is a separate chain of groups.
1193 * <p>
1194 * For example, an ECMP group with 3 buckets, where each bucket points to
1195 * a group chain of L3 Unicast and L2 interface groups will look like this:
1196 * <ul>
1197 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1198 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1199 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1200 * </ul>
1201 * where the first element of each deque is the same, representing the
1202 * top level ECMP group, while every other element represents a unique groupKey.
1203 * <p>
1204 * Also includes information about the next objective that
1205 * resulted in this group-chain.
1206 *
1207 */
1208 protected class OfdpaNextGroup implements NextGroup {
1209 private final NextObjective nextObj;
1210 private final List<Deque<GroupKey>> gkeys;
1211
1212 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1213 this.gkeys = gkeys;
1214 this.nextObj = nextObj;
1215 }
1216
Charles Chan188ebf52015-12-23 00:15:11 -08001217 public List<Deque<GroupKey>> groupKey() {
1218 return gkeys;
1219 }
1220
1221 public NextObjective nextObjective() {
1222 return nextObj;
1223 }
1224
1225 @Override
1226 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001227 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001228 }
1229 }
1230
1231 /**
1232 * Represents a group element that is part of a chain of groups.
1233 * Stores enough information to create a Group Description to add the group
1234 * to the switch by requesting the Group Service. Objects instantiating this
1235 * class are meant to be temporary and live as long as it is needed to wait for
1236 * preceding groups in the group chain to be created.
1237 */
Charles Chan425854b2016-04-11 15:32:12 -07001238 protected class GroupChainElem {
Charles Chan188ebf52015-12-23 00:15:11 -08001239 private GroupDescription groupDescription;
1240 private AtomicInteger waitOnGroups;
1241 private boolean addBucketToGroup;
1242
1243 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1244 boolean addBucketToGroup) {
1245 this.groupDescription = groupDescription;
1246 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1247 this.addBucketToGroup = addBucketToGroup;
1248 }
1249
1250 /**
1251 * This methods atomically decrements the counter for the number of
1252 * groups this GroupChainElement is waiting on, for notifications from
1253 * the Group Service. When this method returns a value of 0, this
1254 * GroupChainElement is ready to be processed.
1255 *
1256 * @return integer indication of the number of notifications being waited on
1257 */
1258 int decrementAndGetGroupsWaitedOn() {
1259 return waitOnGroups.decrementAndGet();
1260 }
1261
1262 @Override
1263 public String toString() {
1264 return (Integer.toHexString(groupDescription.givenGroupId()) +
1265 " groupKey: " + groupDescription.appCookie() +
1266 " waiting-on-groups: " + waitOnGroups.get() +
1267 " addBucketToGroup: " + addBucketToGroup +
1268 " device: " + deviceId);
1269 }
1270 }
1271}