blob: b4440f15d70ec265b914ce4ebf3c52f4b175685e [file] [log] [blame]
Charles Chanc7b3c452018-06-19 20:31:57 -07001/*
2 * Copyright 2018-present Open Networking Foundation
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 */
16package org.onosproject.segmentrouting.xconnect.impl;
17
pier46d557f2018-11-27 11:23:50 -080018import com.google.common.collect.ImmutableList;
Charles Chand5814aa2018-08-19 19:21:46 -070019import com.google.common.collect.ImmutableMap;
Charles Chanc7b3c452018-06-19 20:31:57 -070020import com.google.common.collect.ImmutableSet;
pier46d557f2018-11-27 11:23:50 -080021import com.google.common.collect.Lists;
Charles Chanc7b3c452018-06-19 20:31:57 -070022import com.google.common.collect.Sets;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053023import org.onlab.packet.Ethernet;
Charles Chanc7b3c452018-06-19 20:31:57 -070024import org.onlab.packet.MacAddress;
25import org.onlab.packet.VlanId;
26import org.onlab.util.KryoNamespace;
pier46d557f2018-11-27 11:23:50 -080027import org.onosproject.cluster.ClusterService;
28import org.onosproject.cluster.LeadershipService;
29import org.onosproject.cluster.NodeId;
Charles Chanc7b3c452018-06-19 20:31:57 -070030import org.onosproject.codec.CodecService;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.mastership.MastershipService;
34import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.DeviceId;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053036import org.onosproject.net.Host;
37import org.onosproject.net.HostLocation;
Charles Chanc7b3c452018-06-19 20:31:57 -070038import org.onosproject.net.PortNumber;
39import org.onosproject.net.config.NetworkConfigService;
40import org.onosproject.net.device.DeviceEvent;
41import org.onosproject.net.device.DeviceListener;
42import org.onosproject.net.device.DeviceService;
43import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.DefaultTrafficTreatment;
45import org.onosproject.net.flow.TrafficSelector;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.flow.criteria.Criteria;
48import org.onosproject.net.flowobjective.DefaultFilteringObjective;
49import org.onosproject.net.flowobjective.DefaultForwardingObjective;
50import org.onosproject.net.flowobjective.DefaultNextObjective;
51import org.onosproject.net.flowobjective.DefaultObjectiveContext;
52import org.onosproject.net.flowobjective.FilteringObjective;
53import org.onosproject.net.flowobjective.FlowObjectiveService;
54import org.onosproject.net.flowobjective.ForwardingObjective;
55import org.onosproject.net.flowobjective.NextObjective;
56import org.onosproject.net.flowobjective.Objective;
57import org.onosproject.net.flowobjective.ObjectiveContext;
58import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053059import org.onosproject.net.host.HostEvent;
60import org.onosproject.net.host.HostListener;
61import org.onosproject.net.host.HostService;
62import org.onosproject.net.intf.InterfaceService;
Charles Chanc7b3c452018-06-19 20:31:57 -070063import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053064import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chanc7b3c452018-06-19 20:31:57 -070065import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
66import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
67import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
68import org.onosproject.segmentrouting.xconnect.api.XconnectService;
69import org.onosproject.store.serializers.KryoNamespaces;
70import org.onosproject.store.service.ConsistentMap;
71import org.onosproject.store.service.MapEvent;
72import org.onosproject.store.service.MapEventListener;
73import org.onosproject.store.service.Serializer;
74import org.onosproject.store.service.StorageService;
75import org.onosproject.store.service.Versioned;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070076import org.osgi.service.component.annotations.Activate;
77import org.osgi.service.component.annotations.Component;
78import org.osgi.service.component.annotations.Deactivate;
79import org.osgi.service.component.annotations.Reference;
80import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chanc7b3c452018-06-19 20:31:57 -070081import org.slf4j.Logger;
82import org.slf4j.LoggerFactory;
83
84import java.io.Serializable;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053085import java.util.Collections;
86import java.util.List;
jayakumarthazhath655b9a82018-10-01 00:51:54 +053087import java.util.Optional;
Charles Chanc7b3c452018-06-19 20:31:57 -070088import java.util.Set;
89import java.util.concurrent.CompletableFuture;
Charles Chan168111e2018-08-07 12:48:36 -070090import java.util.concurrent.ExecutorService;
91import java.util.concurrent.Executors;
Charles Chanc7b3c452018-06-19 20:31:57 -070092import java.util.function.BiConsumer;
93import java.util.function.Consumer;
94import java.util.stream.Collectors;
95
Charles Chan168111e2018-08-07 12:48:36 -070096import static org.onlab.util.Tools.groupedThreads;
97
Ray Milkeyd84f89b2018-08-17 14:54:17 -070098@Component(immediate = true, service = XconnectService.class)
Charles Chanc7b3c452018-06-19 20:31:57 -070099public class XconnectManager implements XconnectService {
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700100 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700101 private CoreService coreService;
102
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700103 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700104 private CodecService codecService;
105
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700106 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700107 private StorageService storageService;
108
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700109 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700110 public NetworkConfigService netCfgService;
111
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700113 public DeviceService deviceService;
114
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700116 public FlowObjectiveService flowObjectiveService;
117
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700118 @Reference(cardinality = ReferenceCardinality.MANDATORY)
pier46d557f2018-11-27 11:23:50 -0800119 private LeadershipService leadershipService;
120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
122 private ClusterService clusterService;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc7b3c452018-06-19 20:31:57 -0700125 public MastershipService mastershipService;
126
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700127 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chanc7b3c452018-06-19 20:31:57 -0700128 public SegmentRoutingService srService;
129
Ray Milkeydf521292018-10-04 15:13:33 -0700130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530131 public InterfaceService interfaceService;
132
Ray Milkeydf521292018-10-04 15:13:33 -0700133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530134 HostService hostService;
135
Charles Chanc7b3c452018-06-19 20:31:57 -0700136 private static final String APP_NAME = "org.onosproject.xconnect";
pier46d557f2018-11-27 11:23:50 -0800137 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chanc7b3c452018-06-19 20:31:57 -0700138
139 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
140
141 private ApplicationId appId;
142 private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
Charles Chan3e56d9f2018-09-21 11:29:12 -0700143 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chanc7b3c452018-06-19 20:31:57 -0700144
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530145 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
146 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
147
Charles Chanc7b3c452018-06-19 20:31:57 -0700148 private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
pier46d557f2018-11-27 11:23:50 -0800149 private ExecutorService xConnectExecutor;
Charles Chanc7b3c452018-06-19 20:31:57 -0700150
pier46d557f2018-11-27 11:23:50 -0800151 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan168111e2018-08-07 12:48:36 -0700152 private ExecutorService deviceEventExecutor;
153
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530154 private final HostListener hostListener = new InternalHostListener();
155 private ExecutorService hostEventExecutor;
156
Charles Chanc7b3c452018-06-19 20:31:57 -0700157 @Activate
158 void activate() {
159 appId = coreService.registerApplication(APP_NAME);
160 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
161
162 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
163 .register(KryoNamespaces.API)
Charles Chan871d9182018-07-23 12:53:16 -0700164 .register(XconnectManager.class)
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530165 .register(XconnectKey.class)
166 .register(VlanNextObjectiveStoreKey.class);
Charles Chanc7b3c452018-06-19 20:31:57 -0700167
168 xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
169 .withName("onos-sr-xconnect")
170 .withRelaxedReadConsistency()
171 .withSerializer(Serializer.using(serializer.build()))
172 .build();
pier46d557f2018-11-27 11:23:50 -0800173 xConnectExecutor = Executors.newSingleThreadScheduledExecutor(
174 groupedThreads("sr-xconnect-event", "%d", log));
175 xconnectStore.addListener(xconnectListener, xConnectExecutor);
Charles Chanc7b3c452018-06-19 20:31:57 -0700176
Charles Chan3e56d9f2018-09-21 11:29:12 -0700177 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chanc7b3c452018-06-19 20:31:57 -0700178 .withName("onos-sr-xconnect-next")
179 .withRelaxedReadConsistency()
180 .withSerializer(Serializer.using(serializer.build()))
181 .build();
182
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530183 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
184 .withName("onos-sr-xconnect-l2multicast-next")
185 .withSerializer(Serializer.using(serializer.build()))
186 .build();
187 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
188 .withName("onos-sr-xconnect-l2multicast-ports")
189 .withSerializer(Serializer.using(serializer.build()))
190 .build();
191
Charles Chan168111e2018-08-07 12:48:36 -0700192 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
193 groupedThreads("sr-xconnect-device-event", "%d", log));
Charles Chanc7b3c452018-06-19 20:31:57 -0700194 deviceService.addListener(deviceListener);
195
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530196 hostEventExecutor = Executors.newSingleThreadExecutor(
197 groupedThreads("sr-xconnect-host-event", "%d", log));
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530198 hostService.addListener(hostListener);
199
Charles Chanc7b3c452018-06-19 20:31:57 -0700200 log.info("Started");
201 }
202
203 @Deactivate
204 void deactivate() {
205 xconnectStore.removeListener(xconnectListener);
206 deviceService.removeListener(deviceListener);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530207 hostService.removeListener(hostListener);
Charles Chanc7b3c452018-06-19 20:31:57 -0700208 codecService.unregisterCodec(XconnectDesc.class);
209
Charles Chan168111e2018-08-07 12:48:36 -0700210 deviceEventExecutor.shutdown();
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530211 hostEventExecutor.shutdown();
pier46d557f2018-11-27 11:23:50 -0800212 xConnectExecutor.shutdown();
Charles Chan168111e2018-08-07 12:48:36 -0700213
Charles Chanc7b3c452018-06-19 20:31:57 -0700214 log.info("Stopped");
215 }
216
217 @Override
218 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
219 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530220 deviceId, vlanId, ports);
Charles Chanc7b3c452018-06-19 20:31:57 -0700221 final XconnectKey key = new XconnectKey(deviceId, vlanId);
222 xconnectStore.put(key, ports);
223 }
224
225 @Override
226 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
227 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530228 deviceId, vlanId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700229 final XconnectKey key = new XconnectKey(deviceId, vlanId);
230 xconnectStore.remove(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530231
232 // Cleanup multicasting support, if any.
233 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
234 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
235 });
236
Charles Chanc7b3c452018-06-19 20:31:57 -0700237 }
238
239 @Override
240 public Set<XconnectDesc> getXconnects() {
241 return xconnectStore.asJavaMap().entrySet().stream()
242 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
243 .collect(Collectors.toSet());
244 }
245
246 @Override
247 public boolean hasXconnect(ConnectPoint cp) {
pier46d557f2018-11-27 11:23:50 -0800248 return getXconnects().stream().anyMatch(desc -> desc.key().deviceId().equals(cp.deviceId())
249 && desc.ports().contains(cp.port())
Charles Chanc7b3c452018-06-19 20:31:57 -0700250 );
251 }
252
Charles Chand5814aa2018-08-19 19:21:46 -0700253 @Override
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530254 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
255 return getXconnects().stream()
256 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
pier46d557f2018-11-27 11:23:50 -0800257 .map(desc -> desc.key().vlanId())
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530258 .collect(Collectors.toList());
259 }
260
261 @Override
262 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier46d557f2018-11-27 11:23:50 -0800263 XconnectKey key = new XconnectKey(deviceId, vlanId);
264 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530265 }
266
267 @Override
Charles Chan3e56d9f2018-09-21 11:29:12 -0700268 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chand5814aa2018-08-19 19:21:46 -0700269 if (xconnectNextObjStore != null) {
270 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
271 } else {
272 return ImmutableMap.of();
273 }
274 }
275
276 @Override
pier46d557f2018-11-27 11:23:50 -0800277 public int getNextId(DeviceId deviceId, VlanId vlanId) {
278 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530279 }
280
281 @Override
Charles Chand5814aa2018-08-19 19:21:46 -0700282 public void removeNextId(int nextId) {
283 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700284 if (e.getValue().value() == nextId) {
Charles Chand5814aa2018-08-19 19:21:46 -0700285 xconnectNextObjStore.remove(e.getKey());
286 }
287 });
288 }
289
Charles Chanc7b3c452018-06-19 20:31:57 -0700290 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<PortNumber>> {
291 @Override
292 public void event(MapEvent<XconnectKey, Set<PortNumber>> event) {
293 XconnectKey key = event.key();
294 Versioned<Set<PortNumber>> ports = event.newValue();
295 Versioned<Set<PortNumber>> oldPorts = event.oldValue();
296
297 switch (event.type()) {
298 case INSERT:
299 populateXConnect(key, ports.value());
300 break;
301 case UPDATE:
302 updateXConnect(key, oldPorts.value(), ports.value());
303 break;
304 case REMOVE:
305 revokeXConnect(key, oldPorts.value());
306 break;
307 default:
308 break;
309 }
310 }
311 }
312
313 private class InternalDeviceListener implements DeviceListener {
pier46d557f2018-11-27 11:23:50 -0800314 // Offload the execution to an executor and then process the event
315 // if this instance is the leader of the device
Charles Chanc7b3c452018-06-19 20:31:57 -0700316 @Override
317 public void event(DeviceEvent event) {
Charles Chan168111e2018-08-07 12:48:36 -0700318 deviceEventExecutor.execute(() -> {
319 DeviceId deviceId = event.subject().id();
pier46d557f2018-11-27 11:23:50 -0800320 // Just skip if we are not the leader
321 if (!isLocalLeader(deviceId)) {
322 log.debug("Not the leader of {}. Skip event {}", deviceId, event);
Charles Chan168111e2018-08-07 12:48:36 -0700323 return;
324 }
pier46d557f2018-11-27 11:23:50 -0800325 // Populate or revoke according to the device availability
326 if (deviceService.isAvailable(deviceId)) {
327 init(deviceId);
328 } else {
329 cleanup(deviceId);
Charles Chan168111e2018-08-07 12:48:36 -0700330 }
331 });
Charles Chanc7b3c452018-06-19 20:31:57 -0700332 }
pier46d557f2018-11-27 11:23:50 -0800333 // We want to manage only a subset of events and if we are the leader
334 @Override
335 public boolean isRelevant(DeviceEvent event) {
336 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
337 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
338 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
339 }
Charles Chanc7b3c452018-06-19 20:31:57 -0700340 }
341
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530342 private class InternalHostListener implements HostListener {
343 @Override
344 public void event(HostEvent event) {
345 hostEventExecutor.execute(() -> {
346
347 switch (event.type()) {
348 case HOST_MOVED:
349 log.trace("Processing host event {}", event);
350
351 Host host = event.subject();
352 Set<HostLocation> prevLocations = event.prevSubject().locations();
353 Set<HostLocation> newLocations = host.locations();
354
355 // Dual-home host port failure
356 // For each old location, in failed and paired devices update L2 vlan groups
357 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
358
359 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
360 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
361
362 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
363 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
364 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
365
366 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
367 prevLocation.port());
368 xconnectVlans.forEach(xconnectVlan -> {
369 // Add single-home host into L2 multicast group at paired device side.
370 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
371 newLocations.stream()
372 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
373 .forEach(location -> populateL2Multicast(location.deviceId(),
374 srService.getPairLocalPort(
375 location.deviceId()).get(),
376 xconnectVlan,
377 Collections.singletonList(
378 location.port())));
pier46d557f2018-11-27 11:23:50 -0800379 // Ensure pair-port attached to xconnect vlan flooding group
380 // at dual home failed device.
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530381 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
382 });
383 }
384 });
385
386 // Dual-home host port restoration
387 // For each new location, reverse xconnect loop prevention groups.
388 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
389 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
390 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
391 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
392 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
393
394 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
395 newLocation.port());
396 xconnectVlans.forEach(xconnectVlan -> {
397 // Remove recovered dual homed port from vlan L2 multicast group
398 prevLocations.stream()
399 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier46d557f2018-11-27 11:23:50 -0800400 .forEach(prevLocation -> revokeL2Multicast(
401 prevLocation.deviceId(),
402 xconnectVlan,
403 Collections.singletonList(newLocation.port()))
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530404 );
405
pier46d557f2018-11-27 11:23:50 -0800406 // Remove pair-port from vlan's flooding group at dual home
407 // restored device, if needed.
408 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
409 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530410 updateL2Flooding(newLocation.deviceId(),
411 pairLocalPort.get(),
412 xconnectVlan,
413 false);
414
415 // Clean L2 multicast group at pair-device; also update store.
416 cleanupL2MulticastRule(pairDeviceId.get(),
417 srService.getPairLocalPort(pairDeviceId.get()).get(),
418 xconnectVlan,
419 false);
420 }
421 });
422 }
423 });
424 break;
425
426 default:
427 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
428 break;
429 }
430 });
431 }
432 }
433
Charles Chan3e56d9f2018-09-21 11:29:12 -0700434 private void init(DeviceId deviceId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700435 getXconnects().stream()
436 .filter(desc -> desc.key().deviceId().equals(deviceId))
437 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
438 }
439
Charles Chan3e56d9f2018-09-21 11:29:12 -0700440 private void cleanup(DeviceId deviceId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700441 xconnectNextObjStore.entrySet().stream()
442 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
443 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
444 log.debug("{} is removed from xConnectNextObjStore", deviceId);
445 }
446
447 /**
448 * Populates XConnect groups and flows for given key.
449 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530450 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700451 * @param ports a set of ports to be cross-connected
452 */
453 private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
pier46d557f2018-11-27 11:23:50 -0800454 if (!isLocalLeader(key.deviceId())) {
455 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chanc7b3c452018-06-19 20:31:57 -0700456 return;
457 }
458
Charles Chanc7b3c452018-06-19 20:31:57 -0700459 populateFilter(key, ports);
460 populateFwd(key, populateNext(key, ports));
461 populateAcl(key);
462 }
463
464 /**
465 * Populates filtering objectives for given XConnect.
466 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530467 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700468 * @param ports XConnect ports
469 */
470 private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
471 ports.forEach(port -> {
472 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
473 ObjectiveContext context = new DefaultObjectiveContext(
474 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530475 key, port),
Charles Chanc7b3c452018-06-19 20:31:57 -0700476 (objective, error) ->
477 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530478 key, port, error));
Charles Chanc7b3c452018-06-19 20:31:57 -0700479 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
480 });
481 }
482
483 /**
484 * Populates next objectives for given XConnect.
485 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530486 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700487 * @param ports XConnect ports
488 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700489 private int populateNext(XconnectKey key, Set<PortNumber> ports) {
pier46d557f2018-11-27 11:23:50 -0800490 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
491 if (nextId != -1) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700492 log.debug("NextObj for {} found, id={}", key, nextId);
493 return nextId;
Charles Chanc7b3c452018-06-19 20:31:57 -0700494 } else {
495 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
496 ObjectiveContext nextContext = new DefaultObjectiveContext(
497 // To serialize this with kryo
498 (Serializable & Consumer<Objective>) (objective) ->
499 log.debug("XConnect NextObj for {} added", key),
Charles Chan55b806f2018-08-23 14:30:33 -0700500 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
501 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
502 srService.invalidateNextObj(objective.id());
503 });
Charles Chan3e56d9f2018-09-21 11:29:12 -0700504 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chanc7b3c452018-06-19 20:31:57 -0700505 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan3e56d9f2018-09-21 11:29:12 -0700506 xconnectNextObjStore.put(key, nextObj.id());
Charles Chanc7b3c452018-06-19 20:31:57 -0700507 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan3e56d9f2018-09-21 11:29:12 -0700508 return nextObj.id();
Charles Chanc7b3c452018-06-19 20:31:57 -0700509 }
Charles Chanc7b3c452018-06-19 20:31:57 -0700510 }
511
512 /**
513 * Populates bridging forwarding objectives for given XConnect.
514 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530515 * @param key XConnect store key
Charles Chan3e56d9f2018-09-21 11:29:12 -0700516 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700517 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700518 private void populateFwd(XconnectKey key, int nextId) {
519 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700520 ObjectiveContext fwdContext = new DefaultObjectiveContext(
521 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
522 (objective, error) ->
523 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
524 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
525 }
526
527 /**
528 * Populates ACL forwarding objectives for given XConnect.
529 *
530 * @param key XConnect store key
531 */
532 private void populateAcl(XconnectKey key) {
533 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
534 ObjectiveContext aclContext = new DefaultObjectiveContext(
535 (objective) -> log.debug("XConnect AclObj for {} populated", key),
536 (objective, error) ->
537 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
538 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
539 }
540
541 /**
542 * Revokes XConnect groups and flows for given key.
543 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530544 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700545 * @param ports XConnect ports
546 */
547 private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
pier46d557f2018-11-27 11:23:50 -0800548 if (!isLocalLeader(key.deviceId())) {
549 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chanc7b3c452018-06-19 20:31:57 -0700550 return;
551 }
552
Charles Chanc7b3c452018-06-19 20:31:57 -0700553 revokeFilter(key, ports);
pier46d557f2018-11-27 11:23:50 -0800554 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
555 if (nextId != -1) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700556 revokeFwd(key, nextId, null);
557 revokeNext(key, ports, nextId, null);
Charles Chanc7b3c452018-06-19 20:31:57 -0700558 } else {
559 log.warn("NextObj for {} does not exist in the store.", key);
560 }
561 revokeAcl(key);
562 }
563
564 /**
565 * Revokes filtering objectives for given XConnect.
566 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530567 * @param key XConnect store key
Charles Chanc7b3c452018-06-19 20:31:57 -0700568 * @param ports XConnect ports
569 */
570 private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
571 ports.forEach(port -> {
572 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
573 ObjectiveContext context = new DefaultObjectiveContext(
574 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530575 key, port),
Charles Chanc7b3c452018-06-19 20:31:57 -0700576 (objective, error) ->
577 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530578 key, port, error));
Charles Chanc7b3c452018-06-19 20:31:57 -0700579 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
580 });
581 }
582
583 /**
584 * Revokes next objectives for given XConnect.
585 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530586 * @param key XConnect store key
587 * @param ports ports in the XConnect
588 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700589 * @param nextFuture completable future for this next objective operation
590 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700591 private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
Charles Chanc7b3c452018-06-19 20:31:57 -0700592 CompletableFuture<ObjectiveError> nextFuture) {
593 ObjectiveContext context = new ObjectiveContext() {
594 @Override
595 public void onSuccess(Objective objective) {
596 log.debug("Previous NextObj for {} removed", key);
597 if (nextFuture != null) {
598 nextFuture.complete(null);
599 }
600 }
601
602 @Override
603 public void onError(Objective objective, ObjectiveError error) {
604 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
605 if (nextFuture != null) {
606 nextFuture.complete(error);
607 }
Charles Chan55b806f2018-08-23 14:30:33 -0700608 srService.invalidateNextObj(objective.id());
Charles Chanc7b3c452018-06-19 20:31:57 -0700609 }
610 };
Charles Chan3e56d9f2018-09-21 11:29:12 -0700611 flowObjectiveService.next(key.deviceId(), nextObjBuilder(key, ports, nextId).remove(context));
Charles Chanc7b3c452018-06-19 20:31:57 -0700612 xconnectNextObjStore.remove(key);
613 }
614
615 /**
616 * Revokes bridging forwarding objectives for given XConnect.
617 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530618 * @param key XConnect store key
619 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700620 * @param fwdFuture completable future for this forwarding objective operation
621 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700622 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
623 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700624 ObjectiveContext context = new ObjectiveContext() {
625 @Override
626 public void onSuccess(Objective objective) {
627 log.debug("Previous FwdObj for {} removed", key);
628 if (fwdFuture != null) {
629 fwdFuture.complete(null);
630 }
631 }
632
633 @Override
634 public void onError(Objective objective, ObjectiveError error) {
635 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
636 if (fwdFuture != null) {
637 fwdFuture.complete(error);
638 }
639 }
640 };
641 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
642 }
643
644 /**
645 * Revokes ACL forwarding objectives for given XConnect.
646 *
647 * @param key XConnect store key
648 */
649 private void revokeAcl(XconnectKey key) {
650 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
651 ObjectiveContext aclContext = new DefaultObjectiveContext(
652 (objective) -> log.debug("XConnect AclObj for {} populated", key),
653 (objective, error) ->
654 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
655 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
656 }
657
658 /**
659 * Updates XConnect groups and flows for given key.
660 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530661 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700662 * @param prevPorts previous XConnect ports
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530663 * @param ports new XConnect ports
Charles Chanc7b3c452018-06-19 20:31:57 -0700664 */
665 private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
666 Set<PortNumber> ports) {
pier46d557f2018-11-27 11:23:50 -0800667 if (!isLocalLeader(key.deviceId())) {
668 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
669 return;
670 }
Charles Chanc7b3c452018-06-19 20:31:57 -0700671 // NOTE: ACL flow doesn't include port information. No need to update it.
672 // Pair port is built-in and thus not going to change. No need to update it.
673
674 // remove old filter
675 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530676 revokeFilter(key,
677 ImmutableSet.of(port)));
Charles Chanc7b3c452018-06-19 20:31:57 -0700678 // install new filter
679 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530680 populateFilter(key,
681 ImmutableSet.of(port)));
Charles Chanc7b3c452018-06-19 20:31:57 -0700682
683 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
684 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
685
pier46d557f2018-11-27 11:23:50 -0800686 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
687 if (nextId != -1) {
Charles Chan3e56d9f2018-09-21 11:29:12 -0700688 revokeFwd(key, nextId, fwdFuture);
Charles Chanc7b3c452018-06-19 20:31:57 -0700689
690 fwdFuture.thenAcceptAsync(fwdStatus -> {
691 if (fwdStatus == null) {
692 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan3e56d9f2018-09-21 11:29:12 -0700693 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chanc7b3c452018-06-19 20:31:57 -0700694 }
695 });
696
697 nextFuture.thenAcceptAsync(nextStatus -> {
698 if (nextStatus == null) {
699 log.debug("Installing new group and flow for {}", key);
700 populateFwd(key, populateNext(key, ports));
701 }
702 });
703 } else {
704 log.warn("NextObj for {} does not exist in the store.", key);
705 }
706 }
707
708 /**
Charles Chan3e56d9f2018-09-21 11:29:12 -0700709 * Creates a next objective builder for XConnect with given nextId.
Charles Chanc7b3c452018-06-19 20:31:57 -0700710 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530711 * @param key XConnect key
712 * @param ports set of XConnect ports
Charles Chan3e56d9f2018-09-21 11:29:12 -0700713 * @param nextId next objective id
Charles Chanc7b3c452018-06-19 20:31:57 -0700714 * @return next objective builder
715 */
Charles Chan3e56d9f2018-09-21 11:29:12 -0700716 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports, int nextId) {
Charles Chanc7b3c452018-06-19 20:31:57 -0700717 TrafficSelector metadata =
718 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
719 NextObjective.Builder nextObjBuilder = DefaultNextObjective
720 .builder().withId(nextId)
721 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
722 .withMeta(metadata);
723 ports.forEach(port -> {
724 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
725 tBuilder.setOutput(port);
726 nextObjBuilder.addTreatment(tBuilder.build());
727 });
728 return nextObjBuilder;
729 }
730
731 /**
Charles Chan3e56d9f2018-09-21 11:29:12 -0700732 * Creates a next objective builder for XConnect.
733 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530734 * @param key XConnect key
Charles Chan3e56d9f2018-09-21 11:29:12 -0700735 * @param ports set of XConnect ports
736 * @return next objective builder
737 */
738 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports) {
739 int nextId = flowObjectiveService.allocateNextId();
740 return nextObjBuilder(key, ports, nextId);
741 }
742
743
744 /**
Charles Chanc7b3c452018-06-19 20:31:57 -0700745 * Creates a bridging forwarding objective builder for XConnect.
746 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530747 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700748 * @param nextId next ID of the broadcast group for this XConnect key
749 * @return forwarding objective builder
750 */
751 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
752 /*
753 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
754 * as the VLAN cross-connect broadcast rules
755 */
756 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
757 sbuilder.matchVlanId(key.vlanId());
758 sbuilder.matchEthDst(MacAddress.NONE);
759
760 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
761 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
762 .withSelector(sbuilder.build())
763 .nextStep(nextId)
764 .withPriority(XCONNECT_PRIORITY)
765 .fromApp(appId)
766 .makePermanent();
767 return fob;
768 }
769
770 /**
771 * Creates an ACL forwarding objective builder for XConnect.
772 *
773 * @param vlanId cross connect VLAN id
774 * @return forwarding objective builder
775 */
776 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
777 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
778 sbuilder.matchVlanId(vlanId);
779
780 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
781
782 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
783 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
784 .withSelector(sbuilder.build())
785 .withTreatment(tbuilder.build())
786 .withPriority(XCONNECT_ACL_PRIORITY)
787 .fromApp(appId)
788 .makePermanent();
789 return fob;
790 }
791
792 /**
793 * Creates a filtering objective builder for XConnect.
794 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530795 * @param key XConnect key
Charles Chanc7b3c452018-06-19 20:31:57 -0700796 * @param port XConnect ports
797 * @return next objective builder
798 */
799 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) {
800 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
801 fob.withKey(Criteria.matchInPort(port))
802 .addCondition(Criteria.matchVlanId(key.vlanId()))
803 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
804 .withPriority(XCONNECT_PRIORITY);
805 return fob.permit().fromApp(appId);
806 }
807
808 /**
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530809 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chanc7b3c452018-06-19 20:31:57 -0700810 *
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530811 * @param deviceId Device ID
812 * @param port Port details
813 * @param vlanId VLAN ID
814 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chanc7b3c452018-06-19 20:31:57 -0700815 */
pier46d557f2018-11-27 11:23:50 -0800816 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
817 XconnectKey key = new XconnectKey(deviceId, vlanId);
818 // Ensure leadership on device
819 if (!isLocalLeader(deviceId)) {
820 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530821 return;
Charles Chanc7b3c452018-06-19 20:31:57 -0700822 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530823
824 // Locate L2 flooding group details for given xconnect vlan
pier46d557f2018-11-27 11:23:50 -0800825 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530826 if (nextId == -1) {
827 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
828 "Aborting pair group linking.", vlanId, deviceId);
829 return;
830 }
831
832 // Add pairing-port group to flooding group
833 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
834 // treatment.popVlan();
835 treatment.setOutput(port);
836 ObjectiveContext context = new DefaultObjectiveContext(
837 (objective) ->
838 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
839 vlanId, nextId, deviceId),
840 (objective, error) ->
841 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
842 "Error : {}", vlanId, nextId, deviceId, error)
843 );
844 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
845 .withId(nextId)
846 .withType(NextObjective.Type.BROADCAST)
847 .fromApp(srService.appId())
848 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
849 .addTreatment(treatment.build());
850 if (install) {
851 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
852 } else {
853 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
854 }
855 log.debug("Submitted next objective {} for vlan: {} in device {}",
856 nextId, vlanId, deviceId);
Charles Chanc7b3c452018-06-19 20:31:57 -0700857 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530858
859 /**
860 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
861 * output to given port's L2 mulitcast group.
862 *
863 * @param deviceId Device ID
864 * @param pairPort Pair port number
865 * @param vlanId VLAN ID
866 * @param accessPorts List of access ports to be added into L2 multicast group
867 */
pier46d557f2018-11-27 11:23:50 -0800868 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
869 VlanId vlanId, List<PortNumber> accessPorts) {
870 // Ensure enough rights to program pair device
871 if (!srService.shouldProgram(deviceId)) {
872 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
873 return;
874 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530875
876 boolean multicastGroupExists = true;
877 int vlanMulticastNextId;
pier46d557f2018-11-27 11:23:50 -0800878 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530879
880 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
881 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
882 .builder()
883 .withType(NextObjective.Type.BROADCAST)
884 .fromApp(srService.appId())
885 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
886 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier46d557f2018-11-27 11:23:50 -0800887 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530888 if (vlanMulticastNextId == -1) {
889 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
890 multicastGroupExists = false;
891 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier46d557f2018-11-27 11:23:50 -0800892 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530893 vlanMulticastNextObjBuilder.addTreatment(
894 DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
895 );
896 }
897 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier46d557f2018-11-27 11:23:50 -0800898 int nextId = vlanMulticastNextId;
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530899 accessPorts.forEach(p -> {
900 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
901 // Do vlan popup action based on interface configuration
902 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
903 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
904 egressAction.popVlan();
905 }
906 egressAction.setOutput(p);
907 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier46d557f2018-11-27 11:23:50 -0800908 addMulticastGroupPort(key, p);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530909 });
910 ObjectiveContext context = new DefaultObjectiveContext(
911 (objective) ->
912 log.debug("L2 multicast group installed/updated. "
913 + "NextObject Id {} on {} for subnet {} ",
914 nextId, deviceId, vlanId),
915 (objective, error) ->
916 log.warn("L2 multicast group failed to install/update. "
917 + " NextObject Id {} on {} for subnet {} : {}",
918 nextId, deviceId, vlanId, error)
919 );
920 if (!multicastGroupExists) {
921 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
922
923 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
924 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
925 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
926 multicastSelector.matchInPort(pairPort);
927 multicastSelector.matchVlanId(vlanId);
928 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
929 .withFlag(ForwardingObjective.Flag.VERSATILE)
930 .nextStep(vlanMulticastNextId)
931 .withSelector(multicastSelector.build())
932 .withPriority(100)
933 .fromApp(srService.appId())
934 .makePermanent();
935 context = new DefaultObjectiveContext(
936 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
937 deviceId,
938 pairPort,
939 vlanId),
940 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
941 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
942 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
943 } else {
944 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
945 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
946 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
947 }
948 }
949
950 /**
951 * Removes access ports from VLAN L2 multicast group on given deviceId.
952 *
953 * @param deviceId Device ID
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530954 * @param vlanId VLAN ID
955 * @param accessPorts List of access ports to be added into L2 multicast group
956 */
pier46d557f2018-11-27 11:23:50 -0800957 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530958 // Ensure enough rights to program pair device
959 if (!srService.shouldProgram(deviceId)) {
pier46d557f2018-11-27 11:23:50 -0800960 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530961 return;
962 }
963
pier46d557f2018-11-27 11:23:50 -0800964 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
965
966 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530967 if (vlanMulticastNextId == -1) {
968 return;
969 }
970 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
971 .builder()
972 .withType(NextObjective.Type.BROADCAST)
973 .fromApp(srService.appId())
974 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
975 .withId(vlanMulticastNextId);
976 accessPorts.forEach(p -> {
977 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
978 // Do vlan popup action based on interface configuration
979 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
980 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
981 egressAction.popVlan();
982 }
983 egressAction.setOutput(p);
984 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier46d557f2018-11-27 11:23:50 -0800985 removeMulticastGroupPort(key, p);
jayakumarthazhath655b9a82018-10-01 00:51:54 +0530986 });
987 ObjectiveContext context = new DefaultObjectiveContext(
988 (objective) ->
989 log.debug("L2 multicast group installed/updated. "
990 + "NextObject Id {} on {} for subnet {} ",
991 vlanMulticastNextId, deviceId, vlanId),
992 (objective, error) ->
993 log.warn("L2 multicast group failed to install/update. "
994 + " NextObject Id {} on {} for subnet {} : {}",
995 vlanMulticastNextId, deviceId, vlanId, error)
996 );
997 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
998 }
999
1000 /**
1001 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1002 * Normally multicast group is not removed if it contains access ports; which can be forced
1003 * by "force" flag
1004 *
1005 * @param deviceId Device ID
1006 * @param pairPort Pair port number
1007 * @param vlanId VLAN ID
1008 * @param force Forceful removal
1009 */
1010 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1011
1012 // Ensure enough rights to program pair device
1013 if (!srService.shouldProgram(deviceId)) {
pier46d557f2018-11-27 11:23:50 -08001014 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301015 return;
1016 }
1017
pier46d557f2018-11-27 11:23:50 -08001018 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1019
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301020 // Ensure L2 multicast group doesn't contain access ports
pier46d557f2018-11-27 11:23:50 -08001021 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301022 return;
1023 }
1024
1025 // Load L2 multicast group details
pier46d557f2018-11-27 11:23:50 -08001026 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301027 if (vlanMulticastNextId == -1) {
1028 return;
1029 }
1030
1031 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1032 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1033 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1034 l2MulticastSelector.matchInPort(pairPort);
1035 l2MulticastSelector.matchVlanId(vlanId);
1036 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1037 .withFlag(ForwardingObjective.Flag.VERSATILE)
1038 .nextStep(vlanMulticastNextId)
1039 .withSelector(l2MulticastSelector.build())
1040 .withPriority(100)
1041 .fromApp(srService.appId())
1042 .makePermanent();
1043 ObjectiveContext context = new DefaultObjectiveContext(
1044 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1045 pairPort, vlanId),
1046 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1047 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1048 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1049
1050 // Step 2 : Clear L2 multicast group associated with vlan
1051 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1052 .builder()
1053 .withId(vlanMulticastNextId)
1054 .withType(NextObjective.Type.BROADCAST)
1055 .fromApp(srService.appId())
1056 .withMeta(DefaultTrafficSelector.builder()
1057 .matchVlanId(vlanId)
1058 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1059 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1060 context = new DefaultObjectiveContext(
1061 (objective) ->
1062 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1063 vlanMulticastNextId, deviceId, vlanId),
1064 (objective, error) ->
1065 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1066 vlanMulticastNextId, deviceId, vlanId, error)
1067 );
1068 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1069
1070 // Finally clear store.
pier46d557f2018-11-27 11:23:50 -08001071 removeMulticastGroup(key);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301072 }
1073
pier46d557f2018-11-27 11:23:50 -08001074 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1075 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301076 }
1077
pier46d557f2018-11-27 11:23:50 -08001078 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301079 if (nextId == -1) {
1080 return;
1081 }
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301082 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301083 }
1084
pier46d557f2018-11-27 11:23:50 -08001085 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1086 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1087 if (ports == null) {
1088 ports = Lists.newArrayList();
1089 }
1090 ports.add(port);
1091 return ports;
1092 });
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301093 }
1094
pier46d557f2018-11-27 11:23:50 -08001095 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1096 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1097 if (ports != null && !ports.isEmpty()) {
1098 ports.remove(port);
1099 }
1100 return ports;
1101 });
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301102 }
1103
pier46d557f2018-11-27 11:23:50 -08001104 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1105 xconnectMulticastPortsStore.remove(groupKey);
1106 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301107 }
1108
pier46d557f2018-11-27 11:23:50 -08001109 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1110 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath655b9a82018-10-01 00:51:54 +05301111 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1112 }
1113
pier46d557f2018-11-27 11:23:50 -08001114 // Custom-built function, when the device is not available we need a fallback mechanism
1115 private boolean isLocalLeader(DeviceId deviceId) {
1116 if (!mastershipService.isLocalMaster(deviceId)) {
1117 // When the device is available we just check the mastership
1118 if (deviceService.isAvailable(deviceId)) {
1119 return false;
1120 }
1121 // Fallback with Leadership service - device id is used as topic
1122 NodeId leader = leadershipService.runForLeadership(
1123 deviceId.toString()).leaderNodeId();
1124 // Verify if this node is the leader
1125 return clusterService.getLocalNode().id().equals(leader);
1126 }
1127 return true;
1128 }
1129
Charles Chanc7b3c452018-06-19 20:31:57 -07001130}