blob: 88361aedb7d0677513ce0a96635e22751a637be8 [file] [log] [blame]
Charles Chan4e87b3e2018-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
pier6c48dba2018-11-27 11:23:50 -080018import com.google.common.collect.ImmutableList;
Charles Chan1fa010c2018-08-19 19:21:46 -070019import com.google.common.collect.ImmutableMap;
Charles Chan4e87b3e2018-06-19 20:31:57 -070020import com.google.common.collect.ImmutableSet;
pier6c48dba2018-11-27 11:23:50 -080021import com.google.common.collect.Lists;
Charles Chan4e87b3e2018-06-19 20:31:57 -070022import com.google.common.collect.Sets;
23import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
jayakumarthazhath46945b62018-10-01 00:51:54 +053029import org.onlab.packet.Ethernet;
Charles Chan4e87b3e2018-06-19 20:31:57 -070030import org.onlab.packet.MacAddress;
31import org.onlab.packet.VlanId;
32import org.onlab.util.KryoNamespace;
pier6c48dba2018-11-27 11:23:50 -080033import org.onosproject.cluster.ClusterService;
34import org.onosproject.cluster.LeadershipService;
35import org.onosproject.cluster.NodeId;
Charles Chan4e87b3e2018-06-19 20:31:57 -070036import org.onosproject.codec.CodecService;
37import org.onosproject.core.ApplicationId;
38import org.onosproject.core.CoreService;
39import org.onosproject.mastership.MastershipService;
40import org.onosproject.net.ConnectPoint;
41import org.onosproject.net.DeviceId;
jayakumarthazhath46945b62018-10-01 00:51:54 +053042import org.onosproject.net.Host;
43import org.onosproject.net.HostLocation;
Charles Chan4e87b3e2018-06-19 20:31:57 -070044import org.onosproject.net.PortNumber;
45import org.onosproject.net.config.NetworkConfigService;
46import org.onosproject.net.device.DeviceEvent;
47import org.onosproject.net.device.DeviceListener;
48import org.onosproject.net.device.DeviceService;
49import org.onosproject.net.flow.DefaultTrafficSelector;
50import org.onosproject.net.flow.DefaultTrafficTreatment;
51import org.onosproject.net.flow.TrafficSelector;
52import org.onosproject.net.flow.TrafficTreatment;
53import org.onosproject.net.flow.criteria.Criteria;
54import org.onosproject.net.flowobjective.DefaultFilteringObjective;
55import org.onosproject.net.flowobjective.DefaultForwardingObjective;
56import org.onosproject.net.flowobjective.DefaultNextObjective;
57import org.onosproject.net.flowobjective.DefaultObjectiveContext;
58import org.onosproject.net.flowobjective.FilteringObjective;
59import org.onosproject.net.flowobjective.FlowObjectiveService;
60import org.onosproject.net.flowobjective.ForwardingObjective;
61import org.onosproject.net.flowobjective.NextObjective;
62import org.onosproject.net.flowobjective.Objective;
63import org.onosproject.net.flowobjective.ObjectiveContext;
64import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath46945b62018-10-01 00:51:54 +053065import org.onosproject.net.host.HostEvent;
66import org.onosproject.net.host.HostListener;
67import org.onosproject.net.host.HostService;
68import org.onosproject.net.intf.InterfaceService;
Charles Chan4e87b3e2018-06-19 20:31:57 -070069import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath46945b62018-10-01 00:51:54 +053070import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan4e87b3e2018-06-19 20:31:57 -070071import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
72import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
73import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
74import org.onosproject.segmentrouting.xconnect.api.XconnectService;
75import org.onosproject.store.serializers.KryoNamespaces;
76import org.onosproject.store.service.ConsistentMap;
77import org.onosproject.store.service.MapEvent;
78import org.onosproject.store.service.MapEventListener;
79import org.onosproject.store.service.Serializer;
80import org.onosproject.store.service.StorageService;
81import org.onosproject.store.service.Versioned;
82import org.slf4j.Logger;
83import org.slf4j.LoggerFactory;
84
85import java.io.Serializable;
jayakumarthazhath46945b62018-10-01 00:51:54 +053086import java.util.Collections;
87import java.util.List;
jayakumarthazhath46945b62018-10-01 00:51:54 +053088import java.util.Optional;
Charles Chan4e87b3e2018-06-19 20:31:57 -070089import java.util.Set;
90import java.util.concurrent.CompletableFuture;
Charles Chan1eec6252018-08-07 12:48:36 -070091import java.util.concurrent.ExecutorService;
92import java.util.concurrent.Executors;
Charles Chan4e87b3e2018-06-19 20:31:57 -070093import java.util.function.BiConsumer;
94import java.util.function.Consumer;
95import java.util.stream.Collectors;
96
Charles Chan1eec6252018-08-07 12:48:36 -070097import static org.onlab.util.Tools.groupedThreads;
98
Charles Chan4e87b3e2018-06-19 20:31:57 -070099@Service
100@Component(immediate = true)
101public class XconnectManager implements XconnectService {
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 private CoreService coreService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 private CodecService codecService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 private StorageService storageService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 public NetworkConfigService netCfgService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 public DeviceService deviceService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 public FlowObjectiveService flowObjectiveService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
pier6c48dba2018-11-27 11:23:50 -0800121 private LeadershipService leadershipService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 private ClusterService clusterService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Charles Chan4e87b3e2018-06-19 20:31:57 -0700127 public MastershipService mastershipService;
128
Charles Chan264b4b72018-10-19 16:32:07 -0700129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Charles Chan4e87b3e2018-06-19 20:31:57 -0700130 public SegmentRoutingService srService;
131
jayakumarthazhath46945b62018-10-01 00:51:54 +0530132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 public InterfaceService interfaceService;
134
135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
136 HostService hostService;
137
Charles Chan4e87b3e2018-06-19 20:31:57 -0700138 private static final String APP_NAME = "org.onosproject.xconnect";
pier6c48dba2018-11-27 11:23:50 -0800139 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chan4e87b3e2018-06-19 20:31:57 -0700140
141 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
142
143 private ApplicationId appId;
144 private ConsistentMap<XconnectKey, Set<PortNumber>> xconnectStore;
Charles Chan263e7d92018-09-21 11:29:12 -0700145 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan4e87b3e2018-06-19 20:31:57 -0700146
jayakumarthazhath46945b62018-10-01 00:51:54 +0530147 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
148 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
149
Charles Chan4e87b3e2018-06-19 20:31:57 -0700150 private final MapEventListener<XconnectKey, Set<PortNumber>> xconnectListener = new XconnectMapListener();
pier6c48dba2018-11-27 11:23:50 -0800151 private ExecutorService xConnectExecutor;
Charles Chan4e87b3e2018-06-19 20:31:57 -0700152
pier6c48dba2018-11-27 11:23:50 -0800153 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan1eec6252018-08-07 12:48:36 -0700154 private ExecutorService deviceEventExecutor;
155
jayakumarthazhath46945b62018-10-01 00:51:54 +0530156 private final HostListener hostListener = new InternalHostListener();
157 private ExecutorService hostEventExecutor;
158
Charles Chan4e87b3e2018-06-19 20:31:57 -0700159 @Activate
160 void activate() {
161 appId = coreService.registerApplication(APP_NAME);
162 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
163
164 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
165 .register(KryoNamespaces.API)
Charles Chan397398c2018-07-23 12:53:16 -0700166 .register(XconnectManager.class)
jayakumarthazhath46945b62018-10-01 00:51:54 +0530167 .register(XconnectKey.class)
168 .register(VlanNextObjectiveStoreKey.class);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700169
170 xconnectStore = storageService.<XconnectKey, Set<PortNumber>>consistentMapBuilder()
171 .withName("onos-sr-xconnect")
172 .withRelaxedReadConsistency()
173 .withSerializer(Serializer.using(serializer.build()))
174 .build();
pier6c48dba2018-11-27 11:23:50 -0800175 xConnectExecutor = Executors.newSingleThreadScheduledExecutor(
176 groupedThreads("sr-xconnect-event", "%d", log));
177 xconnectStore.addListener(xconnectListener, xConnectExecutor);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700178
Charles Chan263e7d92018-09-21 11:29:12 -0700179 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chan4e87b3e2018-06-19 20:31:57 -0700180 .withName("onos-sr-xconnect-next")
181 .withRelaxedReadConsistency()
182 .withSerializer(Serializer.using(serializer.build()))
183 .build();
184
jayakumarthazhath46945b62018-10-01 00:51:54 +0530185 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
186 .withName("onos-sr-xconnect-l2multicast-next")
187 .withSerializer(Serializer.using(serializer.build()))
188 .build();
189 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
190 .withName("onos-sr-xconnect-l2multicast-ports")
191 .withSerializer(Serializer.using(serializer.build()))
192 .build();
193
Charles Chan1eec6252018-08-07 12:48:36 -0700194 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
195 groupedThreads("sr-xconnect-device-event", "%d", log));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700196 deviceService.addListener(deviceListener);
197
jayakumarthazhath46945b62018-10-01 00:51:54 +0530198 hostEventExecutor = Executors.newSingleThreadExecutor(
199 groupedThreads("sr-xconnect-host-event", "%d", log));
jayakumarthazhath46945b62018-10-01 00:51:54 +0530200 hostService.addListener(hostListener);
201
Charles Chan4e87b3e2018-06-19 20:31:57 -0700202 log.info("Started");
203 }
204
205 @Deactivate
206 void deactivate() {
207 xconnectStore.removeListener(xconnectListener);
208 deviceService.removeListener(deviceListener);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530209 hostService.removeListener(hostListener);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700210 codecService.unregisterCodec(XconnectDesc.class);
211
Charles Chan1eec6252018-08-07 12:48:36 -0700212 deviceEventExecutor.shutdown();
jayakumarthazhath46945b62018-10-01 00:51:54 +0530213 hostEventExecutor.shutdown();
pier6c48dba2018-11-27 11:23:50 -0800214 xConnectExecutor.shutdown();
Charles Chan1eec6252018-08-07 12:48:36 -0700215
Charles Chan4e87b3e2018-06-19 20:31:57 -0700216 log.info("Stopped");
217 }
218
219 @Override
220 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<PortNumber> ports) {
221 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530222 deviceId, vlanId, ports);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700223 final XconnectKey key = new XconnectKey(deviceId, vlanId);
224 xconnectStore.put(key, ports);
225 }
226
227 @Override
228 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
229 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530230 deviceId, vlanId);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700231 final XconnectKey key = new XconnectKey(deviceId, vlanId);
232 xconnectStore.remove(key);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530233
234 // Cleanup multicasting support, if any.
235 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
236 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
237 });
238
Charles Chan4e87b3e2018-06-19 20:31:57 -0700239 }
240
241 @Override
242 public Set<XconnectDesc> getXconnects() {
243 return xconnectStore.asJavaMap().entrySet().stream()
244 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
245 .collect(Collectors.toSet());
246 }
247
248 @Override
249 public boolean hasXconnect(ConnectPoint cp) {
pier6c48dba2018-11-27 11:23:50 -0800250 return getXconnects().stream().anyMatch(desc -> desc.key().deviceId().equals(cp.deviceId())
251 && desc.ports().contains(cp.port())
Charles Chan4e87b3e2018-06-19 20:31:57 -0700252 );
253 }
254
Charles Chan1fa010c2018-08-19 19:21:46 -0700255 @Override
jayakumarthazhath46945b62018-10-01 00:51:54 +0530256 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
257 return getXconnects().stream()
258 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port))
pier6c48dba2018-11-27 11:23:50 -0800259 .map(desc -> desc.key().vlanId())
jayakumarthazhath46945b62018-10-01 00:51:54 +0530260 .collect(Collectors.toList());
261 }
262
263 @Override
264 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier6c48dba2018-11-27 11:23:50 -0800265 XconnectKey key = new XconnectKey(deviceId, vlanId);
266 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath46945b62018-10-01 00:51:54 +0530267 }
268
269 @Override
Charles Chan263e7d92018-09-21 11:29:12 -0700270 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan1fa010c2018-08-19 19:21:46 -0700271 if (xconnectNextObjStore != null) {
272 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
273 } else {
274 return ImmutableMap.of();
275 }
276 }
277
278 @Override
pier6c48dba2018-11-27 11:23:50 -0800279 public int getNextId(DeviceId deviceId, VlanId vlanId) {
280 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530281 }
282
283 @Override
Charles Chan1fa010c2018-08-19 19:21:46 -0700284 public void removeNextId(int nextId) {
285 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan263e7d92018-09-21 11:29:12 -0700286 if (e.getValue().value() == nextId) {
Charles Chan1fa010c2018-08-19 19:21:46 -0700287 xconnectNextObjStore.remove(e.getKey());
288 }
289 });
290 }
291
Charles Chan4e87b3e2018-06-19 20:31:57 -0700292 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<PortNumber>> {
293 @Override
294 public void event(MapEvent<XconnectKey, Set<PortNumber>> event) {
295 XconnectKey key = event.key();
296 Versioned<Set<PortNumber>> ports = event.newValue();
297 Versioned<Set<PortNumber>> oldPorts = event.oldValue();
298
299 switch (event.type()) {
300 case INSERT:
301 populateXConnect(key, ports.value());
302 break;
303 case UPDATE:
304 updateXConnect(key, oldPorts.value(), ports.value());
305 break;
306 case REMOVE:
307 revokeXConnect(key, oldPorts.value());
308 break;
309 default:
310 break;
311 }
312 }
313 }
314
315 private class InternalDeviceListener implements DeviceListener {
pier6c48dba2018-11-27 11:23:50 -0800316 // Offload the execution to an executor and then process the event
317 // if this instance is the leader of the device
Charles Chan4e87b3e2018-06-19 20:31:57 -0700318 @Override
319 public void event(DeviceEvent event) {
Charles Chan1eec6252018-08-07 12:48:36 -0700320 deviceEventExecutor.execute(() -> {
321 DeviceId deviceId = event.subject().id();
pier6c48dba2018-11-27 11:23:50 -0800322 // Just skip if we are not the leader
323 if (!isLocalLeader(deviceId)) {
324 log.debug("Not the leader of {}. Skip event {}", deviceId, event);
Charles Chan1eec6252018-08-07 12:48:36 -0700325 return;
326 }
pier6c48dba2018-11-27 11:23:50 -0800327 // Populate or revoke according to the device availability
328 if (deviceService.isAvailable(deviceId)) {
329 init(deviceId);
330 } else {
331 cleanup(deviceId);
Charles Chan1eec6252018-08-07 12:48:36 -0700332 }
333 });
Charles Chan4e87b3e2018-06-19 20:31:57 -0700334 }
pier6c48dba2018-11-27 11:23:50 -0800335 // We want to manage only a subset of events and if we are the leader
336 @Override
337 public boolean isRelevant(DeviceEvent event) {
338 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
339 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
340 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
341 }
Charles Chan4e87b3e2018-06-19 20:31:57 -0700342 }
343
jayakumarthazhath46945b62018-10-01 00:51:54 +0530344 private class InternalHostListener implements HostListener {
345 @Override
346 public void event(HostEvent event) {
347 hostEventExecutor.execute(() -> {
348
349 switch (event.type()) {
Sudhir Kumar Maurya85e93132019-05-02 03:03:59 -0400350 case HOST_ADDED:
351 case HOST_REMOVED:
352 case HOST_UPDATED:
353 log.trace("Unhandled host event type: {} received. Ignoring.", event.type());
354 break;
jayakumarthazhath46945b62018-10-01 00:51:54 +0530355 case HOST_MOVED:
356 log.trace("Processing host event {}", event);
357
358 Host host = event.subject();
359 Set<HostLocation> prevLocations = event.prevSubject().locations();
360 Set<HostLocation> newLocations = host.locations();
361
362 // Dual-home host port failure
363 // For each old location, in failed and paired devices update L2 vlan groups
364 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
365
366 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
367 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
368
369 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
370 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
371 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
372
373 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
374 prevLocation.port());
375 xconnectVlans.forEach(xconnectVlan -> {
376 // Add single-home host into L2 multicast group at paired device side.
377 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
378 newLocations.stream()
379 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
380 .forEach(location -> populateL2Multicast(location.deviceId(),
381 srService.getPairLocalPort(
382 location.deviceId()).get(),
383 xconnectVlan,
384 Collections.singletonList(
385 location.port())));
pier6c48dba2018-11-27 11:23:50 -0800386 // Ensure pair-port attached to xconnect vlan flooding group
387 // at dual home failed device.
jayakumarthazhath46945b62018-10-01 00:51:54 +0530388 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
389 });
390 }
391 });
392
393 // Dual-home host port restoration
394 // For each new location, reverse xconnect loop prevention groups.
395 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
396 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
397 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
398 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
399 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
400
401 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
402 newLocation.port());
403 xconnectVlans.forEach(xconnectVlan -> {
404 // Remove recovered dual homed port from vlan L2 multicast group
405 prevLocations.stream()
406 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier6c48dba2018-11-27 11:23:50 -0800407 .forEach(prevLocation -> revokeL2Multicast(
408 prevLocation.deviceId(),
409 xconnectVlan,
410 Collections.singletonList(newLocation.port()))
jayakumarthazhath46945b62018-10-01 00:51:54 +0530411 );
412
pier6c48dba2018-11-27 11:23:50 -0800413 // Remove pair-port from vlan's flooding group at dual home
414 // restored device, if needed.
415 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
416 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath46945b62018-10-01 00:51:54 +0530417 updateL2Flooding(newLocation.deviceId(),
418 pairLocalPort.get(),
419 xconnectVlan,
420 false);
421
422 // Clean L2 multicast group at pair-device; also update store.
423 cleanupL2MulticastRule(pairDeviceId.get(),
424 srService.getPairLocalPort(pairDeviceId.get()).get(),
425 xconnectVlan,
426 false);
427 }
428 });
429 }
430 });
431 break;
432
433 default:
434 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
435 break;
436 }
437 });
438 }
439 }
440
Charles Chan263e7d92018-09-21 11:29:12 -0700441 private void init(DeviceId deviceId) {
Charles Chan4e87b3e2018-06-19 20:31:57 -0700442 getXconnects().stream()
443 .filter(desc -> desc.key().deviceId().equals(deviceId))
444 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
445 }
446
Charles Chan263e7d92018-09-21 11:29:12 -0700447 private void cleanup(DeviceId deviceId) {
Charles Chan4e87b3e2018-06-19 20:31:57 -0700448 xconnectNextObjStore.entrySet().stream()
449 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
450 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
451 log.debug("{} is removed from xConnectNextObjStore", deviceId);
452 }
453
454 /**
455 * Populates XConnect groups and flows for given key.
456 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530457 * @param key XConnect key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700458 * @param ports a set of ports to be cross-connected
459 */
460 private void populateXConnect(XconnectKey key, Set<PortNumber> ports) {
pier6c48dba2018-11-27 11:23:50 -0800461 if (!isLocalLeader(key.deviceId())) {
462 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700463 return;
464 }
465
Charles Chan4e87b3e2018-06-19 20:31:57 -0700466 populateFilter(key, ports);
467 populateFwd(key, populateNext(key, ports));
468 populateAcl(key);
469 }
470
471 /**
472 * Populates filtering objectives for given XConnect.
473 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530474 * @param key XConnect store key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700475 * @param ports XConnect ports
476 */
477 private void populateFilter(XconnectKey key, Set<PortNumber> ports) {
478 ports.forEach(port -> {
479 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
480 ObjectiveContext context = new DefaultObjectiveContext(
481 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530482 key, port),
Charles Chan4e87b3e2018-06-19 20:31:57 -0700483 (objective, error) ->
484 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530485 key, port, error));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700486 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
487 });
488 }
489
490 /**
491 * Populates next objectives for given XConnect.
492 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530493 * @param key XConnect store key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700494 * @param ports XConnect ports
495 */
Charles Chan263e7d92018-09-21 11:29:12 -0700496 private int populateNext(XconnectKey key, Set<PortNumber> ports) {
pier6c48dba2018-11-27 11:23:50 -0800497 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
498 if (nextId != -1) {
Charles Chan263e7d92018-09-21 11:29:12 -0700499 log.debug("NextObj for {} found, id={}", key, nextId);
500 return nextId;
Charles Chan4e87b3e2018-06-19 20:31:57 -0700501 } else {
502 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
503 ObjectiveContext nextContext = new DefaultObjectiveContext(
504 // To serialize this with kryo
505 (Serializable & Consumer<Objective>) (objective) ->
506 log.debug("XConnect NextObj for {} added", key),
Charles Chan1dbadbd2018-08-23 14:30:33 -0700507 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
508 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
509 srService.invalidateNextObj(objective.id());
510 });
Charles Chan263e7d92018-09-21 11:29:12 -0700511 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700512 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan263e7d92018-09-21 11:29:12 -0700513 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan4e87b3e2018-06-19 20:31:57 -0700514 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan263e7d92018-09-21 11:29:12 -0700515 return nextObj.id();
Charles Chan4e87b3e2018-06-19 20:31:57 -0700516 }
Charles Chan4e87b3e2018-06-19 20:31:57 -0700517 }
518
519 /**
520 * Populates bridging forwarding objectives for given XConnect.
521 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530522 * @param key XConnect store key
Charles Chan263e7d92018-09-21 11:29:12 -0700523 * @param nextId next objective id
Charles Chan4e87b3e2018-06-19 20:31:57 -0700524 */
Charles Chan263e7d92018-09-21 11:29:12 -0700525 private void populateFwd(XconnectKey key, int nextId) {
526 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700527 ObjectiveContext fwdContext = new DefaultObjectiveContext(
528 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
529 (objective, error) ->
530 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
531 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
532 }
533
534 /**
535 * Populates ACL forwarding objectives for given XConnect.
536 *
537 * @param key XConnect store key
538 */
539 private void populateAcl(XconnectKey key) {
540 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
541 ObjectiveContext aclContext = new DefaultObjectiveContext(
542 (objective) -> log.debug("XConnect AclObj for {} populated", key),
543 (objective, error) ->
544 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
545 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
546 }
547
548 /**
549 * Revokes XConnect groups and flows for given key.
550 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530551 * @param key XConnect key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700552 * @param ports XConnect ports
553 */
554 private void revokeXConnect(XconnectKey key, Set<PortNumber> ports) {
pier6c48dba2018-11-27 11:23:50 -0800555 if (!isLocalLeader(key.deviceId())) {
556 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700557 return;
558 }
559
Charles Chan4e87b3e2018-06-19 20:31:57 -0700560 revokeFilter(key, ports);
pier6c48dba2018-11-27 11:23:50 -0800561 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
562 if (nextId != -1) {
Charles Chan263e7d92018-09-21 11:29:12 -0700563 revokeFwd(key, nextId, null);
564 revokeNext(key, ports, nextId, null);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700565 } else {
566 log.warn("NextObj for {} does not exist in the store.", key);
567 }
568 revokeAcl(key);
569 }
570
571 /**
572 * Revokes filtering objectives for given XConnect.
573 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530574 * @param key XConnect store key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700575 * @param ports XConnect ports
576 */
577 private void revokeFilter(XconnectKey key, Set<PortNumber> ports) {
578 ports.forEach(port -> {
579 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port);
580 ObjectiveContext context = new DefaultObjectiveContext(
581 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530582 key, port),
Charles Chan4e87b3e2018-06-19 20:31:57 -0700583 (objective, error) ->
584 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath46945b62018-10-01 00:51:54 +0530585 key, port, error));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700586 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
587 });
588 }
589
590 /**
591 * Revokes next objectives for given XConnect.
592 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530593 * @param key XConnect store key
594 * @param ports ports in the XConnect
595 * @param nextId next objective id
Charles Chan4e87b3e2018-06-19 20:31:57 -0700596 * @param nextFuture completable future for this next objective operation
597 */
Charles Chan263e7d92018-09-21 11:29:12 -0700598 private void revokeNext(XconnectKey key, Set<PortNumber> ports, int nextId,
Charles Chan4e87b3e2018-06-19 20:31:57 -0700599 CompletableFuture<ObjectiveError> nextFuture) {
600 ObjectiveContext context = new ObjectiveContext() {
601 @Override
602 public void onSuccess(Objective objective) {
603 log.debug("Previous NextObj for {} removed", key);
604 if (nextFuture != null) {
605 nextFuture.complete(null);
606 }
607 }
608
609 @Override
610 public void onError(Objective objective, ObjectiveError error) {
611 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
612 if (nextFuture != null) {
613 nextFuture.complete(error);
614 }
Charles Chan1dbadbd2018-08-23 14:30:33 -0700615 srService.invalidateNextObj(objective.id());
Charles Chan4e87b3e2018-06-19 20:31:57 -0700616 }
617 };
Charles Chan263e7d92018-09-21 11:29:12 -0700618 flowObjectiveService.next(key.deviceId(), nextObjBuilder(key, ports, nextId).remove(context));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700619 xconnectNextObjStore.remove(key);
620 }
621
622 /**
623 * Revokes bridging forwarding objectives for given XConnect.
624 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530625 * @param key XConnect store key
626 * @param nextId next objective id
Charles Chan4e87b3e2018-06-19 20:31:57 -0700627 * @param fwdFuture completable future for this forwarding objective operation
628 */
Charles Chan263e7d92018-09-21 11:29:12 -0700629 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
630 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700631 ObjectiveContext context = new ObjectiveContext() {
632 @Override
633 public void onSuccess(Objective objective) {
634 log.debug("Previous FwdObj for {} removed", key);
635 if (fwdFuture != null) {
636 fwdFuture.complete(null);
637 }
638 }
639
640 @Override
641 public void onError(Objective objective, ObjectiveError error) {
642 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
643 if (fwdFuture != null) {
644 fwdFuture.complete(error);
645 }
646 }
647 };
648 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
649 }
650
651 /**
652 * Revokes ACL forwarding objectives for given XConnect.
653 *
654 * @param key XConnect store key
655 */
656 private void revokeAcl(XconnectKey key) {
657 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
658 ObjectiveContext aclContext = new DefaultObjectiveContext(
659 (objective) -> log.debug("XConnect AclObj for {} populated", key),
660 (objective, error) ->
661 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
662 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
663 }
664
665 /**
666 * Updates XConnect groups and flows for given key.
667 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530668 * @param key XConnect key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700669 * @param prevPorts previous XConnect ports
jayakumarthazhath46945b62018-10-01 00:51:54 +0530670 * @param ports new XConnect ports
Charles Chan4e87b3e2018-06-19 20:31:57 -0700671 */
672 private void updateXConnect(XconnectKey key, Set<PortNumber> prevPorts,
673 Set<PortNumber> ports) {
pier6c48dba2018-11-27 11:23:50 -0800674 if (!isLocalLeader(key.deviceId())) {
675 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
676 return;
677 }
Charles Chan4e87b3e2018-06-19 20:31:57 -0700678 // NOTE: ACL flow doesn't include port information. No need to update it.
679 // Pair port is built-in and thus not going to change. No need to update it.
680
681 // remove old filter
682 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
jayakumarthazhath46945b62018-10-01 00:51:54 +0530683 revokeFilter(key,
684 ImmutableSet.of(port)));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700685 // install new filter
686 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
jayakumarthazhath46945b62018-10-01 00:51:54 +0530687 populateFilter(key,
688 ImmutableSet.of(port)));
Charles Chan4e87b3e2018-06-19 20:31:57 -0700689
690 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
691 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
692
pier6c48dba2018-11-27 11:23:50 -0800693 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
694 if (nextId != -1) {
Charles Chan263e7d92018-09-21 11:29:12 -0700695 revokeFwd(key, nextId, fwdFuture);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700696
697 fwdFuture.thenAcceptAsync(fwdStatus -> {
698 if (fwdStatus == null) {
699 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan263e7d92018-09-21 11:29:12 -0700700 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700701 }
702 });
703
704 nextFuture.thenAcceptAsync(nextStatus -> {
705 if (nextStatus == null) {
706 log.debug("Installing new group and flow for {}", key);
707 populateFwd(key, populateNext(key, ports));
708 }
709 });
710 } else {
711 log.warn("NextObj for {} does not exist in the store.", key);
712 }
713 }
714
715 /**
Charles Chan263e7d92018-09-21 11:29:12 -0700716 * Creates a next objective builder for XConnect with given nextId.
Charles Chan4e87b3e2018-06-19 20:31:57 -0700717 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530718 * @param key XConnect key
719 * @param ports set of XConnect ports
Charles Chan263e7d92018-09-21 11:29:12 -0700720 * @param nextId next objective id
Charles Chan4e87b3e2018-06-19 20:31:57 -0700721 * @return next objective builder
722 */
Charles Chan263e7d92018-09-21 11:29:12 -0700723 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports, int nextId) {
Charles Chan4e87b3e2018-06-19 20:31:57 -0700724 TrafficSelector metadata =
725 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
726 NextObjective.Builder nextObjBuilder = DefaultNextObjective
727 .builder().withId(nextId)
728 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
729 .withMeta(metadata);
730 ports.forEach(port -> {
731 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
732 tBuilder.setOutput(port);
733 nextObjBuilder.addTreatment(tBuilder.build());
734 });
735 return nextObjBuilder;
736 }
737
738 /**
Charles Chan263e7d92018-09-21 11:29:12 -0700739 * Creates a next objective builder for XConnect.
740 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530741 * @param key XConnect key
Charles Chan263e7d92018-09-21 11:29:12 -0700742 * @param ports set of XConnect ports
743 * @return next objective builder
744 */
745 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<PortNumber> ports) {
746 int nextId = flowObjectiveService.allocateNextId();
747 return nextObjBuilder(key, ports, nextId);
748 }
749
750
751 /**
Charles Chan4e87b3e2018-06-19 20:31:57 -0700752 * Creates a bridging forwarding objective builder for XConnect.
753 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530754 * @param key XConnect key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700755 * @param nextId next ID of the broadcast group for this XConnect key
756 * @return forwarding objective builder
757 */
758 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
759 /*
760 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
761 * as the VLAN cross-connect broadcast rules
762 */
763 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
764 sbuilder.matchVlanId(key.vlanId());
765 sbuilder.matchEthDst(MacAddress.NONE);
766
767 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
768 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
769 .withSelector(sbuilder.build())
770 .nextStep(nextId)
771 .withPriority(XCONNECT_PRIORITY)
772 .fromApp(appId)
773 .makePermanent();
774 return fob;
775 }
776
777 /**
778 * Creates an ACL forwarding objective builder for XConnect.
779 *
780 * @param vlanId cross connect VLAN id
781 * @return forwarding objective builder
782 */
783 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
784 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
785 sbuilder.matchVlanId(vlanId);
786
787 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
788
789 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
790 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
791 .withSelector(sbuilder.build())
792 .withTreatment(tbuilder.build())
793 .withPriority(XCONNECT_ACL_PRIORITY)
794 .fromApp(appId)
795 .makePermanent();
796 return fob;
797 }
798
799 /**
800 * Creates a filtering objective builder for XConnect.
801 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530802 * @param key XConnect key
Charles Chan4e87b3e2018-06-19 20:31:57 -0700803 * @param port XConnect ports
804 * @return next objective builder
805 */
806 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) {
807 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
808 fob.withKey(Criteria.matchInPort(port))
809 .addCondition(Criteria.matchVlanId(key.vlanId()))
810 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
811 .withPriority(XCONNECT_PRIORITY);
812 return fob.permit().fromApp(appId);
813 }
814
815 /**
jayakumarthazhath46945b62018-10-01 00:51:54 +0530816 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan4e87b3e2018-06-19 20:31:57 -0700817 *
jayakumarthazhath46945b62018-10-01 00:51:54 +0530818 * @param deviceId Device ID
819 * @param port Port details
820 * @param vlanId VLAN ID
821 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan4e87b3e2018-06-19 20:31:57 -0700822 */
pier6c48dba2018-11-27 11:23:50 -0800823 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
824 XconnectKey key = new XconnectKey(deviceId, vlanId);
825 // Ensure leadership on device
826 if (!isLocalLeader(deviceId)) {
827 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530828 return;
Charles Chan4e87b3e2018-06-19 20:31:57 -0700829 }
jayakumarthazhath46945b62018-10-01 00:51:54 +0530830
831 // Locate L2 flooding group details for given xconnect vlan
pier6c48dba2018-11-27 11:23:50 -0800832 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530833 if (nextId == -1) {
834 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
835 "Aborting pair group linking.", vlanId, deviceId);
836 return;
837 }
838
839 // Add pairing-port group to flooding group
840 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
841 // treatment.popVlan();
842 treatment.setOutput(port);
843 ObjectiveContext context = new DefaultObjectiveContext(
844 (objective) ->
845 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
846 vlanId, nextId, deviceId),
847 (objective, error) ->
848 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
849 "Error : {}", vlanId, nextId, deviceId, error)
850 );
851 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
852 .withId(nextId)
853 .withType(NextObjective.Type.BROADCAST)
854 .fromApp(srService.appId())
855 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
856 .addTreatment(treatment.build());
857 if (install) {
858 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
859 } else {
860 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
861 }
862 log.debug("Submitted next objective {} for vlan: {} in device {}",
863 nextId, vlanId, deviceId);
Charles Chan4e87b3e2018-06-19 20:31:57 -0700864 }
jayakumarthazhath46945b62018-10-01 00:51:54 +0530865
866 /**
867 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
868 * output to given port's L2 mulitcast group.
869 *
870 * @param deviceId Device ID
871 * @param pairPort Pair port number
872 * @param vlanId VLAN ID
873 * @param accessPorts List of access ports to be added into L2 multicast group
874 */
pier6c48dba2018-11-27 11:23:50 -0800875 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
876 VlanId vlanId, List<PortNumber> accessPorts) {
877 // Ensure enough rights to program pair device
878 if (!srService.shouldProgram(deviceId)) {
879 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
880 return;
881 }
jayakumarthazhath46945b62018-10-01 00:51:54 +0530882
883 boolean multicastGroupExists = true;
884 int vlanMulticastNextId;
pier6c48dba2018-11-27 11:23:50 -0800885 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530886
887 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
888 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
889 .builder()
890 .withType(NextObjective.Type.BROADCAST)
891 .fromApp(srService.appId())
892 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
893 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier6c48dba2018-11-27 11:23:50 -0800894 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530895 if (vlanMulticastNextId == -1) {
896 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
897 multicastGroupExists = false;
898 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier6c48dba2018-11-27 11:23:50 -0800899 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530900 vlanMulticastNextObjBuilder.addTreatment(
psneha9118a5a2019-07-01 05:34:23 -0400901 DefaultTrafficTreatment.builder().setOutput(pairPort).build()
jayakumarthazhath46945b62018-10-01 00:51:54 +0530902 );
903 }
904 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier6c48dba2018-11-27 11:23:50 -0800905 int nextId = vlanMulticastNextId;
jayakumarthazhath46945b62018-10-01 00:51:54 +0530906 accessPorts.forEach(p -> {
907 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
908 // Do vlan popup action based on interface configuration
909 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
910 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
911 egressAction.popVlan();
912 }
913 egressAction.setOutput(p);
914 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6c48dba2018-11-27 11:23:50 -0800915 addMulticastGroupPort(key, p);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530916 });
917 ObjectiveContext context = new DefaultObjectiveContext(
918 (objective) ->
919 log.debug("L2 multicast group installed/updated. "
920 + "NextObject Id {} on {} for subnet {} ",
921 nextId, deviceId, vlanId),
922 (objective, error) ->
923 log.warn("L2 multicast group failed to install/update. "
924 + " NextObject Id {} on {} for subnet {} : {}",
925 nextId, deviceId, vlanId, error)
926 );
927 if (!multicastGroupExists) {
928 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
929
930 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
931 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
932 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
933 multicastSelector.matchInPort(pairPort);
934 multicastSelector.matchVlanId(vlanId);
935 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
936 .withFlag(ForwardingObjective.Flag.VERSATILE)
937 .nextStep(vlanMulticastNextId)
938 .withSelector(multicastSelector.build())
939 .withPriority(100)
940 .fromApp(srService.appId())
941 .makePermanent();
942 context = new DefaultObjectiveContext(
943 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
944 deviceId,
945 pairPort,
946 vlanId),
947 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
948 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
949 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
950 } else {
951 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
952 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
953 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
954 }
955 }
956
957 /**
958 * Removes access ports from VLAN L2 multicast group on given deviceId.
959 *
960 * @param deviceId Device ID
jayakumarthazhath46945b62018-10-01 00:51:54 +0530961 * @param vlanId VLAN ID
962 * @param accessPorts List of access ports to be added into L2 multicast group
963 */
pier6c48dba2018-11-27 11:23:50 -0800964 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath46945b62018-10-01 00:51:54 +0530965 // Ensure enough rights to program pair device
966 if (!srService.shouldProgram(deviceId)) {
pier6c48dba2018-11-27 11:23:50 -0800967 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530968 return;
969 }
970
pier6c48dba2018-11-27 11:23:50 -0800971 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
972
973 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530974 if (vlanMulticastNextId == -1) {
975 return;
976 }
977 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
978 .builder()
979 .withType(NextObjective.Type.BROADCAST)
980 .fromApp(srService.appId())
981 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
982 .withId(vlanMulticastNextId);
983 accessPorts.forEach(p -> {
984 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
985 // Do vlan popup action based on interface configuration
986 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
987 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
988 egressAction.popVlan();
989 }
990 egressAction.setOutput(p);
991 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6c48dba2018-11-27 11:23:50 -0800992 removeMulticastGroupPort(key, p);
jayakumarthazhath46945b62018-10-01 00:51:54 +0530993 });
994 ObjectiveContext context = new DefaultObjectiveContext(
995 (objective) ->
996 log.debug("L2 multicast group installed/updated. "
997 + "NextObject Id {} on {} for subnet {} ",
998 vlanMulticastNextId, deviceId, vlanId),
999 (objective, error) ->
1000 log.warn("L2 multicast group failed to install/update. "
1001 + " NextObject Id {} on {} for subnet {} : {}",
1002 vlanMulticastNextId, deviceId, vlanId, error)
1003 );
1004 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
1005 }
1006
1007 /**
1008 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1009 * Normally multicast group is not removed if it contains access ports; which can be forced
1010 * by "force" flag
1011 *
1012 * @param deviceId Device ID
1013 * @param pairPort Pair port number
1014 * @param vlanId VLAN ID
1015 * @param force Forceful removal
1016 */
1017 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1018
1019 // Ensure enough rights to program pair device
1020 if (!srService.shouldProgram(deviceId)) {
pier6c48dba2018-11-27 11:23:50 -08001021 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301022 return;
1023 }
1024
pier6c48dba2018-11-27 11:23:50 -08001025 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1026
jayakumarthazhath46945b62018-10-01 00:51:54 +05301027 // Ensure L2 multicast group doesn't contain access ports
pier6c48dba2018-11-27 11:23:50 -08001028 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath46945b62018-10-01 00:51:54 +05301029 return;
1030 }
1031
1032 // Load L2 multicast group details
pier6c48dba2018-11-27 11:23:50 -08001033 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301034 if (vlanMulticastNextId == -1) {
1035 return;
1036 }
1037
1038 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1039 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1040 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1041 l2MulticastSelector.matchInPort(pairPort);
1042 l2MulticastSelector.matchVlanId(vlanId);
1043 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1044 .withFlag(ForwardingObjective.Flag.VERSATILE)
1045 .nextStep(vlanMulticastNextId)
1046 .withSelector(l2MulticastSelector.build())
1047 .withPriority(100)
1048 .fromApp(srService.appId())
1049 .makePermanent();
1050 ObjectiveContext context = new DefaultObjectiveContext(
1051 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1052 pairPort, vlanId),
1053 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1054 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1055 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1056
1057 // Step 2 : Clear L2 multicast group associated with vlan
1058 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1059 .builder()
1060 .withId(vlanMulticastNextId)
1061 .withType(NextObjective.Type.BROADCAST)
1062 .fromApp(srService.appId())
1063 .withMeta(DefaultTrafficSelector.builder()
1064 .matchVlanId(vlanId)
1065 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1066 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1067 context = new DefaultObjectiveContext(
1068 (objective) ->
1069 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1070 vlanMulticastNextId, deviceId, vlanId),
1071 (objective, error) ->
1072 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1073 vlanMulticastNextId, deviceId, vlanId, error)
1074 );
1075 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1076
1077 // Finally clear store.
pier6c48dba2018-11-27 11:23:50 -08001078 removeMulticastGroup(key);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301079 }
1080
pier6c48dba2018-11-27 11:23:50 -08001081 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1082 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301083 }
1084
pier6c48dba2018-11-27 11:23:50 -08001085 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath46945b62018-10-01 00:51:54 +05301086 if (nextId == -1) {
1087 return;
1088 }
jayakumarthazhath46945b62018-10-01 00:51:54 +05301089 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301090 }
1091
pier6c48dba2018-11-27 11:23:50 -08001092 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1093 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1094 if (ports == null) {
1095 ports = Lists.newArrayList();
1096 }
1097 ports.add(port);
1098 return ports;
1099 });
jayakumarthazhath46945b62018-10-01 00:51:54 +05301100 }
1101
pier6c48dba2018-11-27 11:23:50 -08001102 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1103 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1104 if (ports != null && !ports.isEmpty()) {
1105 ports.remove(port);
1106 }
1107 return ports;
1108 });
jayakumarthazhath46945b62018-10-01 00:51:54 +05301109 }
1110
pier6c48dba2018-11-27 11:23:50 -08001111 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1112 xconnectMulticastPortsStore.remove(groupKey);
1113 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath46945b62018-10-01 00:51:54 +05301114 }
1115
pier6c48dba2018-11-27 11:23:50 -08001116 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1117 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath46945b62018-10-01 00:51:54 +05301118 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1119 }
1120
pier6c48dba2018-11-27 11:23:50 -08001121 // Custom-built function, when the device is not available we need a fallback mechanism
1122 private boolean isLocalLeader(DeviceId deviceId) {
1123 if (!mastershipService.isLocalMaster(deviceId)) {
1124 // When the device is available we just check the mastership
1125 if (deviceService.isAvailable(deviceId)) {
1126 return false;
1127 }
1128 // Fallback with Leadership service - device id is used as topic
1129 NodeId leader = leadershipService.runForLeadership(
1130 deviceId.toString()).leaderNodeId();
1131 // Verify if this node is the leader
1132 return clusterService.getLocalNode().id().equals(leader);
1133 }
1134 return true;
1135 }
1136
Charles Chan4e87b3e2018-06-19 20:31:57 -07001137}