blob: fecba5a678ded139e7f380849f8abc8e8effdefe [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;
pier6fd24fd2018-11-27 11:23:50 -080030import org.onosproject.cluster.ClusterService;
31import org.onosproject.cluster.LeadershipService;
32import org.onosproject.cluster.NodeId;
Charles Chan8d316332018-06-19 20:31:57 -070033import org.onosproject.codec.CodecService;
34import org.onosproject.core.ApplicationId;
35import org.onosproject.core.CoreService;
Charles Chanc0a499b2019-01-16 15:30:39 -080036import org.onosproject.portloadbalancer.api.PortLoadBalancerEvent;
37import org.onosproject.portloadbalancer.api.PortLoadBalancerId;
38import org.onosproject.portloadbalancer.api.PortLoadBalancerListener;
39import org.onosproject.portloadbalancer.api.PortLoadBalancerService;
Charles Chan8d316332018-06-19 20:31:57 -070040import org.onosproject.mastership.MastershipService;
41import org.onosproject.net.ConnectPoint;
42import org.onosproject.net.DeviceId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053043import org.onosproject.net.Host;
44import org.onosproject.net.HostLocation;
Charles Chan8d316332018-06-19 20:31:57 -070045import org.onosproject.net.PortNumber;
William Davies8518e802019-07-23 21:18:53 +000046import org.onosproject.net.Port;
47import org.onosproject.net.config.NetworkConfigRegistry;
Charles Chan8d316332018-06-19 20:31:57 -070048import org.onosproject.net.config.NetworkConfigService;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceListener;
51import org.onosproject.net.device.DeviceService;
52import org.onosproject.net.flow.DefaultTrafficSelector;
53import org.onosproject.net.flow.DefaultTrafficTreatment;
54import org.onosproject.net.flow.TrafficSelector;
55import org.onosproject.net.flow.TrafficTreatment;
56import org.onosproject.net.flow.criteria.Criteria;
57import org.onosproject.net.flowobjective.DefaultFilteringObjective;
58import org.onosproject.net.flowobjective.DefaultForwardingObjective;
59import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070060import org.onosproject.net.flowobjective.DefaultNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070061import org.onosproject.net.flowobjective.DefaultObjectiveContext;
62import org.onosproject.net.flowobjective.FilteringObjective;
63import org.onosproject.net.flowobjective.FlowObjectiveService;
64import org.onosproject.net.flowobjective.ForwardingObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070065import org.onosproject.net.flowobjective.IdNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070066import org.onosproject.net.flowobjective.NextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070067import org.onosproject.net.flowobjective.NextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070068import org.onosproject.net.flowobjective.Objective;
69import org.onosproject.net.flowobjective.ObjectiveContext;
70import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053071import org.onosproject.net.host.HostEvent;
72import org.onosproject.net.host.HostListener;
73import org.onosproject.net.host.HostService;
74import org.onosproject.net.intf.InterfaceService;
Charles Chan8d316332018-06-19 20:31:57 -070075import org.onosproject.segmentrouting.SegmentRoutingService;
William Davies8518e802019-07-23 21:18:53 +000076import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053077import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan8d316332018-06-19 20:31:57 -070078import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
79import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
Charles Chan445659f2019-01-02 13:46:16 -080080import org.onosproject.segmentrouting.xconnect.api.XconnectEndpoint;
Charles Chan8d316332018-06-19 20:31:57 -070081import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
Charles Chan445659f2019-01-02 13:46:16 -080082import org.onosproject.segmentrouting.xconnect.api.XconnectLoadBalancerEndpoint;
83import org.onosproject.segmentrouting.xconnect.api.XconnectPortEndpoint;
Charles Chan8d316332018-06-19 20:31:57 -070084import org.onosproject.segmentrouting.xconnect.api.XconnectService;
85import org.onosproject.store.serializers.KryoNamespaces;
86import org.onosproject.store.service.ConsistentMap;
87import org.onosproject.store.service.MapEvent;
88import org.onosproject.store.service.MapEventListener;
89import org.onosproject.store.service.Serializer;
90import org.onosproject.store.service.StorageService;
91import org.onosproject.store.service.Versioned;
Ray Milkey2bd24a92018-08-17 14:54:17 -070092import org.osgi.service.component.annotations.Activate;
93import org.osgi.service.component.annotations.Component;
94import org.osgi.service.component.annotations.Deactivate;
95import org.osgi.service.component.annotations.Reference;
96import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chan8d316332018-06-19 20:31:57 -070097import org.slf4j.Logger;
98import org.slf4j.LoggerFactory;
99
100import java.io.Serializable;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530101import java.util.Collections;
102import java.util.List;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530103import java.util.Optional;
Charles Chan8d316332018-06-19 20:31:57 -0700104import java.util.Set;
William Davies8518e802019-07-23 21:18:53 +0000105import java.util.Iterator;
Charles Chan8d316332018-06-19 20:31:57 -0700106import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -0700107import java.util.concurrent.ExecutorService;
108import java.util.concurrent.Executors;
pier567465b2018-11-24 11:16:28 -0800109import java.util.concurrent.ScheduledExecutorService;
110import java.util.concurrent.TimeUnit;
Charles Chan8d316332018-06-19 20:31:57 -0700111import java.util.function.BiConsumer;
112import java.util.function.Consumer;
113import java.util.stream.Collectors;
114
pier567465b2018-11-24 11:16:28 -0800115import static java.util.concurrent.Executors.newScheduledThreadPool;
Charles Chan56542b62018-08-07 12:48:36 -0700116import static org.onlab.util.Tools.groupedThreads;
117
Ray Milkey2bd24a92018-08-17 14:54:17 -0700118@Component(immediate = true, service = XconnectService.class)
Charles Chan8d316332018-06-19 20:31:57 -0700119public class XconnectManager implements XconnectService {
Ray Milkey2bd24a92018-08-17 14:54:17 -0700120 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700121 private CoreService coreService;
122
Ray Milkey2bd24a92018-08-17 14:54:17 -0700123 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700124 private CodecService codecService;
125
Ray Milkey2bd24a92018-08-17 14:54:17 -0700126 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700127 private StorageService storageService;
128
Ray Milkey2bd24a92018-08-17 14:54:17 -0700129 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700130 public NetworkConfigService netCfgService;
131
Ray Milkey2bd24a92018-08-17 14:54:17 -0700132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700133 public DeviceService deviceService;
134
Ray Milkey2bd24a92018-08-17 14:54:17 -0700135 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700136 public FlowObjectiveService flowObjectiveService;
137
Ray Milkey2bd24a92018-08-17 14:54:17 -0700138 @Reference(cardinality = ReferenceCardinality.MANDATORY)
pier6fd24fd2018-11-27 11:23:50 -0800139 private LeadershipService leadershipService;
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY)
142 private ClusterService clusterService;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700145 public MastershipService mastershipService;
146
Ray Milkey2bd24a92018-08-17 14:54:17 -0700147 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chan8d316332018-06-19 20:31:57 -0700148 public SegmentRoutingService srService;
149
Ray Milkey3cad4db2018-10-04 15:13:33 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530151 public InterfaceService interfaceService;
152
Ray Milkey3cad4db2018-10-04 15:13:33 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530154 HostService hostService;
155
Charles Chan48df9ad2018-10-30 18:08:59 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanc0a499b2019-01-16 15:30:39 -0800157 private PortLoadBalancerService portLoadBalancerService;
Charles Chan48df9ad2018-10-30 18:08:59 -0700158
William Davies8518e802019-07-23 21:18:53 +0000159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
160 public NetworkConfigRegistry cfgService;
161
Charles Chan8d316332018-06-19 20:31:57 -0700162 private static final String APP_NAME = "org.onosproject.xconnect";
pier6fd24fd2018-11-27 11:23:50 -0800163 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chan48df9ad2018-10-30 18:08:59 -0700164 private static final String ERROR_NEXT_OBJ_BUILDER = "Unable to construct next objective builder";
165 private static final String ERROR_NEXT_ID = "Unable to get next id";
William Davies8518e802019-07-23 21:18:53 +0000166 private static final String ERROR_NOT_EDGE_ROUTER = "Device is not Edge Router";
167 private static final String ERROR_PORT_NOT_RANGE = "Ports for the device are not in the range";
Charles Chan8d316332018-06-19 20:31:57 -0700168
169 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
170
171 private ApplicationId appId;
Charles Chan445659f2019-01-02 13:46:16 -0800172 private ConsistentMap<XconnectKey, Set<XconnectEndpoint>> xconnectStore;
Charles Chan1fb65132018-09-21 11:29:12 -0700173 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan8d316332018-06-19 20:31:57 -0700174
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530175 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
176 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
177
Charles Chan445659f2019-01-02 13:46:16 -0800178 private final MapEventListener<XconnectKey, Set<XconnectEndpoint>> xconnectListener = new XconnectMapListener();
pier6fd24fd2018-11-27 11:23:50 -0800179 private ExecutorService xConnectExecutor;
Charles Chan8d316332018-06-19 20:31:57 -0700180
pier6fd24fd2018-11-27 11:23:50 -0800181 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan56542b62018-08-07 12:48:36 -0700182 private ExecutorService deviceEventExecutor;
183
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530184 private final HostListener hostListener = new InternalHostListener();
185 private ExecutorService hostEventExecutor;
186
pier567465b2018-11-24 11:16:28 -0800187 // Wait time for the cache
188 private static final int WAIT_TIME_MS = 15000;
Charles Chanc0a499b2019-01-16 15:30:39 -0800189 //The cache is implemented as buffer for waiting the installation of PortLoadBalancer when present
190 private Cache<PortLoadBalancerId, XconnectKey> portLoadBalancerCache;
pier567465b2018-11-24 11:16:28 -0800191 // Executor for the cache
Charles Chanc0a499b2019-01-16 15:30:39 -0800192 private ScheduledExecutorService portLoadBalancerExecutor;
193 // We need to listen for some events to properly installed the xconnect with portloadbalancer
194 private final PortLoadBalancerListener portLoadBalancerListener = new InternalPortLoadBalancerListener();
pier567465b2018-11-24 11:16:28 -0800195
Charles Chan8d316332018-06-19 20:31:57 -0700196 @Activate
197 void activate() {
198 appId = coreService.registerApplication(APP_NAME);
199 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
200
201 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
202 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700203 .register(XconnectManager.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530204 .register(XconnectKey.class)
Charles Chan445659f2019-01-02 13:46:16 -0800205 .register(XconnectEndpoint.class)
206 .register(XconnectPortEndpoint.class)
207 .register(XconnectLoadBalancerEndpoint.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530208 .register(VlanNextObjectiveStoreKey.class);
Charles Chan8d316332018-06-19 20:31:57 -0700209
Charles Chan445659f2019-01-02 13:46:16 -0800210 xconnectStore = storageService.<XconnectKey, Set<XconnectEndpoint>>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700211 .withName("onos-sr-xconnect")
212 .withRelaxedReadConsistency()
213 .withSerializer(Serializer.using(serializer.build()))
214 .build();
pier6fd24fd2018-11-27 11:23:50 -0800215 xConnectExecutor = Executors.newSingleThreadScheduledExecutor(
216 groupedThreads("sr-xconnect-event", "%d", log));
217 xconnectStore.addListener(xconnectListener, xConnectExecutor);
Charles Chan8d316332018-06-19 20:31:57 -0700218
Charles Chan1fb65132018-09-21 11:29:12 -0700219 xconnectNextObjStore = storageService.<XconnectKey, Integer>consistentMapBuilder()
Charles Chan8d316332018-06-19 20:31:57 -0700220 .withName("onos-sr-xconnect-next")
221 .withRelaxedReadConsistency()
222 .withSerializer(Serializer.using(serializer.build()))
223 .build();
224
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530225 xconnectMulticastNextStore = storageService.<VlanNextObjectiveStoreKey, Integer>consistentMapBuilder()
226 .withName("onos-sr-xconnect-l2multicast-next")
227 .withSerializer(Serializer.using(serializer.build()))
228 .build();
229 xconnectMulticastPortsStore = storageService.<VlanNextObjectiveStoreKey, List<PortNumber>>consistentMapBuilder()
230 .withName("onos-sr-xconnect-l2multicast-ports")
231 .withSerializer(Serializer.using(serializer.build()))
232 .build();
233
Charles Chan56542b62018-08-07 12:48:36 -0700234 deviceEventExecutor = Executors.newSingleThreadScheduledExecutor(
235 groupedThreads("sr-xconnect-device-event", "%d", log));
Charles Chan8d316332018-06-19 20:31:57 -0700236 deviceService.addListener(deviceListener);
237
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530238 hostEventExecutor = Executors.newSingleThreadExecutor(
239 groupedThreads("sr-xconnect-host-event", "%d", log));
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530240 hostService.addListener(hostListener);
241
Charles Chanc0a499b2019-01-16 15:30:39 -0800242 portLoadBalancerCache = CacheBuilder.newBuilder()
pier567465b2018-11-24 11:16:28 -0800243 .expireAfterWrite(WAIT_TIME_MS, TimeUnit.MILLISECONDS)
Charles Chanc0a499b2019-01-16 15:30:39 -0800244 .removalListener((RemovalNotification<PortLoadBalancerId, XconnectKey> notification) ->
245 log.debug("PortLoadBalancer cache removal event. portLoadBalancerId={}, xConnectKey={}",
pier567465b2018-11-24 11:16:28 -0800246 notification.getKey(), notification.getValue())).build();
Charles Chanc0a499b2019-01-16 15:30:39 -0800247 portLoadBalancerExecutor = newScheduledThreadPool(1,
248 groupedThreads("portLoadBalancerCacheWorker", "-%d", log));
pier567465b2018-11-24 11:16:28 -0800249 // Let's schedule the cleanup of the cache
Charles Chanc0a499b2019-01-16 15:30:39 -0800250 portLoadBalancerExecutor.scheduleAtFixedRate(portLoadBalancerCache::cleanUp, 0,
pier567465b2018-11-24 11:16:28 -0800251 WAIT_TIME_MS, TimeUnit.MILLISECONDS);
Charles Chanc0a499b2019-01-16 15:30:39 -0800252 portLoadBalancerService.addListener(portLoadBalancerListener);
pier567465b2018-11-24 11:16:28 -0800253
Charles Chan8d316332018-06-19 20:31:57 -0700254 log.info("Started");
255 }
256
257 @Deactivate
258 void deactivate() {
259 xconnectStore.removeListener(xconnectListener);
260 deviceService.removeListener(deviceListener);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530261 hostService.removeListener(hostListener);
pier98345be2019-04-01 23:38:42 -0700262 portLoadBalancerService.removeListener(portLoadBalancerListener);
Charles Chan8d316332018-06-19 20:31:57 -0700263 codecService.unregisterCodec(XconnectDesc.class);
264
Charles Chan56542b62018-08-07 12:48:36 -0700265 deviceEventExecutor.shutdown();
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530266 hostEventExecutor.shutdown();
pier6fd24fd2018-11-27 11:23:50 -0800267 xConnectExecutor.shutdown();
Charles Chanc0a499b2019-01-16 15:30:39 -0800268 portLoadBalancerExecutor.shutdown();
Charles Chan56542b62018-08-07 12:48:36 -0700269
Charles Chan8d316332018-06-19 20:31:57 -0700270 log.info("Stopped");
271 }
272
273 @Override
Charles Chan445659f2019-01-02 13:46:16 -0800274 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<XconnectEndpoint> endpoints) {
275 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, endpoints={}",
276 deviceId, vlanId, endpoints);
William Davies8518e802019-07-23 21:18:53 +0000277 SegmentRoutingDeviceConfig config = cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class);
278
279 List<PortNumber> devicePorts = deviceService.getPorts(deviceId).stream()
280 .map(Port::number)
281 .collect(Collectors.toList());
282 if (!config.isEdgeRouter()) {
283 throw new IllegalArgumentException(ERROR_NOT_EDGE_ROUTER);
284 } else {
285 Iterator<XconnectEndpoint> itr = endpoints.iterator();
286 while (itr.hasNext()) {
287 XconnectEndpoint ep = itr.next();
288 // Note: we don't validate an endpoint with LOAD_BALANCER type
289 if (ep.type() != XconnectEndpoint.Type.PORT) {
290 continue;
291 }
292 if (!devicePorts.contains(((XconnectPortEndpoint) ep).port())) {
293 throw new IllegalArgumentException(ERROR_PORT_NOT_RANGE);
294 }
295 }
296 }
Charles Chan8d316332018-06-19 20:31:57 -0700297 final XconnectKey key = new XconnectKey(deviceId, vlanId);
Charles Chan445659f2019-01-02 13:46:16 -0800298 xconnectStore.put(key, endpoints);
Charles Chan8d316332018-06-19 20:31:57 -0700299 }
300
301 @Override
302 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
303 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530304 deviceId, vlanId);
Charles Chan8d316332018-06-19 20:31:57 -0700305 final XconnectKey key = new XconnectKey(deviceId, vlanId);
306 xconnectStore.remove(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530307
308 // Cleanup multicasting support, if any.
Charles Chan445659f2019-01-02 13:46:16 -0800309 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId ->
310 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true)
311 );
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530312
Charles Chan8d316332018-06-19 20:31:57 -0700313 }
314
315 @Override
316 public Set<XconnectDesc> getXconnects() {
317 return xconnectStore.asJavaMap().entrySet().stream()
318 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
319 .collect(Collectors.toSet());
320 }
321
322 @Override
323 public boolean hasXconnect(ConnectPoint cp) {
Charles Chan445659f2019-01-02 13:46:16 -0800324 return getXconnects().stream().anyMatch(desc ->
325 desc.key().deviceId().equals(cp.deviceId()) && desc.endpoints().stream().anyMatch(ep ->
326 ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(cp.port())
327 )
Charles Chan8d316332018-06-19 20:31:57 -0700328 );
329 }
330
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700331 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530332 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
333 return getXconnects().stream()
Charles Chan445659f2019-01-02 13:46:16 -0800334 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.endpoints().stream().anyMatch(ep ->
335 ep.type() == XconnectEndpoint.Type.PORT && ((XconnectPortEndpoint) ep).port().equals(port)))
336 .map(XconnectDesc::key)
337 .map(XconnectKey::vlanId)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530338 .collect(Collectors.toList());
339 }
340
341 @Override
342 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier6fd24fd2018-11-27 11:23:50 -0800343 XconnectKey key = new XconnectKey(deviceId, vlanId);
344 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530345 }
346
347 @Override
Charles Chan1fb65132018-09-21 11:29:12 -0700348 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700349 if (xconnectNextObjStore != null) {
350 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
351 } else {
352 return ImmutableMap.of();
353 }
354 }
355
356 @Override
pier6fd24fd2018-11-27 11:23:50 -0800357 public int getNextId(DeviceId deviceId, VlanId vlanId) {
358 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530359 }
360
361 @Override
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700362 public void removeNextId(int nextId) {
363 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan1fb65132018-09-21 11:29:12 -0700364 if (e.getValue().value() == nextId) {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700365 xconnectNextObjStore.remove(e.getKey());
366 }
367 });
368 }
369
Charles Chan445659f2019-01-02 13:46:16 -0800370 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<XconnectEndpoint>> {
Charles Chan8d316332018-06-19 20:31:57 -0700371 @Override
Charles Chan445659f2019-01-02 13:46:16 -0800372 public void event(MapEvent<XconnectKey, Set<XconnectEndpoint>> event) {
Charles Chan8d316332018-06-19 20:31:57 -0700373 XconnectKey key = event.key();
Charles Chan445659f2019-01-02 13:46:16 -0800374 Set<XconnectEndpoint> ports = Versioned.valueOrNull(event.newValue());
375 Set<XconnectEndpoint> oldPorts = Versioned.valueOrNull(event.oldValue());
Charles Chan8d316332018-06-19 20:31:57 -0700376
377 switch (event.type()) {
378 case INSERT:
Charles Chan48df9ad2018-10-30 18:08:59 -0700379 populateXConnect(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700380 break;
381 case UPDATE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700382 updateXConnect(key, oldPorts, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700383 break;
384 case REMOVE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700385 revokeXConnect(key, oldPorts);
Charles Chan8d316332018-06-19 20:31:57 -0700386 break;
387 default:
388 break;
389 }
390 }
391 }
392
393 private class InternalDeviceListener implements DeviceListener {
pier6fd24fd2018-11-27 11:23:50 -0800394 // Offload the execution to an executor and then process the event
395 // if this instance is the leader of the device
Charles Chan8d316332018-06-19 20:31:57 -0700396 @Override
397 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700398 deviceEventExecutor.execute(() -> {
399 DeviceId deviceId = event.subject().id();
pier6fd24fd2018-11-27 11:23:50 -0800400 // Just skip if we are not the leader
401 if (!isLocalLeader(deviceId)) {
402 log.debug("Not the leader of {}. Skip event {}", deviceId, event);
Charles Chan56542b62018-08-07 12:48:36 -0700403 return;
404 }
pier6fd24fd2018-11-27 11:23:50 -0800405 // Populate or revoke according to the device availability
406 if (deviceService.isAvailable(deviceId)) {
407 init(deviceId);
408 } else {
409 cleanup(deviceId);
Charles Chan56542b62018-08-07 12:48:36 -0700410 }
411 });
Charles Chan8d316332018-06-19 20:31:57 -0700412 }
pier6fd24fd2018-11-27 11:23:50 -0800413 // We want to manage only a subset of events and if we are the leader
414 @Override
415 public boolean isRelevant(DeviceEvent event) {
416 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
417 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
418 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
419 }
Charles Chan8d316332018-06-19 20:31:57 -0700420 }
421
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530422 private class InternalHostListener implements HostListener {
423 @Override
424 public void event(HostEvent event) {
425 hostEventExecutor.execute(() -> {
426
427 switch (event.type()) {
Sudhir Kumar Mauryafcc42f82019-05-02 03:03:59 -0400428 case HOST_ADDED:
429 case HOST_REMOVED:
430 case HOST_UPDATED:
431 log.trace("Unhandled host event type: {} received. Ignoring.", event.type());
432 break;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530433 case HOST_MOVED:
434 log.trace("Processing host event {}", event);
435
436 Host host = event.subject();
437 Set<HostLocation> prevLocations = event.prevSubject().locations();
438 Set<HostLocation> newLocations = host.locations();
439
440 // Dual-home host port failure
441 // For each old location, in failed and paired devices update L2 vlan groups
442 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
443
444 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
445 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
446
447 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
448 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
449 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
450
451 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
452 prevLocation.port());
453 xconnectVlans.forEach(xconnectVlan -> {
454 // Add single-home host into L2 multicast group at paired device side.
455 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
456 newLocations.stream()
457 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
458 .forEach(location -> populateL2Multicast(location.deviceId(),
459 srService.getPairLocalPort(
460 location.deviceId()).get(),
461 xconnectVlan,
462 Collections.singletonList(
463 location.port())));
pier6fd24fd2018-11-27 11:23:50 -0800464 // Ensure pair-port attached to xconnect vlan flooding group
465 // at dual home failed device.
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530466 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
467 });
468 }
469 });
470
471 // Dual-home host port restoration
472 // For each new location, reverse xconnect loop prevention groups.
473 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
474 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
475 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
476 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
477 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
478
479 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
480 newLocation.port());
481 xconnectVlans.forEach(xconnectVlan -> {
482 // Remove recovered dual homed port from vlan L2 multicast group
483 prevLocations.stream()
484 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier6fd24fd2018-11-27 11:23:50 -0800485 .forEach(prevLocation -> revokeL2Multicast(
486 prevLocation.deviceId(),
487 xconnectVlan,
488 Collections.singletonList(newLocation.port()))
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530489 );
490
pier6fd24fd2018-11-27 11:23:50 -0800491 // Remove pair-port from vlan's flooding group at dual home
492 // restored device, if needed.
493 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
494 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530495 updateL2Flooding(newLocation.deviceId(),
496 pairLocalPort.get(),
497 xconnectVlan,
498 false);
499
500 // Clean L2 multicast group at pair-device; also update store.
501 cleanupL2MulticastRule(pairDeviceId.get(),
502 srService.getPairLocalPort(pairDeviceId.get()).get(),
503 xconnectVlan,
504 false);
505 }
506 });
507 }
508 });
509 break;
510
511 default:
512 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
513 break;
514 }
515 });
516 }
517 }
518
Charles Chan1fb65132018-09-21 11:29:12 -0700519 private void init(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700520 getXconnects().stream()
521 .filter(desc -> desc.key().deviceId().equals(deviceId))
Charles Chan445659f2019-01-02 13:46:16 -0800522 .forEach(desc -> populateXConnect(desc.key(), desc.endpoints()));
Charles Chan8d316332018-06-19 20:31:57 -0700523 }
524
Charles Chan1fb65132018-09-21 11:29:12 -0700525 private void cleanup(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700526 xconnectNextObjStore.entrySet().stream()
527 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
528 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
529 log.debug("{} is removed from xConnectNextObjStore", deviceId);
530 }
531
532 /**
533 * Populates XConnect groups and flows for given key.
534 *
Charles Chan445659f2019-01-02 13:46:16 -0800535 * @param key XConnect key
536 * @param endpoints a set of endpoints to be cross-connected
Charles Chan8d316332018-06-19 20:31:57 -0700537 */
Charles Chan445659f2019-01-02 13:46:16 -0800538 private void populateXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pier6fd24fd2018-11-27 11:23:50 -0800539 if (!isLocalLeader(key.deviceId())) {
540 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700541 return;
542 }
543
Charles Chan445659f2019-01-02 13:46:16 -0800544 int nextId = populateNext(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700545 if (nextId == -1) {
546 log.warn("Fail to populateXConnect {}: {}", key, ERROR_NEXT_ID);
547 return;
548 }
Charles Chan445659f2019-01-02 13:46:16 -0800549 populateFilter(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700550 populateFwd(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700551 populateAcl(key);
552 }
553
554 /**
555 * Populates filtering objectives for given XConnect.
556 *
Charles Chan445659f2019-01-02 13:46:16 -0800557 * @param key XConnect store key
558 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700559 */
Charles Chan445659f2019-01-02 13:46:16 -0800560 private void populateFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan48df9ad2018-10-30 18:08:59 -0700561 // FIXME Improve the logic
Charles Chanc0a499b2019-01-16 15:30:39 -0800562 // If port load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
Charles Chan48df9ad2018-10-30 18:08:59 -0700563 // The purpose is to make sure existing XConnect logic can still work on a configured port.
Charles Chan445659f2019-01-02 13:46:16 -0800564 boolean filtered = endpoints.stream()
565 .map(ep -> getNextTreatment(key.deviceId(), ep, false))
Charles Chan48df9ad2018-10-30 18:08:59 -0700566 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
567
Charles Chan445659f2019-01-02 13:46:16 -0800568 endpoints.stream()
569 .map(ep -> getPhysicalPorts(key.deviceId(), ep))
Charles Chan48df9ad2018-10-30 18:08:59 -0700570 .flatMap(Set::stream).forEach(port -> {
Charles Chan445659f2019-01-02 13:46:16 -0800571 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
572 ObjectiveContext context = new DefaultObjectiveContext(
573 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
574 key, port),
575 (objective, error) ->
576 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
577 key, port, error));
578 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
579 });
Charles Chan8d316332018-06-19 20:31:57 -0700580 }
581
582 /**
583 * Populates next objectives for given XConnect.
584 *
Charles Chan445659f2019-01-02 13:46:16 -0800585 * @param key XConnect store key
586 * @param endpoints XConnect endpoints
Charles Chan48df9ad2018-10-30 18:08:59 -0700587 * @return next id
Charles Chan8d316332018-06-19 20:31:57 -0700588 */
Charles Chan445659f2019-01-02 13:46:16 -0800589 private int populateNext(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pier6fd24fd2018-11-27 11:23:50 -0800590 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
591 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700592 log.debug("NextObj for {} found, id={}", key, nextId);
593 return nextId;
Charles Chan8d316332018-06-19 20:31:57 -0700594 } else {
Charles Chan445659f2019-01-02 13:46:16 -0800595 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700596 if (nextObjBuilder == null) {
597 log.warn("Fail to populate {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
598 return -1;
599 }
Charles Chan8d316332018-06-19 20:31:57 -0700600 ObjectiveContext nextContext = new DefaultObjectiveContext(
601 // To serialize this with kryo
602 (Serializable & Consumer<Objective>) (objective) ->
603 log.debug("XConnect NextObj for {} added", key),
Charles Chanfacfbef2018-08-23 14:30:33 -0700604 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
605 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
606 srService.invalidateNextObj(objective.id());
607 });
Charles Chan1fb65132018-09-21 11:29:12 -0700608 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan8d316332018-06-19 20:31:57 -0700609 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan1fb65132018-09-21 11:29:12 -0700610 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan8d316332018-06-19 20:31:57 -0700611 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan1fb65132018-09-21 11:29:12 -0700612 return nextObj.id();
Charles Chan8d316332018-06-19 20:31:57 -0700613 }
Charles Chan8d316332018-06-19 20:31:57 -0700614 }
615
616 /**
617 * Populates bridging forwarding objectives for given XConnect.
618 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530619 * @param key XConnect store key
Charles Chan1fb65132018-09-21 11:29:12 -0700620 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700621 */
Charles Chan1fb65132018-09-21 11:29:12 -0700622 private void populateFwd(XconnectKey key, int nextId) {
623 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700624 ObjectiveContext fwdContext = new DefaultObjectiveContext(
625 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
626 (objective, error) ->
627 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
628 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
629 }
630
631 /**
632 * Populates ACL forwarding objectives for given XConnect.
633 *
634 * @param key XConnect store key
635 */
636 private void populateAcl(XconnectKey key) {
637 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
638 ObjectiveContext aclContext = new DefaultObjectiveContext(
639 (objective) -> log.debug("XConnect AclObj for {} populated", key),
640 (objective, error) ->
641 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
642 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
643 }
644
645 /**
646 * Revokes XConnect groups and flows for given key.
647 *
Charles Chan445659f2019-01-02 13:46:16 -0800648 * @param key XConnect key
649 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700650 */
Charles Chan445659f2019-01-02 13:46:16 -0800651 private void revokeXConnect(XconnectKey key, Set<XconnectEndpoint> endpoints) {
pier6fd24fd2018-11-27 11:23:50 -0800652 if (!isLocalLeader(key.deviceId())) {
653 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700654 return;
655 }
656
Charles Chan445659f2019-01-02 13:46:16 -0800657 revokeFilter(key, endpoints);
pier6fd24fd2018-11-27 11:23:50 -0800658 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
659 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700660 revokeFwd(key, nextId, null);
Charles Chan445659f2019-01-02 13:46:16 -0800661 revokeNext(key, endpoints, nextId, null);
Charles Chan8d316332018-06-19 20:31:57 -0700662 } else {
663 log.warn("NextObj for {} does not exist in the store.", key);
664 }
Charles Chan445659f2019-01-02 13:46:16 -0800665 revokeFilter(key, endpoints);
Charles Chan8d316332018-06-19 20:31:57 -0700666 revokeAcl(key);
667 }
668
669 /**
670 * Revokes filtering objectives for given XConnect.
671 *
Charles Chan445659f2019-01-02 13:46:16 -0800672 * @param key XConnect store key
673 * @param endpoints XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700674 */
Charles Chan445659f2019-01-02 13:46:16 -0800675 private void revokeFilter(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan48df9ad2018-10-30 18:08:59 -0700676 // FIXME Improve the logic
Charles Chanc0a499b2019-01-16 15:30:39 -0800677 // If port load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
Charles Chan48df9ad2018-10-30 18:08:59 -0700678 // The purpose is to make sure existing XConnect logic can still work on a configured port.
Charles Chan445659f2019-01-02 13:46:16 -0800679 boolean filtered = endpoints.stream()
680 .map(ep -> getNextTreatment(key.deviceId(), ep, false))
681 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
682
683 endpoints.stream()
684 .map(ep -> getPhysicalPorts(key.deviceId(), ep)).
685 flatMap(Set::stream).forEach(port -> {
686 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
687 ObjectiveContext context = new DefaultObjectiveContext(
688 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
689 key, port),
690 (objective, error) ->
691 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
692 key, port, error));
693 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
694 });
Charles Chan8d316332018-06-19 20:31:57 -0700695 }
696
697 /**
698 * Revokes next objectives for given XConnect.
699 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530700 * @param key XConnect store key
Charles Chan445659f2019-01-02 13:46:16 -0800701 * @param endpoints XConnect endpoints
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530702 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700703 * @param nextFuture completable future for this next objective operation
704 */
Charles Chan445659f2019-01-02 13:46:16 -0800705 private void revokeNext(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId,
Charles Chan8d316332018-06-19 20:31:57 -0700706 CompletableFuture<ObjectiveError> nextFuture) {
707 ObjectiveContext context = new ObjectiveContext() {
708 @Override
709 public void onSuccess(Objective objective) {
710 log.debug("Previous NextObj for {} removed", key);
711 if (nextFuture != null) {
712 nextFuture.complete(null);
713 }
714 }
715
716 @Override
717 public void onError(Objective objective, ObjectiveError error) {
718 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
719 if (nextFuture != null) {
720 nextFuture.complete(error);
721 }
Charles Chanfacfbef2018-08-23 14:30:33 -0700722 srService.invalidateNextObj(objective.id());
Charles Chan8d316332018-06-19 20:31:57 -0700723 }
724 };
Charles Chan48df9ad2018-10-30 18:08:59 -0700725
Charles Chan445659f2019-01-02 13:46:16 -0800726 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, endpoints, nextId);
Charles Chan48df9ad2018-10-30 18:08:59 -0700727 if (nextObjBuilder == null) {
728 log.warn("Fail to revokeNext {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
729 return;
730 }
Charles Chanc0a499b2019-01-16 15:30:39 -0800731 // Release the port load balancer if present
Charles Chan445659f2019-01-02 13:46:16 -0800732 endpoints.stream()
733 .filter(endpoint -> endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER)
734 .forEach(endpoint -> {
Charles Chanc0a499b2019-01-16 15:30:39 -0800735 String portLoadBalancerKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
736 portLoadBalancerService.release(new PortLoadBalancerId(key.deviceId(),
737 Integer.parseInt(portLoadBalancerKey)), appId);
Charles Chan445659f2019-01-02 13:46:16 -0800738 });
Charles Chan48df9ad2018-10-30 18:08:59 -0700739 flowObjectiveService.next(key.deviceId(), nextObjBuilder.remove(context));
Charles Chan8d316332018-06-19 20:31:57 -0700740 xconnectNextObjStore.remove(key);
741 }
742
743 /**
744 * Revokes bridging forwarding objectives for given XConnect.
745 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530746 * @param key XConnect store key
747 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700748 * @param fwdFuture completable future for this forwarding objective operation
749 */
Charles Chan1fb65132018-09-21 11:29:12 -0700750 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
751 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700752 ObjectiveContext context = new ObjectiveContext() {
753 @Override
754 public void onSuccess(Objective objective) {
755 log.debug("Previous FwdObj for {} removed", key);
756 if (fwdFuture != null) {
757 fwdFuture.complete(null);
758 }
759 }
760
761 @Override
762 public void onError(Objective objective, ObjectiveError error) {
763 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
764 if (fwdFuture != null) {
765 fwdFuture.complete(error);
766 }
767 }
768 };
769 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
770 }
771
772 /**
773 * Revokes ACL forwarding objectives for given XConnect.
774 *
775 * @param key XConnect store key
776 */
777 private void revokeAcl(XconnectKey key) {
778 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
779 ObjectiveContext aclContext = new DefaultObjectiveContext(
780 (objective) -> log.debug("XConnect AclObj for {} populated", key),
781 (objective, error) ->
782 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
783 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
784 }
785
786 /**
787 * Updates XConnect groups and flows for given key.
788 *
Charles Chan445659f2019-01-02 13:46:16 -0800789 * @param key XConnect key
790 * @param prevEndpoints previous XConnect endpoints
791 * @param endpoints new XConnect endpoints
Charles Chan8d316332018-06-19 20:31:57 -0700792 */
Charles Chan445659f2019-01-02 13:46:16 -0800793 private void updateXConnect(XconnectKey key, Set<XconnectEndpoint> prevEndpoints,
794 Set<XconnectEndpoint> endpoints) {
pier6fd24fd2018-11-27 11:23:50 -0800795 if (!isLocalLeader(key.deviceId())) {
796 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
797 return;
798 }
Charles Chan8d316332018-06-19 20:31:57 -0700799 // NOTE: ACL flow doesn't include port information. No need to update it.
800 // Pair port is built-in and thus not going to change. No need to update it.
801
802 // remove old filter
Charles Chan445659f2019-01-02 13:46:16 -0800803 prevEndpoints.stream().filter(prevEndpoint -> !endpoints.contains(prevEndpoint)).forEach(prevEndpoint ->
804 revokeFilter(key, ImmutableSet.of(prevEndpoint)));
Charles Chan8d316332018-06-19 20:31:57 -0700805 // install new filter
Charles Chan445659f2019-01-02 13:46:16 -0800806 endpoints.stream().filter(endpoint -> !prevEndpoints.contains(endpoint)).forEach(endpoint ->
807 populateFilter(key, ImmutableSet.of(endpoint)));
Charles Chan8d316332018-06-19 20:31:57 -0700808
809 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
810 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
811
pier6fd24fd2018-11-27 11:23:50 -0800812 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
813 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700814 revokeFwd(key, nextId, fwdFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700815
816 fwdFuture.thenAcceptAsync(fwdStatus -> {
817 if (fwdStatus == null) {
818 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan445659f2019-01-02 13:46:16 -0800819 revokeNext(key, prevEndpoints, nextId, nextFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700820 }
821 });
822
823 nextFuture.thenAcceptAsync(nextStatus -> {
824 if (nextStatus == null) {
825 log.debug("Installing new group and flow for {}", key);
Charles Chan445659f2019-01-02 13:46:16 -0800826 int newNextId = populateNext(key, endpoints);
Charles Chan48df9ad2018-10-30 18:08:59 -0700827 if (newNextId == -1) {
828 log.warn("Fail to updateXConnect {}: {}", key, ERROR_NEXT_ID);
829 return;
830 }
831 populateFwd(key, newNextId);
Charles Chan8d316332018-06-19 20:31:57 -0700832 }
833 });
834 } else {
835 log.warn("NextObj for {} does not exist in the store.", key);
836 }
837 }
838
839 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700840 * Creates a next objective builder for XConnect with given nextId.
Charles Chan8d316332018-06-19 20:31:57 -0700841 *
Charles Chan445659f2019-01-02 13:46:16 -0800842 * @param key XConnect key
843 * @param endpoints XConnect endpoints
Charles Chan48df9ad2018-10-30 18:08:59 -0700844 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700845 * @return next objective builder
846 */
Charles Chan445659f2019-01-02 13:46:16 -0800847 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints, int nextId) {
Charles Chan8d316332018-06-19 20:31:57 -0700848 TrafficSelector metadata =
849 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
850 NextObjective.Builder nextObjBuilder = DefaultNextObjective
851 .builder().withId(nextId)
852 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
853 .withMeta(metadata);
Charles Chan48df9ad2018-10-30 18:08:59 -0700854
Charles Chan445659f2019-01-02 13:46:16 -0800855 for (XconnectEndpoint endpoint : endpoints) {
856 NextTreatment nextTreatment = getNextTreatment(key.deviceId(), endpoint, true);
Charles Chan48df9ad2018-10-30 18:08:59 -0700857 if (nextTreatment == null) {
Charles Chanc0a499b2019-01-16 15:30:39 -0800858 // If a PortLoadBalancer is used in the XConnect - putting on hold
Charles Chan445659f2019-01-02 13:46:16 -0800859 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -0800860 log.warn("Unable to create nextObj. PortLoadBalancer not ready");
861 String portLoadBalancerKey = String.valueOf(((XconnectLoadBalancerEndpoint) endpoint).key());
862 portLoadBalancerCache.asMap().putIfAbsent(new PortLoadBalancerId(key.deviceId(),
863 Integer.parseInt(portLoadBalancerKey)), key);
pier567465b2018-11-24 11:16:28 -0800864 } else {
865 log.warn("Unable to create nextObj. Null NextTreatment");
866 }
Charles Chan48df9ad2018-10-30 18:08:59 -0700867 return null;
868 }
869 nextObjBuilder.addTreatment(nextTreatment);
870 }
871
Charles Chan8d316332018-06-19 20:31:57 -0700872 return nextObjBuilder;
873 }
874
875 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700876 * Creates a next objective builder for XConnect.
877 *
Charles Chan445659f2019-01-02 13:46:16 -0800878 * @param key XConnect key
879 * @param endpoints Xconnect endpoints
Charles Chan1fb65132018-09-21 11:29:12 -0700880 * @return next objective builder
881 */
Charles Chan445659f2019-01-02 13:46:16 -0800882 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<XconnectEndpoint> endpoints) {
Charles Chan1fb65132018-09-21 11:29:12 -0700883 int nextId = flowObjectiveService.allocateNextId();
Charles Chan445659f2019-01-02 13:46:16 -0800884 return nextObjBuilder(key, endpoints, nextId);
Charles Chan1fb65132018-09-21 11:29:12 -0700885 }
886
887
888 /**
Charles Chan8d316332018-06-19 20:31:57 -0700889 * Creates a bridging forwarding objective builder for XConnect.
890 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530891 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700892 * @param nextId next ID of the broadcast group for this XConnect key
893 * @return forwarding objective builder
894 */
895 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
896 /*
897 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
898 * as the VLAN cross-connect broadcast rules
899 */
900 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
901 sbuilder.matchVlanId(key.vlanId());
902 sbuilder.matchEthDst(MacAddress.NONE);
903
904 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
905 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
906 .withSelector(sbuilder.build())
907 .nextStep(nextId)
908 .withPriority(XCONNECT_PRIORITY)
909 .fromApp(appId)
910 .makePermanent();
911 return fob;
912 }
913
914 /**
915 * Creates an ACL forwarding objective builder for XConnect.
916 *
917 * @param vlanId cross connect VLAN id
918 * @return forwarding objective builder
919 */
920 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
921 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
922 sbuilder.matchVlanId(vlanId);
923
924 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
925
926 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
927 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
928 .withSelector(sbuilder.build())
929 .withTreatment(tbuilder.build())
930 .withPriority(XCONNECT_ACL_PRIORITY)
931 .fromApp(appId)
932 .makePermanent();
933 return fob;
934 }
935
936 /**
937 * Creates a filtering objective builder for XConnect.
938 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530939 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700940 * @param port XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700941 * @param filtered true if this is a filtered port
Charles Chan8d316332018-06-19 20:31:57 -0700942 * @return next objective builder
943 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700944 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port, boolean filtered) {
Charles Chan8d316332018-06-19 20:31:57 -0700945 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
946 fob.withKey(Criteria.matchInPort(port))
Charles Chan8d316332018-06-19 20:31:57 -0700947 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
948 .withPriority(XCONNECT_PRIORITY);
Charles Chan48df9ad2018-10-30 18:08:59 -0700949 if (filtered) {
950 fob.addCondition(Criteria.matchVlanId(key.vlanId()));
951 } else {
952 fob.addCondition(Criteria.matchVlanId(VlanId.ANY));
953 }
Charles Chan8d316332018-06-19 20:31:57 -0700954 return fob.permit().fromApp(appId);
955 }
956
957 /**
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530958 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan8d316332018-06-19 20:31:57 -0700959 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530960 * @param deviceId Device ID
961 * @param port Port details
962 * @param vlanId VLAN ID
963 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan8d316332018-06-19 20:31:57 -0700964 */
pier6fd24fd2018-11-27 11:23:50 -0800965 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
966 XconnectKey key = new XconnectKey(deviceId, vlanId);
967 // Ensure leadership on device
968 if (!isLocalLeader(deviceId)) {
969 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530970 return;
Charles Chan8d316332018-06-19 20:31:57 -0700971 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530972
973 // Locate L2 flooding group details for given xconnect vlan
pier6fd24fd2018-11-27 11:23:50 -0800974 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530975 if (nextId == -1) {
976 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
977 "Aborting pair group linking.", vlanId, deviceId);
978 return;
979 }
980
981 // Add pairing-port group to flooding group
982 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
983 // treatment.popVlan();
984 treatment.setOutput(port);
985 ObjectiveContext context = new DefaultObjectiveContext(
986 (objective) ->
987 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
988 vlanId, nextId, deviceId),
989 (objective, error) ->
990 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
991 "Error : {}", vlanId, nextId, deviceId, error)
992 );
993 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
994 .withId(nextId)
995 .withType(NextObjective.Type.BROADCAST)
996 .fromApp(srService.appId())
997 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
998 .addTreatment(treatment.build());
999 if (install) {
1000 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
1001 } else {
1002 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
1003 }
1004 log.debug("Submitted next objective {} for vlan: {} in device {}",
1005 nextId, vlanId, deviceId);
Charles Chan8d316332018-06-19 20:31:57 -07001006 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301007
1008 /**
1009 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
1010 * output to given port's L2 mulitcast group.
1011 *
1012 * @param deviceId Device ID
1013 * @param pairPort Pair port number
1014 * @param vlanId VLAN ID
1015 * @param accessPorts List of access ports to be added into L2 multicast group
1016 */
pier6fd24fd2018-11-27 11:23:50 -08001017 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
1018 VlanId vlanId, List<PortNumber> accessPorts) {
1019 // Ensure enough rights to program pair device
1020 if (!srService.shouldProgram(deviceId)) {
1021 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
1022 return;
1023 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301024
1025 boolean multicastGroupExists = true;
1026 int vlanMulticastNextId;
pier6fd24fd2018-11-27 11:23:50 -08001027 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301028
1029 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
1030 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1031 .builder()
1032 .withType(NextObjective.Type.BROADCAST)
1033 .fromApp(srService.appId())
1034 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
1035 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier6fd24fd2018-11-27 11:23:50 -08001036 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301037 if (vlanMulticastNextId == -1) {
1038 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
1039 multicastGroupExists = false;
1040 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier6fd24fd2018-11-27 11:23:50 -08001041 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301042 vlanMulticastNextObjBuilder.addTreatment(
psneha94e1d302019-07-01 05:34:23 -04001043 DefaultTrafficTreatment.builder().setOutput(pairPort).build()
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301044 );
1045 }
1046 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier6fd24fd2018-11-27 11:23:50 -08001047 int nextId = vlanMulticastNextId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301048 accessPorts.forEach(p -> {
1049 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1050 // Do vlan popup action based on interface configuration
1051 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1052 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1053 egressAction.popVlan();
1054 }
1055 egressAction.setOutput(p);
1056 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001057 addMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301058 });
1059 ObjectiveContext context = new DefaultObjectiveContext(
1060 (objective) ->
1061 log.debug("L2 multicast group installed/updated. "
1062 + "NextObject Id {} on {} for subnet {} ",
1063 nextId, deviceId, vlanId),
1064 (objective, error) ->
1065 log.warn("L2 multicast group failed to install/update. "
1066 + " NextObject Id {} on {} for subnet {} : {}",
1067 nextId, deviceId, vlanId, error)
1068 );
1069 if (!multicastGroupExists) {
1070 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
1071
1072 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1073 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
1074 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
1075 multicastSelector.matchInPort(pairPort);
1076 multicastSelector.matchVlanId(vlanId);
1077 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1078 .withFlag(ForwardingObjective.Flag.VERSATILE)
1079 .nextStep(vlanMulticastNextId)
1080 .withSelector(multicastSelector.build())
1081 .withPriority(100)
1082 .fromApp(srService.appId())
1083 .makePermanent();
1084 context = new DefaultObjectiveContext(
1085 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
1086 deviceId,
1087 pairPort,
1088 vlanId),
1089 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
1090 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1091 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
1092 } else {
1093 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
1094 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
1095 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
1096 }
1097 }
1098
1099 /**
1100 * Removes access ports from VLAN L2 multicast group on given deviceId.
1101 *
1102 * @param deviceId Device ID
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301103 * @param vlanId VLAN ID
1104 * @param accessPorts List of access ports to be added into L2 multicast group
1105 */
pier6fd24fd2018-11-27 11:23:50 -08001106 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301107 // Ensure enough rights to program pair device
1108 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001109 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301110 return;
1111 }
1112
pier6fd24fd2018-11-27 11:23:50 -08001113 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1114
1115 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301116 if (vlanMulticastNextId == -1) {
1117 return;
1118 }
1119 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1120 .builder()
1121 .withType(NextObjective.Type.BROADCAST)
1122 .fromApp(srService.appId())
1123 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
1124 .withId(vlanMulticastNextId);
1125 accessPorts.forEach(p -> {
1126 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1127 // Do vlan popup action based on interface configuration
1128 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1129 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1130 egressAction.popVlan();
1131 }
1132 egressAction.setOutput(p);
1133 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001134 removeMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301135 });
1136 ObjectiveContext context = new DefaultObjectiveContext(
1137 (objective) ->
1138 log.debug("L2 multicast group installed/updated. "
1139 + "NextObject Id {} on {} for subnet {} ",
1140 vlanMulticastNextId, deviceId, vlanId),
1141 (objective, error) ->
1142 log.warn("L2 multicast group failed to install/update. "
1143 + " NextObject Id {} on {} for subnet {} : {}",
1144 vlanMulticastNextId, deviceId, vlanId, error)
1145 );
1146 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
1147 }
1148
1149 /**
1150 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1151 * Normally multicast group is not removed if it contains access ports; which can be forced
1152 * by "force" flag
1153 *
1154 * @param deviceId Device ID
1155 * @param pairPort Pair port number
1156 * @param vlanId VLAN ID
1157 * @param force Forceful removal
1158 */
1159 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1160
1161 // Ensure enough rights to program pair device
1162 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001163 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301164 return;
1165 }
1166
pier6fd24fd2018-11-27 11:23:50 -08001167 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1168
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301169 // Ensure L2 multicast group doesn't contain access ports
pier6fd24fd2018-11-27 11:23:50 -08001170 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301171 return;
1172 }
1173
1174 // Load L2 multicast group details
pier6fd24fd2018-11-27 11:23:50 -08001175 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301176 if (vlanMulticastNextId == -1) {
1177 return;
1178 }
1179
1180 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1181 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1182 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1183 l2MulticastSelector.matchInPort(pairPort);
1184 l2MulticastSelector.matchVlanId(vlanId);
1185 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1186 .withFlag(ForwardingObjective.Flag.VERSATILE)
1187 .nextStep(vlanMulticastNextId)
1188 .withSelector(l2MulticastSelector.build())
1189 .withPriority(100)
1190 .fromApp(srService.appId())
1191 .makePermanent();
1192 ObjectiveContext context = new DefaultObjectiveContext(
1193 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1194 pairPort, vlanId),
1195 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1196 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1197 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1198
1199 // Step 2 : Clear L2 multicast group associated with vlan
1200 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1201 .builder()
1202 .withId(vlanMulticastNextId)
1203 .withType(NextObjective.Type.BROADCAST)
1204 .fromApp(srService.appId())
1205 .withMeta(DefaultTrafficSelector.builder()
1206 .matchVlanId(vlanId)
1207 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1208 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1209 context = new DefaultObjectiveContext(
1210 (objective) ->
1211 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1212 vlanMulticastNextId, deviceId, vlanId),
1213 (objective, error) ->
1214 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1215 vlanMulticastNextId, deviceId, vlanId, error)
1216 );
1217 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1218
1219 // Finally clear store.
pier6fd24fd2018-11-27 11:23:50 -08001220 removeMulticastGroup(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301221 }
1222
pier6fd24fd2018-11-27 11:23:50 -08001223 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1224 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301225 }
1226
pier6fd24fd2018-11-27 11:23:50 -08001227 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301228 if (nextId == -1) {
1229 return;
1230 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301231 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301232 }
1233
pier6fd24fd2018-11-27 11:23:50 -08001234 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1235 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1236 if (ports == null) {
1237 ports = Lists.newArrayList();
1238 }
1239 ports.add(port);
1240 return ports;
1241 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301242 }
1243
pier6fd24fd2018-11-27 11:23:50 -08001244 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1245 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1246 if (ports != null && !ports.isEmpty()) {
1247 ports.remove(port);
1248 }
1249 return ports;
1250 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301251 }
1252
pier6fd24fd2018-11-27 11:23:50 -08001253 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1254 xconnectMulticastPortsStore.remove(groupKey);
1255 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301256 }
1257
pier6fd24fd2018-11-27 11:23:50 -08001258 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1259 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301260 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1261 }
1262
pier6fd24fd2018-11-27 11:23:50 -08001263 // Custom-built function, when the device is not available we need a fallback mechanism
1264 private boolean isLocalLeader(DeviceId deviceId) {
1265 if (!mastershipService.isLocalMaster(deviceId)) {
1266 // When the device is available we just check the mastership
1267 if (deviceService.isAvailable(deviceId)) {
1268 return false;
1269 }
1270 // Fallback with Leadership service - device id is used as topic
1271 NodeId leader = leadershipService.runForLeadership(
1272 deviceId.toString()).leaderNodeId();
1273 // Verify if this node is the leader
1274 return clusterService.getLocalNode().id().equals(leader);
1275 }
1276 return true;
1277 }
1278
Charles Chan445659f2019-01-02 13:46:16 -08001279 private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, XconnectEndpoint endpoint) {
1280 if (endpoint.type() == XconnectEndpoint.Type.PORT) {
1281 PortNumber port = ((XconnectPortEndpoint) endpoint).port();
1282 return Sets.newHashSet(port);
Charles Chan48df9ad2018-10-30 18:08:59 -07001283 }
Charles Chan445659f2019-01-02 13:46:16 -08001284 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001285 PortLoadBalancerId portLoadBalancerId = new PortLoadBalancerId(deviceId,
1286 ((XconnectLoadBalancerEndpoint) endpoint).key());
1287 Set<PortNumber> ports = portLoadBalancerService.getPortLoadBalancer(portLoadBalancerId).ports();
Charles Chan445659f2019-01-02 13:46:16 -08001288 return Sets.newHashSet(ports);
Charles Chan48df9ad2018-10-30 18:08:59 -07001289 }
Charles Chan48df9ad2018-10-30 18:08:59 -07001290 return Sets.newHashSet();
1291 }
1292
Charles Chan445659f2019-01-02 13:46:16 -08001293 private NextTreatment getNextTreatment(DeviceId deviceId, XconnectEndpoint endpoint, boolean reserve) {
1294 if (endpoint.type() == XconnectEndpoint.Type.PORT) {
1295 PortNumber port = ((XconnectPortEndpoint) endpoint).port();
1296 return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(port).build());
Charles Chan48df9ad2018-10-30 18:08:59 -07001297 }
Charles Chan445659f2019-01-02 13:46:16 -08001298 if (endpoint.type() == XconnectEndpoint.Type.LOAD_BALANCER) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001299 PortLoadBalancerId portLoadBalancerId = new PortLoadBalancerId(deviceId,
1300 ((XconnectLoadBalancerEndpoint) endpoint).key());
1301 NextTreatment idNextTreatment = IdNextTreatment.of(portLoadBalancerService
1302 .getPortLoadBalancerNext(portLoadBalancerId));
pier567465b2018-11-24 11:16:28 -08001303 // Reserve only one time during next objective creation
1304 if (reserve) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001305 if (!portLoadBalancerService.reserve(portLoadBalancerId, appId)) {
1306 log.warn("Reservation failed for {}", portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001307 idNextTreatment = null;
1308 }
1309 }
1310 return idNextTreatment;
Charles Chan48df9ad2018-10-30 18:08:59 -07001311 }
Charles Chan48df9ad2018-10-30 18:08:59 -07001312 return null;
1313 }
pier567465b2018-11-24 11:16:28 -08001314
Charles Chanc0a499b2019-01-16 15:30:39 -08001315 private class InternalPortLoadBalancerListener implements PortLoadBalancerListener {
1316 // Populate xconnect once portloadbalancer is available
pier567465b2018-11-24 11:16:28 -08001317 @Override
Charles Chanc0a499b2019-01-16 15:30:39 -08001318 public void event(PortLoadBalancerEvent event) {
1319 portLoadBalancerExecutor.execute(() -> dequeue(event.subject().portLoadBalancerId()));
pier567465b2018-11-24 11:16:28 -08001320 }
Charles Chanc0a499b2019-01-16 15:30:39 -08001321 // When we receive INSTALLED port load balancing is ready
pier567465b2018-11-24 11:16:28 -08001322 @Override
Charles Chanc0a499b2019-01-16 15:30:39 -08001323 public boolean isRelevant(PortLoadBalancerEvent event) {
1324 return event.type() == PortLoadBalancerEvent.Type.INSTALLED;
pier567465b2018-11-24 11:16:28 -08001325 }
1326 }
1327
1328 // Invalidate the cache and re-start the xconnect installation
Charles Chanc0a499b2019-01-16 15:30:39 -08001329 private void dequeue(PortLoadBalancerId portLoadBalancerId) {
1330 XconnectKey xconnectKey = portLoadBalancerCache.getIfPresent(portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001331 if (xconnectKey == null) {
Charles Chanc0a499b2019-01-16 15:30:39 -08001332 log.trace("{} not present in the cache", portLoadBalancerId);
pier567465b2018-11-24 11:16:28 -08001333 return;
1334 }
Charles Chanc0a499b2019-01-16 15:30:39 -08001335 log.debug("Dequeue {}", portLoadBalancerId);
1336 portLoadBalancerCache.invalidate(portLoadBalancerId);
Charles Chan445659f2019-01-02 13:46:16 -08001337 Set<XconnectEndpoint> endpoints = Versioned.valueOrNull(xconnectStore.get(xconnectKey));
1338 if (endpoints == null || endpoints.isEmpty()) {
1339 log.warn("Endpoints not found for XConnect {}", xconnectKey);
pier567465b2018-11-24 11:16:28 -08001340 return;
1341 }
Charles Chan445659f2019-01-02 13:46:16 -08001342 populateXConnect(xconnectKey, endpoints);
Charles Chanc0a499b2019-01-16 15:30:39 -08001343 log.trace("PortLoadBalancer cache size {}", portLoadBalancerCache.size());
pier567465b2018-11-24 11:16:28 -08001344 }
1345
Charles Chan8d316332018-06-19 20:31:57 -07001346}