69a0d86fa6c40060509e7149b9ac0a450d71beb1
[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.Set;
29 import java.util.stream.Collectors;
30
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;
53
54 /**
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.
58  */
59 public class DefaultGroupHandler {
60     protected static final Logger log = getLogger(DefaultGroupHandler.class);
61
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;
71
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;
80
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);
88
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;
109
110         populateNeighborMaps();
111     }
112
113     /**
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
116      * handler.
117      *
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
126      */
127     public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
128                                                          ApplicationId appId,
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,
139                                                linkService,
140                                                flowObjService,
141                                                nsNextObjStore,
142                                                subnetNextObjStore);
143         } else {
144             return new DefaultTransitGroupHandler(deviceId, appId, config,
145                                                   linkService,
146                                                   flowObjService,
147                                                   nsNextObjStore,
148                                                   subnetNextObjStore);
149         }
150     }
151
152     /**
153      * Creates the auto created groups for this device based on the current
154      * snapshot of the topology.
155      */
156     // Empty implementations to be overridden by derived classes
157     public void createGroups() {
158     }
159
160     /**
161      * Performs group creation or update procedures when a new link is
162      * discovered on this device.
163      *
164      * @param newLink new neighbor link
165      */
166     public void linkUp(Link newLink) {
167
168         if (newLink.type() != Link.Type.DIRECT) {
169             log.warn("linkUp: unknown link type");
170             return;
171         }
172
173         if (!newLink.src().deviceId().equals(deviceId)) {
174             log.warn("linkUp: deviceId{} doesn't match with link src{}",
175                      deviceId, newLink.src().deviceId());
176             return;
177         }
178
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) {
184             // New Neighbor
185             newNeighbor(newLink);
186         } else {
187             // Old Neighbor
188             newPortToExistingNeighbor(newLink);
189         }*/
190         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
191                 .stream()
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 {}:",
198                 deviceId,
199                 nsSet);
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) {
209                 tBuilder.pushMpls()
210                         .setMpls(MplsLabel.
211                                  mplsLabel(ns.getEdgeLabel()));
212             }
213
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);
220
221                 nextObjBuilder.addTreatment(tBuilder.build());
222
223                 log.debug("linkUp in device {}: Adding Bucket "
224                         + "with Port {} to next object id {}",
225                         deviceId,
226                         newLink.src().port(),
227                         nextId);
228                 NextObjective nextObjective = nextObjBuilder.
229                         add(new SRNextObjectiveContext(deviceId));
230                 flowObjectiveService.next(deviceId, nextObjective);
231             }
232         }
233     }
234
235     /**
236      * Performs group recovery procedures when a port goes down on this device.
237      *
238      * @param port port number that has gone down
239      */
240     public void portDown(PortNumber port) {
241         if (portDeviceMap.get(port) == null) {
242             log.warn("portDown: unknown port");
243             return;
244         }
245         log.debug("Device {} portDown {} to neighbor {}", deviceId, port,
246                   portDeviceMap.get(port));
247         /*Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap
248                                                                                 .get(port),
249                                                                         devicePortMap
250                                                                                 .keySet());*/
251         Set<NeighborSet> nsSet = nsNextObjStore.keySet()
252                 .stream()
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 {}:",
259                   deviceId,
260                   nsSet);
261         for (NeighborSet ns : nsSet) {
262             // Create the bucket to be removed
263             TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
264                     .builder();
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
270                                                     .getEdgeLabel()));
271             }
272
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);
278
279                 nextObjBuilder.addTreatment(tBuilder.build());
280
281                 log.debug("portDown in device {}: Removing Bucket "
282                         + "with Port {} to next object id {}",
283                         deviceId,
284                         port,
285                         nextId);
286                 NextObjective nextObjective = nextObjBuilder.
287                         remove(new SRNextObjectiveContext(deviceId));
288
289                 flowObjectiveService.next(deviceId, nextObjective);
290             }
291
292         }
293
294         devicePortMap.get(portDeviceMap.get(port)).remove(port);
295         portDeviceMap.remove(port);
296     }
297
298     /**
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.
302      *
303      * @param ns neighborset
304      * @return int if found or -1
305      */
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 {}: {}",
313                       deviceId,
314                       nsNextObjStore.entrySet()
315                       .stream()
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");
324                 return -1;
325             } else {
326                 log.debug("getNextObjectiveId in device{}: Next objective id {} "
327                     + "created for {}", deviceId, nextId, ns);
328             }
329         } else {
330             log.trace("getNextObjectiveId in device{}: Next objective id {} "
331                     + "found for {}", deviceId, nextId, ns);
332         }
333         return nextId;
334     }
335
336     /**
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.
340      *
341      * @param prefix subnet information
342      * @return int if found or -1
343      */
344     public int getSubnetNextObjectiveId(IpPrefix prefix) {
345         Integer nextId = subnetNextObjStore.
346                 get(new SubnetNextObjectiveStoreKey(deviceId, prefix));
347
348         return (nextId != null) ? nextId : -1;
349     }
350
351     /**
352      * Checks if the next objective ID (group) for the neighbor set exists or not.
353      *
354      * @param ns neighbor set to check
355      * @return true if it exists, false otherwise
356      */
357     public boolean hasNextObjectiveId(NeighborSet ns) {
358         Integer nextId = nsNextObjStore.
359                 get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
360         if (nextId == null) {
361             return false;
362         }
363
364         return true;
365     }
366
367     // Empty implementation
368     protected void newNeighbor(Link newLink) {
369     }
370
371     // Empty implementation
372     protected void newPortToExistingNeighbor(Link newLink) {
373     }
374
375     // Empty implementation
376     protected Set<NeighborSet>
377         computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor,
378                                                Set<DeviceId> updatedNeighbors) {
379         return null;
380     }
381
382     private void populateNeighborMaps() {
383         Set<Link> outgoingLinks = linkService.getDeviceEgressLinks(deviceId);
384         for (Link link : outgoingLinks) {
385             if (link.type() != Link.Type.DIRECT) {
386                 continue;
387             }
388             addNeighborAtPort(link.dst().deviceId(), link.src().port());
389         }
390     }
391
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);
399         } else {
400             Set<PortNumber> ports = new HashSet<>();
401             ports.add(portToNeighbor);
402             devicePortMap.put(neighborId, ports);
403         }
404
405         // Update portToDevice database
406         if (portDeviceMap.get(portToNeighbor) == null) {
407             portDeviceMap.put(portToNeighbor, neighborId);
408         }
409     }
410
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);
419
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));
427                 }
428             }
429             sets.add(neighborSubSet);
430         }
431         return sets;
432     }
433
434     private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) {
435         return (deviceConfig.getSegmentId(deviceId) == sId);
436     }
437
438     protected List<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) {
439
440         List<Integer> nsSegmentIds = new ArrayList<>();
441
442         // Always pair up with no edge label
443         // If (neighbors.size() == 1) {
444         nsSegmentIds.add(-1);
445         // }
446
447         // Filter out SegmentIds matching with the
448         // nodes in the combo
449         for (Integer sId : allSegmentIds) {
450             if (sId.equals(nodeSegmentId)) {
451                 continue;
452             }
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)) {
458                     filterOut = true;
459                     break;
460                 }
461             }
462             if (!filterOut) {
463                 nsSegmentIds.add(sId);
464             }
465         }
466         return nsSegmentIds;
467     }
468
469     /**
470      * Creates Groups from a set of NeighborSet given.
471      *
472      * @param nsSet a set of NeighborSet
473      */
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);
483                     return;
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);
487                     return;
488                 }
489
490                 for (PortNumber sp : devicePortMap.get(d)) {
491                     TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
492                             .builder();
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
498                                 .getEdgeLabel()));
499                     }
500                     nextObjBuilder.addTreatment(tBuilder.build());
501                 }
502             }
503
504             NextObjective nextObj = nextObjBuilder.
505                     add(new SRNextObjectiveContext(deviceId));
506             flowObjectiveService.next(deviceId, nextObj);
507             log.debug("createGroupsFromNeighborsets: Submited "
508                             + "next objective {} in device {}",
509                     nextId, deviceId);
510             nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns),
511                                nextId);
512         }
513     }
514
515     public void createGroupsFromSubnetConfig() {
516         Map<Ip4Prefix, List<PortNumber>> subnetPortMap =
517                 this.deviceConfig.getSubnetPortsMap(this.deviceId);
518
519         // Construct a broadcast group for each subnet
520         subnetPortMap.forEach((subnet, ports) -> {
521             SubnetNextObjectiveStoreKey key =
522                     new SubnetNextObjectiveStoreKey(deviceId, subnet);
523
524             if (subnetNextObjStore.containsKey(key)) {
525                 log.debug("Broadcast group for device {} and subnet {} exists",
526                           deviceId, subnet);
527                 return;
528             }
529
530             int nextId = flowObjectiveService.allocateNextId();
531
532             NextObjective.Builder nextObjBuilder = DefaultNextObjective
533                     .builder().withId(nextId)
534                     .withType(NextObjective.Type.BROADCAST).fromApp(appId);
535
536             ports.forEach(port -> {
537                 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
538                 tBuilder.setOutput(port);
539                 nextObjBuilder.addTreatment(tBuilder.build());
540             });
541
542             NextObjective nextObj = nextObjBuilder.add();
543             flowObjectiveService.next(deviceId, nextObj);
544             log.debug("createGroupFromSubnetConfig: Submited "
545                               + "next objective {} in device {}",
546                       nextId, deviceId);
547
548             subnetNextObjStore.put(key, nextId);
549         });
550     }
551
552     public GroupKey getGroupKey(Object obj) {
553         return new DefaultGroupKey(kryo.build().serialize(obj));
554     }
555
556     /**
557      * Removes groups for the next objective ID given.
558      *
559      * @param objectiveId next objective ID to remove
560      * @return true if succeeds, false otherwise
561      */
562     public boolean removeGroup(int objectiveId) {
563
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);
571
572             for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) {
573                 if (entry.getValue().equals(objectiveId)) {
574                     nsNextObjStore.remove(entry.getKey());
575                     break;
576                 }
577             }
578             return true;
579         }
580
581         return false;
582     }
583
584     protected static class SRNextObjectiveContext implements ObjectiveContext {
585         final DeviceId deviceId;
586
587         SRNextObjectiveContext(DeviceId deviceId) {
588             this.deviceId = deviceId;
589         }
590         @Override
591         public void onSuccess(Objective objective) {
592             log.debug("Next objective operation successful in device {}",
593                       deviceId);
594         }
595
596         @Override
597         public void onError(Objective objective, ObjectiveError error) {
598             log.warn("Next objective {} operation failed with error: {} in device {}",
599                      objective, error, deviceId);
600         }
601     }
602 }