9bbde2f35b3ecb7989f525cdbde545c64e4df503
[onosfw.git] /
1 /*
2  * Copyright 2015 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  */
16 package org.onosproject.segmentrouting.grouphandler;
17
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static org.slf4j.LoggerFactory.getLogger;
20
21 import java.net.URI;
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;
27 import java.util.Map;
28 import java.util.Random;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31
32 import org.onlab.packet.MacAddress;
33 import org.onlab.packet.MplsLabel;
34 import org.onlab.util.KryoNamespace;
35 import org.onosproject.core.ApplicationId;
36 import org.onosproject.net.DeviceId;
37 import org.onosproject.net.Link;
38 import org.onosproject.net.PortNumber;
39 import org.onosproject.net.flow.DefaultTrafficTreatment;
40 import org.onosproject.net.flow.TrafficTreatment;
41 import org.onosproject.net.flowobjective.DefaultNextObjective;
42 import org.onosproject.net.flowobjective.FlowObjectiveService;
43 import org.onosproject.net.flowobjective.NextObjective;
44 import org.onosproject.net.flowobjective.Objective;
45 import org.onosproject.net.flowobjective.ObjectiveContext;
46 import org.onosproject.net.flowobjective.ObjectiveError;
47 import org.onosproject.net.group.DefaultGroupKey;
48 import org.onosproject.net.group.GroupKey;
49 import org.onosproject.net.link.LinkService;
50 import org.onosproject.store.service.EventuallyConsistentMap;
51 import org.slf4j.Logger;
52
53 /**
54  * Default ECMP group handler creation module. This component creates a set of
55  * ECMP groups for every neighbor that this device is connected to based on
56  * whether the current device is an edge device or a transit device.
57  */
58 public class DefaultGroupHandler {
59     protected static final Logger log = getLogger(DefaultGroupHandler.class);
60
61     protected final DeviceId deviceId;
62     protected final ApplicationId appId;
63     protected final DeviceProperties deviceConfig;
64     protected final List<Integer> allSegmentIds;
65     protected final int nodeSegmentId;
66     protected final boolean isEdgeRouter;
67     protected final MacAddress nodeMacAddr;
68     protected LinkService linkService;
69     protected FlowObjectiveService flowObjectiveService;
70
71     protected HashMap<DeviceId, Set<PortNumber>> devicePortMap = new HashMap<>();
72     protected HashMap<PortNumber, DeviceId> portDeviceMap = new HashMap<>();
73     //protected HashMap<NeighborSet, Integer> deviceNextObjectiveIds =
74     //        new HashMap<NeighborSet, Integer>();
75     protected EventuallyConsistentMap<
76         NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null;
77     protected Random rand = new Random();
78
79     protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
80             .register(URI.class).register(HashSet.class)
81             .register(DeviceId.class).register(PortNumber.class)
82             .register(NeighborSet.class).register(PolicyGroupIdentifier.class)
83             .register(PolicyGroupParams.class)
84             .register(GroupBucketIdentifier.class)
85             .register(GroupBucketIdentifier.BucketOutputType.class);
86
87     protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId,
88                                   DeviceProperties config,
89                                   LinkService linkService,
90                                   FlowObjectiveService flowObjService,
91                                   EventuallyConsistentMap<
92                                   NeighborSetNextObjectiveStoreKey,
93                                   Integer> nsNextObjStore) {
94         this.deviceId = checkNotNull(deviceId);
95         this.appId = checkNotNull(appId);
96         this.deviceConfig = checkNotNull(config);
97         this.linkService = checkNotNull(linkService);
98         allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds());
99         nodeSegmentId = config.getSegmentId(deviceId);
100         isEdgeRouter = config.isEdgeDevice(deviceId);
101         nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId));
102         this.flowObjectiveService = flowObjService;
103         this.nsNextObjStore = nsNextObjStore;
104
105         populateNeighborMaps();
106     }
107
108     /**
109      * Creates a group handler object based on the type of device. If device is
110      * of edge type it returns edge group handler, else it returns transit group
111      * handler.
112      *
113      * @param deviceId device identifier
114      * @param appId application identifier
115      * @param config interface to retrieve the device properties
116      * @param linkService link service object
117      * @param flowObjService flow objective service object
118      * @param nsNextObjStore next objective store map
119      * @return default group handler type
120      */
121     public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
122                                                          ApplicationId appId,
123                                                          DeviceProperties config,
124                                                          LinkService linkService,
125                                                          FlowObjectiveService flowObjService,
126                                                          EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
127                                                                  Integer> nsNextObjStore) {
128         if (config.isEdgeDevice(deviceId)) {
129             return new DefaultEdgeGroupHandler(deviceId, appId, config,
130                                                linkService,
131                                                flowObjService,
132                                                nsNextObjStore);
133         } else {
134             return new DefaultTransitGroupHandler(deviceId, appId, config,
135                                                   linkService,
136                                                   flowObjService,
137                                                   nsNextObjStore);
138         }
139     }
140
141     /**
142      * Creates the auto created groups for this device based on the current
143      * snapshot of the topology.
144      */
145     // Empty implementations to be overridden by derived classes
146     public void createGroups() {
147     }
148
149     /**
150      * Performs group creation or update procedures when a new link is
151      * discovered on this device.
152      *
153      * @param newLink new neighbor link
154      */
155     public void linkUp(Link newLink) {
156
157         if (newLink.type() != Link.Type.DIRECT) {
158             log.warn("linkUp: unknown link type");
159             return;
160         }
161
162         if (!newLink.src().deviceId().equals(deviceId)) {
163             log.warn("linkUp: deviceId{} doesn't match with link src{}",
164                      deviceId, newLink.src().deviceId());
165             return;
166         }
167
168         log.debug("Device {} linkUp at local port {} to neighbor {}", deviceId,
169                   newLink.src().port(), newLink.dst().deviceId());
170         addNeighborAtPort(newLink.dst().deviceId(),
171                           newLink.src().port());
172         /*if (devicePortMap.get(newLink.dst().deviceId()) == null) {
173             // New Neighbor
174             newNeighbor(newLink);
175         } else {
176             // Old Neighbor
177             newPortToExistingNeighbor(newLink);
178         }*/
179         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
180                 .stream()
181                 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
182                 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
183                 .filter((ns) -> (ns.getDeviceIds()
184                         .contains(newLink.dst().deviceId())))
185                 .collect(Collectors.toSet());
186         log.trace("linkUp: nsNextObjStore contents for device {}:",
187                 deviceId,
188                 nsSet);
189         for (NeighborSet ns : nsSet) {
190             // Create the new bucket to be updated
191             TrafficTreatment.Builder tBuilder =
192                     DefaultTrafficTreatment.builder();
193             tBuilder.setOutput(newLink.src().port())
194                     .setEthDst(deviceConfig.getDeviceMac(
195                           newLink.dst().deviceId()))
196                     .setEthSrc(nodeMacAddr);
197             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
198                 tBuilder.pushMpls()
199                         .setMpls(MplsLabel.
200                                  mplsLabel(ns.getEdgeLabel()));
201             }
202
203             Integer nextId = nsNextObjStore.
204                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
205             if (nextId != null) {
206                 NextObjective.Builder nextObjBuilder = DefaultNextObjective
207                         .builder().withId(nextId)
208                         .withType(NextObjective.Type.HASHED).fromApp(appId);
209
210                 nextObjBuilder.addTreatment(tBuilder.build());
211
212                 log.debug("linkUp in device {}: Adding Bucket "
213                         + "with Port {} to next object id {}",
214                         deviceId,
215                         newLink.src().port(),
216                         nextId);
217                 NextObjective nextObjective = nextObjBuilder.
218                         add(new SRNextObjectiveContext(deviceId));
219                 flowObjectiveService.next(deviceId, nextObjective);
220             }
221         }
222     }
223
224     /**
225      * Performs group recovery procedures when a port goes down on this device.
226      *
227      * @param port port number that has gone down
228      */
229     public void portDown(PortNumber port) {
230         if (portDeviceMap.get(port) == null) {
231             log.warn("portDown: unknown port");
232             return;
233         }
234         log.debug("Device {} portDown {} to neighbor {}", deviceId, port,
235                   portDeviceMap.get(port));
236         /*Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap
237                                                                                 .get(port),
238                                                                         devicePortMap
239                                                                                 .keySet());*/
240         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
241                 .stream()
242                 .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId)))
243                 .map((nsStoreEntry) -> (nsStoreEntry.neighborSet()))
244                 .filter((ns) -> (ns.getDeviceIds()
245                         .contains(portDeviceMap.get(port))))
246                 .collect(Collectors.toSet());
247         log.trace("portDown: nsNextObjStore contents for device {}:",
248                   deviceId,
249                   nsSet);
250         for (NeighborSet ns : nsSet) {
251             // Create the bucket to be removed
252             TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
253                     .builder();
254             tBuilder.setOutput(port)
255                     .setEthDst(deviceConfig.getDeviceMac(portDeviceMap
256                                        .get(port))).setEthSrc(nodeMacAddr);
257             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
258                 tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
259                                                     .getEdgeLabel()));
260             }
261
262             Integer nextId = nsNextObjStore.
263                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
264             if (nextId != null) {
265                 NextObjective.Builder nextObjBuilder = DefaultNextObjective
266                         .builder().withType(NextObjective.Type.SIMPLE).withId(nextId).fromApp(appId);
267
268                 nextObjBuilder.addTreatment(tBuilder.build());
269
270                 log.debug("portDown in device {}: Removing Bucket "
271                         + "with Port {} to next object id {}",
272                         deviceId,
273                         port,
274                         nextId);
275                 NextObjective nextObjective = nextObjBuilder.
276                         remove(new SRNextObjectiveContext(deviceId));
277
278                 flowObjectiveService.next(deviceId, nextObjective);
279             }
280
281         }
282
283         devicePortMap.get(portDeviceMap.get(port)).remove(port);
284         portDeviceMap.remove(port);
285     }
286
287     /**
288      * Returns the next objective associated with the neighborset.
289      * If there is no next objective for this neighborset, this API
290      * would create a next objective and return.
291      *
292      * @param ns neighborset
293      * @return int if found or -1
294      */
295     public int getNextObjectiveId(NeighborSet ns) {
296         Integer nextId = nsNextObjStore.
297                 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
298         if (nextId == null) {
299             log.trace("getNextObjectiveId in device{}: Next objective id "
300                     + "not found for {} and creating", deviceId, ns);
301             log.trace("getNextObjectiveId: nsNextObjStore contents for device {}: {}",
302                       deviceId,
303                       nsNextObjStore.entrySet()
304                       .stream()
305                       .filter((nsStoreEntry) ->
306                       (nsStoreEntry.getKey().deviceId().equals(deviceId)))
307                       .collect(Collectors.toList()));
308             createGroupsFromNeighborsets(Collections.singleton(ns));
309             nextId = nsNextObjStore.
310                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
311             if (nextId == null) {
312                 log.warn("getNextObjectiveId: unable to create next objective");
313                 return -1;
314             } else {
315                 log.debug("getNextObjectiveId in device{}: Next objective id {} "
316                     + "created for {}", deviceId, nextId, ns);
317             }
318         } else {
319             log.trace("getNextObjectiveId in device{}: Next objective id {} "
320                     + "found for {}", deviceId, nextId, ns);
321         }
322         return nextId;
323     }
324
325     /**
326      * Checks if the next objective ID (group) for the neighbor set exists or not.
327      *
328      * @param ns neighbor set to check
329      * @return true if it exists, false otherwise
330      */
331     public boolean hasNextObjectiveId(NeighborSet ns) {
332         Integer nextId = nsNextObjStore.
333                 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
334         if (nextId == null) {
335             return false;
336         }
337
338         return true;
339     }
340
341     // Empty implementation
342     protected void newNeighbor(Link newLink) {
343     }
344
345     // Empty implementation
346     protected void newPortToExistingNeighbor(Link newLink) {
347     }
348
349     // Empty implementation
350     protected Set<NeighborSet>
351         computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor,
352                                                Set<DeviceId> updatedNeighbors) {
353         return null;
354     }
355
356     private void populateNeighborMaps() {
357         Set<Link> outgoingLinks = linkService.getDeviceEgressLinks(deviceId);
358         for (Link link : outgoingLinks) {
359             if (link.type() != Link.Type.DIRECT) {
360                 continue;
361             }
362             addNeighborAtPort(link.dst().deviceId(), link.src().port());
363         }
364     }
365
366     protected void addNeighborAtPort(DeviceId neighborId,
367                                      PortNumber portToNeighbor) {
368         // Update DeviceToPort database
369         log.debug("Device {} addNeighborAtPort: neighbor {} at port {}",
370                   deviceId, neighborId, portToNeighbor);
371         if (devicePortMap.get(neighborId) != null) {
372             devicePortMap.get(neighborId).add(portToNeighbor);
373         } else {
374             Set<PortNumber> ports = new HashSet<>();
375             ports.add(portToNeighbor);
376             devicePortMap.put(neighborId, ports);
377         }
378
379         // Update portToDevice database
380         if (portDeviceMap.get(portToNeighbor) == null) {
381             portDeviceMap.put(portToNeighbor, neighborId);
382         }
383     }
384
385     protected Set<Set<DeviceId>> getPowerSetOfNeighbors(Set<DeviceId> neighbors) {
386         List<DeviceId> list = new ArrayList<>(neighbors);
387         Set<Set<DeviceId>> sets = new HashSet<>();
388         // get the number of elements in the neighbors
389         int elements = list.size();
390         // the number of members of a power set is 2^n
391         // including the empty set
392         int powerElements = (1 << elements);
393
394         // run a binary counter for the number of power elements
395         // NOTE: Exclude empty set
396         for (long i = 1; i < powerElements; i++) {
397             Set<DeviceId> neighborSubSet = new HashSet<>();
398             for (int j = 0; j < elements; j++) {
399                 if ((i >> j) % 2 == 1) {
400                     neighborSubSet.add(list.get(j));
401                 }
402             }
403             sets.add(neighborSubSet);
404         }
405         return sets;
406     }
407
408     private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) {
409         return (deviceConfig.getSegmentId(deviceId) == sId);
410     }
411
412     protected List<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) {
413
414         List<Integer> nsSegmentIds = new ArrayList<>();
415
416         // Always pair up with no edge label
417         // If (neighbors.size() == 1) {
418         nsSegmentIds.add(-1);
419         // }
420
421         // Filter out SegmentIds matching with the
422         // nodes in the combo
423         for (Integer sId : allSegmentIds) {
424             if (sId.equals(nodeSegmentId)) {
425                 continue;
426             }
427             boolean filterOut = false;
428             // Check if the edge label being set is of
429             // any node in the Neighbor set
430             for (DeviceId deviceId : neighbors) {
431                 if (isSegmentIdSameAsNodeSegmentId(deviceId, sId)) {
432                     filterOut = true;
433                     break;
434                 }
435             }
436             if (!filterOut) {
437                 nsSegmentIds.add(sId);
438             }
439         }
440         return nsSegmentIds;
441     }
442
443     /**
444      * Creates Groups from a set of NeighborSet given.
445      *
446      * @param nsSet a set of NeighborSet
447      */
448     public void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) {
449         for (NeighborSet ns : nsSet) {
450             int nextId = flowObjectiveService.allocateNextId();
451             NextObjective.Builder nextObjBuilder = DefaultNextObjective
452                     .builder().withId(nextId)
453                     .withType(NextObjective.Type.HASHED).fromApp(appId);
454             for (DeviceId d : ns.getDeviceIds()) {
455                 if (devicePortMap.get(d) == null) {
456                     log.warn("Device {} is not in the port map yet", d);
457                     return;
458                 } else if (devicePortMap.get(d).size() == 0) {
459                     log.warn("There are no ports for "
460                             + "the Device {} in the port map yet", d);
461                     return;
462                 }
463
464                 for (PortNumber sp : devicePortMap.get(d)) {
465                     TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
466                             .builder();
467                     tBuilder.setOutput(sp)
468                             .setEthDst(deviceConfig.getDeviceMac(d))
469                             .setEthSrc(nodeMacAddr);
470                     if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
471                         tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
472                                 .getEdgeLabel()));
473                     }
474                     nextObjBuilder.addTreatment(tBuilder.build());
475                 }
476             }
477
478             NextObjective nextObj = nextObjBuilder.
479                     add(new SRNextObjectiveContext(deviceId));
480             flowObjectiveService.next(deviceId, nextObj);
481             log.debug("createGroupsFromNeighborsets: Submited "
482                             + "next objective {} in device {}",
483                     nextId, deviceId);
484             nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns),
485                                nextId);
486         }
487     }
488
489     public GroupKey getGroupKey(Object obj) {
490         return new DefaultGroupKey(kryo.build().serialize(obj));
491     }
492
493     /**
494      * Removes groups for the next objective ID given.
495      *
496      * @param objectiveId next objective ID to remove
497      * @return true if succeeds, false otherwise
498      */
499     public boolean removeGroup(int objectiveId) {
500
501         if (nsNextObjStore.containsValue(objectiveId)) {
502             NextObjective.Builder nextObjBuilder = DefaultNextObjective
503                     .builder().withId(objectiveId)
504                     .withType(NextObjective.Type.HASHED).fromApp(appId);
505             NextObjective nextObjective = nextObjBuilder.
506                     remove(new SRNextObjectiveContext(deviceId));
507             flowObjectiveService.next(deviceId, nextObjective);
508
509             for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) {
510                 if (entry.getValue().equals(objectiveId)) {
511                     nsNextObjStore.remove(entry.getKey());
512                     break;
513                 }
514             }
515             return true;
516         }
517
518         return false;
519     }
520
521     protected static class SRNextObjectiveContext implements ObjectiveContext {
522         final DeviceId deviceId;
523
524         SRNextObjectiveContext(DeviceId deviceId) {
525             this.deviceId = deviceId;
526         }
527         @Override
528         public void onSuccess(Objective objective) {
529             log.debug("Next objective operation successful in device {}",
530                       deviceId);
531         }
532
533         @Override
534         public void onError(Objective objective, ObjectiveError error) {
535             log.warn("Next objective {} operation failed with error: {} in device {}",
536                      objective, error, deviceId);
537         }
538     }
539 }