blob: a20192d265612bd46aa751dc089ca3577ee241c8 [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;
pier567465b2018-11-24 11:16:28 -080019import com.google.common.cache.Cache;
20import com.google.common.cache.CacheBuilder;
21import com.google.common.cache.RemovalNotification;
Charles Chan0b1dd7e2018-08-19 19:21:46 -070022import com.google.common.collect.ImmutableMap;
Charles Chan8d316332018-06-19 20:31:57 -070023import com.google.common.collect.ImmutableSet;
pier6fd24fd2018-11-27 11:23:50 -080024import com.google.common.collect.Lists;
Charles Chan8d316332018-06-19 20:31:57 -070025import com.google.common.collect.Sets;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053026import org.onlab.packet.Ethernet;
Charles Chan8d316332018-06-19 20:31:57 -070027import org.onlab.packet.MacAddress;
28import org.onlab.packet.VlanId;
29import org.onlab.util.KryoNamespace;
30import org.onosproject.codec.CodecService;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
Charles Chanc0a499b2019-01-16 15:30:39 -080033import org.onosproject.portloadbalancer.api.PortLoadBalancerEvent;
34import org.onosproject.portloadbalancer.api.PortLoadBalancerId;
35import org.onosproject.portloadbalancer.api.PortLoadBalancerListener;
36import org.onosproject.portloadbalancer.api.PortLoadBalancerService;
Charles Chan8d316332018-06-19 20:31:57 -070037import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.DeviceId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053039import org.onosproject.net.Host;
40import org.onosproject.net.HostLocation;
Charles Chan8d316332018-06-19 20:31:57 -070041import org.onosproject.net.PortNumber;
William Davies8518e802019-07-23 21:18:53 +000042import org.onosproject.net.Port;
43import org.onosproject.net.config.NetworkConfigRegistry;
Charles Chan8d316332018-06-19 20:31:57 -070044import org.onosproject.net.config.NetworkConfigService;
45import org.onosproject.net.device.DeviceEvent;
46import org.onosproject.net.device.DeviceListener;
47import org.onosproject.net.device.DeviceService;
48import org.onosproject.net.flow.DefaultTrafficSelector;
49import org.onosproject.net.flow.DefaultTrafficTreatment;
50import org.onosproject.net.flow.TrafficSelector;
51import org.onosproject.net.flow.TrafficTreatment;
52import org.onosproject.net.flow.criteria.Criteria;
53import org.onosproject.net.flowobjective.DefaultFilteringObjective;
54import org.onosproject.net.flowobjective.DefaultForwardingObjective;
55import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070056import org.onosproject.net.flowobjective.DefaultNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070057import org.onosproject.net.flowobjective.DefaultObjectiveContext;
58import org.onosproject.net.flowobjective.FilteringObjective;
59import org.onosproject.net.flowobjective.FlowObjectiveService;
60import org.onosproject.net.flowobjective.ForwardingObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070061import org.onosproject.net.flowobjective.IdNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070062import org.onosproject.net.flowobjective.NextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070063import org.onosproject.net.flowobjective.NextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070064import org.onosproject.net.flowobjective.Objective;
65import org.onosproject.net.flowobjective.ObjectiveContext;
66import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053067import org.onosproject.net.host.HostEvent;
68import org.onosproject.net.host.HostListener;
69import org.onosproject.net.host.HostService;
70import org.onosproject.net.intf.InterfaceService;
Charles Chan8d316332018-06-19 20:31:57 -070071import org.onosproject.segmentrouting.SegmentRoutingService;
William Davies8518e802019-07-23 21:18:53 +000072import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053073import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan8d316332018-06-19 20:31:57 -070074import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
75import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
Charles Chan445659f2019-01-02 13:46:16 -080076import org.onosproject.segmentrouting.xconnect.api.XconnectEndpoint;
Charles Chan8d316332018-06-19 20:31:57 -070077import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
Charles Chan445659f2019-01-02 13:46:16 -080078import org.onosproject.segmentrouting.xconnect.api.XconnectLoadBalancerEndpoint;
79import org.onosproject.segmentrouting.xconnect.api.XconnectPortEndpoint;
Charles Chan8d316332018-06-19 20:31:57 -070080import org.onosproject.segmentrouting.xconnect.api.XconnectService;
81import org.onosproject.store.serializers.KryoNamespaces;
82import org.onosproject.store.service.ConsistentMap;
83import org.onosproject.store.service.MapEvent;
84import org.onosproject.store.service.MapEventListener;
85import org.onosproject.store.service.Serializer;
86import org.onosproject.store.service.StorageService;
87import org.onosproject.store.service.Versioned;
Ray Milkey2bd24a92018-08-17 14:54:17 -070088import org.osgi.service.component.annotations.Activate;
89import org.osgi.service.component.annotations.Component;
90import org.osgi.service.component.annotations.Deactivate;
91import org.osgi.service.component.annotations.Reference;
92import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chan8d316332018-06-19 20:31:57 -070093import org.slf4j.Logger;
94import org.slf4j.LoggerFactory;
95
96import java.io.Serializable;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053097import java.util.Collections;
98import java.util.List;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053099import java.util.Optional;
Charles Chan8d316332018-06-19 20:31:57 -0700100import java.util.Set;
William Davies8518e802019-07-23 21:18:53 +0000101import java.util.Iterator;
Charles Chan8d316332018-06-19 20:31:57 -0700102import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -0700103import java.util.concurrent.ExecutorService;
104import java.util.concurrent.Executors;
pier567465b2018-11-24 11:16:28 -0800105import java.util.concurrent.ScheduledExecutorService;
106import java.util.concurrent.TimeUnit;
Charles Chan8d316332018-06-19 20:31:57 -0700107import java.util.function.BiConsumer;
108import java.util.function.Consumer;
109import java.util.stream.Collectors;
110
pier567465b2018-11-24 11:16:28 -0800111import static java.util.concurrent.Executors.newScheduledThreadPool;
Charles Chan56542b62018-08-07 12:48:36 -0700112import static org.onlab.util.Tools.groupedThreads;
113
Ray Milkey2bd24a92018-08-17 14:54:17 -0700114@Component(immediate = true, service = XconnectService.class)
Charles Chan8d316332018-06-19 20:31:57 -0700115public class XconnectManager implements XconnectService {
Ray Milkey2bd24a92018-08-17 14:54:17 -0700116 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700117 private CoreService coreService;
118
Ray Milkey2bd24a92018-08-17 14:54:17 -0700119 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700120 private CodecService codecService;
121
Ray Milkey2bd24a92018-08-17 14:54:17 -0700122 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700123 private StorageService storageService;
124
Ray Milkey2bd24a92018-08-17 14:54:17 -0700125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700126 public NetworkConfigService netCfgService;
127
Ray Milkey2bd24a92018-08-17 14:54:17 -0700128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700129 public DeviceService deviceService;
130
Ray Milkey2bd24a92018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700132 public FlowObjectiveService flowObjectiveService;
133
Ray Milkey2bd24a92018-08-17 14:54:17 -0700134 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chan8d316332018-06-19 20:31:57 -0700135 public SegmentRoutingService srService;
136
Ray Milkey3cad4db2018-10-04 15:13:33 -0700137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530138 public InterfaceService interfaceService;
139
Ray Milkey3cad4db2018-10-04 15:13:33 -0700140 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530141 HostService hostService;
142
Charles Chan48df9ad2018-10-30 18:08:59 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc0a499b2019-01-16 15:30:39 -0800144 private PortLoadBalancerService portLoadBalancerService;
Charles Chan48df9ad2018-10-30 18:08:59 -0700145
William Davies8518e802019-07-23 21:18:53 +0000146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
147 public NetworkConfigRegistry cfgService;
148
Charles Chan8d316332018-06-19 20:31:57 -0700149 private static final String APP_NAME = "org.onosproject.xconnect";
pier6fd24fd2018-11-27 11:23:50 -0800150 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chan48df9ad2018-10-30 18:08:59 -0700151 private static final String ERROR_NEXT_OBJ_BUILDER = "Unable to construct next objective builder";
152 private static final String ERROR_NEXT_ID = "Unable to get next id";
William Davies8518e802019-07-23 21:18:53 +0000153 private static final String ERROR_NOT_EDGE_ROUTER = "Device is not Edge Router";
154 private static final String ERROR_PORT_NOT_RANGE = "Ports for the device are not in the range";
Charles Chan8d316332018-06-19 20:31:57 -0700155
156 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
157
158 private ApplicationId appId;
Charles Chan445659f2019-01-02 13:46:16 -0800159 private ConsistentMap<XconnectKey, Set<XconnectEndpoint>> xconnectStore;
Charles Chan1fb65132018-09-21 11:29:12 -0700160 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan8d316332018-06-19 20:31:57 -0700161
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530162 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
163 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
164
Charles Chan445659f2019-01-02 13:46:16 -0800165 private final MapEventListener<XconnectKey, Set<XconnectEndpoint>> xconnectListener = new XconnectMapListener();
pier6fd24fd2018-11-27 11:23:50 -0800166 private ExecutorService xConnectExecutor;
Charles Chan8d316332018-06-19 20:31:57 -0700167
pier6fd24fd2018-11-27 11:23:50 -0800168 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan56542b62018-08-07 12:48:36 -0700169 private ExecutorService deviceEventExecutor;
170
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530171 private final HostListener hostListener = new InternalHostListener();
172 private ExecutorService hostEventExecutor;
173
pier567465b2018-11-24 11:16:28 -0800174 // Wait time for the cache
175 private static final int WAIT_TIME_MS = 15000;
Charles Chanc0a499b2019-01-16 15:30:39 -0800176 //The cache is implemented as buffer for waiting the installation of PortLoadBalancer when present
177 private Cache<PortLoadBalancerId, XconnectKey> portLoadBalancerCache;
pier567465b2018-11-24 11:16:28 -0800178 // Executor for the cache
Charles Chanc0a499b2019-01-16 15:30:39 -0800179 private ScheduledExecutorService portLoadBalancerExecutor;
180 // We need to listen for some events to properly installed the xconnect with portloadbalancer
181 private final PortLoadBalancerListener portLoadBalancerListener = new InternalPortLoadBalancerListener();
pier567465b2018-11-24 11:16:28 -0800182
Charles Chan8d316332018-06-19 20:31:57 -0700183 @Activate
184 void activate() {
185 appId = coreService.registerApplication(APP_NAME);
186 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
187
188 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
189 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700190 .register(XconnectManager.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530191 .register(XconnectKey.class)
Charles Chan445659f2019-01-02 13:46:16 -0800192 .register(XconnectEndpoint.class)
193 .register(XconnectPortEndpoint.class)
194 .register(XconnectLoadBalancerEndpoint.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530195 .register(VlanNextObjectiveStoreKey.class);
Charles Chan8d316332018-06-19 20:31:57 -0700196
Charles Chan445659f2019-01-02 13:46:16 -0800197 xconnectStore = storageService.<XconnectKey, Set<XconnectEndpoint>>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700198 .withName("onos-sr-xconnect")
199 .withRelaxedReadConsistency()
200 .withSerializer(Serializer.using(serializer.build()))
201 .build();
pier6fd24fd2018-11-27 11:23:50 -0800202 xConnectExecutor = Executors.newSingleThreadScheduledExecutor(
203 groupedThreads("sr-xconnect-event", "%d", log));
204 xconnectStore.addListener(xconnectListener, xConnectExecutor);
Charles Chan8d316332018-06-19 20:31:57 -0700205
Charles Chan1fb65132018-09-21 11:29:12 -0700206 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700207 .withName("onos-sr-xconnect-next")
208 .withRelaxedReadConsistency()
209 .withSerializer(Serializer.using(serializer.build()))
210 .build();
211
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530212 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
213 .withName("onos-sr-xconnect-l2multicast-next")
214 .withSerializer(Serializer.using(serializer.build()))
215 .build();
216 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
217 .withName("onos-sr-xconnect-l2multicast-ports")
218 .withSerializer(Serializer.using(serializer.build()))
219 .build();
220
Charles Chan56542b62018-08-07 12:48:36 -0700221 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
222 groupedThreads("sr-xconnect-device-event", "%d", log));
Charles Chan8d316332018-06-19 20:31:57 -0700223 deviceService.addListener(deviceListener);
224
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530225 hostEventExecutor = Executors.newSingleThreadExecutor(
226 groupedThreads("sr-xconnect-host-event", "%d", log));
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530227 hostService.addListener(hostListener);
228
Charles Chanc0a499b2019-01-16 15:30:39 -0800229 portLoadBalancerCache = CacheBuilder.newBuilder()
pier567465b2018-11-24 11:16:28 -0800230 .expireAfterWrite(WAIT_TIME_MS, TimeUnit.MILLISECONDS)
Charles Chanc0a499b2019-01-16 15:30:39 -0800231 .removalListener((RemovalNotification<PortLoadBalancerId, XconnectKey> notification) ->
232 log.debug("PortLoadBalancer cache removal event. portLoadBalancerId={}, xConnectKey={}",
pier567465b2018-11-24 11:16:28 -0800233 notification.getKey(), notification.getValue())).build();
Charles Chanc0a499b2019-01-16 15:30:39 -0800234 portLoadBalancerExecutor = newScheduledThreadPool(1,
235 groupedThreads("portLoadBalancerCacheWorker", "-%d", log));
pier567465b2018-11-24 11:16:28 -0800236 // Let's schedule the cleanup of the cache
Charles Chanc0a499b2019-01-16 15:30:39 -0800237 portLoadBalancerExecutor.scheduleAtFixedRate(portLoadBalancerCache::cleanUp, 0,
pier567465b2018-11-24 11:16:28 -0800238 WAIT_TIME_MS, TimeUnit.MILLISECONDS);
Charles Chanc0a499b2019-01-16 15:30:39 -0800239 portLoadBalancerService.addListener(portLoadBalancerListener);
pier567465b2018-11-24 11:16:28 -0800240
Charles Chan8d316332018-06-19 20:31:57 -0700241 log.info("Started");
242 }
243
244 @Deactivate
245 void deactivate() {
246 xconnectStore.removeListener(xconnectListener);
247 deviceService.removeListener(deviceListener);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530248 hostService.removeListener(hostListener);
pier98345be2019-04-01 23:38:42 -0700249 portLoadBalancerService.removeListener(portLoadBalancerListener);
Charles Chan8d316332018-06-19 20:31:57 -0700250 codecService.unregisterCodec(XconnectDesc.class);
251
Charles Chan56542b62018-08-07 12:48:36 -0700252 deviceEventExecutor.shutdown();
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530253 hostEventExecutor.shutdown();
pier6fd24fd2018-11-27 11:23:50 -0800254 xConnectExecutor.shutdown();
Charles Chanc0a499b2019-01-16 15:30:39 -0800255 portLoadBalancerExecutor.shutdown();
Charles Chan56542b62018-08-07 12:48:36 -0700256
Charles Chan8d316332018-06-19 20:31:57 -0700257 log.info("Stopped");
258 }
259
260 @Override
Charles Chan445659f2019-01-02 13:46:16 -0800261 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<XconnectEndpoint> endpoints) {
262 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, endpoints={}",
263 deviceId, vlanId, endpoints);
William Davies8518e802019-07-23 21:18:53 +0000264 SegmentRoutingDeviceConfig config = cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class);
265
266 List<PortNumber> devicePorts = deviceService.getPorts(deviceId).stream()
267 .map(Port::number)
268 .collect(Collectors.toList());
269 if (!config.isEdgeRouter()) {
270 throw new IllegalArgumentException(ERROR_NOT_EDGE_ROUTER);
271 } else {
272 Iterator<XconnectEndpoint> itr = endpoints.iterator();
273 while (itr.hasNext()) {
274 XconnectEndpoint ep = itr.next();
275 // Note: we don't validate an endpoint with LOAD_BALANCER type
276 if (ep.type() != XconnectEndpoint.Type.PORT) {
277 continue;
278 }
279 if (!devicePorts.contains(((XconnectPortEndpoint) ep).port())) {
280 throw new IllegalArgumentException(ERROR_PORT_NOT_RANGE);
281 }
282 }
283 }
Charles Chan8d316332018-06-19 20:31:57 -0700284 final XconnectKey key = new XconnectKey(deviceId, vlanId);
Charles Chan445659f2019-01-02 13:46:16 -0800285 xconnectStore.put(key, endpoints);
Charles Chan8d316332018-06-19 20:31:57 -0700286 }
287
288 @Override
289 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
290 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530291 deviceId, vlanId);
Charles Chan8d316332018-06-19 20:31:57 -0700292 final XconnectKey key = new XconnectKey(deviceId, vlanId);
293 xconnectStore.remove(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530294
295 // Cleanup multicasting support, if any.
Charles Chan445659f2019-01-02 13:46:16 -0800296 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId ->
297 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true)
298 );
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530299
Charles Chan8d316332018-06-19 20:31:57 -0700300 }
301
302 @Override
303 public Set<XconnectDesc> getXconnects() {
304 return xconnectStore.asJavaMap().entrySet().stream()
305 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
306 .collect(Collectors.toSet());
307 }
308
309 @Override
310 public boolean hasXconnect(ConnectPoint cp) {
Charles Chan445659f2019-01-02 13:46:16 -0800311 return getXconnects().stream().anyMatch(desc ->
312 desc.key().deviceId().equals(cp.deviceId()) && desc.endpoints().stream().anyMatch(ep ->
313 ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(cp.port())
314 )
Charles Chan8d316332018-06-19 20:31:57 -0700315 );
316 }
317
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700318 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530319 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
320 return getXconnects().stream()
Charles Chan445659f2019-01-02 13:46:16 -0800321 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.endpoints().stream().anyMatch(ep ->
322 ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(port)))
323 .map(XconnectDesc::key)
324 .map(XconnectKey::vlanId)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530325 .collect(Collectors.toList());
326 }
327
328 @Override
329 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier6fd24fd2018-11-27 11:23:50 -0800330 XconnectKey key = new XconnectKey(deviceId, vlanId);
331 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530332 }
333
334 @Override
Charles Chan1fb65132018-09-21 11:29:12 -0700335 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700336 if (xconnectNextObjStore != null) {
337 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
338 } else {
339 return ImmutableMap.of();
340 }
341 }
342
343 @Override
pier6fd24fd2018-11-27 11:23:50 -0800344 public int getNextId(DeviceId deviceId, VlanId vlanId) {
345 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530346 }
347
348 @Override
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700349 public void removeNextId(int nextId) {
350 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan1fb65132018-09-21 11:29:12 -0700351 if (e.getValue().value() == nextId) {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700352 xconnectNextObjStore.remove(e.getKey());
353 }
354 });
355 }
356
Charles Chan445659f2019-01-02 13:46:16 -0800357 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<XconnectEndpoint>> {
Charles Chan8d316332018-06-19 20:31:57 -0700358 @Override
Charles Chan445659f2019-01-02 13:46:16 -0800359 public void event(MapEvent<XconnectKey, Set<XconnectEndpoint>> event) {
Charles Chan8d316332018-06-19 20:31:57 -0700360 XconnectKey key = event.key();
Charles Chan445659f2019-01-02 13:46:16 -0800361 Set<XconnectEndpoint> ports = Versioned.valueOrNull(event.newValue());
362 Set<XconnectEndpoint> oldPorts = Versioned.valueOrNull(event.oldValue());
Charles Chan8d316332018-06-19 20:31:57 -0700363
364 switch (event.type()) {
365 case INSERT:
Charles Chan48df9ad2018-10-30 18:08:59 -0700366 populateXConnect(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700367 break;
368 case UPDATE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700369 updateXConnect(key, oldPorts, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700370 break;
371 case REMOVE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700372 revokeXConnect(key, oldPorts);
Charles Chan8d316332018-06-19 20:31:57 -0700373 break;
374 default:
375 break;
376 }
377 }
378 }
379
380 private class InternalDeviceListener implements DeviceListener {
pier6fd24fd2018-11-27 11:23:50 -0800381 // Offload the execution to an executor and then process the event
382 // if this instance is the leader of the device
Charles Chan8d316332018-06-19 20:31:57 -0700383 @Override
384 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700385 deviceEventExecutor.execute(() -> {
386 DeviceId deviceId = event.subject().id();
pier6fd24fd2018-11-27 11:23:50 -0800387 // Just skip if we are not the leader
pierventre37dcf4c2021-09-16 18:43:06 +0200388 if (!srService.shouldProgram(deviceId)) {
389 log.debug("Not leading the programming of {}. Skip event {}", deviceId, event);
Charles Chan56542b62018-08-07 12:48:36 -0700390 return;
391 }
pier6fd24fd2018-11-27 11:23:50 -0800392 // Populate or revoke according to the device availability
393 if (deviceService.isAvailable(deviceId)) {
394 init(deviceId);
395 } else {
396 cleanup(deviceId);
Charles Chan56542b62018-08-07 12:48:36 -0700397 }
398 });
Charles Chan8d316332018-06-19 20:31:57 -0700399 }
pier6fd24fd2018-11-27 11:23:50 -0800400 // We want to manage only a subset of events and if we are the leader
401 @Override
402 public boolean isRelevant(DeviceEvent event) {
403 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
404 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
405 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
406 }
Charles Chan8d316332018-06-19 20:31:57 -0700407 }
408
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530409 private class InternalHostListener implements HostListener {
410 @Override
411 public void event(HostEvent event) {
412 hostEventExecutor.execute(() -> {
413
414 switch (event.type()) {
Sudhir Kumar Mauryafcc42f82019-05-02 03:03:59 -0400415 case HOST_ADDED:
416 case HOST_REMOVED:
417 case HOST_UPDATED:
418 log.trace("Unhandled host event type: {} received. Ignoring.", event.type());
419 break;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530420 case HOST_MOVED:
421 log.trace("Processing host event {}", event);
422
423 Host host = event.subject();
424 Set<HostLocation> prevLocations = event.prevSubject().locations();
425 Set<HostLocation> newLocations = host.locations();
426
427 // Dual-home host port failure
428 // For each old location, in failed and paired devices update L2 vlan groups
429 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
430
431 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
432 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
433
434 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
435 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
436 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
437
438 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
439 prevLocation.port());
440 xconnectVlans.forEach(xconnectVlan -> {
441 // Add single-home host into L2 multicast group at paired device side.
442 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
443 newLocations.stream()
444 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
445 .forEach(location -> populateL2Multicast(location.deviceId(),
446 srService.getPairLocalPort(
447 location.deviceId()).get(),
448 xconnectVlan,
449 Collections.singletonList(
450 location.port())));
pier6fd24fd2018-11-27 11:23:50 -0800451 // Ensure pair-port attached to xconnect vlan flooding group
452 // at dual home failed device.
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530453 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
454 });
455 }
456 });
457
458 // Dual-home host port restoration
459 // For each new location, reverse xconnect loop prevention groups.
460 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
461 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
462 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
463 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
464 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
465
466 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
467 newLocation.port());
468 xconnectVlans.forEach(xconnectVlan -> {
469 // Remove recovered dual homed port from vlan L2 multicast group
470 prevLocations.stream()
471 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier6fd24fd2018-11-27 11:23:50 -0800472 .forEach(prevLocation -> revokeL2Multicast(
473 prevLocation.deviceId(),
474 xconnectVlan,
475 Collections.singletonList(newLocation.port()))
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530476 );
477
pier6fd24fd2018-11-27 11:23:50 -0800478 // Remove pair-port from vlan's flooding group at dual home
479 // restored device, if needed.
480 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
481 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530482 updateL2Flooding(newLocation.deviceId(),
483 pairLocalPort.get(),
484 xconnectVlan,
485 false);
486
487 // Clean L2 multicast group at pair-device; also update store.
488 cleanupL2MulticastRule(pairDeviceId.get(),
489 srService.getPairLocalPort(pairDeviceId.get()).get(),
490 xconnectVlan,
491 false);
492 }
493 });
494 }
495 });
496 break;
497
498 default:
499 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
500 break;
501 }
502 });
503 }
504 }
505
Charles Chan1fb65132018-09-21 11:29:12 -0700506 private void init(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700507 getXconnects().stream()
508 .filter(desc -> desc.key().deviceId().equals(deviceId))
Charles Chan445659f2019-01-02 13:46:16 -0800509 .forEach(desc -> populateXConnect(desc.key(), desc.endpoints()));
Charles Chan8d316332018-06-19 20:31:57 -0700510 }
511
Charles Chan1fb65132018-09-21 11:29:12 -0700512 private void cleanup(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700513 xconnectNextObjStore.entrySet().stream()
514 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
515 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
516 log.debug("{} is removed from xConnectNextObjStore", deviceId);
517 }
518
519 /**
520 * Populates XConnect groups and flows for given key.
521 *
Charles Chan445659f2019-01-02 13:46:16 -0800522 * @param key XConnect key
523 * @param endpoints a set of endpoints to be cross-connected
Charles Chan8d316332018-06-19 20:31:57 -0700524 */
Charles Chan445659f2019-01-02 13:46:16 -0800525 private void populateXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pierventre37dcf4c2021-09-16 18:43:06 +0200526 if (!srService.shouldProgram(key.deviceId())) {
pier6fd24fd2018-11-27 11:23:50 -0800527 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700528 return;
529 }
530
Charles Chan445659f2019-01-02 13:46:16 -0800531 int nextId = populateNext(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700532 if (nextId == -1) {
533 log.warn("Fail to populateXConnect {}: {}", key, ERROR_NEXT_ID);
534 return;
535 }
Charles Chan445659f2019-01-02 13:46:16 -0800536 populateFilter(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700537 populateFwd(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700538 populateAcl(key);
539 }
540
541 /**
542 * Populates filtering objectives for given XConnect.
543 *
Charles Chan445659f2019-01-02 13:46:16 -0800544 * @param key XConnect store key
545 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700546 */
Charles Chan445659f2019-01-02 13:46:16 -0800547 private void populateFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan48df9ad2018-10-30 18:08:59 -0700548 // FIXME Improve the logic
Charles Chanc0a499b2019-01-16 15:30:39 -0800549 // If port load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
Charles Chan48df9ad2018-10-30 18:08:59 -0700550 // The purpose is to make sure existing XConnect logic can still work on a configured port.
Charles Chan445659f2019-01-02 13:46:16 -0800551 boolean filtered = endpoints.stream()
552 .map(ep -> getNextTreatment(key.deviceId(), ep, false))
Charles Chan48df9ad2018-10-30 18:08:59 -0700553 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
554
Charles Chan445659f2019-01-02 13:46:16 -0800555 endpoints.stream()
556 .map(ep -> getPhysicalPorts(key.deviceId(), ep))
Charles Chan48df9ad2018-10-30 18:08:59 -0700557 .flatMap(Set::stream).forEach(port -> {
Charles Chan445659f2019-01-02 13:46:16 -0800558 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
559 ObjectiveContext context = new DefaultObjectiveContext(
560 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
561 key, port),
562 (objective, error) ->
563 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
564 key, port, error));
565 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
566 });
Charles Chan8d316332018-06-19 20:31:57 -0700567 }
568
569 /**
570 * Populates next objectives for given XConnect.
571 *
Charles Chan445659f2019-01-02 13:46:16 -0800572 * @param key XConnect store key
573 * @param endpoints XConnect endpoints
Charles Chan48df9ad2018-10-30 18:08:59 -0700574 * @return next id
Charles Chan8d316332018-06-19 20:31:57 -0700575 */
Charles Chan445659f2019-01-02 13:46:16 -0800576 private int populateNext(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pier6fd24fd2018-11-27 11:23:50 -0800577 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
578 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700579 log.debug("NextObj for {} found, id={}", key, nextId);
580 return nextId;
Charles Chan8d316332018-06-19 20:31:57 -0700581 } else {
Charles Chan445659f2019-01-02 13:46:16 -0800582 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700583 if (nextObjBuilder == null) {
584 log.warn("Fail to populate {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
585 return -1;
586 }
Charles Chan8d316332018-06-19 20:31:57 -0700587 ObjectiveContext nextContext = new DefaultObjectiveContext(
588 // To serialize this with kryo
589 (Serializable & Consumer<Objective>) (objective) ->
590 log.debug("XConnect NextObj for {} added", key),
Charles Chanfacfbef2018-08-23 14:30:33 -0700591 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
592 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
593 srService.invalidateNextObj(objective.id());
594 });
Charles Chan1fb65132018-09-21 11:29:12 -0700595 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan8d316332018-06-19 20:31:57 -0700596 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan1fb65132018-09-21 11:29:12 -0700597 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan8d316332018-06-19 20:31:57 -0700598 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan1fb65132018-09-21 11:29:12 -0700599 return nextObj.id();
Charles Chan8d316332018-06-19 20:31:57 -0700600 }
Charles Chan8d316332018-06-19 20:31:57 -0700601 }
602
603 /**
604 * Populates bridging forwarding objectives for given XConnect.
605 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530606 * @param key XConnect store key
Charles Chan1fb65132018-09-21 11:29:12 -0700607 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700608 */
Charles Chan1fb65132018-09-21 11:29:12 -0700609 private void populateFwd(XconnectKey key, int nextId) {
610 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700611 ObjectiveContext fwdContext = new DefaultObjectiveContext(
612 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
613 (objective, error) ->
614 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
615 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
616 }
617
618 /**
619 * Populates ACL forwarding objectives for given XConnect.
620 *
621 * @param key XConnect store key
622 */
623 private void populateAcl(XconnectKey key) {
624 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
625 ObjectiveContext aclContext = new DefaultObjectiveContext(
626 (objective) -> log.debug("XConnect AclObj for {} populated", key),
627 (objective, error) ->
628 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
629 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
630 }
631
632 /**
633 * Revokes XConnect groups and flows for given key.
634 *
Charles Chan445659f2019-01-02 13:46:16 -0800635 * @param key XConnect key
636 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700637 */
Charles Chan445659f2019-01-02 13:46:16 -0800638 private void revokeXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pierventre37dcf4c2021-09-16 18:43:06 +0200639 if (!srService.shouldProgram(key.deviceId())) {
pier6fd24fd2018-11-27 11:23:50 -0800640 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700641 return;
642 }
643
Charles Chan445659f2019-01-02 13:46:16 -0800644 revokeFilter(key, endpoints);
pier6fd24fd2018-11-27 11:23:50 -0800645 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
646 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700647 revokeFwd(key, nextId, null);
Charles Chan445659f2019-01-02 13:46:16 -0800648 revokeNext(key, endpoints, nextId, null);
Charles Chan8d316332018-06-19 20:31:57 -0700649 } else {
650 log.warn("NextObj for {} does not exist in the store.", key);
651 }
Charles Chan445659f2019-01-02 13:46:16 -0800652 revokeFilter(key, endpoints);
Charles Chan8d316332018-06-19 20:31:57 -0700653 revokeAcl(key);
654 }
655
656 /**
657 * Revokes filtering objectives for given XConnect.
658 *
Charles Chan445659f2019-01-02 13:46:16 -0800659 * @param key XConnect store key
660 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700661 */
Charles Chan445659f2019-01-02 13:46:16 -0800662 private void revokeFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan48df9ad2018-10-30 18:08:59 -0700663 // FIXME Improve the logic
Charles Chanc0a499b2019-01-16 15:30:39 -0800664 // If port load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
Charles Chan48df9ad2018-10-30 18:08:59 -0700665 // The purpose is to make sure existing XConnect logic can still work on a configured port.
Charles Chan445659f2019-01-02 13:46:16 -0800666 boolean filtered = endpoints.stream()
667 .map(ep -> getNextTreatment(key.deviceId(), ep, false))
668 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
669
670 endpoints.stream()
671 .map(ep -> getPhysicalPorts(key.deviceId(), ep)).
672 flatMap(Set::stream).forEach(port -> {
673 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
674 ObjectiveContext context = new DefaultObjectiveContext(
675 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
676 key, port),
677 (objective, error) ->
678 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
679 key, port, error));
680 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
681 });
Charles Chan8d316332018-06-19 20:31:57 -0700682 }
683
684 /**
685 * Revokes next objectives for given XConnect.
686 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530687 * @param key XConnect store key
Charles Chan445659f2019-01-02 13:46:16 -0800688 * @param endpoints XConnect endpoints
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530689 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700690 * @param nextFuture completable future for this next objective operation
691 */
Charles Chan445659f2019-01-02 13:46:16 -0800692 private void revokeNext(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId,
Charles Chan8d316332018-06-19 20:31:57 -0700693 CompletableFuture<ObjectiveError> nextFuture) {
694 ObjectiveContext context = new ObjectiveContext() {
695 @Override
696 public void onSuccess(Objective objective) {
697 log.debug("Previous NextObj for {} removed", key);
698 if (nextFuture != null) {
699 nextFuture.complete(null);
700 }
701 }
702
703 @Override
704 public void onError(Objective objective, ObjectiveError error) {
705 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
706 if (nextFuture != null) {
707 nextFuture.complete(error);
708 }
Charles Chanfacfbef2018-08-23 14:30:33 -0700709 srService.invalidateNextObj(objective.id());
Charles Chan8d316332018-06-19 20:31:57 -0700710 }
711 };
Charles Chan48df9ad2018-10-30 18:08:59 -0700712
Charles Chan445659f2019-01-02 13:46:16 -0800713 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints, nextId);
Charles Chan48df9ad2018-10-30 18:08:59 -0700714 if (nextObjBuilder == null) {
715 log.warn("Fail to revokeNext {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
716 return;
717 }
Charles Chanc0a499b2019-01-16 15:30:39 -0800718 // Release the port load balancer if present
Charles Chan445659f2019-01-02 13:46:16 -0800719 endpoints.stream()
720 .filter(endpoint -> endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER)
721 .forEach(endpoint -> {
Charles Chanc0a499b2019-01-16 15:30:39 -0800722 String portLoadBalancerKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
723 portLoadBalancerService.release(new PortLoadBalancerId(key.deviceId(),
724 Integer.parseInt(portLoadBalancerKey)), appId);
Charles Chan445659f2019-01-02 13:46:16 -0800725 });
Charles Chan48df9ad2018-10-30 18:08:59 -0700726 flowObjectiveService.next(key.deviceId(), nextObjBuilder.remove(context));
Charles Chan8d316332018-06-19 20:31:57 -0700727 xconnectNextObjStore.remove(key);
728 }
729
730 /**
731 * Revokes bridging forwarding objectives for given XConnect.
732 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530733 * @param key XConnect store key
734 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700735 * @param fwdFuture completable future for this forwarding objective operation
736 */
Charles Chan1fb65132018-09-21 11:29:12 -0700737 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
738 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700739 ObjectiveContext context = new ObjectiveContext() {
740 @Override
741 public void onSuccess(Objective objective) {
742 log.debug("Previous FwdObj for {} removed", key);
743 if (fwdFuture != null) {
744 fwdFuture.complete(null);
745 }
746 }
747
748 @Override
749 public void onError(Objective objective, ObjectiveError error) {
750 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
751 if (fwdFuture != null) {
752 fwdFuture.complete(error);
753 }
754 }
755 };
756 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
757 }
758
759 /**
760 * Revokes ACL forwarding objectives for given XConnect.
761 *
762 * @param key XConnect store key
763 */
764 private void revokeAcl(XconnectKey key) {
765 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
766 ObjectiveContext aclContext = new DefaultObjectiveContext(
767 (objective) -> log.debug("XConnect AclObj for {} populated", key),
768 (objective, error) ->
769 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
770 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
771 }
772
773 /**
774 * Updates XConnect groups and flows for given key.
775 *
Charles Chan445659f2019-01-02 13:46:16 -0800776 * @param key XConnect key
777 * @param prevEndpoints previous XConnect endpoints
778 * @param endpoints new XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700779 */
Charles Chan445659f2019-01-02 13:46:16 -0800780 private void updateXConnect(XconnectKey key, Set<XconnectEndpoint> prevEndpoints,
781 Set<XconnectEndpoint> endpoints) {
pierventre37dcf4c2021-09-16 18:43:06 +0200782 if (!srService.shouldProgram(key.deviceId())) {
pier6fd24fd2018-11-27 11:23:50 -0800783 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
784 return;
785 }
Charles Chan8d316332018-06-19 20:31:57 -0700786 // NOTE: ACL flow doesn't include port information. No need to update it.
787 // Pair port is built-in and thus not going to change. No need to update it.
788
789 // remove old filter
Charles Chan445659f2019-01-02 13:46:16 -0800790 prevEndpoints.stream().filter(prevEndpoint -> !endpoints.contains(prevEndpoint)).forEach(prevEndpoint ->
791 revokeFilter(key, ImmutableSet.of(prevEndpoint)));
Charles Chan8d316332018-06-19 20:31:57 -0700792 // install new filter
Charles Chan445659f2019-01-02 13:46:16 -0800793 endpoints.stream().filter(endpoint -> !prevEndpoints.contains(endpoint)).forEach(endpoint ->
794 populateFilter(key, ImmutableSet.of(endpoint)));
Charles Chan8d316332018-06-19 20:31:57 -0700795
796 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
797 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
798
pier6fd24fd2018-11-27 11:23:50 -0800799 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
800 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700801 revokeFwd(key, nextId, fwdFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700802
803 fwdFuture.thenAcceptAsync(fwdStatus -> {
804 if (fwdStatus == null) {
805 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan445659f2019-01-02 13:46:16 -0800806 revokeNext(key, prevEndpoints, nextId, nextFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700807 }
808 });
809
810 nextFuture.thenAcceptAsync(nextStatus -> {
811 if (nextStatus == null) {
812 log.debug("Installing new group and flow for {}", key);
Charles Chan445659f2019-01-02 13:46:16 -0800813 int newNextId = populateNext(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700814 if (newNextId == -1) {
815 log.warn("Fail to updateXConnect {}: {}", key, ERROR_NEXT_ID);
816 return;
817 }
818 populateFwd(key, newNextId);
Charles Chan8d316332018-06-19 20:31:57 -0700819 }
820 });
821 } else {
822 log.warn("NextObj for {} does not exist in the store.", key);
823 }
824 }
825
826 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700827 * Creates a next objective builder for XConnect with given nextId.
Charles Chan8d316332018-06-19 20:31:57 -0700828 *
Charles Chan445659f2019-01-02 13:46:16 -0800829 * @param key XConnect key
830 * @param endpoints XConnect endpoints
Charles Chan48df9ad2018-10-30 18:08:59 -0700831 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700832 * @return next objective builder
833 */
Charles Chan445659f2019-01-02 13:46:16 -0800834 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId) {
Charles Chan8d316332018-06-19 20:31:57 -0700835 TrafficSelector metadata =
836 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
837 NextObjective.Builder nextObjBuilder = DefaultNextObjective
838 .builder().withId(nextId)
839 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
840 .withMeta(metadata);
Charles Chan48df9ad2018-10-30 18:08:59 -0700841
Charles Chan445659f2019-01-02 13:46:16 -0800842 for (XconnectEndpoint endpoint : endpoints) {
843 NextTreatment nextTreatment = getNextTreatment(key.deviceId(), endpoint, true);
Charles Chan48df9ad2018-10-30 18:08:59 -0700844 if (nextTreatment == null) {
Charles Chanc0a499b2019-01-16 15:30:39 -0800845 // If a PortLoadBalancer is used in the XConnect - putting on hold
Charles Chan445659f2019-01-02 13:46:16 -0800846 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -0800847 log.warn("Unable to create nextObj. PortLoadBalancer not ready");
848 String portLoadBalancerKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
849 portLoadBalancerCache.asMap().putIfAbsent(new PortLoadBalancerId(key.deviceId(),
850 Integer.parseInt(portLoadBalancerKey)), key);
pier567465b2018-11-24 11:16:28 -0800851 } else {
852 log.warn("Unable to create nextObj. Null NextTreatment");
853 }
Charles Chan48df9ad2018-10-30 18:08:59 -0700854 return null;
855 }
856 nextObjBuilder.addTreatment(nextTreatment);
857 }
858
Charles Chan8d316332018-06-19 20:31:57 -0700859 return nextObjBuilder;
860 }
861
862 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700863 * Creates a next objective builder for XConnect.
864 *
Charles Chan445659f2019-01-02 13:46:16 -0800865 * @param key XConnect key
866 * @param endpoints Xconnect endpoints
Charles Chan1fb65132018-09-21 11:29:12 -0700867 * @return next objective builder
868 */
Charles Chan445659f2019-01-02 13:46:16 -0800869 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan1fb65132018-09-21 11:29:12 -0700870 int nextId = flowObjectiveService.allocateNextId();
Charles Chan445659f2019-01-02 13:46:16 -0800871 return nextObjBuilder(key, endpoints, nextId);
Charles Chan1fb65132018-09-21 11:29:12 -0700872 }
873
874
875 /**
Charles Chan8d316332018-06-19 20:31:57 -0700876 * Creates a bridging forwarding objective builder for XConnect.
877 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530878 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700879 * @param nextId next ID of the broadcast group for this XConnect key
880 * @return forwarding objective builder
881 */
882 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
883 /*
884 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
885 * as the VLAN cross-connect broadcast rules
886 */
887 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
888 sbuilder.matchVlanId(key.vlanId());
889 sbuilder.matchEthDst(MacAddress.NONE);
890
891 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
892 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
893 .withSelector(sbuilder.build())
894 .nextStep(nextId)
895 .withPriority(XCONNECT_PRIORITY)
896 .fromApp(appId)
897 .makePermanent();
898 return fob;
899 }
900
901 /**
902 * Creates an ACL forwarding objective builder for XConnect.
903 *
904 * @param vlanId cross connect VLAN id
905 * @return forwarding objective builder
906 */
907 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
908 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
909 sbuilder.matchVlanId(vlanId);
910
911 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
912
913 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
914 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
915 .withSelector(sbuilder.build())
916 .withTreatment(tbuilder.build())
917 .withPriority(XCONNECT_ACL_PRIORITY)
918 .fromApp(appId)
919 .makePermanent();
920 return fob;
921 }
922
923 /**
924 * Creates a filtering objective builder for XConnect.
925 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530926 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700927 * @param port XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700928 * @param filtered true if this is a filtered port
Charles Chan8d316332018-06-19 20:31:57 -0700929 * @return next objective builder
930 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700931 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port, boolean filtered) {
Charles Chan8d316332018-06-19 20:31:57 -0700932 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
933 fob.withKey(Criteria.matchInPort(port))
Charles Chan8d316332018-06-19 20:31:57 -0700934 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
935 .withPriority(XCONNECT_PRIORITY);
Charles Chan48df9ad2018-10-30 18:08:59 -0700936 if (filtered) {
937 fob.addCondition(Criteria.matchVlanId(key.vlanId()));
938 } else {
939 fob.addCondition(Criteria.matchVlanId(VlanId.ANY));
940 }
Charles Chan8d316332018-06-19 20:31:57 -0700941 return fob.permit().fromApp(appId);
942 }
943
944 /**
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530945 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan8d316332018-06-19 20:31:57 -0700946 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530947 * @param deviceId Device ID
948 * @param port Port details
949 * @param vlanId VLAN ID
950 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan8d316332018-06-19 20:31:57 -0700951 */
pier6fd24fd2018-11-27 11:23:50 -0800952 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
953 XconnectKey key = new XconnectKey(deviceId, vlanId);
954 // Ensure leadership on device
pierventre37dcf4c2021-09-16 18:43:06 +0200955 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -0800956 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530957 return;
Charles Chan8d316332018-06-19 20:31:57 -0700958 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530959
960 // Locate L2 flooding group details for given xconnect vlan
pier6fd24fd2018-11-27 11:23:50 -0800961 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530962 if (nextId == -1) {
963 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
964 "Aborting pair group linking.", vlanId, deviceId);
965 return;
966 }
967
968 // Add pairing-port group to flooding group
969 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
970 // treatment.popVlan();
971 treatment.setOutput(port);
972 ObjectiveContext context = new DefaultObjectiveContext(
973 (objective) ->
974 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
975 vlanId, nextId, deviceId),
976 (objective, error) ->
977 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
978 "Error : {}", vlanId, nextId, deviceId, error)
979 );
980 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
981 .withId(nextId)
982 .withType(NextObjective.Type.BROADCAST)
983 .fromApp(srService.appId())
984 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
985 .addTreatment(treatment.build());
986 if (install) {
987 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
988 } else {
989 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
990 }
991 log.debug("Submitted next objective {} for vlan: {} in device {}",
992 nextId, vlanId, deviceId);
Charles Chan8d316332018-06-19 20:31:57 -0700993 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530994
995 /**
996 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
997 * output to given port's L2 mulitcast group.
998 *
999 * @param deviceId Device ID
1000 * @param pairPort Pair port number
1001 * @param vlanId VLAN ID
1002 * @param accessPorts List of access ports to be added into L2 multicast group
1003 */
pier6fd24fd2018-11-27 11:23:50 -08001004 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
1005 VlanId vlanId, List<PortNumber> accessPorts) {
1006 // Ensure enough rights to program pair device
1007 if (!srService.shouldProgram(deviceId)) {
1008 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
1009 return;
1010 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301011
1012 boolean multicastGroupExists = true;
1013 int vlanMulticastNextId;
pier6fd24fd2018-11-27 11:23:50 -08001014 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301015
1016 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
1017 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1018 .builder()
1019 .withType(NextObjective.Type.BROADCAST)
1020 .fromApp(srService.appId())
1021 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
1022 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier6fd24fd2018-11-27 11:23:50 -08001023 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301024 if (vlanMulticastNextId == -1) {
1025 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
1026 multicastGroupExists = false;
1027 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier6fd24fd2018-11-27 11:23:50 -08001028 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301029 vlanMulticastNextObjBuilder.addTreatment(
psneha94e1d302019-07-01 05:34:23 -04001030 DefaultTrafficTreatment.builder().setOutput(pairPort).build()
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301031 );
1032 }
1033 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier6fd24fd2018-11-27 11:23:50 -08001034 int nextId = vlanMulticastNextId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301035 accessPorts.forEach(p -> {
1036 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1037 // Do vlan popup action based on interface configuration
1038 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1039 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1040 egressAction.popVlan();
1041 }
1042 egressAction.setOutput(p);
1043 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001044 addMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301045 });
1046 ObjectiveContext context = new DefaultObjectiveContext(
1047 (objective) ->
1048 log.debug("L2 multicast group installed/updated. "
1049 + "NextObject Id {} on {} for subnet {} ",
1050 nextId, deviceId, vlanId),
1051 (objective, error) ->
1052 log.warn("L2 multicast group failed to install/update. "
1053 + " NextObject Id {} on {} for subnet {} : {}",
1054 nextId, deviceId, vlanId, error)
1055 );
1056 if (!multicastGroupExists) {
1057 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
1058
1059 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1060 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
1061 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
1062 multicastSelector.matchInPort(pairPort);
1063 multicastSelector.matchVlanId(vlanId);
1064 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1065 .withFlag(ForwardingObjective.Flag.VERSATILE)
1066 .nextStep(vlanMulticastNextId)
1067 .withSelector(multicastSelector.build())
1068 .withPriority(100)
1069 .fromApp(srService.appId())
1070 .makePermanent();
1071 context = new DefaultObjectiveContext(
1072 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
1073 deviceId,
1074 pairPort,
1075 vlanId),
1076 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
1077 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1078 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
1079 } else {
1080 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
1081 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
1082 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
1083 }
1084 }
1085
1086 /**
1087 * Removes access ports from VLAN L2 multicast group on given deviceId.
1088 *
1089 * @param deviceId Device ID
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301090 * @param vlanId VLAN ID
1091 * @param accessPorts List of access ports to be added into L2 multicast group
1092 */
pier6fd24fd2018-11-27 11:23:50 -08001093 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301094 // Ensure enough rights to program pair device
1095 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001096 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301097 return;
1098 }
1099
pier6fd24fd2018-11-27 11:23:50 -08001100 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1101
1102 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301103 if (vlanMulticastNextId == -1) {
1104 return;
1105 }
1106 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1107 .builder()
1108 .withType(NextObjective.Type.BROADCAST)
1109 .fromApp(srService.appId())
1110 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
1111 .withId(vlanMulticastNextId);
1112 accessPorts.forEach(p -> {
1113 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1114 // Do vlan popup action based on interface configuration
1115 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1116 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1117 egressAction.popVlan();
1118 }
1119 egressAction.setOutput(p);
1120 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001121 removeMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301122 });
1123 ObjectiveContext context = new DefaultObjectiveContext(
1124 (objective) ->
1125 log.debug("L2 multicast group installed/updated. "
1126 + "NextObject Id {} on {} for subnet {} ",
1127 vlanMulticastNextId, deviceId, vlanId),
1128 (objective, error) ->
1129 log.warn("L2 multicast group failed to install/update. "
1130 + " NextObject Id {} on {} for subnet {} : {}",
1131 vlanMulticastNextId, deviceId, vlanId, error)
1132 );
1133 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
1134 }
1135
1136 /**
1137 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1138 * Normally multicast group is not removed if it contains access ports; which can be forced
1139 * by "force" flag
1140 *
1141 * @param deviceId Device ID
1142 * @param pairPort Pair port number
1143 * @param vlanId VLAN ID
1144 * @param force Forceful removal
1145 */
1146 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1147
1148 // Ensure enough rights to program pair device
1149 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001150 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301151 return;
1152 }
1153
pier6fd24fd2018-11-27 11:23:50 -08001154 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1155
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301156 // Ensure L2 multicast group doesn't contain access ports
pier6fd24fd2018-11-27 11:23:50 -08001157 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301158 return;
1159 }
1160
1161 // Load L2 multicast group details
pier6fd24fd2018-11-27 11:23:50 -08001162 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301163 if (vlanMulticastNextId == -1) {
1164 return;
1165 }
1166
1167 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1168 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1169 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1170 l2MulticastSelector.matchInPort(pairPort);
1171 l2MulticastSelector.matchVlanId(vlanId);
1172 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1173 .withFlag(ForwardingObjective.Flag.VERSATILE)
1174 .nextStep(vlanMulticastNextId)
1175 .withSelector(l2MulticastSelector.build())
1176 .withPriority(100)
1177 .fromApp(srService.appId())
1178 .makePermanent();
1179 ObjectiveContext context = new DefaultObjectiveContext(
1180 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1181 pairPort, vlanId),
1182 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1183 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1184 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1185
1186 // Step 2 : Clear L2 multicast group associated with vlan
1187 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1188 .builder()
1189 .withId(vlanMulticastNextId)
1190 .withType(NextObjective.Type.BROADCAST)
1191 .fromApp(srService.appId())
1192 .withMeta(DefaultTrafficSelector.builder()
1193 .matchVlanId(vlanId)
1194 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1195 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1196 context = new DefaultObjectiveContext(
1197 (objective) ->
1198 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1199 vlanMulticastNextId, deviceId, vlanId),
1200 (objective, error) ->
1201 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1202 vlanMulticastNextId, deviceId, vlanId, error)
1203 );
1204 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1205
1206 // Finally clear store.
pier6fd24fd2018-11-27 11:23:50 -08001207 removeMulticastGroup(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301208 }
1209
pier6fd24fd2018-11-27 11:23:50 -08001210 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1211 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301212 }
1213
pier6fd24fd2018-11-27 11:23:50 -08001214 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301215 if (nextId == -1) {
1216 return;
1217 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301218 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301219 }
1220
pier6fd24fd2018-11-27 11:23:50 -08001221 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1222 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1223 if (ports == null) {
1224 ports = Lists.newArrayList();
1225 }
1226 ports.add(port);
1227 return ports;
1228 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301229 }
1230
pier6fd24fd2018-11-27 11:23:50 -08001231 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1232 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1233 if (ports != null && !ports.isEmpty()) {
1234 ports.remove(port);
1235 }
1236 return ports;
1237 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301238 }
1239
pier6fd24fd2018-11-27 11:23:50 -08001240 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1241 xconnectMulticastPortsStore.remove(groupKey);
1242 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301243 }
1244
pier6fd24fd2018-11-27 11:23:50 -08001245 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1246 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301247 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1248 }
1249
Charles Chan445659f2019-01-02 13:46:16 -08001250 private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, XconnectEndpoint endpoint) {
1251 if (endpoint.type() == XconnectEndpoint.Type.PORT) {
1252 PortNumber port = ((XconnectPortEndpoint) endpoint).port();
1253 return Sets.newHashSet(port);
Charles Chan48df9ad2018-10-30 18:08:59 -07001254 }
Charles Chan445659f2019-01-02 13:46:16 -08001255 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001256 PortLoadBalancerId portLoadBalancerId = new PortLoadBalancerId(deviceId,
1257 ((XconnectLoadBalancerEndpoint) endpoint).key());
1258 Set<PortNumber> ports = portLoadBalancerService.getPortLoadBalancer(portLoadBalancerId).ports();
Charles Chan445659f2019-01-02 13:46:16 -08001259 return Sets.newHashSet(ports);
Charles Chan48df9ad2018-10-30 18:08:59 -07001260 }
Charles Chan48df9ad2018-10-30 18:08:59 -07001261 return Sets.newHashSet();
1262 }
1263
Charles Chan445659f2019-01-02 13:46:16 -08001264 private NextTreatment getNextTreatment(DeviceId deviceId, XconnectEndpoint endpoint, boolean reserve) {
1265 if (endpoint.type() == XconnectEndpoint.Type.PORT) {
1266 PortNumber port = ((XconnectPortEndpoint) endpoint).port();
1267 return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(port).build());
Charles Chan48df9ad2018-10-30 18:08:59 -07001268 }
Charles Chan445659f2019-01-02 13:46:16 -08001269 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001270 PortLoadBalancerId portLoadBalancerId = new PortLoadBalancerId(deviceId,
1271 ((XconnectLoadBalancerEndpoint) endpoint).key());
1272 NextTreatment idNextTreatment = IdNextTreatment.of(portLoadBalancerService
1273 .getPortLoadBalancerNext(portLoadBalancerId));
pier567465b2018-11-24 11:16:28 -08001274 // Reserve only one time during next objective creation
1275 if (reserve) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001276 if (!portLoadBalancerService.reserve(portLoadBalancerId, appId)) {
1277 log.warn("Reservation failed for {}", portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001278 idNextTreatment = null;
1279 }
1280 }
1281 return idNextTreatment;
Charles Chan48df9ad2018-10-30 18:08:59 -07001282 }
Charles Chan48df9ad2018-10-30 18:08:59 -07001283 return null;
1284 }
pier567465b2018-11-24 11:16:28 -08001285
Charles Chanc0a499b2019-01-16 15:30:39 -08001286 private class InternalPortLoadBalancerListener implements PortLoadBalancerListener {
1287 // Populate xconnect once portloadbalancer is available
pier567465b2018-11-24 11:16:28 -08001288 @Override
Charles Chanc0a499b2019-01-16 15:30:39 -08001289 public void event(PortLoadBalancerEvent event) {
1290 portLoadBalancerExecutor.execute(() -> dequeue(event.subject().portLoadBalancerId()));
pier567465b2018-11-24 11:16:28 -08001291 }
Charles Chanc0a499b2019-01-16 15:30:39 -08001292 // When we receive INSTALLED port load balancing is ready
pier567465b2018-11-24 11:16:28 -08001293 @Override
Charles Chanc0a499b2019-01-16 15:30:39 -08001294 public boolean isRelevant(PortLoadBalancerEvent event) {
1295 return event.type() == PortLoadBalancerEvent.Type.INSTALLED;
pier567465b2018-11-24 11:16:28 -08001296 }
1297 }
1298
1299 // Invalidate the cache and re-start the xconnect installation
Charles Chanc0a499b2019-01-16 15:30:39 -08001300 private void dequeue(PortLoadBalancerId portLoadBalancerId) {
1301 XconnectKey xconnectKey = portLoadBalancerCache.getIfPresent(portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001302 if (xconnectKey == null) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001303 log.trace("{} not present in the cache", portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001304 return;
1305 }
Charles Chanc0a499b2019-01-16 15:30:39 -08001306 log.debug("Dequeue {}", portLoadBalancerId);
1307 portLoadBalancerCache.invalidate(portLoadBalancerId);
Charles Chan445659f2019-01-02 13:46:16 -08001308 Set<XconnectEndpoint> endpoints = Versioned.valueOrNull(xconnectStore.get(xconnectKey));
1309 if (endpoints == null || endpoints.isEmpty()) {
1310 log.warn("Endpoints not found for XConnect {}", xconnectKey);
pier567465b2018-11-24 11:16:28 -08001311 return;
1312 }
Charles Chan445659f2019-01-02 13:46:16 -08001313 populateXConnect(xconnectKey, endpoints);
Charles Chanc0a499b2019-01-16 15:30:39 -08001314 log.trace("PortLoadBalancer cache size {}", portLoadBalancerCache.size());
pier567465b2018-11-24 11:16:28 -08001315 }
1316
Charles Chan8d316332018-06-19 20:31:57 -07001317}