blob: aacc16a6ae46751e38d66016a07a6bc02bd277b5 [file] [log] [blame]
Charles Chan8d316332018-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
pier6fd24fd2018-11-27 11:23:50 -080018import com.google.common.collect.ImmutableList;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070019import com.google.common.collect.ImmutableMap;
Charles Chan8d316332018-06-19 20:31:57 -070020import com.google.common.collect.ImmutableSet;
pier6fd24fd2018-11-27 11:23:50 -080021import com.google.common.collect.Lists;
Charles Chan8d316332018-06-19 20:31:57 -070022import com.google.common.collect.Sets;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053023import org.onlab.packet.Ethernet;
Charles Chan8d316332018-06-19 20:31:57 -070024import org.onlab.packet.MacAddress;
25import org.onlab.packet.VlanId;
26import org.onlab.util.KryoNamespace;
pier6fd24fd2018-11-27 11:23:50 -080027import org.onosproject.cluster.ClusterService;
28import org.onosproject.cluster.LeadershipService;
29import org.onosproject.cluster.NodeId;
Charles Chan8d316332018-06-19 20:31:57 -070030import org.onosproject.codec.CodecService;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
Charles Chan48df9ad2018-10-30 18:08:59 -070033import org.onosproject.l2lb.api.L2LbService;
Charles Chan8d316332018-06-19 20:31:57 -070034import org.onosproject.mastership.MastershipService;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.DeviceId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053037import org.onosproject.net.Host;
38import org.onosproject.net.HostLocation;
Charles Chan8d316332018-06-19 20:31:57 -070039import org.onosproject.net.PortNumber;
40import org.onosproject.net.config.NetworkConfigService;
41import org.onosproject.net.device.DeviceEvent;
42import org.onosproject.net.device.DeviceListener;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.net.flow.DefaultTrafficSelector;
45import org.onosproject.net.flow.DefaultTrafficTreatment;
46import org.onosproject.net.flow.TrafficSelector;
47import org.onosproject.net.flow.TrafficTreatment;
48import org.onosproject.net.flow.criteria.Criteria;
49import org.onosproject.net.flowobjective.DefaultFilteringObjective;
50import org.onosproject.net.flowobjective.DefaultForwardingObjective;
51import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070052import org.onosproject.net.flowobjective.DefaultNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070053import org.onosproject.net.flowobjective.DefaultObjectiveContext;
54import org.onosproject.net.flowobjective.FilteringObjective;
55import org.onosproject.net.flowobjective.FlowObjectiveService;
56import org.onosproject.net.flowobjective.ForwardingObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070057import org.onosproject.net.flowobjective.IdNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070058import org.onosproject.net.flowobjective.NextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070059import org.onosproject.net.flowobjective.NextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070060import org.onosproject.net.flowobjective.Objective;
61import org.onosproject.net.flowobjective.ObjectiveContext;
62import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053063import org.onosproject.net.host.HostEvent;
64import org.onosproject.net.host.HostListener;
65import org.onosproject.net.host.HostService;
66import org.onosproject.net.intf.InterfaceService;
Charles Chan8d316332018-06-19 20:31:57 -070067import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053068import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan8d316332018-06-19 20:31:57 -070069import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
70import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
71import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
72import org.onosproject.segmentrouting.xconnect.api.XconnectService;
73import org.onosproject.store.serializers.KryoNamespaces;
74import org.onosproject.store.service.ConsistentMap;
75import org.onosproject.store.service.MapEvent;
76import org.onosproject.store.service.MapEventListener;
77import org.onosproject.store.service.Serializer;
78import org.onosproject.store.service.StorageService;
79import org.onosproject.store.service.Versioned;
Ray Milkey2bd24a92018-08-17 14:54:17 -070080import org.osgi.service.component.annotations.Activate;
81import org.osgi.service.component.annotations.Component;
82import org.osgi.service.component.annotations.Deactivate;
83import org.osgi.service.component.annotations.Reference;
84import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chan8d316332018-06-19 20:31:57 -070085import org.slf4j.Logger;
86import org.slf4j.LoggerFactory;
87
88import java.io.Serializable;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053089import java.util.Collections;
90import java.util.List;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053091import java.util.Optional;
Charles Chan8d316332018-06-19 20:31:57 -070092import java.util.Set;
93import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -070094import java.util.concurrent.ExecutorService;
95import java.util.concurrent.Executors;
Charles Chan8d316332018-06-19 20:31:57 -070096import java.util.function.BiConsumer;
97import java.util.function.Consumer;
98import java.util.stream.Collectors;
99
Charles Chan56542b62018-08-07 12:48:36 -0700100import static org.onlab.util.Tools.groupedThreads;
101
Ray Milkey2bd24a92018-08-17 14:54:17 -0700102@Component(immediate = true, service = XconnectService.class)
Charles Chan8d316332018-06-19 20:31:57 -0700103public class XconnectManager implements XconnectService {
Ray Milkey2bd24a92018-08-17 14:54:17 -0700104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700105 private CoreService coreService;
106
Ray Milkey2bd24a92018-08-17 14:54:17 -0700107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700108 private CodecService codecService;
109
Ray Milkey2bd24a92018-08-17 14:54:17 -0700110 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700111 private StorageService storageService;
112
Ray Milkey2bd24a92018-08-17 14:54:17 -0700113 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700114 public NetworkConfigService netCfgService;
115
Ray Milkey2bd24a92018-08-17 14:54:17 -0700116 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700117 public DeviceService deviceService;
118
Ray Milkey2bd24a92018-08-17 14:54:17 -0700119 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700120 public FlowObjectiveService flowObjectiveService;
121
Ray Milkey2bd24a92018-08-17 14:54:17 -0700122 @Reference(cardinality = ReferenceCardinality.MANDATORY)
pier6fd24fd2018-11-27 11:23:50 -0800123 private LeadershipService leadershipService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
126 private ClusterService clusterService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700129 public MastershipService mastershipService;
130
Ray Milkey2bd24a92018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chan8d316332018-06-19 20:31:57 -0700132 public SegmentRoutingService srService;
133
Ray Milkey3cad4db2018-10-04 15:13:33 -0700134 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530135 public InterfaceService interfaceService;
136
Ray Milkey3cad4db2018-10-04 15:13:33 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530138 HostService hostService;
139
Charles Chan48df9ad2018-10-30 18:08:59 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
141 L2LbService l2LbService;
142
Charles Chan8d316332018-06-19 20:31:57 -0700143 private static final String APP_NAME = "org.onosproject.xconnect";
pier6fd24fd2018-11-27 11:23:50 -0800144 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chan48df9ad2018-10-30 18:08:59 -0700145 private static final String ERROR_NEXT_OBJ_BUILDER = "Unable to construct next objective builder";
146 private static final String ERROR_NEXT_ID = "Unable to get next id";
Charles Chan8d316332018-06-19 20:31:57 -0700147
148 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
149
150 private ApplicationId appId;
Charles Chan48df9ad2018-10-30 18:08:59 -0700151 private ConsistentMap<XconnectKey, Set<String>> xconnectStore;
Charles Chan1fb65132018-09-21 11:29:12 -0700152 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan8d316332018-06-19 20:31:57 -0700153
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530154 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
155 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
156
Charles Chan48df9ad2018-10-30 18:08:59 -0700157 private final MapEventListener<XconnectKey, Set<String>> xconnectListener = new XconnectMapListener();
pier6fd24fd2018-11-27 11:23:50 -0800158 private ExecutorService xConnectExecutor;
Charles Chan8d316332018-06-19 20:31:57 -0700159
pier6fd24fd2018-11-27 11:23:50 -0800160 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan56542b62018-08-07 12:48:36 -0700161 private ExecutorService deviceEventExecutor;
162
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530163 private final HostListener hostListener = new InternalHostListener();
164 private ExecutorService hostEventExecutor;
165
Charles Chan8d316332018-06-19 20:31:57 -0700166 @Activate
167 void activate() {
168 appId = coreService.registerApplication(APP_NAME);
169 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
170
171 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
172 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700173 .register(XconnectManager.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530174 .register(XconnectKey.class)
175 .register(VlanNextObjectiveStoreKey.class);
Charles Chan8d316332018-06-19 20:31:57 -0700176
Charles Chan48df9ad2018-10-30 18:08:59 -0700177 xconnectStore = storageService.<XconnectKey, Set<String>>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700178 .withName("onos-sr-xconnect")
179 .withRelaxedReadConsistency()
180 .withSerializer(Serializer.using(serializer.build()))
181 .build();
pier6fd24fd2018-11-27 11:23:50 -0800182 xConnectExecutor = Executors.newSingleThreadScheduledExecutor(
183 groupedThreads("sr-xconnect-event", "%d", log));
184 xconnectStore.addListener(xconnectListener, xConnectExecutor);
Charles Chan8d316332018-06-19 20:31:57 -0700185
Charles Chan1fb65132018-09-21 11:29:12 -0700186 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700187 .withName("onos-sr-xconnect-next")
188 .withRelaxedReadConsistency()
189 .withSerializer(Serializer.using(serializer.build()))
190 .build();
191
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530192 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
193 .withName("onos-sr-xconnect-l2multicast-next")
194 .withSerializer(Serializer.using(serializer.build()))
195 .build();
196 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
197 .withName("onos-sr-xconnect-l2multicast-ports")
198 .withSerializer(Serializer.using(serializer.build()))
199 .build();
200
Charles Chan56542b62018-08-07 12:48:36 -0700201 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
202 groupedThreads("sr-xconnect-device-event", "%d", log));
Charles Chan8d316332018-06-19 20:31:57 -0700203 deviceService.addListener(deviceListener);
204
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530205 hostEventExecutor = Executors.newSingleThreadExecutor(
206 groupedThreads("sr-xconnect-host-event", "%d", log));
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530207 hostService.addListener(hostListener);
208
Charles Chan8d316332018-06-19 20:31:57 -0700209 log.info("Started");
210 }
211
212 @Deactivate
213 void deactivate() {
214 xconnectStore.removeListener(xconnectListener);
215 deviceService.removeListener(deviceListener);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530216 hostService.removeListener(hostListener);
Charles Chan8d316332018-06-19 20:31:57 -0700217 codecService.unregisterCodec(XconnectDesc.class);
218
Charles Chan56542b62018-08-07 12:48:36 -0700219 deviceEventExecutor.shutdown();
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530220 hostEventExecutor.shutdown();
pier6fd24fd2018-11-27 11:23:50 -0800221 xConnectExecutor.shutdown();
Charles Chan56542b62018-08-07 12:48:36 -0700222
Charles Chan8d316332018-06-19 20:31:57 -0700223 log.info("Stopped");
224 }
225
226 @Override
Charles Chan48df9ad2018-10-30 18:08:59 -0700227 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<String> ports) {
Charles Chan8d316332018-06-19 20:31:57 -0700228 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530229 deviceId, vlanId, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700230 final XconnectKey key = new XconnectKey(deviceId, vlanId);
231 xconnectStore.put(key, ports);
232 }
233
234 @Override
235 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
236 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530237 deviceId, vlanId);
Charles Chan8d316332018-06-19 20:31:57 -0700238 final XconnectKey key = new XconnectKey(deviceId, vlanId);
239 xconnectStore.remove(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530240
241 // Cleanup multicasting support, if any.
242 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
243 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
244 });
245
Charles Chan8d316332018-06-19 20:31:57 -0700246 }
247
248 @Override
249 public Set<XconnectDesc> getXconnects() {
250 return xconnectStore.asJavaMap().entrySet().stream()
251 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
252 .collect(Collectors.toSet());
253 }
254
255 @Override
256 public boolean hasXconnect(ConnectPoint cp) {
pier6fd24fd2018-11-27 11:23:50 -0800257 return getXconnects().stream().anyMatch(desc -> desc.key().deviceId().equals(cp.deviceId())
Charles Chan48df9ad2018-10-30 18:08:59 -0700258 && desc.ports().contains(cp.port().toString())
Charles Chan8d316332018-06-19 20:31:57 -0700259 );
260 }
261
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700262 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530263 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
264 return getXconnects().stream()
Charles Chan48df9ad2018-10-30 18:08:59 -0700265 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port.toString()))
pier6fd24fd2018-11-27 11:23:50 -0800266 .map(desc -> desc.key().vlanId())
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530267 .collect(Collectors.toList());
268 }
269
270 @Override
271 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier6fd24fd2018-11-27 11:23:50 -0800272 XconnectKey key = new XconnectKey(deviceId, vlanId);
273 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530274 }
275
276 @Override
Charles Chan1fb65132018-09-21 11:29:12 -0700277 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700278 if (xconnectNextObjStore != null) {
279 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
280 } else {
281 return ImmutableMap.of();
282 }
283 }
284
285 @Override
pier6fd24fd2018-11-27 11:23:50 -0800286 public int getNextId(DeviceId deviceId, VlanId vlanId) {
287 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530288 }
289
290 @Override
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700291 public void removeNextId(int nextId) {
292 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan1fb65132018-09-21 11:29:12 -0700293 if (e.getValue().value() == nextId) {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700294 xconnectNextObjStore.remove(e.getKey());
295 }
296 });
297 }
298
Charles Chan48df9ad2018-10-30 18:08:59 -0700299 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<String>> {
Charles Chan8d316332018-06-19 20:31:57 -0700300 @Override
Charles Chan48df9ad2018-10-30 18:08:59 -0700301 public void event(MapEvent<XconnectKey, Set<String>> event) {
Charles Chan8d316332018-06-19 20:31:57 -0700302 XconnectKey key = event.key();
Charles Chan48df9ad2018-10-30 18:08:59 -0700303 Set<String> ports = Versioned.valueOrNull(event.newValue());
304 Set<String> oldPorts = Versioned.valueOrNull(event.oldValue());
Charles Chan8d316332018-06-19 20:31:57 -0700305
306 switch (event.type()) {
307 case INSERT:
Charles Chan48df9ad2018-10-30 18:08:59 -0700308 populateXConnect(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700309 break;
310 case UPDATE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700311 updateXConnect(key, oldPorts, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700312 break;
313 case REMOVE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700314 revokeXConnect(key, oldPorts);
Charles Chan8d316332018-06-19 20:31:57 -0700315 break;
316 default:
317 break;
318 }
319 }
320 }
321
322 private class InternalDeviceListener implements DeviceListener {
pier6fd24fd2018-11-27 11:23:50 -0800323 // Offload the execution to an executor and then process the event
324 // if this instance is the leader of the device
Charles Chan8d316332018-06-19 20:31:57 -0700325 @Override
326 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700327 deviceEventExecutor.execute(() -> {
328 DeviceId deviceId = event.subject().id();
pier6fd24fd2018-11-27 11:23:50 -0800329 // Just skip if we are not the leader
330 if (!isLocalLeader(deviceId)) {
331 log.debug("Not the leader of {}. Skip event {}", deviceId, event);
Charles Chan56542b62018-08-07 12:48:36 -0700332 return;
333 }
pier6fd24fd2018-11-27 11:23:50 -0800334 // Populate or revoke according to the device availability
335 if (deviceService.isAvailable(deviceId)) {
336 init(deviceId);
337 } else {
338 cleanup(deviceId);
Charles Chan56542b62018-08-07 12:48:36 -0700339 }
340 });
Charles Chan8d316332018-06-19 20:31:57 -0700341 }
pier6fd24fd2018-11-27 11:23:50 -0800342 // We want to manage only a subset of events and if we are the leader
343 @Override
344 public boolean isRelevant(DeviceEvent event) {
345 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
346 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
347 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
348 }
Charles Chan8d316332018-06-19 20:31:57 -0700349 }
350
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530351 private class InternalHostListener implements HostListener {
352 @Override
353 public void event(HostEvent event) {
354 hostEventExecutor.execute(() -> {
355
356 switch (event.type()) {
357 case HOST_MOVED:
358 log.trace("Processing host event {}", event);
359
360 Host host = event.subject();
361 Set<HostLocation> prevLocations = event.prevSubject().locations();
362 Set<HostLocation> newLocations = host.locations();
363
364 // Dual-home host port failure
365 // For each old location, in failed and paired devices update L2 vlan groups
366 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
367
368 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
369 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
370
371 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
372 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
373 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
374
375 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
376 prevLocation.port());
377 xconnectVlans.forEach(xconnectVlan -> {
378 // Add single-home host into L2 multicast group at paired device side.
379 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
380 newLocations.stream()
381 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
382 .forEach(location -> populateL2Multicast(location.deviceId(),
383 srService.getPairLocalPort(
384 location.deviceId()).get(),
385 xconnectVlan,
386 Collections.singletonList(
387 location.port())));
pier6fd24fd2018-11-27 11:23:50 -0800388 // Ensure pair-port attached to xconnect vlan flooding group
389 // at dual home failed device.
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530390 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
391 });
392 }
393 });
394
395 // Dual-home host port restoration
396 // For each new location, reverse xconnect loop prevention groups.
397 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
398 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
399 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
400 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
401 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
402
403 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
404 newLocation.port());
405 xconnectVlans.forEach(xconnectVlan -> {
406 // Remove recovered dual homed port from vlan L2 multicast group
407 prevLocations.stream()
408 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier6fd24fd2018-11-27 11:23:50 -0800409 .forEach(prevLocation -> revokeL2Multicast(
410 prevLocation.deviceId(),
411 xconnectVlan,
412 Collections.singletonList(newLocation.port()))
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530413 );
414
pier6fd24fd2018-11-27 11:23:50 -0800415 // Remove pair-port from vlan's flooding group at dual home
416 // restored device, if needed.
417 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
418 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530419 updateL2Flooding(newLocation.deviceId(),
420 pairLocalPort.get(),
421 xconnectVlan,
422 false);
423
424 // Clean L2 multicast group at pair-device; also update store.
425 cleanupL2MulticastRule(pairDeviceId.get(),
426 srService.getPairLocalPort(pairDeviceId.get()).get(),
427 xconnectVlan,
428 false);
429 }
430 });
431 }
432 });
433 break;
434
435 default:
436 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
437 break;
438 }
439 });
440 }
441 }
442
Charles Chan1fb65132018-09-21 11:29:12 -0700443 private void init(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700444 getXconnects().stream()
445 .filter(desc -> desc.key().deviceId().equals(deviceId))
446 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
447 }
448
Charles Chan1fb65132018-09-21 11:29:12 -0700449 private void cleanup(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700450 xconnectNextObjStore.entrySet().stream()
451 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
452 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
453 log.debug("{} is removed from xConnectNextObjStore", deviceId);
454 }
455
456 /**
457 * Populates XConnect groups and flows for given key.
458 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530459 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700460 * @param ports a set of ports to be cross-connected
461 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700462 private void populateXConnect(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800463 if (!isLocalLeader(key.deviceId())) {
464 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700465 return;
466 }
467
Charles Chan48df9ad2018-10-30 18:08:59 -0700468 int nextId = populateNext(key, ports);
469 if (nextId == -1) {
470 log.warn("Fail to populateXConnect {}: {}", key, ERROR_NEXT_ID);
471 return;
472 }
Charles Chan8d316332018-06-19 20:31:57 -0700473 populateFilter(key, ports);
Charles Chan48df9ad2018-10-30 18:08:59 -0700474 populateFwd(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700475 populateAcl(key);
476 }
477
478 /**
479 * Populates filtering objectives for given XConnect.
480 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530481 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700482 * @param ports XConnect ports
483 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700484 private void populateFilter(XconnectKey key, Set<String> ports) {
485 // FIXME Improve the logic
486 // If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
487 // The purpose is to make sure existing XConnect logic can still work on a configured port.
488 boolean filtered = ports.stream()
489 .map(p -> getNextTreatment(key.deviceId(), p))
490 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
491
492 ports.stream()
493 .map(p -> getPhysicalPorts(key.deviceId(), p))
494 .flatMap(Set::stream).forEach(port -> {
495 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
Charles Chan8d316332018-06-19 20:31:57 -0700496 ObjectiveContext context = new DefaultObjectiveContext(
497 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
Charles Chan48df9ad2018-10-30 18:08:59 -0700498 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700499 (objective, error) ->
500 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
Charles Chan48df9ad2018-10-30 18:08:59 -0700501 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700502 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
503 });
504 }
505
506 /**
507 * Populates next objectives for given XConnect.
508 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530509 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700510 * @param ports XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700511 * @return next id
Charles Chan8d316332018-06-19 20:31:57 -0700512 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700513 private int populateNext(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800514 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
515 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700516 log.debug("NextObj for {} found, id={}", key, nextId);
517 return nextId;
Charles Chan8d316332018-06-19 20:31:57 -0700518 } else {
519 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
Charles Chan48df9ad2018-10-30 18:08:59 -0700520 if (nextObjBuilder == null) {
521 log.warn("Fail to populate {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
522 return -1;
523 }
Charles Chan8d316332018-06-19 20:31:57 -0700524 ObjectiveContext nextContext = new DefaultObjectiveContext(
525 // To serialize this with kryo
526 (Serializable & Consumer<Objective>) (objective) ->
527 log.debug("XConnect NextObj for {} added", key),
Charles Chanfacfbef2018-08-23 14:30:33 -0700528 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
529 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
530 srService.invalidateNextObj(objective.id());
531 });
Charles Chan1fb65132018-09-21 11:29:12 -0700532 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan8d316332018-06-19 20:31:57 -0700533 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan1fb65132018-09-21 11:29:12 -0700534 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan8d316332018-06-19 20:31:57 -0700535 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan1fb65132018-09-21 11:29:12 -0700536 return nextObj.id();
Charles Chan8d316332018-06-19 20:31:57 -0700537 }
Charles Chan8d316332018-06-19 20:31:57 -0700538 }
539
540 /**
541 * Populates bridging forwarding objectives for given XConnect.
542 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530543 * @param key XConnect store key
Charles Chan1fb65132018-09-21 11:29:12 -0700544 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700545 */
Charles Chan1fb65132018-09-21 11:29:12 -0700546 private void populateFwd(XconnectKey key, int nextId) {
547 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700548 ObjectiveContext fwdContext = new DefaultObjectiveContext(
549 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
550 (objective, error) ->
551 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
552 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
553 }
554
555 /**
556 * Populates ACL forwarding objectives for given XConnect.
557 *
558 * @param key XConnect store key
559 */
560 private void populateAcl(XconnectKey key) {
561 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
562 ObjectiveContext aclContext = new DefaultObjectiveContext(
563 (objective) -> log.debug("XConnect AclObj for {} populated", key),
564 (objective, error) ->
565 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
566 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
567 }
568
569 /**
570 * Revokes XConnect groups and flows for given key.
571 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530572 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700573 * @param ports XConnect ports
574 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700575 private void revokeXConnect(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800576 if (!isLocalLeader(key.deviceId())) {
577 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700578 return;
579 }
580
Charles Chan8d316332018-06-19 20:31:57 -0700581 revokeFilter(key, ports);
pier6fd24fd2018-11-27 11:23:50 -0800582 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
583 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700584 revokeFwd(key, nextId, null);
585 revokeNext(key, ports, nextId, null);
Charles Chan8d316332018-06-19 20:31:57 -0700586 } else {
587 log.warn("NextObj for {} does not exist in the store.", key);
588 }
Charles Chan48df9ad2018-10-30 18:08:59 -0700589 revokeFilter(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700590 revokeAcl(key);
591 }
592
593 /**
594 * Revokes filtering objectives for given XConnect.
595 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530596 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700597 * @param ports XConnect ports
598 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700599 private void revokeFilter(XconnectKey key, Set<String> ports) {
600 // FIXME Improve the logic
601 // If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
602 // The purpose is to make sure existing XConnect logic can still work on a configured port.
603 Set<Set<PortNumber>> portsSet = ports.stream()
604 .map(p -> getPhysicalPorts(key.deviceId(), p)).collect(Collectors.toSet());
605 boolean filtered = portsSet.stream().allMatch(s -> s.size() == 1);
606 portsSet.stream().flatMap(Set::stream).forEach(port -> {
607 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
Charles Chan8d316332018-06-19 20:31:57 -0700608 ObjectiveContext context = new DefaultObjectiveContext(
609 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530610 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700611 (objective, error) ->
612 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530613 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700614 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
615 });
616 }
617
618 /**
619 * Revokes next objectives for given XConnect.
620 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530621 * @param key XConnect store key
622 * @param ports ports in the XConnect
623 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700624 * @param nextFuture completable future for this next objective operation
625 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700626 private void revokeNext(XconnectKey key, Set<String> ports, int nextId,
Charles Chan8d316332018-06-19 20:31:57 -0700627 CompletableFuture<ObjectiveError> nextFuture) {
628 ObjectiveContext context = new ObjectiveContext() {
629 @Override
630 public void onSuccess(Objective objective) {
631 log.debug("Previous NextObj for {} removed", key);
632 if (nextFuture != null) {
633 nextFuture.complete(null);
634 }
635 }
636
637 @Override
638 public void onError(Objective objective, ObjectiveError error) {
639 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
640 if (nextFuture != null) {
641 nextFuture.complete(error);
642 }
Charles Chanfacfbef2018-08-23 14:30:33 -0700643 srService.invalidateNextObj(objective.id());
Charles Chan8d316332018-06-19 20:31:57 -0700644 }
645 };
Charles Chan48df9ad2018-10-30 18:08:59 -0700646
647 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports, nextId);
648 if (nextObjBuilder == null) {
649 log.warn("Fail to revokeNext {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
650 return;
651 }
652 flowObjectiveService.next(key.deviceId(), nextObjBuilder.remove(context));
Charles Chan8d316332018-06-19 20:31:57 -0700653 xconnectNextObjStore.remove(key);
654 }
655
656 /**
657 * Revokes bridging forwarding objectives for given XConnect.
658 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530659 * @param key XConnect store key
660 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700661 * @param fwdFuture completable future for this forwarding objective operation
662 */
Charles Chan1fb65132018-09-21 11:29:12 -0700663 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
664 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700665 ObjectiveContext context = new ObjectiveContext() {
666 @Override
667 public void onSuccess(Objective objective) {
668 log.debug("Previous FwdObj for {} removed", key);
669 if (fwdFuture != null) {
670 fwdFuture.complete(null);
671 }
672 }
673
674 @Override
675 public void onError(Objective objective, ObjectiveError error) {
676 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
677 if (fwdFuture != null) {
678 fwdFuture.complete(error);
679 }
680 }
681 };
682 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
683 }
684
685 /**
686 * Revokes ACL forwarding objectives for given XConnect.
687 *
688 * @param key XConnect store key
689 */
690 private void revokeAcl(XconnectKey key) {
691 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
692 ObjectiveContext aclContext = new DefaultObjectiveContext(
693 (objective) -> log.debug("XConnect AclObj for {} populated", key),
694 (objective, error) ->
695 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
696 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
697 }
698
699 /**
700 * Updates XConnect groups and flows for given key.
701 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530702 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700703 * @param prevPorts previous XConnect ports
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530704 * @param ports new XConnect ports
Charles Chan8d316332018-06-19 20:31:57 -0700705 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700706 private void updateXConnect(XconnectKey key, Set<String> prevPorts,
707 Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800708 if (!isLocalLeader(key.deviceId())) {
709 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
710 return;
711 }
Charles Chan8d316332018-06-19 20:31:57 -0700712 // NOTE: ACL flow doesn't include port information. No need to update it.
713 // Pair port is built-in and thus not going to change. No need to update it.
714
715 // remove old filter
716 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
Charles Chan48df9ad2018-10-30 18:08:59 -0700717 revokeFilter(key, ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700718 // install new filter
719 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
Charles Chan48df9ad2018-10-30 18:08:59 -0700720 populateFilter(key, ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700721
722 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
723 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
724
pier6fd24fd2018-11-27 11:23:50 -0800725 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
726 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700727 revokeFwd(key, nextId, fwdFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700728
729 fwdFuture.thenAcceptAsync(fwdStatus -> {
730 if (fwdStatus == null) {
731 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan1fb65132018-09-21 11:29:12 -0700732 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700733 }
734 });
735
736 nextFuture.thenAcceptAsync(nextStatus -> {
737 if (nextStatus == null) {
738 log.debug("Installing new group and flow for {}", key);
Charles Chan48df9ad2018-10-30 18:08:59 -0700739 int newNextId = populateNext(key, ports);
740 if (newNextId == -1) {
741 log.warn("Fail to updateXConnect {}: {}", key, ERROR_NEXT_ID);
742 return;
743 }
744 populateFwd(key, newNextId);
Charles Chan8d316332018-06-19 20:31:57 -0700745 }
746 });
747 } else {
748 log.warn("NextObj for {} does not exist in the store.", key);
749 }
750 }
751
752 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700753 * Creates a next objective builder for XConnect with given nextId.
Charles Chan8d316332018-06-19 20:31:57 -0700754 *
Charles Chan48df9ad2018-10-30 18:08:59 -0700755 * @param key XConnect key
756 * @param ports ports or L2 load balancer key
757 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700758 * @return next objective builder
759 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700760 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports, int nextId) {
Charles Chan8d316332018-06-19 20:31:57 -0700761 TrafficSelector metadata =
762 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
763 NextObjective.Builder nextObjBuilder = DefaultNextObjective
764 .builder().withId(nextId)
765 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
766 .withMeta(metadata);
Charles Chan48df9ad2018-10-30 18:08:59 -0700767
768 for (String port : ports) {
769 NextTreatment nextTreatment = getNextTreatment(key.deviceId(), port);
770 if (nextTreatment == null) {
771 log.warn("Unable to create nextObj. Null NextTreatment");
772 return null;
773 }
774 nextObjBuilder.addTreatment(nextTreatment);
775 }
776
Charles Chan8d316332018-06-19 20:31:57 -0700777 return nextObjBuilder;
778 }
779
780 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700781 * Creates a next objective builder for XConnect.
782 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530783 * @param key XConnect key
Charles Chan1fb65132018-09-21 11:29:12 -0700784 * @param ports set of XConnect ports
785 * @return next objective builder
786 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700787 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports) {
Charles Chan1fb65132018-09-21 11:29:12 -0700788 int nextId = flowObjectiveService.allocateNextId();
789 return nextObjBuilder(key, ports, nextId);
790 }
791
792
793 /**
Charles Chan8d316332018-06-19 20:31:57 -0700794 * Creates a bridging forwarding objective builder for XConnect.
795 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530796 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700797 * @param nextId next ID of the broadcast group for this XConnect key
798 * @return forwarding objective builder
799 */
800 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
801 /*
802 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
803 * as the VLAN cross-connect broadcast rules
804 */
805 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
806 sbuilder.matchVlanId(key.vlanId());
807 sbuilder.matchEthDst(MacAddress.NONE);
808
809 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
810 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
811 .withSelector(sbuilder.build())
812 .nextStep(nextId)
813 .withPriority(XCONNECT_PRIORITY)
814 .fromApp(appId)
815 .makePermanent();
816 return fob;
817 }
818
819 /**
820 * Creates an ACL forwarding objective builder for XConnect.
821 *
822 * @param vlanId cross connect VLAN id
823 * @return forwarding objective builder
824 */
825 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
826 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
827 sbuilder.matchVlanId(vlanId);
828
829 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
830
831 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
832 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
833 .withSelector(sbuilder.build())
834 .withTreatment(tbuilder.build())
835 .withPriority(XCONNECT_ACL_PRIORITY)
836 .fromApp(appId)
837 .makePermanent();
838 return fob;
839 }
840
841 /**
842 * Creates a filtering objective builder for XConnect.
843 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530844 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700845 * @param port XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700846 * @param filtered true if this is a filtered port
Charles Chan8d316332018-06-19 20:31:57 -0700847 * @return next objective builder
848 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700849 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port, boolean filtered) {
Charles Chan8d316332018-06-19 20:31:57 -0700850 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
851 fob.withKey(Criteria.matchInPort(port))
Charles Chan8d316332018-06-19 20:31:57 -0700852 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
853 .withPriority(XCONNECT_PRIORITY);
Charles Chan48df9ad2018-10-30 18:08:59 -0700854 if (filtered) {
855 fob.addCondition(Criteria.matchVlanId(key.vlanId()));
856 } else {
857 fob.addCondition(Criteria.matchVlanId(VlanId.ANY));
858 }
Charles Chan8d316332018-06-19 20:31:57 -0700859 return fob.permit().fromApp(appId);
860 }
861
862 /**
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530863 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan8d316332018-06-19 20:31:57 -0700864 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530865 * @param deviceId Device ID
866 * @param port Port details
867 * @param vlanId VLAN ID
868 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan8d316332018-06-19 20:31:57 -0700869 */
pier6fd24fd2018-11-27 11:23:50 -0800870 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
871 XconnectKey key = new XconnectKey(deviceId, vlanId);
872 // Ensure leadership on device
873 if (!isLocalLeader(deviceId)) {
874 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530875 return;
Charles Chan8d316332018-06-19 20:31:57 -0700876 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530877
878 // Locate L2 flooding group details for given xconnect vlan
pier6fd24fd2018-11-27 11:23:50 -0800879 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530880 if (nextId == -1) {
881 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
882 "Aborting pair group linking.", vlanId, deviceId);
883 return;
884 }
885
886 // Add pairing-port group to flooding group
887 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
888 // treatment.popVlan();
889 treatment.setOutput(port);
890 ObjectiveContext context = new DefaultObjectiveContext(
891 (objective) ->
892 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
893 vlanId, nextId, deviceId),
894 (objective, error) ->
895 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
896 "Error : {}", vlanId, nextId, deviceId, error)
897 );
898 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
899 .withId(nextId)
900 .withType(NextObjective.Type.BROADCAST)
901 .fromApp(srService.appId())
902 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
903 .addTreatment(treatment.build());
904 if (install) {
905 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
906 } else {
907 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
908 }
909 log.debug("Submitted next objective {} for vlan: {} in device {}",
910 nextId, vlanId, deviceId);
Charles Chan8d316332018-06-19 20:31:57 -0700911 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530912
913 /**
914 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
915 * output to given port's L2 mulitcast group.
916 *
917 * @param deviceId Device ID
918 * @param pairPort Pair port number
919 * @param vlanId VLAN ID
920 * @param accessPorts List of access ports to be added into L2 multicast group
921 */
pier6fd24fd2018-11-27 11:23:50 -0800922 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
923 VlanId vlanId, List<PortNumber> accessPorts) {
924 // Ensure enough rights to program pair device
925 if (!srService.shouldProgram(deviceId)) {
926 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
927 return;
928 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530929
930 boolean multicastGroupExists = true;
931 int vlanMulticastNextId;
pier6fd24fd2018-11-27 11:23:50 -0800932 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530933
934 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
935 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
936 .builder()
937 .withType(NextObjective.Type.BROADCAST)
938 .fromApp(srService.appId())
939 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
940 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier6fd24fd2018-11-27 11:23:50 -0800941 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530942 if (vlanMulticastNextId == -1) {
943 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
944 multicastGroupExists = false;
945 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier6fd24fd2018-11-27 11:23:50 -0800946 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530947 vlanMulticastNextObjBuilder.addTreatment(
948 DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
949 );
950 }
951 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier6fd24fd2018-11-27 11:23:50 -0800952 int nextId = vlanMulticastNextId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530953 accessPorts.forEach(p -> {
954 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
955 // Do vlan popup action based on interface configuration
956 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
957 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
958 egressAction.popVlan();
959 }
960 egressAction.setOutput(p);
961 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -0800962 addMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530963 });
964 ObjectiveContext context = new DefaultObjectiveContext(
965 (objective) ->
966 log.debug("L2 multicast group installed/updated. "
967 + "NextObject Id {} on {} for subnet {} ",
968 nextId, deviceId, vlanId),
969 (objective, error) ->
970 log.warn("L2 multicast group failed to install/update. "
971 + " NextObject Id {} on {} for subnet {} : {}",
972 nextId, deviceId, vlanId, error)
973 );
974 if (!multicastGroupExists) {
975 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
976
977 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
978 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
979 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
980 multicastSelector.matchInPort(pairPort);
981 multicastSelector.matchVlanId(vlanId);
982 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
983 .withFlag(ForwardingObjective.Flag.VERSATILE)
984 .nextStep(vlanMulticastNextId)
985 .withSelector(multicastSelector.build())
986 .withPriority(100)
987 .fromApp(srService.appId())
988 .makePermanent();
989 context = new DefaultObjectiveContext(
990 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
991 deviceId,
992 pairPort,
993 vlanId),
994 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
995 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
996 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
997 } else {
998 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
999 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
1000 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
1001 }
1002 }
1003
1004 /**
1005 * Removes access ports from VLAN L2 multicast group on given deviceId.
1006 *
1007 * @param deviceId Device ID
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301008 * @param vlanId VLAN ID
1009 * @param accessPorts List of access ports to be added into L2 multicast group
1010 */
pier6fd24fd2018-11-27 11:23:50 -08001011 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301012 // Ensure enough rights to program pair device
1013 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001014 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301015 return;
1016 }
1017
pier6fd24fd2018-11-27 11:23:50 -08001018 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1019
1020 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301021 if (vlanMulticastNextId == -1) {
1022 return;
1023 }
1024 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1025 .builder()
1026 .withType(NextObjective.Type.BROADCAST)
1027 .fromApp(srService.appId())
1028 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
1029 .withId(vlanMulticastNextId);
1030 accessPorts.forEach(p -> {
1031 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1032 // Do vlan popup action based on interface configuration
1033 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1034 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1035 egressAction.popVlan();
1036 }
1037 egressAction.setOutput(p);
1038 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001039 removeMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301040 });
1041 ObjectiveContext context = new DefaultObjectiveContext(
1042 (objective) ->
1043 log.debug("L2 multicast group installed/updated. "
1044 + "NextObject Id {} on {} for subnet {} ",
1045 vlanMulticastNextId, deviceId, vlanId),
1046 (objective, error) ->
1047 log.warn("L2 multicast group failed to install/update. "
1048 + " NextObject Id {} on {} for subnet {} : {}",
1049 vlanMulticastNextId, deviceId, vlanId, error)
1050 );
1051 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
1052 }
1053
1054 /**
1055 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1056 * Normally multicast group is not removed if it contains access ports; which can be forced
1057 * by "force" flag
1058 *
1059 * @param deviceId Device ID
1060 * @param pairPort Pair port number
1061 * @param vlanId VLAN ID
1062 * @param force Forceful removal
1063 */
1064 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1065
1066 // Ensure enough rights to program pair device
1067 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001068 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301069 return;
1070 }
1071
pier6fd24fd2018-11-27 11:23:50 -08001072 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1073
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301074 // Ensure L2 multicast group doesn't contain access ports
pier6fd24fd2018-11-27 11:23:50 -08001075 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301076 return;
1077 }
1078
1079 // Load L2 multicast group details
pier6fd24fd2018-11-27 11:23:50 -08001080 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301081 if (vlanMulticastNextId == -1) {
1082 return;
1083 }
1084
1085 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1086 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1087 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1088 l2MulticastSelector.matchInPort(pairPort);
1089 l2MulticastSelector.matchVlanId(vlanId);
1090 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1091 .withFlag(ForwardingObjective.Flag.VERSATILE)
1092 .nextStep(vlanMulticastNextId)
1093 .withSelector(l2MulticastSelector.build())
1094 .withPriority(100)
1095 .fromApp(srService.appId())
1096 .makePermanent();
1097 ObjectiveContext context = new DefaultObjectiveContext(
1098 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1099 pairPort, vlanId),
1100 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1101 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1102 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1103
1104 // Step 2 : Clear L2 multicast group associated with vlan
1105 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1106 .builder()
1107 .withId(vlanMulticastNextId)
1108 .withType(NextObjective.Type.BROADCAST)
1109 .fromApp(srService.appId())
1110 .withMeta(DefaultTrafficSelector.builder()
1111 .matchVlanId(vlanId)
1112 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1113 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1114 context = new DefaultObjectiveContext(
1115 (objective) ->
1116 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1117 vlanMulticastNextId, deviceId, vlanId),
1118 (objective, error) ->
1119 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1120 vlanMulticastNextId, deviceId, vlanId, error)
1121 );
1122 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1123
1124 // Finally clear store.
pier6fd24fd2018-11-27 11:23:50 -08001125 removeMulticastGroup(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301126 }
1127
pier6fd24fd2018-11-27 11:23:50 -08001128 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1129 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301130 }
1131
pier6fd24fd2018-11-27 11:23:50 -08001132 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301133 if (nextId == -1) {
1134 return;
1135 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301136 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301137 }
1138
pier6fd24fd2018-11-27 11:23:50 -08001139 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1140 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1141 if (ports == null) {
1142 ports = Lists.newArrayList();
1143 }
1144 ports.add(port);
1145 return ports;
1146 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301147 }
1148
pier6fd24fd2018-11-27 11:23:50 -08001149 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1150 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1151 if (ports != null && !ports.isEmpty()) {
1152 ports.remove(port);
1153 }
1154 return ports;
1155 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301156 }
1157
pier6fd24fd2018-11-27 11:23:50 -08001158 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1159 xconnectMulticastPortsStore.remove(groupKey);
1160 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301161 }
1162
pier6fd24fd2018-11-27 11:23:50 -08001163 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1164 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301165 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1166 }
1167
pier6fd24fd2018-11-27 11:23:50 -08001168 // Custom-built function, when the device is not available we need a fallback mechanism
1169 private boolean isLocalLeader(DeviceId deviceId) {
1170 if (!mastershipService.isLocalMaster(deviceId)) {
1171 // When the device is available we just check the mastership
1172 if (deviceService.isAvailable(deviceId)) {
1173 return false;
1174 }
1175 // Fallback with Leadership service - device id is used as topic
1176 NodeId leader = leadershipService.runForLeadership(
1177 deviceId.toString()).leaderNodeId();
1178 // Verify if this node is the leader
1179 return clusterService.getLocalNode().id().equals(leader);
1180 }
1181 return true;
1182 }
1183
Charles Chan48df9ad2018-10-30 18:08:59 -07001184 private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, String port) {
1185 // If port is numeric, treat it as regular port.
1186 // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
1187 try {
1188 return Sets.newHashSet(PortNumber.portNumber(Integer.parseInt(port)));
1189 } catch (NumberFormatException e) {
1190 log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
1191 }
1192
1193 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
1194 try {
1195 return Sets.newHashSet(l2LbService.getL2Lb(deviceId, Integer.parseInt(l2LbKey)).ports());
1196 } catch (NumberFormatException e) {
1197 log.debug("Port {} is not load balancer key either. Ignore", port);
1198 } catch (NullPointerException e) {
1199 log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
1200 }
1201
1202 return Sets.newHashSet();
1203 }
1204
1205 private NextTreatment getNextTreatment(DeviceId deviceId, String port) {
1206 // If port is numeric, treat it as regular port.
1207 // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
1208 try {
1209 PortNumber portNumber = PortNumber.portNumber(Integer.parseInt(port));
1210 return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(portNumber).build());
1211 } catch (NumberFormatException e) {
1212 log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
1213 }
1214
1215 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
1216 try {
1217 return IdNextTreatment.of(l2LbService.getL2LbNexts(deviceId, Integer.parseInt(l2LbKey)));
1218 } catch (NumberFormatException e) {
1219 log.debug("Port {} is not load balancer key either. Ignore", port);
1220 } catch (NullPointerException e) {
1221 log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
1222 }
1223
1224 return null;
1225 }
Charles Chan8d316332018-06-19 20:31:57 -07001226}