2 * Copyright 2015 Open Networking Laboratory
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.onosproject.segmentrouting.grouphandler;
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static org.slf4j.LoggerFactory.getLogger;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
29 import java.util.stream.Collectors;
31 import org.onlab.packet.Ip4Prefix;
32 import org.onlab.packet.IpPrefix;
33 import org.onlab.packet.MacAddress;
34 import org.onlab.packet.MplsLabel;
35 import org.onlab.util.KryoNamespace;
36 import org.onosproject.core.ApplicationId;
37 import org.onosproject.net.DeviceId;
38 import org.onosproject.net.Link;
39 import org.onosproject.net.PortNumber;
40 import org.onosproject.net.flow.DefaultTrafficTreatment;
41 import org.onosproject.net.flow.TrafficTreatment;
42 import org.onosproject.net.flowobjective.DefaultNextObjective;
43 import org.onosproject.net.flowobjective.FlowObjectiveService;
44 import org.onosproject.net.flowobjective.NextObjective;
45 import org.onosproject.net.flowobjective.Objective;
46 import org.onosproject.net.flowobjective.ObjectiveContext;
47 import org.onosproject.net.flowobjective.ObjectiveError;
48 import org.onosproject.net.group.DefaultGroupKey;
49 import org.onosproject.net.group.GroupKey;
50 import org.onosproject.net.link.LinkService;
51 import org.onosproject.store.service.EventuallyConsistentMap;
52 import org.slf4j.Logger;
55 * Default ECMP group handler creation module. This component creates a set of
56 * ECMP groups for every neighbor that this device is connected to based on
57 * whether the current device is an edge device or a transit device.
59 public class DefaultGroupHandler {
60 protected static final Logger log = getLogger(DefaultGroupHandler.class);
62 protected final DeviceId deviceId;
63 protected final ApplicationId appId;
64 protected final DeviceProperties deviceConfig;
65 protected final List<Integer> allSegmentIds;
66 protected final int nodeSegmentId;
67 protected final boolean isEdgeRouter;
68 protected final MacAddress nodeMacAddr;
69 protected LinkService linkService;
70 protected FlowObjectiveService flowObjectiveService;
72 protected HashMap<DeviceId, Set<PortNumber>> devicePortMap = new HashMap<>();
73 protected HashMap<PortNumber, DeviceId> portDeviceMap = new HashMap<>();
74 //protected HashMap<NeighborSet, Integer> deviceNextObjectiveIds =
75 // new HashMap<NeighborSet, Integer>();
76 protected EventuallyConsistentMap<
77 NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null;
78 protected EventuallyConsistentMap<
79 SubnetNextObjectiveStoreKey, Integer> subnetNextObjStore = null;
81 protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
82 .register(URI.class).register(HashSet.class)
83 .register(DeviceId.class).register(PortNumber.class)
84 .register(NeighborSet.class).register(PolicyGroupIdentifier.class)
85 .register(PolicyGroupParams.class)
86 .register(GroupBucketIdentifier.class)
87 .register(GroupBucketIdentifier.BucketOutputType.class);
89 protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId,
90 DeviceProperties config,
91 LinkService linkService,
92 FlowObjectiveService flowObjService,
93 EventuallyConsistentMap<
94 NeighborSetNextObjectiveStoreKey,
95 Integer> nsNextObjStore,
96 EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
97 Integer> subnetNextObjStore) {
98 this.deviceId = checkNotNull(deviceId);
99 this.appId = checkNotNull(appId);
100 this.deviceConfig = checkNotNull(config);
101 this.linkService = checkNotNull(linkService);
102 allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds());
103 nodeSegmentId = config.getSegmentId(deviceId);
104 isEdgeRouter = config.isEdgeDevice(deviceId);
105 nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId));
106 this.flowObjectiveService = flowObjService;
107 this.nsNextObjStore = nsNextObjStore;
108 this.subnetNextObjStore = subnetNextObjStore;
110 populateNeighborMaps();
114 * Creates a group handler object based on the type of device. If device is
115 * of edge type it returns edge group handler, else it returns transit group
118 * @param deviceId device identifier
119 * @param appId application identifier
120 * @param config interface to retrieve the device properties
121 * @param linkService link service object
122 * @param flowObjService flow objective service object
123 * @param nsNextObjStore NeighborSet next objective store map
124 * @param subnetNextObjStore subnet next objective store map
125 * @return default group handler type
127 public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
129 DeviceProperties config,
130 LinkService linkService,
131 FlowObjectiveService flowObjService,
132 EventuallyConsistentMap<
133 NeighborSetNextObjectiveStoreKey,
134 Integer> nsNextObjStore,
135 EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
136 Integer> subnetNextObjStore) {
137 if (config.isEdgeDevice(deviceId)) {
138 return new DefaultEdgeGroupHandler(deviceId, appId, config,
144 return new DefaultTransitGroupHandler(deviceId, appId, config,
153 * Creates the auto created groups for this device based on the current
154 * snapshot of the topology.
156 // Empty implementations to be overridden by derived classes
157 public void createGroups() {
161 * Performs group creation or update procedures when a new link is
162 * discovered on this device.
164 * @param newLink new neighbor link
166 public void linkUp(Link newLink) {
168 if (newLink.type() != Link.Type.DIRECT) {
169 log.warn("linkUp: unknown link type");
173 if (!newLink.src().deviceId().equals(deviceId)) {
174 log.warn("linkUp: deviceId{} doesn't match with link src{}",
175 deviceId, newLink.src().deviceId());
179 log.debug("Device {} linkUp at local port {} to neighbor {}", deviceId,
180 newLink.src().port(), newLink.dst().deviceId());
181 addNeighborAtPort(newLink.dst().deviceId(),
182 newLink.src().port());
183 /*if (devicePortMap.get(newLink.dst().deviceId()) == null) {
185 newNeighbor(newLink);
188 newPortToExistingNeighbor(newLink);
190 Set<NeighborSet> nsSet = nsNextObjStore.keySet()
192 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
193 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
194 .filter((ns) -> (ns.getDeviceIds()
195 .contains(newLink.dst().deviceId())))
196 .collect(Collectors.toSet());
197 log.trace("linkUp: nsNextObjStore contents for device {}:",
200 for (NeighborSet ns : nsSet) {
201 // Create the new bucket to be updated
202 TrafficTreatment.Builder tBuilder =
203 DefaultTrafficTreatment.builder();
204 tBuilder.setOutput(newLink.src().port())
205 .setEthDst(deviceConfig.getDeviceMac(
206 newLink.dst().deviceId()))
207 .setEthSrc(nodeMacAddr);
208 if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
211 mplsLabel(ns.getEdgeLabel()));
214 Integer nextId = nsNextObjStore.
215 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
216 if (nextId != null) {
217 NextObjective.Builder nextObjBuilder = DefaultNextObjective
218 .builder().withId(nextId)
219 .withType(NextObjective.Type.HASHED).fromApp(appId);
221 nextObjBuilder.addTreatment(tBuilder.build());
223 log.debug("linkUp in device {}: Adding Bucket "
224 + "with Port {} to next object id {}",
226 newLink.src().port(),
228 NextObjective nextObjective = nextObjBuilder.
229 add(new SRNextObjectiveContext(deviceId));
230 flowObjectiveService.next(deviceId, nextObjective);
236 * Performs group recovery procedures when a port goes down on this device.
238 * @param port port number that has gone down
240 public void portDown(PortNumber port) {
241 if (portDeviceMap.get(port) == null) {
242 log.warn("portDown: unknown port");
245 log.debug("Device {} portDown {} to neighbor {}", deviceId, port,
246 portDeviceMap.get(port));
247 /*Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap
251 Set<NeighborSet> nsSet = nsNextObjStore.keySet()
253 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
254 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
255 .filter((ns) -> (ns.getDeviceIds()
256 .contains(portDeviceMap.get(port))))
257 .collect(Collectors.toSet());
258 log.trace("portDown: nsNextObjStore contents for device {}:",
261 for (NeighborSet ns : nsSet) {
262 // Create the bucket to be removed
263 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
265 tBuilder.setOutput(port)
266 .setEthDst(deviceConfig.getDeviceMac(portDeviceMap
267 .get(port))).setEthSrc(nodeMacAddr);
268 if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
269 tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
273 Integer nextId = nsNextObjStore.
274 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
275 if (nextId != null) {
276 NextObjective.Builder nextObjBuilder = DefaultNextObjective
277 .builder().withType(NextObjective.Type.SIMPLE).withId(nextId).fromApp(appId);
279 nextObjBuilder.addTreatment(tBuilder.build());
281 log.debug("portDown in device {}: Removing Bucket "
282 + "with Port {} to next object id {}",
286 NextObjective nextObjective = nextObjBuilder.
287 remove(new SRNextObjectiveContext(deviceId));
289 flowObjectiveService.next(deviceId, nextObjective);
294 devicePortMap.get(portDeviceMap.get(port)).remove(port);
295 portDeviceMap.remove(port);
299 * Returns the next objective associated with the neighborset.
300 * If there is no next objective for this neighborset, this API
301 * would create a next objective and return.
303 * @param ns neighborset
304 * @return int if found or -1
306 public int getNextObjectiveId(NeighborSet ns) {
307 Integer nextId = nsNextObjStore.
308 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
309 if (nextId == null) {
310 log.trace("getNextObjectiveId in device{}: Next objective id "
311 + "not found for {} and creating", deviceId, ns);
312 log.trace("getNextObjectiveId: nsNextObjStore contents for device {}: {}",
314 nsNextObjStore.entrySet()
316 .filter((nsStoreEntry) ->
317 (nsStoreEntry.getKey().deviceId().equals(deviceId)))
318 .collect(Collectors.toList()));
319 createGroupsFromNeighborsets(Collections.singleton(ns));
320 nextId = nsNextObjStore.
321 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
322 if (nextId == null) {
323 log.warn("getNextObjectiveId: unable to create next objective");
326 log.debug("getNextObjectiveId in device{}: Next objective id {} "
327 + "created for {}", deviceId, nextId, ns);
330 log.trace("getNextObjectiveId in device{}: Next objective id {} "
331 + "found for {}", deviceId, nextId, ns);
337 * Returns the next objective associated with the subnet.
338 * If there is no next objective for this subnet, this API
339 * would create a next objective and return.
341 * @param prefix subnet information
342 * @return int if found or -1
344 public int getSubnetNextObjectiveId(IpPrefix prefix) {
345 Integer nextId = subnetNextObjStore.
346 get(new SubnetNextObjectiveStoreKey(deviceId, prefix));
348 return (nextId != null) ? nextId : -1;
352 * Checks if the next objective ID (group) for the neighbor set exists or not.
354 * @param ns neighbor set to check
355 * @return true if it exists, false otherwise
357 public boolean hasNextObjectiveId(NeighborSet ns) {
358 Integer nextId = nsNextObjStore.
359 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
360 if (nextId == null) {
367 // Empty implementation
368 protected void newNeighbor(Link newLink) {
371 // Empty implementation
372 protected void newPortToExistingNeighbor(Link newLink) {
375 // Empty implementation
376 protected Set<NeighborSet>
377 computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor,
378 Set<DeviceId> updatedNeighbors) {
382 private void populateNeighborMaps() {
383 Set<Link> outgoingLinks = linkService.getDeviceEgressLinks(deviceId);
384 for (Link link : outgoingLinks) {
385 if (link.type() != Link.Type.DIRECT) {
388 addNeighborAtPort(link.dst().deviceId(), link.src().port());
392 protected void addNeighborAtPort(DeviceId neighborId,
393 PortNumber portToNeighbor) {
394 // Update DeviceToPort database
395 log.debug("Device {} addNeighborAtPort: neighbor {} at port {}",
396 deviceId, neighborId, portToNeighbor);
397 if (devicePortMap.get(neighborId) != null) {
398 devicePortMap.get(neighborId).add(portToNeighbor);
400 Set<PortNumber> ports = new HashSet<>();
401 ports.add(portToNeighbor);
402 devicePortMap.put(neighborId, ports);
405 // Update portToDevice database
406 if (portDeviceMap.get(portToNeighbor) == null) {
407 portDeviceMap.put(portToNeighbor, neighborId);
411 protected Set<Set<DeviceId>> getPowerSetOfNeighbors(Set<DeviceId> neighbors) {
412 List<DeviceId> list = new ArrayList<>(neighbors);
413 Set<Set<DeviceId>> sets = new HashSet<>();
414 // get the number of elements in the neighbors
415 int elements = list.size();
416 // the number of members of a power set is 2^n
417 // including the empty set
418 int powerElements = (1 << elements);
420 // run a binary counter for the number of power elements
421 // NOTE: Exclude empty set
422 for (long i = 1; i < powerElements; i++) {
423 Set<DeviceId> neighborSubSet = new HashSet<>();
424 for (int j = 0; j < elements; j++) {
425 if ((i >> j) % 2 == 1) {
426 neighborSubSet.add(list.get(j));
429 sets.add(neighborSubSet);
434 private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) {
435 return (deviceConfig.getSegmentId(deviceId) == sId);
438 protected List<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) {
440 List<Integer> nsSegmentIds = new ArrayList<>();
442 // Always pair up with no edge label
443 // If (neighbors.size() == 1) {
444 nsSegmentIds.add(-1);
447 // Filter out SegmentIds matching with the
448 // nodes in the combo
449 for (Integer sId : allSegmentIds) {
450 if (sId.equals(nodeSegmentId)) {
453 boolean filterOut = false;
454 // Check if the edge label being set is of
455 // any node in the Neighbor set
456 for (DeviceId deviceId : neighbors) {
457 if (isSegmentIdSameAsNodeSegmentId(deviceId, sId)) {
463 nsSegmentIds.add(sId);
470 * Creates Groups from a set of NeighborSet given.
472 * @param nsSet a set of NeighborSet
474 public void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) {
475 for (NeighborSet ns : nsSet) {
476 int nextId = flowObjectiveService.allocateNextId();
477 NextObjective.Builder nextObjBuilder = DefaultNextObjective
478 .builder().withId(nextId)
479 .withType(NextObjective.Type.HASHED).fromApp(appId);
480 for (DeviceId d : ns.getDeviceIds()) {
481 if (devicePortMap.get(d) == null) {
482 log.warn("Device {} is not in the port map yet", d);
484 } else if (devicePortMap.get(d).size() == 0) {
485 log.warn("There are no ports for "
486 + "the Device {} in the port map yet", d);
490 for (PortNumber sp : devicePortMap.get(d)) {
491 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
493 tBuilder.setOutput(sp)
494 .setEthDst(deviceConfig.getDeviceMac(d))
495 .setEthSrc(nodeMacAddr);
496 if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
497 tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
500 nextObjBuilder.addTreatment(tBuilder.build());
504 NextObjective nextObj = nextObjBuilder.
505 add(new SRNextObjectiveContext(deviceId));
506 flowObjectiveService.next(deviceId, nextObj);
507 log.debug("createGroupsFromNeighborsets: Submited "
508 + "next objective {} in device {}",
510 nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns),
515 public void createGroupsFromSubnetConfig() {
516 Map<Ip4Prefix, List<PortNumber>> subnetPortMap =
517 this.deviceConfig.getSubnetPortsMap(this.deviceId);
519 // Construct a broadcast group for each subnet
520 subnetPortMap.forEach((subnet, ports) -> {
521 SubnetNextObjectiveStoreKey key =
522 new SubnetNextObjectiveStoreKey(deviceId, subnet);
524 if (subnetNextObjStore.containsKey(key)) {
525 log.debug("Broadcast group for device {} and subnet {} exists",
530 int nextId = flowObjectiveService.allocateNextId();
532 NextObjective.Builder nextObjBuilder = DefaultNextObjective
533 .builder().withId(nextId)
534 .withType(NextObjective.Type.BROADCAST).fromApp(appId);
536 ports.forEach(port -> {
537 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
538 tBuilder.setOutput(port);
539 nextObjBuilder.addTreatment(tBuilder.build());
542 NextObjective nextObj = nextObjBuilder.add();
543 flowObjectiveService.next(deviceId, nextObj);
544 log.debug("createGroupFromSubnetConfig: Submited "
545 + "next objective {} in device {}",
548 subnetNextObjStore.put(key, nextId);
552 public GroupKey getGroupKey(Object obj) {
553 return new DefaultGroupKey(kryo.build().serialize(obj));
557 * Removes groups for the next objective ID given.
559 * @param objectiveId next objective ID to remove
560 * @return true if succeeds, false otherwise
562 public boolean removeGroup(int objectiveId) {
564 if (nsNextObjStore.containsValue(objectiveId)) {
565 NextObjective.Builder nextObjBuilder = DefaultNextObjective
566 .builder().withId(objectiveId)
567 .withType(NextObjective.Type.HASHED).fromApp(appId);
568 NextObjective nextObjective = nextObjBuilder.
569 remove(new SRNextObjectiveContext(deviceId));
570 flowObjectiveService.next(deviceId, nextObjective);
572 for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) {
573 if (entry.getValue().equals(objectiveId)) {
574 nsNextObjStore.remove(entry.getKey());
584 protected static class SRNextObjectiveContext implements ObjectiveContext {
585 final DeviceId deviceId;
587 SRNextObjectiveContext(DeviceId deviceId) {
588 this.deviceId = deviceId;
591 public void onSuccess(Objective objective) {
592 log.debug("Next objective operation successful in device {}",
597 public void onError(Objective objective, ObjectiveError error) {
598 log.warn("Next objective {} operation failed with error: {} in device {}",
599 objective, error, deviceId);