blob: d8f03aa7ba1aa446869195ac952d4d16c77b1234 [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;
pier567465b2018-11-24 11:16:28 -080036import org.onosproject.l2lb.api.L2LbEvent;
37import org.onosproject.l2lb.api.L2LbId;
38import org.onosproject.l2lb.api.L2LbListener;
Charles Chan48df9ad2018-10-30 18:08:59 -070039import org.onosproject.l2lb.api.L2LbService;
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;
46import org.onosproject.net.config.NetworkConfigService;
47import org.onosproject.net.device.DeviceEvent;
48import org.onosproject.net.device.DeviceListener;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.net.flow.DefaultTrafficSelector;
51import org.onosproject.net.flow.DefaultTrafficTreatment;
52import org.onosproject.net.flow.TrafficSelector;
53import org.onosproject.net.flow.TrafficTreatment;
54import org.onosproject.net.flow.criteria.Criteria;
55import org.onosproject.net.flowobjective.DefaultFilteringObjective;
56import org.onosproject.net.flowobjective.DefaultForwardingObjective;
57import org.onosproject.net.flowobjective.DefaultNextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070058import org.onosproject.net.flowobjective.DefaultNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070059import org.onosproject.net.flowobjective.DefaultObjectiveContext;
60import org.onosproject.net.flowobjective.FilteringObjective;
61import org.onosproject.net.flowobjective.FlowObjectiveService;
62import org.onosproject.net.flowobjective.ForwardingObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070063import org.onosproject.net.flowobjective.IdNextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070064import org.onosproject.net.flowobjective.NextObjective;
Charles Chan48df9ad2018-10-30 18:08:59 -070065import org.onosproject.net.flowobjective.NextTreatment;
Charles Chan8d316332018-06-19 20:31:57 -070066import org.onosproject.net.flowobjective.Objective;
67import org.onosproject.net.flowobjective.ObjectiveContext;
68import org.onosproject.net.flowobjective.ObjectiveError;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053069import org.onosproject.net.host.HostEvent;
70import org.onosproject.net.host.HostListener;
71import org.onosproject.net.host.HostService;
72import org.onosproject.net.intf.InterfaceService;
Charles Chan8d316332018-06-19 20:31:57 -070073import org.onosproject.segmentrouting.SegmentRoutingService;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053074import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
Charles Chan8d316332018-06-19 20:31:57 -070075import org.onosproject.segmentrouting.xconnect.api.XconnectCodec;
76import org.onosproject.segmentrouting.xconnect.api.XconnectDesc;
77import org.onosproject.segmentrouting.xconnect.api.XconnectKey;
78import org.onosproject.segmentrouting.xconnect.api.XconnectService;
79import org.onosproject.store.serializers.KryoNamespaces;
80import org.onosproject.store.service.ConsistentMap;
81import org.onosproject.store.service.MapEvent;
82import org.onosproject.store.service.MapEventListener;
83import org.onosproject.store.service.Serializer;
84import org.onosproject.store.service.StorageService;
85import org.onosproject.store.service.Versioned;
Ray Milkey2bd24a92018-08-17 14:54:17 -070086import org.osgi.service.component.annotations.Activate;
87import org.osgi.service.component.annotations.Component;
88import org.osgi.service.component.annotations.Deactivate;
89import org.osgi.service.component.annotations.Reference;
90import org.osgi.service.component.annotations.ReferenceCardinality;
Charles Chan8d316332018-06-19 20:31:57 -070091import org.slf4j.Logger;
92import org.slf4j.LoggerFactory;
93
94import java.io.Serializable;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053095import java.util.Collections;
96import java.util.List;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +053097import java.util.Optional;
Charles Chan8d316332018-06-19 20:31:57 -070098import java.util.Set;
99import java.util.concurrent.CompletableFuture;
Charles Chan56542b62018-08-07 12:48:36 -0700100import java.util.concurrent.ExecutorService;
101import java.util.concurrent.Executors;
pier567465b2018-11-24 11:16:28 -0800102import java.util.concurrent.ScheduledExecutorService;
103import java.util.concurrent.TimeUnit;
Charles Chan8d316332018-06-19 20:31:57 -0700104import java.util.function.BiConsumer;
105import java.util.function.Consumer;
106import java.util.stream.Collectors;
107
pier567465b2018-11-24 11:16:28 -0800108import static java.util.concurrent.Executors.newScheduledThreadPool;
Charles Chan56542b62018-08-07 12:48:36 -0700109import static org.onlab.util.Tools.groupedThreads;
110
Ray Milkey2bd24a92018-08-17 14:54:17 -0700111@Component(immediate = true, service = XconnectService.class)
Charles Chan8d316332018-06-19 20:31:57 -0700112public class XconnectManager implements XconnectService {
Ray Milkey2bd24a92018-08-17 14:54:17 -0700113 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700114 private CoreService coreService;
115
Ray Milkey2bd24a92018-08-17 14:54:17 -0700116 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700117 private CodecService codecService;
118
Ray Milkey2bd24a92018-08-17 14:54:17 -0700119 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700120 private StorageService storageService;
121
Ray Milkey2bd24a92018-08-17 14:54:17 -0700122 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700123 public NetworkConfigService netCfgService;
124
Ray Milkey2bd24a92018-08-17 14:54:17 -0700125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700126 public DeviceService deviceService;
127
Ray Milkey2bd24a92018-08-17 14:54:17 -0700128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700129 public FlowObjectiveService flowObjectiveService;
130
Ray Milkey2bd24a92018-08-17 14:54:17 -0700131 @Reference(cardinality = ReferenceCardinality.MANDATORY)
pier6fd24fd2018-11-27 11:23:50 -0800132 private LeadershipService leadershipService;
133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY)
135 private ClusterService clusterService;
136
137 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan8d316332018-06-19 20:31:57 -0700138 public MastershipService mastershipService;
139
Ray Milkey2bd24a92018-08-17 14:54:17 -0700140 @Reference(cardinality = ReferenceCardinality.OPTIONAL)
Charles Chan8d316332018-06-19 20:31:57 -0700141 public SegmentRoutingService srService;
142
Ray Milkey3cad4db2018-10-04 15:13:33 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530144 public InterfaceService interfaceService;
145
Ray Milkey3cad4db2018-10-04 15:13:33 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530147 HostService hostService;
148
Charles Chan48df9ad2018-10-30 18:08:59 -0700149 @Reference(cardinality = ReferenceCardinality.MANDATORY)
150 L2LbService l2LbService;
151
Charles Chan8d316332018-06-19 20:31:57 -0700152 private static final String APP_NAME = "org.onosproject.xconnect";
pier6fd24fd2018-11-27 11:23:50 -0800153 private static final String ERROR_NOT_LEADER = "Not leader controller";
Charles Chan48df9ad2018-10-30 18:08:59 -0700154 private static final String ERROR_NEXT_OBJ_BUILDER = "Unable to construct next objective builder";
155 private static final String ERROR_NEXT_ID = "Unable to get next id";
Charles Chan8d316332018-06-19 20:31:57 -0700156
157 private static Logger log = LoggerFactory.getLogger(XconnectManager.class);
158
159 private ApplicationId appId;
Charles Chan48df9ad2018-10-30 18:08:59 -0700160 private ConsistentMap<XconnectKey, Set<String>> xconnectStore;
Charles Chan1fb65132018-09-21 11:29:12 -0700161 private ConsistentMap<XconnectKey, Integer> xconnectNextObjStore;
Charles Chan8d316332018-06-19 20:31:57 -0700162
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530163 private ConsistentMap<VlanNextObjectiveStoreKey, Integer> xconnectMulticastNextStore;
164 private ConsistentMap<VlanNextObjectiveStoreKey, List<PortNumber>> xconnectMulticastPortsStore;
165
Charles Chan48df9ad2018-10-30 18:08:59 -0700166 private final MapEventListener<XconnectKey, Set<String>> xconnectListener = new XconnectMapListener();
pier6fd24fd2018-11-27 11:23:50 -0800167 private ExecutorService xConnectExecutor;
Charles Chan8d316332018-06-19 20:31:57 -0700168
pier6fd24fd2018-11-27 11:23:50 -0800169 private final DeviceListener deviceListener = new InternalDeviceListener();
Charles Chan56542b62018-08-07 12:48:36 -0700170 private ExecutorService deviceEventExecutor;
171
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530172 private final HostListener hostListener = new InternalHostListener();
173 private ExecutorService hostEventExecutor;
174
pier567465b2018-11-24 11:16:28 -0800175 // Wait time for the cache
176 private static final int WAIT_TIME_MS = 15000;
177 //The cache is implemented as buffer for waiting the installation of L2Lb when present
178 private Cache<L2LbId, XconnectKey> l2LbCache;
179 // Executor for the cache
180 private ScheduledExecutorService l2lbExecutor;
181 // Pattern for L2Lb key
182 private static final String L2LB_PATTERN = "^(L2LB\\(\\d*\\))$";
183 // We need to listen for some events to properly installed the xconnect with l2lb
184 private final L2LbListener l2LbListener = new InternalL2LbListener();
185
Charles Chan8d316332018-06-19 20:31:57 -0700186 @Activate
187 void activate() {
188 appId = coreService.registerApplication(APP_NAME);
189 codecService.registerCodec(XconnectDesc.class, new XconnectCodec());
190
191 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
192 .register(KryoNamespaces.API)
Charles Chanfbaad962018-07-23 12:53:16 -0700193 .register(XconnectManager.class)
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530194 .register(XconnectKey.class)
195 .register(VlanNextObjectiveStoreKey.class);
Charles Chan8d316332018-06-19 20:31:57 -0700196
Charles Chan48df9ad2018-10-30 18:08:59 -0700197 xconnectStore = storageService.<XconnectKey, Set<String>>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
pier567465b2018-11-24 11:16:28 -0800229 l2LbCache = CacheBuilder.newBuilder()
230 .expireAfterWrite(WAIT_TIME_MS, TimeUnit.MILLISECONDS)
231 .removalListener((RemovalNotification<L2LbId, XconnectKey> notification) ->
232 log.debug("L2Lb cache removal event. l2LbId={}, xConnectKey={}",
233 notification.getKey(), notification.getValue())).build();
234 l2lbExecutor = newScheduledThreadPool(1,
235 groupedThreads("l2LbCacheWorker", "l2LbCacheWorker-%d", log));
236 // Let's schedule the cleanup of the cache
237 l2lbExecutor.scheduleAtFixedRate(l2LbCache::cleanUp, 0,
238 WAIT_TIME_MS, TimeUnit.MILLISECONDS);
239 l2LbService.addListener(l2LbListener);
240
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);
Charles Chan8d316332018-06-19 20:31:57 -0700249 codecService.unregisterCodec(XconnectDesc.class);
250
Charles Chan56542b62018-08-07 12:48:36 -0700251 deviceEventExecutor.shutdown();
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530252 hostEventExecutor.shutdown();
pier6fd24fd2018-11-27 11:23:50 -0800253 xConnectExecutor.shutdown();
pier567465b2018-11-24 11:16:28 -0800254 l2lbExecutor.shutdown();
Charles Chan56542b62018-08-07 12:48:36 -0700255
Charles Chan8d316332018-06-19 20:31:57 -0700256 log.info("Stopped");
257 }
258
259 @Override
Charles Chan48df9ad2018-10-30 18:08:59 -0700260 public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set<String> ports) {
Charles Chan8d316332018-06-19 20:31:57 -0700261 log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530262 deviceId, vlanId, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700263 final XconnectKey key = new XconnectKey(deviceId, vlanId);
264 xconnectStore.put(key, ports);
265 }
266
267 @Override
268 public void removeXonnect(DeviceId deviceId, VlanId vlanId) {
269 log.info("Removing xconnect. deviceId={}, vlanId={}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530270 deviceId, vlanId);
Charles Chan8d316332018-06-19 20:31:57 -0700271 final XconnectKey key = new XconnectKey(deviceId, vlanId);
272 xconnectStore.remove(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530273
274 // Cleanup multicasting support, if any.
275 srService.getPairDeviceId(deviceId).ifPresent(pairDeviceId -> {
276 cleanupL2MulticastRule(pairDeviceId, srService.getPairLocalPort(pairDeviceId).get(), vlanId, true);
277 });
278
Charles Chan8d316332018-06-19 20:31:57 -0700279 }
280
281 @Override
282 public Set<XconnectDesc> getXconnects() {
283 return xconnectStore.asJavaMap().entrySet().stream()
284 .map(e -> new XconnectDesc(e.getKey(), e.getValue()))
285 .collect(Collectors.toSet());
286 }
287
288 @Override
289 public boolean hasXconnect(ConnectPoint cp) {
pier6fd24fd2018-11-27 11:23:50 -0800290 return getXconnects().stream().anyMatch(desc -> desc.key().deviceId().equals(cp.deviceId())
Charles Chan48df9ad2018-10-30 18:08:59 -0700291 && desc.ports().contains(cp.port().toString())
Charles Chan8d316332018-06-19 20:31:57 -0700292 );
293 }
294
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700295 @Override
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530296 public List<VlanId> getXconnectVlans(DeviceId deviceId, PortNumber port) {
297 return getXconnects().stream()
Charles Chan48df9ad2018-10-30 18:08:59 -0700298 .filter(desc -> desc.key().deviceId().equals(deviceId) && desc.ports().contains(port.toString()))
pier6fd24fd2018-11-27 11:23:50 -0800299 .map(desc -> desc.key().vlanId())
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530300 .collect(Collectors.toList());
301 }
302
303 @Override
304 public boolean isXconnectVlan(DeviceId deviceId, VlanId vlanId) {
pier6fd24fd2018-11-27 11:23:50 -0800305 XconnectKey key = new XconnectKey(deviceId, vlanId);
306 return Versioned.valueOrNull(xconnectStore.get(key)) != null;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530307 }
308
309 @Override
Charles Chan1fb65132018-09-21 11:29:12 -0700310 public ImmutableMap<XconnectKey, Integer> getNext() {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700311 if (xconnectNextObjStore != null) {
312 return ImmutableMap.copyOf(xconnectNextObjStore.asJavaMap());
313 } else {
314 return ImmutableMap.of();
315 }
316 }
317
318 @Override
pier6fd24fd2018-11-27 11:23:50 -0800319 public int getNextId(DeviceId deviceId, VlanId vlanId) {
320 return Versioned.valueOrElse(xconnectNextObjStore.get(new XconnectKey(deviceId, vlanId)), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530321 }
322
323 @Override
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700324 public void removeNextId(int nextId) {
325 xconnectNextObjStore.entrySet().forEach(e -> {
Charles Chan1fb65132018-09-21 11:29:12 -0700326 if (e.getValue().value() == nextId) {
Charles Chan0b1dd7e2018-08-19 19:21:46 -0700327 xconnectNextObjStore.remove(e.getKey());
328 }
329 });
330 }
331
Charles Chan48df9ad2018-10-30 18:08:59 -0700332 private class XconnectMapListener implements MapEventListener<XconnectKey, Set<String>> {
Charles Chan8d316332018-06-19 20:31:57 -0700333 @Override
Charles Chan48df9ad2018-10-30 18:08:59 -0700334 public void event(MapEvent<XconnectKey, Set<String>> event) {
Charles Chan8d316332018-06-19 20:31:57 -0700335 XconnectKey key = event.key();
Charles Chan48df9ad2018-10-30 18:08:59 -0700336 Set<String> ports = Versioned.valueOrNull(event.newValue());
337 Set<String> oldPorts = Versioned.valueOrNull(event.oldValue());
Charles Chan8d316332018-06-19 20:31:57 -0700338
339 switch (event.type()) {
340 case INSERT:
Charles Chan48df9ad2018-10-30 18:08:59 -0700341 populateXConnect(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700342 break;
343 case UPDATE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700344 updateXConnect(key, oldPorts, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700345 break;
346 case REMOVE:
Charles Chan48df9ad2018-10-30 18:08:59 -0700347 revokeXConnect(key, oldPorts);
Charles Chan8d316332018-06-19 20:31:57 -0700348 break;
349 default:
350 break;
351 }
352 }
353 }
354
355 private class InternalDeviceListener implements DeviceListener {
pier6fd24fd2018-11-27 11:23:50 -0800356 // Offload the execution to an executor and then process the event
357 // if this instance is the leader of the device
Charles Chan8d316332018-06-19 20:31:57 -0700358 @Override
359 public void event(DeviceEvent event) {
Charles Chan56542b62018-08-07 12:48:36 -0700360 deviceEventExecutor.execute(() -> {
361 DeviceId deviceId = event.subject().id();
pier6fd24fd2018-11-27 11:23:50 -0800362 // Just skip if we are not the leader
363 if (!isLocalLeader(deviceId)) {
364 log.debug("Not the leader of {}. Skip event {}", deviceId, event);
Charles Chan56542b62018-08-07 12:48:36 -0700365 return;
366 }
pier6fd24fd2018-11-27 11:23:50 -0800367 // Populate or revoke according to the device availability
368 if (deviceService.isAvailable(deviceId)) {
369 init(deviceId);
370 } else {
371 cleanup(deviceId);
Charles Chan56542b62018-08-07 12:48:36 -0700372 }
373 });
Charles Chan8d316332018-06-19 20:31:57 -0700374 }
pier6fd24fd2018-11-27 11:23:50 -0800375 // We want to manage only a subset of events and if we are the leader
376 @Override
377 public boolean isRelevant(DeviceEvent event) {
378 return event.type() == DeviceEvent.Type.DEVICE_ADDED ||
379 event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
380 event.type() == DeviceEvent.Type.DEVICE_UPDATED;
381 }
Charles Chan8d316332018-06-19 20:31:57 -0700382 }
383
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530384 private class InternalHostListener implements HostListener {
385 @Override
386 public void event(HostEvent event) {
387 hostEventExecutor.execute(() -> {
388
389 switch (event.type()) {
390 case HOST_MOVED:
391 log.trace("Processing host event {}", event);
392
393 Host host = event.subject();
394 Set<HostLocation> prevLocations = event.prevSubject().locations();
395 Set<HostLocation> newLocations = host.locations();
396
397 // Dual-home host port failure
398 // For each old location, in failed and paired devices update L2 vlan groups
399 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
400
401 Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(prevLocation.deviceId());
402 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(prevLocation.deviceId());
403
404 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
405 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get())) &&
406 hasXconnect(new ConnectPoint(prevLocation.deviceId(), prevLocation.port()))) {
407
408 List<VlanId> xconnectVlans = getXconnectVlans(prevLocation.deviceId(),
409 prevLocation.port());
410 xconnectVlans.forEach(xconnectVlan -> {
411 // Add single-home host into L2 multicast group at paired device side.
412 // Also append ACL rule to forward traffic from paired port to L2 multicast group.
413 newLocations.stream()
414 .filter(location -> location.deviceId().equals(pairDeviceId.get()))
415 .forEach(location -> populateL2Multicast(location.deviceId(),
416 srService.getPairLocalPort(
417 location.deviceId()).get(),
418 xconnectVlan,
419 Collections.singletonList(
420 location.port())));
pier6fd24fd2018-11-27 11:23:50 -0800421 // Ensure pair-port attached to xconnect vlan flooding group
422 // at dual home failed device.
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530423 updateL2Flooding(prevLocation.deviceId(), pairLocalPort.get(), xconnectVlan, true);
424 });
425 }
426 });
427
428 // Dual-home host port restoration
429 // For each new location, reverse xconnect loop prevention groups.
430 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
431 final Optional<DeviceId> pairDeviceId = srService.getPairDeviceId(newLocation.deviceId());
432 Optional<PortNumber> pairLocalPort = srService.getPairLocalPort(newLocation.deviceId());
433 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
434 hasXconnect((new ConnectPoint(newLocation.deviceId(), newLocation.port())))) {
435
436 List<VlanId> xconnectVlans = getXconnectVlans(newLocation.deviceId(),
437 newLocation.port());
438 xconnectVlans.forEach(xconnectVlan -> {
439 // Remove recovered dual homed port from vlan L2 multicast group
440 prevLocations.stream()
441 .filter(prevLocation -> prevLocation.deviceId().equals(pairDeviceId.get()))
pier6fd24fd2018-11-27 11:23:50 -0800442 .forEach(prevLocation -> revokeL2Multicast(
443 prevLocation.deviceId(),
444 xconnectVlan,
445 Collections.singletonList(newLocation.port()))
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530446 );
447
pier6fd24fd2018-11-27 11:23:50 -0800448 // Remove pair-port from vlan's flooding group at dual home
449 // restored device, if needed.
450 if (!hasAccessPortInMulticastGroup(new VlanNextObjectiveStoreKey(
451 newLocation.deviceId(), xconnectVlan), pairLocalPort.get())) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530452 updateL2Flooding(newLocation.deviceId(),
453 pairLocalPort.get(),
454 xconnectVlan,
455 false);
456
457 // Clean L2 multicast group at pair-device; also update store.
458 cleanupL2MulticastRule(pairDeviceId.get(),
459 srService.getPairLocalPort(pairDeviceId.get()).get(),
460 xconnectVlan,
461 false);
462 }
463 });
464 }
465 });
466 break;
467
468 default:
469 log.warn("Unsupported host event type: {} received. Ignoring.", event.type());
470 break;
471 }
472 });
473 }
474 }
475
Charles Chan1fb65132018-09-21 11:29:12 -0700476 private void init(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700477 getXconnects().stream()
478 .filter(desc -> desc.key().deviceId().equals(deviceId))
479 .forEach(desc -> populateXConnect(desc.key(), desc.ports()));
480 }
481
Charles Chan1fb65132018-09-21 11:29:12 -0700482 private void cleanup(DeviceId deviceId) {
Charles Chan8d316332018-06-19 20:31:57 -0700483 xconnectNextObjStore.entrySet().stream()
484 .filter(entry -> entry.getKey().deviceId().equals(deviceId))
485 .forEach(entry -> xconnectNextObjStore.remove(entry.getKey()));
486 log.debug("{} is removed from xConnectNextObjStore", deviceId);
487 }
488
489 /**
490 * Populates XConnect groups and flows for given key.
491 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530492 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700493 * @param ports a set of ports to be cross-connected
494 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700495 private void populateXConnect(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800496 if (!isLocalLeader(key.deviceId())) {
497 log.debug("Abort populating XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700498 return;
499 }
500
Charles Chan48df9ad2018-10-30 18:08:59 -0700501 int nextId = populateNext(key, ports);
502 if (nextId == -1) {
503 log.warn("Fail to populateXConnect {}: {}", key, ERROR_NEXT_ID);
504 return;
505 }
Charles Chan8d316332018-06-19 20:31:57 -0700506 populateFilter(key, ports);
Charles Chan48df9ad2018-10-30 18:08:59 -0700507 populateFwd(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700508 populateAcl(key);
509 }
510
511 /**
512 * Populates filtering objectives for given XConnect.
513 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530514 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700515 * @param ports XConnect ports
516 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700517 private void populateFilter(XconnectKey key, Set<String> ports) {
518 // FIXME Improve the logic
519 // If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
520 // The purpose is to make sure existing XConnect logic can still work on a configured port.
521 boolean filtered = ports.stream()
pier567465b2018-11-24 11:16:28 -0800522 .map(p -> getNextTreatment(key.deviceId(), p, false))
Charles Chan48df9ad2018-10-30 18:08:59 -0700523 .allMatch(t -> t.type().equals(NextTreatment.Type.TREATMENT));
524
525 ports.stream()
526 .map(p -> getPhysicalPorts(key.deviceId(), p))
527 .flatMap(Set::stream).forEach(port -> {
528 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
Charles Chan8d316332018-06-19 20:31:57 -0700529 ObjectiveContext context = new DefaultObjectiveContext(
530 (objective) -> log.debug("XConnect FilterObj for {} on port {} populated",
Charles Chan48df9ad2018-10-30 18:08:59 -0700531 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700532 (objective, error) ->
533 log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}",
Charles Chan48df9ad2018-10-30 18:08:59 -0700534 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700535 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context));
536 });
537 }
538
539 /**
540 * Populates next objectives for given XConnect.
541 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530542 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700543 * @param ports XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700544 * @return next id
Charles Chan8d316332018-06-19 20:31:57 -0700545 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700546 private int populateNext(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800547 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
548 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700549 log.debug("NextObj for {} found, id={}", key, nextId);
550 return nextId;
Charles Chan8d316332018-06-19 20:31:57 -0700551 } else {
552 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports);
Charles Chan48df9ad2018-10-30 18:08:59 -0700553 if (nextObjBuilder == null) {
554 log.warn("Fail to populate {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
555 return -1;
556 }
Charles Chan8d316332018-06-19 20:31:57 -0700557 ObjectiveContext nextContext = new DefaultObjectiveContext(
558 // To serialize this with kryo
559 (Serializable & Consumer<Objective>) (objective) ->
560 log.debug("XConnect NextObj for {} added", key),
Charles Chanfacfbef2018-08-23 14:30:33 -0700561 (Serializable & BiConsumer<Objective, ObjectiveError>) (objective, error) -> {
562 log.warn("Failed to add XConnect NextObj for {}: {}", key, error);
563 srService.invalidateNextObj(objective.id());
564 });
Charles Chan1fb65132018-09-21 11:29:12 -0700565 NextObjective nextObj = nextObjBuilder.add(nextContext);
Charles Chan8d316332018-06-19 20:31:57 -0700566 flowObjectiveService.next(key.deviceId(), nextObj);
Charles Chan1fb65132018-09-21 11:29:12 -0700567 xconnectNextObjStore.put(key, nextObj.id());
Charles Chan8d316332018-06-19 20:31:57 -0700568 log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id());
Charles Chan1fb65132018-09-21 11:29:12 -0700569 return nextObj.id();
Charles Chan8d316332018-06-19 20:31:57 -0700570 }
Charles Chan8d316332018-06-19 20:31:57 -0700571 }
572
573 /**
574 * Populates bridging forwarding objectives for given XConnect.
575 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530576 * @param key XConnect store key
Charles Chan1fb65132018-09-21 11:29:12 -0700577 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700578 */
Charles Chan1fb65132018-09-21 11:29:12 -0700579 private void populateFwd(XconnectKey key, int nextId) {
580 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700581 ObjectiveContext fwdContext = new DefaultObjectiveContext(
582 (objective) -> log.debug("XConnect FwdObj for {} populated", key),
583 (objective, error) ->
584 log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error));
585 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext));
586 }
587
588 /**
589 * Populates ACL forwarding objectives for given XConnect.
590 *
591 * @param key XConnect store key
592 */
593 private void populateAcl(XconnectKey key) {
594 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
595 ObjectiveContext aclContext = new DefaultObjectiveContext(
596 (objective) -> log.debug("XConnect AclObj for {} populated", key),
597 (objective, error) ->
598 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
599 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext));
600 }
601
602 /**
603 * Revokes XConnect groups and flows for given key.
604 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530605 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700606 * @param ports XConnect ports
607 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700608 private void revokeXConnect(XconnectKey key, Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800609 if (!isLocalLeader(key.deviceId())) {
610 log.debug("Abort revoking XConnect {}: {}", key, ERROR_NOT_LEADER);
Charles Chan8d316332018-06-19 20:31:57 -0700611 return;
612 }
613
Charles Chan8d316332018-06-19 20:31:57 -0700614 revokeFilter(key, ports);
pier6fd24fd2018-11-27 11:23:50 -0800615 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
616 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700617 revokeFwd(key, nextId, null);
618 revokeNext(key, ports, nextId, null);
Charles Chan8d316332018-06-19 20:31:57 -0700619 } else {
620 log.warn("NextObj for {} does not exist in the store.", key);
621 }
Charles Chan48df9ad2018-10-30 18:08:59 -0700622 revokeFilter(key, ports);
Charles Chan8d316332018-06-19 20:31:57 -0700623 revokeAcl(key);
624 }
625
626 /**
627 * Revokes filtering objectives for given XConnect.
628 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530629 * @param key XConnect store key
Charles Chan8d316332018-06-19 20:31:57 -0700630 * @param ports XConnect ports
631 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700632 private void revokeFilter(XconnectKey key, Set<String> ports) {
633 // FIXME Improve the logic
634 // If L2 load balancer is not involved, use filtered port. Otherwise, use unfiltered port.
635 // The purpose is to make sure existing XConnect logic can still work on a configured port.
636 Set<Set<PortNumber>> portsSet = ports.stream()
637 .map(p -> getPhysicalPorts(key.deviceId(), p)).collect(Collectors.toSet());
638 boolean filtered = portsSet.stream().allMatch(s -> s.size() == 1);
639 portsSet.stream().flatMap(Set::stream).forEach(port -> {
640 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port, filtered);
Charles Chan8d316332018-06-19 20:31:57 -0700641 ObjectiveContext context = new DefaultObjectiveContext(
642 (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530643 key, port),
Charles Chan8d316332018-06-19 20:31:57 -0700644 (objective, error) ->
645 log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}",
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530646 key, port, error));
Charles Chan8d316332018-06-19 20:31:57 -0700647 flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context));
648 });
649 }
650
651 /**
652 * Revokes next objectives for given XConnect.
653 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530654 * @param key XConnect store key
655 * @param ports ports in the XConnect
656 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700657 * @param nextFuture completable future for this next objective operation
658 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700659 private void revokeNext(XconnectKey key, Set<String> ports, int nextId,
Charles Chan8d316332018-06-19 20:31:57 -0700660 CompletableFuture<ObjectiveError> nextFuture) {
661 ObjectiveContext context = new ObjectiveContext() {
662 @Override
663 public void onSuccess(Objective objective) {
664 log.debug("Previous NextObj for {} removed", key);
665 if (nextFuture != null) {
666 nextFuture.complete(null);
667 }
668 }
669
670 @Override
671 public void onError(Objective objective, ObjectiveError error) {
672 log.warn("Failed to remove previous NextObj for {}: {}", key, error);
673 if (nextFuture != null) {
674 nextFuture.complete(error);
675 }
Charles Chanfacfbef2018-08-23 14:30:33 -0700676 srService.invalidateNextObj(objective.id());
Charles Chan8d316332018-06-19 20:31:57 -0700677 }
678 };
Charles Chan48df9ad2018-10-30 18:08:59 -0700679
680 NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports, nextId);
681 if (nextObjBuilder == null) {
682 log.warn("Fail to revokeNext {}: {}", key, ERROR_NEXT_OBJ_BUILDER);
683 return;
684 }
pier567465b2018-11-24 11:16:28 -0800685 // Release the L2Lbs if present
686 ports.forEach(port -> {
687 if (isL2LbKey(port)) {
688 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
689 l2LbService.release(new L2LbId(key.deviceId(), Integer.parseInt(l2LbKey)), appId);
690 }
691 });
Charles Chan48df9ad2018-10-30 18:08:59 -0700692 flowObjectiveService.next(key.deviceId(), nextObjBuilder.remove(context));
Charles Chan8d316332018-06-19 20:31:57 -0700693 xconnectNextObjStore.remove(key);
694 }
695
696 /**
697 * Revokes bridging forwarding objectives for given XConnect.
698 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530699 * @param key XConnect store key
700 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700701 * @param fwdFuture completable future for this forwarding objective operation
702 */
Charles Chan1fb65132018-09-21 11:29:12 -0700703 private void revokeFwd(XconnectKey key, int nextId, CompletableFuture<ObjectiveError> fwdFuture) {
704 ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextId);
Charles Chan8d316332018-06-19 20:31:57 -0700705 ObjectiveContext context = new ObjectiveContext() {
706 @Override
707 public void onSuccess(Objective objective) {
708 log.debug("Previous FwdObj for {} removed", key);
709 if (fwdFuture != null) {
710 fwdFuture.complete(null);
711 }
712 }
713
714 @Override
715 public void onError(Objective objective, ObjectiveError error) {
716 log.warn("Failed to remove previous FwdObj for {}: {}", key, error);
717 if (fwdFuture != null) {
718 fwdFuture.complete(error);
719 }
720 }
721 };
722 flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context));
723 }
724
725 /**
726 * Revokes ACL forwarding objectives for given XConnect.
727 *
728 * @param key XConnect store key
729 */
730 private void revokeAcl(XconnectKey key) {
731 ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId());
732 ObjectiveContext aclContext = new DefaultObjectiveContext(
733 (objective) -> log.debug("XConnect AclObj for {} populated", key),
734 (objective, error) ->
735 log.warn("Failed to populate XConnect AclObj for {}: {}", key, error));
736 flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext));
737 }
738
739 /**
740 * Updates XConnect groups and flows for given key.
741 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530742 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700743 * @param prevPorts previous XConnect ports
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530744 * @param ports new XConnect ports
Charles Chan8d316332018-06-19 20:31:57 -0700745 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700746 private void updateXConnect(XconnectKey key, Set<String> prevPorts,
747 Set<String> ports) {
pier6fd24fd2018-11-27 11:23:50 -0800748 if (!isLocalLeader(key.deviceId())) {
749 log.debug("Abort updating XConnect {}: {}", key, ERROR_NOT_LEADER);
750 return;
751 }
Charles Chan8d316332018-06-19 20:31:57 -0700752 // NOTE: ACL flow doesn't include port information. No need to update it.
753 // Pair port is built-in and thus not going to change. No need to update it.
754
755 // remove old filter
756 prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port ->
Charles Chan48df9ad2018-10-30 18:08:59 -0700757 revokeFilter(key, ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700758 // install new filter
759 ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port ->
Charles Chan48df9ad2018-10-30 18:08:59 -0700760 populateFilter(key, ImmutableSet.of(port)));
Charles Chan8d316332018-06-19 20:31:57 -0700761
762 CompletableFuture<ObjectiveError> fwdFuture = new CompletableFuture<>();
763 CompletableFuture<ObjectiveError> nextFuture = new CompletableFuture<>();
764
pier6fd24fd2018-11-27 11:23:50 -0800765 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
766 if (nextId != -1) {
Charles Chan1fb65132018-09-21 11:29:12 -0700767 revokeFwd(key, nextId, fwdFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700768
769 fwdFuture.thenAcceptAsync(fwdStatus -> {
770 if (fwdStatus == null) {
771 log.debug("Fwd removed. Now remove group {}", key);
Charles Chan1fb65132018-09-21 11:29:12 -0700772 revokeNext(key, prevPorts, nextId, nextFuture);
Charles Chan8d316332018-06-19 20:31:57 -0700773 }
774 });
775
776 nextFuture.thenAcceptAsync(nextStatus -> {
777 if (nextStatus == null) {
778 log.debug("Installing new group and flow for {}", key);
Charles Chan48df9ad2018-10-30 18:08:59 -0700779 int newNextId = populateNext(key, ports);
780 if (newNextId == -1) {
781 log.warn("Fail to updateXConnect {}: {}", key, ERROR_NEXT_ID);
782 return;
783 }
784 populateFwd(key, newNextId);
Charles Chan8d316332018-06-19 20:31:57 -0700785 }
786 });
787 } else {
788 log.warn("NextObj for {} does not exist in the store.", key);
789 }
790 }
791
792 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700793 * Creates a next objective builder for XConnect with given nextId.
Charles Chan8d316332018-06-19 20:31:57 -0700794 *
Charles Chan48df9ad2018-10-30 18:08:59 -0700795 * @param key XConnect key
796 * @param ports ports or L2 load balancer key
797 * @param nextId next objective id
Charles Chan8d316332018-06-19 20:31:57 -0700798 * @return next objective builder
799 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700800 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports, int nextId) {
Charles Chan8d316332018-06-19 20:31:57 -0700801 TrafficSelector metadata =
802 DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build();
803 NextObjective.Builder nextObjBuilder = DefaultNextObjective
804 .builder().withId(nextId)
805 .withType(NextObjective.Type.BROADCAST).fromApp(appId)
806 .withMeta(metadata);
Charles Chan48df9ad2018-10-30 18:08:59 -0700807
808 for (String port : ports) {
pier567465b2018-11-24 11:16:28 -0800809 NextTreatment nextTreatment = getNextTreatment(key.deviceId(), port, true);
Charles Chan48df9ad2018-10-30 18:08:59 -0700810 if (nextTreatment == null) {
pier567465b2018-11-24 11:16:28 -0800811 // If a L2Lb is used in the XConnect - putting on hold
812 if (isL2LbKey(port)) {
813 log.warn("Unable to create nextObj. L2Lb not ready");
814 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
815 l2LbCache.asMap().putIfAbsent(new L2LbId(key.deviceId(), Integer.parseInt(l2LbKey)),
816 key);
817 } else {
818 log.warn("Unable to create nextObj. Null NextTreatment");
819 }
Charles Chan48df9ad2018-10-30 18:08:59 -0700820 return null;
821 }
822 nextObjBuilder.addTreatment(nextTreatment);
823 }
824
Charles Chan8d316332018-06-19 20:31:57 -0700825 return nextObjBuilder;
826 }
827
828 /**
Charles Chan1fb65132018-09-21 11:29:12 -0700829 * Creates a next objective builder for XConnect.
830 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530831 * @param key XConnect key
Charles Chan1fb65132018-09-21 11:29:12 -0700832 * @param ports set of XConnect ports
833 * @return next objective builder
834 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700835 private NextObjective.Builder nextObjBuilder(XconnectKey key, Set<String> ports) {
Charles Chan1fb65132018-09-21 11:29:12 -0700836 int nextId = flowObjectiveService.allocateNextId();
837 return nextObjBuilder(key, ports, nextId);
838 }
839
840
841 /**
Charles Chan8d316332018-06-19 20:31:57 -0700842 * Creates a bridging forwarding objective builder for XConnect.
843 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530844 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700845 * @param nextId next ID of the broadcast group for this XConnect key
846 * @return forwarding objective builder
847 */
848 private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) {
849 /*
850 * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE
851 * as the VLAN cross-connect broadcast rules
852 */
853 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
854 sbuilder.matchVlanId(key.vlanId());
855 sbuilder.matchEthDst(MacAddress.NONE);
856
857 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
858 fob.withFlag(ForwardingObjective.Flag.SPECIFIC)
859 .withSelector(sbuilder.build())
860 .nextStep(nextId)
861 .withPriority(XCONNECT_PRIORITY)
862 .fromApp(appId)
863 .makePermanent();
864 return fob;
865 }
866
867 /**
868 * Creates an ACL forwarding objective builder for XConnect.
869 *
870 * @param vlanId cross connect VLAN id
871 * @return forwarding objective builder
872 */
873 private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) {
874 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
875 sbuilder.matchVlanId(vlanId);
876
877 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
878
879 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
880 fob.withFlag(ForwardingObjective.Flag.VERSATILE)
881 .withSelector(sbuilder.build())
882 .withTreatment(tbuilder.build())
883 .withPriority(XCONNECT_ACL_PRIORITY)
884 .fromApp(appId)
885 .makePermanent();
886 return fob;
887 }
888
889 /**
890 * Creates a filtering objective builder for XConnect.
891 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530892 * @param key XConnect key
Charles Chan8d316332018-06-19 20:31:57 -0700893 * @param port XConnect ports
Charles Chan48df9ad2018-10-30 18:08:59 -0700894 * @param filtered true if this is a filtered port
Charles Chan8d316332018-06-19 20:31:57 -0700895 * @return next objective builder
896 */
Charles Chan48df9ad2018-10-30 18:08:59 -0700897 private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port, boolean filtered) {
Charles Chan8d316332018-06-19 20:31:57 -0700898 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
899 fob.withKey(Criteria.matchInPort(port))
Charles Chan8d316332018-06-19 20:31:57 -0700900 .addCondition(Criteria.matchEthDst(MacAddress.NONE))
901 .withPriority(XCONNECT_PRIORITY);
Charles Chan48df9ad2018-10-30 18:08:59 -0700902 if (filtered) {
903 fob.addCondition(Criteria.matchVlanId(key.vlanId()));
904 } else {
905 fob.addCondition(Criteria.matchVlanId(VlanId.ANY));
906 }
Charles Chan8d316332018-06-19 20:31:57 -0700907 return fob.permit().fromApp(appId);
908 }
909
910 /**
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530911 * Updates L2 flooding groups; add pair link into L2 flooding group of given xconnect vlan.
Charles Chan8d316332018-06-19 20:31:57 -0700912 *
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530913 * @param deviceId Device ID
914 * @param port Port details
915 * @param vlanId VLAN ID
916 * @param install Whether to add or revoke pair link addition to flooding group
Charles Chan8d316332018-06-19 20:31:57 -0700917 */
pier6fd24fd2018-11-27 11:23:50 -0800918 private void updateL2Flooding(DeviceId deviceId, PortNumber port, VlanId vlanId, boolean install) {
919 XconnectKey key = new XconnectKey(deviceId, vlanId);
920 // Ensure leadership on device
921 if (!isLocalLeader(deviceId)) {
922 log.debug("Abort updating L2Flood {}: {}", key, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530923 return;
Charles Chan8d316332018-06-19 20:31:57 -0700924 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530925
926 // Locate L2 flooding group details for given xconnect vlan
pier6fd24fd2018-11-27 11:23:50 -0800927 int nextId = Versioned.valueOrElse(xconnectNextObjStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530928 if (nextId == -1) {
929 log.debug("XConnect vlan {} broadcast group for device {} doesn't exists. " +
930 "Aborting pair group linking.", vlanId, deviceId);
931 return;
932 }
933
934 // Add pairing-port group to flooding group
935 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
936 // treatment.popVlan();
937 treatment.setOutput(port);
938 ObjectiveContext context = new DefaultObjectiveContext(
939 (objective) ->
940 log.debug("Pair port added/removed to vlan {} next objective {} on {}",
941 vlanId, nextId, deviceId),
942 (objective, error) ->
943 log.warn("Failed adding/removing pair port to vlan {} next objective {} on {}." +
944 "Error : {}", vlanId, nextId, deviceId, error)
945 );
946 NextObjective.Builder vlanNextObjectiveBuilder = DefaultNextObjective.builder()
947 .withId(nextId)
948 .withType(NextObjective.Type.BROADCAST)
949 .fromApp(srService.appId())
950 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
951 .addTreatment(treatment.build());
952 if (install) {
953 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.addToExisting(context));
954 } else {
955 flowObjectiveService.next(deviceId, vlanNextObjectiveBuilder.removeFromExisting(context));
956 }
957 log.debug("Submitted next objective {} for vlan: {} in device {}",
958 nextId, vlanId, deviceId);
Charles Chan8d316332018-06-19 20:31:57 -0700959 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530960
961 /**
962 * Populate L2 multicast rule on given deviceId that matches given mac, given vlan and
963 * output to given port's L2 mulitcast group.
964 *
965 * @param deviceId Device ID
966 * @param pairPort Pair port number
967 * @param vlanId VLAN ID
968 * @param accessPorts List of access ports to be added into L2 multicast group
969 */
pier6fd24fd2018-11-27 11:23:50 -0800970 private void populateL2Multicast(DeviceId deviceId, PortNumber pairPort,
971 VlanId vlanId, List<PortNumber> accessPorts) {
972 // Ensure enough rights to program pair device
973 if (!srService.shouldProgram(deviceId)) {
974 log.debug("Abort populate L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
975 return;
976 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530977
978 boolean multicastGroupExists = true;
979 int vlanMulticastNextId;
pier6fd24fd2018-11-27 11:23:50 -0800980 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530981
982 // Step 1 : Populate single homed access ports into vlan's L2 multicast group
983 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
984 .builder()
985 .withType(NextObjective.Type.BROADCAST)
986 .fromApp(srService.appId())
987 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId)
988 .matchEthDst(MacAddress.IPV4_MULTICAST).build());
pier6fd24fd2018-11-27 11:23:50 -0800989 vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530990 if (vlanMulticastNextId == -1) {
991 // Vlan's L2 multicast group doesn't exist; create it, update store and add pair port as sub-group
992 multicastGroupExists = false;
993 vlanMulticastNextId = flowObjectiveService.allocateNextId();
pier6fd24fd2018-11-27 11:23:50 -0800994 addMulticastGroupNextObjectiveId(key, vlanMulticastNextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +0530995 vlanMulticastNextObjBuilder.addTreatment(
996 DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build()
997 );
998 }
999 vlanMulticastNextObjBuilder.withId(vlanMulticastNextId);
pier6fd24fd2018-11-27 11:23:50 -08001000 int nextId = vlanMulticastNextId;
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301001 accessPorts.forEach(p -> {
1002 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1003 // Do vlan popup action based on interface configuration
1004 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1005 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1006 egressAction.popVlan();
1007 }
1008 egressAction.setOutput(p);
1009 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001010 addMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301011 });
1012 ObjectiveContext context = new DefaultObjectiveContext(
1013 (objective) ->
1014 log.debug("L2 multicast group installed/updated. "
1015 + "NextObject Id {} on {} for subnet {} ",
1016 nextId, deviceId, vlanId),
1017 (objective, error) ->
1018 log.warn("L2 multicast group failed to install/update. "
1019 + " NextObject Id {} on {} for subnet {} : {}",
1020 nextId, deviceId, vlanId, error)
1021 );
1022 if (!multicastGroupExists) {
1023 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.add(context));
1024
1025 // Step 2 : Populate ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1026 TrafficSelector.Builder multicastSelector = DefaultTrafficSelector.builder();
1027 multicastSelector.matchEthType(Ethernet.TYPE_VLAN);
1028 multicastSelector.matchInPort(pairPort);
1029 multicastSelector.matchVlanId(vlanId);
1030 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1031 .withFlag(ForwardingObjective.Flag.VERSATILE)
1032 .nextStep(vlanMulticastNextId)
1033 .withSelector(multicastSelector.build())
1034 .withPriority(100)
1035 .fromApp(srService.appId())
1036 .makePermanent();
1037 context = new DefaultObjectiveContext(
1038 (objective) -> log.debug("L2 multicasting versatile rule for device {}, port/vlan {}/{} populated",
1039 deviceId,
1040 pairPort,
1041 vlanId),
1042 (objective, error) -> log.warn("Failed to populate L2 multicasting versatile rule for device {}, " +
1043 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1044 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.add(context));
1045 } else {
1046 // L2_MULTICAST & BROADCAST are similar structure in subgroups; so going with BROADCAST type.
1047 vlanMulticastNextObjBuilder.withType(NextObjective.Type.BROADCAST);
1048 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.addToExisting(context));
1049 }
1050 }
1051
1052 /**
1053 * Removes access ports from VLAN L2 multicast group on given deviceId.
1054 *
1055 * @param deviceId Device ID
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301056 * @param vlanId VLAN ID
1057 * @param accessPorts List of access ports to be added into L2 multicast group
1058 */
pier6fd24fd2018-11-27 11:23:50 -08001059 private void revokeL2Multicast(DeviceId deviceId, VlanId vlanId, List<PortNumber> accessPorts) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301060 // Ensure enough rights to program pair device
1061 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001062 log.debug("Abort revoke L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301063 return;
1064 }
1065
pier6fd24fd2018-11-27 11:23:50 -08001066 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1067
1068 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301069 if (vlanMulticastNextId == -1) {
1070 return;
1071 }
1072 NextObjective.Builder vlanMulticastNextObjBuilder = DefaultNextObjective
1073 .builder()
1074 .withType(NextObjective.Type.BROADCAST)
1075 .fromApp(srService.appId())
1076 .withMeta(DefaultTrafficSelector.builder().matchVlanId(vlanId).build())
1077 .withId(vlanMulticastNextId);
1078 accessPorts.forEach(p -> {
1079 TrafficTreatment.Builder egressAction = DefaultTrafficTreatment.builder();
1080 // Do vlan popup action based on interface configuration
1081 if (interfaceService.getInterfacesByPort(new ConnectPoint(deviceId, p))
1082 .stream().noneMatch(i -> i.vlanTagged().contains(vlanId))) {
1083 egressAction.popVlan();
1084 }
1085 egressAction.setOutput(p);
1086 vlanMulticastNextObjBuilder.addTreatment(egressAction.build());
pier6fd24fd2018-11-27 11:23:50 -08001087 removeMulticastGroupPort(key, p);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301088 });
1089 ObjectiveContext context = new DefaultObjectiveContext(
1090 (objective) ->
1091 log.debug("L2 multicast group installed/updated. "
1092 + "NextObject Id {} on {} for subnet {} ",
1093 vlanMulticastNextId, deviceId, vlanId),
1094 (objective, error) ->
1095 log.warn("L2 multicast group failed to install/update. "
1096 + " NextObject Id {} on {} for subnet {} : {}",
1097 vlanMulticastNextId, deviceId, vlanId, error)
1098 );
1099 flowObjectiveService.next(deviceId, vlanMulticastNextObjBuilder.removeFromExisting(context));
1100 }
1101
1102 /**
1103 * Cleans up VLAN L2 multicast group on given deviceId. ACL rules for the group will also be deleted.
1104 * Normally multicast group is not removed if it contains access ports; which can be forced
1105 * by "force" flag
1106 *
1107 * @param deviceId Device ID
1108 * @param pairPort Pair port number
1109 * @param vlanId VLAN ID
1110 * @param force Forceful removal
1111 */
1112 private void cleanupL2MulticastRule(DeviceId deviceId, PortNumber pairPort, VlanId vlanId, boolean force) {
1113
1114 // Ensure enough rights to program pair device
1115 if (!srService.shouldProgram(deviceId)) {
pier6fd24fd2018-11-27 11:23:50 -08001116 log.debug("Abort cleanup L2Multicast {}-{}: {}", deviceId, vlanId, ERROR_NOT_LEADER);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301117 return;
1118 }
1119
pier6fd24fd2018-11-27 11:23:50 -08001120 VlanNextObjectiveStoreKey key = new VlanNextObjectiveStoreKey(deviceId, vlanId);
1121
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301122 // Ensure L2 multicast group doesn't contain access ports
pier6fd24fd2018-11-27 11:23:50 -08001123 if (hasAccessPortInMulticastGroup(key, pairPort) && !force) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301124 return;
1125 }
1126
1127 // Load L2 multicast group details
pier6fd24fd2018-11-27 11:23:50 -08001128 int vlanMulticastNextId = getMulticastGroupNextObjectiveId(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301129 if (vlanMulticastNextId == -1) {
1130 return;
1131 }
1132
1133 // Step 1 : Clear ACL rule; selector = vlan + pair-port, output = vlan L2 multicast group
1134 TrafficSelector.Builder l2MulticastSelector = DefaultTrafficSelector.builder();
1135 l2MulticastSelector.matchEthType(Ethernet.TYPE_VLAN);
1136 l2MulticastSelector.matchInPort(pairPort);
1137 l2MulticastSelector.matchVlanId(vlanId);
1138 ForwardingObjective.Builder vlanMulticastForwardingObj = DefaultForwardingObjective.builder()
1139 .withFlag(ForwardingObjective.Flag.VERSATILE)
1140 .nextStep(vlanMulticastNextId)
1141 .withSelector(l2MulticastSelector.build())
1142 .withPriority(100)
1143 .fromApp(srService.appId())
1144 .makePermanent();
1145 ObjectiveContext context = new DefaultObjectiveContext(
1146 (objective) -> log.debug("L2 multicasting rule for device {}, port/vlan {}/{} deleted", deviceId,
1147 pairPort, vlanId),
1148 (objective, error) -> log.warn("Failed to delete L2 multicasting rule for device {}, " +
1149 "ports/vlan {}/{}: {}", deviceId, pairPort, vlanId, error));
1150 flowObjectiveService.forward(deviceId, vlanMulticastForwardingObj.remove(context));
1151
1152 // Step 2 : Clear L2 multicast group associated with vlan
1153 NextObjective.Builder l2MulticastGroupBuilder = DefaultNextObjective
1154 .builder()
1155 .withId(vlanMulticastNextId)
1156 .withType(NextObjective.Type.BROADCAST)
1157 .fromApp(srService.appId())
1158 .withMeta(DefaultTrafficSelector.builder()
1159 .matchVlanId(vlanId)
1160 .matchEthDst(MacAddress.IPV4_MULTICAST).build())
1161 .addTreatment(DefaultTrafficTreatment.builder().popVlan().setOutput(pairPort).build());
1162 context = new DefaultObjectiveContext(
1163 (objective) ->
1164 log.debug("L2 multicast group with NextObject Id {} deleted on {} for subnet {} ",
1165 vlanMulticastNextId, deviceId, vlanId),
1166 (objective, error) ->
1167 log.warn("L2 multicast group with NextObject Id {} failed to delete on {} for subnet {} : {}",
1168 vlanMulticastNextId, deviceId, vlanId, error)
1169 );
1170 flowObjectiveService.next(deviceId, l2MulticastGroupBuilder.remove(context));
1171
1172 // Finally clear store.
pier6fd24fd2018-11-27 11:23:50 -08001173 removeMulticastGroup(key);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301174 }
1175
pier6fd24fd2018-11-27 11:23:50 -08001176 private int getMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key) {
1177 return Versioned.valueOrElse(xconnectMulticastNextStore.get(key), -1);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301178 }
1179
pier6fd24fd2018-11-27 11:23:50 -08001180 private void addMulticastGroupNextObjectiveId(VlanNextObjectiveStoreKey key, int nextId) {
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301181 if (nextId == -1) {
1182 return;
1183 }
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301184 xconnectMulticastNextStore.put(key, nextId);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301185 }
1186
pier6fd24fd2018-11-27 11:23:50 -08001187 private void addMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1188 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1189 if (ports == null) {
1190 ports = Lists.newArrayList();
1191 }
1192 ports.add(port);
1193 return ports;
1194 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301195 }
1196
pier6fd24fd2018-11-27 11:23:50 -08001197 private void removeMulticastGroupPort(VlanNextObjectiveStoreKey groupKey, PortNumber port) {
1198 xconnectMulticastPortsStore.compute(groupKey, (key, ports) -> {
1199 if (ports != null && !ports.isEmpty()) {
1200 ports.remove(port);
1201 }
1202 return ports;
1203 });
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301204 }
1205
pier6fd24fd2018-11-27 11:23:50 -08001206 private void removeMulticastGroup(VlanNextObjectiveStoreKey groupKey) {
1207 xconnectMulticastPortsStore.remove(groupKey);
1208 xconnectMulticastNextStore.remove(groupKey);
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301209 }
1210
pier6fd24fd2018-11-27 11:23:50 -08001211 private boolean hasAccessPortInMulticastGroup(VlanNextObjectiveStoreKey groupKey, PortNumber pairPort) {
1212 List<PortNumber> ports = Versioned.valueOrElse(xconnectMulticastPortsStore.get(groupKey), ImmutableList.of());
jayakumarthazhath66c9ec12018-10-01 00:51:54 +05301213 return ports.stream().anyMatch(p -> !p.equals(pairPort));
1214 }
1215
pier6fd24fd2018-11-27 11:23:50 -08001216 // Custom-built function, when the device is not available we need a fallback mechanism
1217 private boolean isLocalLeader(DeviceId deviceId) {
1218 if (!mastershipService.isLocalMaster(deviceId)) {
1219 // When the device is available we just check the mastership
1220 if (deviceService.isAvailable(deviceId)) {
1221 return false;
1222 }
1223 // Fallback with Leadership service - device id is used as topic
1224 NodeId leader = leadershipService.runForLeadership(
1225 deviceId.toString()).leaderNodeId();
1226 // Verify if this node is the leader
1227 return clusterService.getLocalNode().id().equals(leader);
1228 }
1229 return true;
1230 }
1231
Charles Chan48df9ad2018-10-30 18:08:59 -07001232 private Set<PortNumber> getPhysicalPorts(DeviceId deviceId, String port) {
1233 // If port is numeric, treat it as regular port.
1234 // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
1235 try {
1236 return Sets.newHashSet(PortNumber.portNumber(Integer.parseInt(port)));
1237 } catch (NumberFormatException e) {
1238 log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
1239 }
1240
1241 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
Charles Chanb15096f2019-01-01 19:36:05 -08001242 L2LbId l2LbId = new L2LbId(deviceId, Integer.parseInt(l2LbKey));
Charles Chan48df9ad2018-10-30 18:08:59 -07001243 try {
Charles Chanb15096f2019-01-01 19:36:05 -08001244 return Sets.newHashSet(l2LbService.getL2Lb(l2LbId).ports());
Charles Chan48df9ad2018-10-30 18:08:59 -07001245 } catch (NumberFormatException e) {
1246 log.debug("Port {} is not load balancer key either. Ignore", port);
1247 } catch (NullPointerException e) {
1248 log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
1249 }
1250
1251 return Sets.newHashSet();
1252 }
1253
pier567465b2018-11-24 11:16:28 -08001254 private NextTreatment getNextTreatment(DeviceId deviceId, String port, boolean reserve) {
Charles Chan48df9ad2018-10-30 18:08:59 -07001255 // If port is numeric, treat it as regular port.
1256 // Otherwise try to parse it as load balancer key and get the physical port the LB maps to.
1257 try {
1258 PortNumber portNumber = PortNumber.portNumber(Integer.parseInt(port));
1259 return DefaultNextTreatment.of(DefaultTrafficTreatment.builder().setOutput(portNumber).build());
1260 } catch (NumberFormatException e) {
1261 log.debug("Port {} is not numeric. Try to parse it as load balancer key", port);
1262 }
1263
1264 String l2LbKey = port.substring("L2LB(".length(), port.length() - 1);
1265 try {
Charles Chanb15096f2019-01-01 19:36:05 -08001266 L2LbId l2LbId = new L2LbId(deviceId, Integer.parseInt(l2LbKey));
pier567465b2018-11-24 11:16:28 -08001267 NextTreatment idNextTreatment = IdNextTreatment.of(
Charles Chanb15096f2019-01-01 19:36:05 -08001268 l2LbService.getL2LbNext(l2LbId));
pier567465b2018-11-24 11:16:28 -08001269 // Reserve only one time during next objective creation
1270 if (reserve) {
pier567465b2018-11-24 11:16:28 -08001271 if (!l2LbService.reserve(new L2LbId(deviceId, Integer.parseInt(l2LbKey)), appId)) {
1272 log.warn("Reservation failed for {}", l2LbId);
1273 idNextTreatment = null;
1274 }
1275 }
1276 return idNextTreatment;
Charles Chan48df9ad2018-10-30 18:08:59 -07001277 } catch (NumberFormatException e) {
1278 log.debug("Port {} is not load balancer key either. Ignore", port);
1279 } catch (NullPointerException e) {
1280 log.debug("L2 load balancer {} not found. Ignore", l2LbKey);
1281 }
1282
1283 return null;
1284 }
pier567465b2018-11-24 11:16:28 -08001285
1286 private boolean isL2LbKey(String l2LbKey) {
1287 return l2LbKey.matches(L2LB_PATTERN);
1288 }
1289
1290 private class InternalL2LbListener implements L2LbListener {
1291 // Populate xconnect once l2lb is available
1292 @Override
1293 public void event(L2LbEvent event) {
1294 l2lbExecutor.execute(() -> dequeue(event.subject().l2LbId()));
1295 }
1296 // When we receive INSTALLED l2 load balancing is ready
1297 @Override
1298 public boolean isRelevant(L2LbEvent event) {
1299 return event.type() == L2LbEvent.Type.INSTALLED;
1300 }
1301 }
1302
1303 // Invalidate the cache and re-start the xconnect installation
1304 private void dequeue(L2LbId l2LbId) {
1305 XconnectKey xconnectKey = l2LbCache.getIfPresent(l2LbId);
1306 if (xconnectKey == null) {
1307 log.trace("{} not present in the cache", l2LbId);
1308 return;
1309 }
1310 log.debug("Dequeue {}", l2LbId);
1311 l2LbCache.invalidate(l2LbId);
1312 Set<String> ports = Versioned.valueOrNull(xconnectStore.get(xconnectKey));
1313 if (ports == null || ports.isEmpty()) {
1314 log.warn("Ports not found for XConnect {}", xconnectKey);
1315 return;
1316 }
1317 populateXConnect(xconnectKey, ports);
1318 log.trace("L2Lb cache size {}", l2LbCache.size());
1319 }
1320
Charles Chan8d316332018-06-19 20:31:57 -07001321}