blob: 674121d3e3a1ea3d3768bf867d07d477f42a809e [file] [log] [blame]
alshabib3b1eadc2016-02-01 17:57:00 -08001/*
Brian O'Connorcf85aa82017-08-03 22:46:01 -07002 * Copyright 2016-present Open Networking Foundation
alshabib3b1eadc2016-02-01 17:57:00 -08003 *
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 */
Daniele Moro8ea9e102020-03-24 18:56:52 +010016package org.opencord.cordmcast.impl;
alshabib3b1eadc2016-02-01 17:57:00 -080017
Esin Karaman39b24852019-08-28 13:57:30 +000018import com.google.common.collect.ImmutableSet;
19import com.google.common.collect.Sets;
alshabib09069c92016-02-21 14:49:51 -080020import org.apache.commons.lang3.tuple.ImmutablePair;
alshabib3b1eadc2016-02-01 17:57:00 -080021import org.onlab.packet.Ethernet;
alshabib3b1eadc2016-02-01 17:57:00 -080022import org.onlab.packet.IpAddress;
23import org.onlab.packet.VlanId;
Esin Karaman39b24852019-08-28 13:57:30 +000024import org.onlab.util.KryoNamespace;
Jonathan Hart28271642016-02-10 16:13:54 -080025import org.onosproject.cfg.ComponentConfigService;
Esin Karaman39b24852019-08-28 13:57:30 +000026import org.onosproject.cluster.ClusterService;
27import org.onosproject.cluster.LeadershipService;
28import org.onosproject.cluster.NodeId;
alshabib3b1eadc2016-02-01 17:57:00 -080029import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
Esin Karaman39b24852019-08-28 13:57:30 +000031import org.onosproject.mastership.MastershipService;
32import org.onosproject.mcast.api.McastEvent;
33import org.onosproject.mcast.api.McastListener;
34import org.onosproject.mcast.api.McastRoute;
35import org.onosproject.mcast.api.MulticastRouteService;
alshabib3b1eadc2016-02-01 17:57:00 -080036import org.onosproject.net.ConnectPoint;
Daniele Moro8ea9e102020-03-24 18:56:52 +010037import org.onosproject.net.Device;
ke han9590c812017-02-28 15:02:26 +080038import org.onosproject.net.DeviceId;
Esin Karaman39b24852019-08-28 13:57:30 +000039import org.onosproject.net.HostId;
ke han9590c812017-02-28 15:02:26 +080040import org.onosproject.net.PortNumber;
ke hanf1709e82016-08-12 10:48:17 +080041import org.onosproject.net.config.ConfigFactory;
42import org.onosproject.net.config.NetworkConfigEvent;
43import org.onosproject.net.config.NetworkConfigListener;
44import org.onosproject.net.config.NetworkConfigRegistry;
Esin Karaman39b24852019-08-28 13:57:30 +000045import org.onosproject.net.config.basics.McastConfig;
ke hanf1709e82016-08-12 10:48:17 +080046import org.onosproject.net.config.basics.SubjectFactories;
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +020047import org.onosproject.net.device.DeviceEvent;
48import org.onosproject.net.device.DeviceListener;
Esin Karaman39b24852019-08-28 13:57:30 +000049import org.onosproject.net.device.DeviceService;
alshabib3b1eadc2016-02-01 17:57:00 -080050import org.onosproject.net.flow.DefaultTrafficSelector;
51import org.onosproject.net.flow.DefaultTrafficTreatment;
52import org.onosproject.net.flow.TrafficSelector;
53import org.onosproject.net.flowobjective.DefaultForwardingObjective;
54import org.onosproject.net.flowobjective.DefaultNextObjective;
Esin Karaman39b24852019-08-28 13:57:30 +000055import org.onosproject.net.flowobjective.DefaultObjectiveContext;
alshabib3b1eadc2016-02-01 17:57:00 -080056import org.onosproject.net.flowobjective.FlowObjectiveService;
57import org.onosproject.net.flowobjective.ForwardingObjective;
58import org.onosproject.net.flowobjective.NextObjective;
59import org.onosproject.net.flowobjective.Objective;
60import org.onosproject.net.flowobjective.ObjectiveContext;
61import org.onosproject.net.flowobjective.ObjectiveError;
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +020062import org.onosproject.net.group.GroupService;
Esin Karaman39b24852019-08-28 13:57:30 +000063import org.onosproject.store.serializers.KryoNamespaces;
64import org.onosproject.store.service.ConsistentMap;
65import org.onosproject.store.service.Serializer;
66import org.onosproject.store.service.StorageService;
67import org.onosproject.store.service.Versioned;
Daniele Moro8ea9e102020-03-24 18:56:52 +010068import org.opencord.cordmcast.CordMcastService;
69import org.opencord.cordmcast.CordMcastStatisticsService;
70import org.opencord.sadis.SadisService;
71import org.opencord.sadis.SubscriberAndDeviceInformation;
Jonathan Hart28271642016-02-10 16:13:54 -080072import org.osgi.service.component.ComponentContext;
Daniele Moro8ea9e102020-03-24 18:56:52 +010073import org.osgi.service.component.annotations.Activate;
74import org.osgi.service.component.annotations.Component;
75import org.osgi.service.component.annotations.Deactivate;
76import org.osgi.service.component.annotations.Modified;
77import org.osgi.service.component.annotations.Reference;
78import org.osgi.service.component.annotations.ReferenceCardinality;
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +000079import org.osgi.service.component.annotations.ReferencePolicy;
alshabib3b1eadc2016-02-01 17:57:00 -080080import org.slf4j.Logger;
81
Jonathan Hart28271642016-02-10 16:13:54 -080082import java.util.Dictionary;
alshabib3b1eadc2016-02-01 17:57:00 -080083import java.util.Map;
ke han9590c812017-02-28 15:02:26 +080084import java.util.Objects;
Jonathan Hart0c194962016-05-23 17:08:15 -070085import java.util.Optional;
alshabibfc1cb032016-02-17 15:37:56 -080086import java.util.Properties;
Esin Karaman39b24852019-08-28 13:57:30 +000087import java.util.Set;
88import java.util.concurrent.ExecutorService;
89import java.util.concurrent.locks.Lock;
90import java.util.concurrent.locks.ReentrantLock;
Andrea Campanella03652352020-05-06 16:12:00 +020091import java.util.function.Consumer;
alshabib3b1eadc2016-02-01 17:57:00 -080092
alshabibfc1cb032016-02-17 15:37:56 -080093import static com.google.common.base.Strings.isNullOrEmpty;
Esin Karaman39b24852019-08-28 13:57:30 +000094import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
alshabibfc1cb032016-02-17 15:37:56 -080095import static org.onlab.util.Tools.get;
Esin Karaman39b24852019-08-28 13:57:30 +000096import static org.onlab.util.Tools.groupedThreads;
Daniele Moro8ea9e102020-03-24 18:56:52 +010097import static org.opencord.cordmcast.impl.OsgiPropertyConstants.*;
alshabib3b1eadc2016-02-01 17:57:00 -080098import static org.slf4j.LoggerFactory.getLogger;
99
Esin Karaman39b24852019-08-28 13:57:30 +0000100
alshabib3b1eadc2016-02-01 17:57:00 -0800101/**
Jonathan Hartc3f84eb2016-02-19 12:44:36 -0800102 * CORD multicast provisioning application. Operates by listening to
Jonathan Hart28271642016-02-10 16:13:54 -0800103 * events on the multicast rib and provisioning groups to program multicast
alshabib3b1eadc2016-02-01 17:57:00 -0800104 * flows on the dataplane.
105 */
Carmelo Cascone995fd682019-11-14 14:22:39 -0800106@Component(immediate = true,
107 property = {
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000108 VLAN_ENABLED + ":Boolean=" + DEFAULT_VLAN_ENABLED,
109 PRIORITY + ":Integer=" + DEFAULT_PRIORITY,
110 })
Daniele Moro8ea9e102020-03-24 18:56:52 +0100111public class CordMcast implements CordMcastService {
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000112 private static final String MCAST_NOT_RUNNING = "Multicast is not running.";
113 private static final String SADIS_NOT_RUNNING = "Sadis is not running.";
Daniele Moro8ea9e102020-03-24 18:56:52 +0100114 private static final String APP_NAME = "org.opencord.mcast";
alshabib3b1eadc2016-02-01 17:57:00 -0800115
Jonathan Hart0c194962016-05-23 17:08:15 -0700116 private final Logger log = getLogger(getClass());
alshabib09069c92016-02-21 14:49:51 -0800117
alshabib09069c92016-02-21 14:49:51 -0800118 private static final int DEFAULT_PRIORITY = 500;
alshabib3b1eadc2016-02-01 17:57:00 -0800119 private static final short DEFAULT_MCAST_VLAN = 4000;
alshabibfc1cb032016-02-17 15:37:56 -0800120
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000121 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
122 bind = "bindMcastRouteService",
123 unbind = "unbindMcastRouteService",
124 policy = ReferencePolicy.DYNAMIC)
125 protected volatile MulticastRouteService mcastService;
alshabib3b1eadc2016-02-01 17:57:00 -0800126
Carmelo Cascone995fd682019-11-14 14:22:39 -0800127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabib3b1eadc2016-02-01 17:57:00 -0800128 protected FlowObjectiveService flowObjectiveService;
129
Carmelo Cascone995fd682019-11-14 14:22:39 -0800130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabib3b1eadc2016-02-01 17:57:00 -0800131 protected CoreService coreService;
132
Carmelo Cascone995fd682019-11-14 14:22:39 -0800133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart28271642016-02-10 16:13:54 -0800134 protected ComponentConfigService componentConfigService;
135
Carmelo Cascone995fd682019-11-14 14:22:39 -0800136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke hanf1709e82016-08-12 10:48:17 +0800137 protected NetworkConfigRegistry networkConfig;
138
Carmelo Cascone995fd682019-11-14 14:22:39 -0800139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman39b24852019-08-28 13:57:30 +0000140 protected StorageService storageService;
141
Carmelo Cascone995fd682019-11-14 14:22:39 -0800142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman39b24852019-08-28 13:57:30 +0000143 protected MastershipService mastershipService;
144
Carmelo Cascone995fd682019-11-14 14:22:39 -0800145 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman39b24852019-08-28 13:57:30 +0000146 public DeviceService deviceService;
147
Carmelo Cascone995fd682019-11-14 14:22:39 -0800148 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman39b24852019-08-28 13:57:30 +0000149 private ClusterService clusterService;
150
Carmelo Cascone995fd682019-11-14 14:22:39 -0800151 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman39b24852019-08-28 13:57:30 +0000152 private LeadershipService leadershipService;
153
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000154 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
155 bind = "bindSadisService",
156 unbind = "unbindSadisService",
157 policy = ReferencePolicy.DYNAMIC)
158 protected volatile SadisService sadisService;
Esin Karaman996177c2020-03-05 13:21:09 +0000159
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
161 protected CordMcastStatisticsService cordMcastStatisticsService;
162
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200163 @Reference(cardinality = ReferenceCardinality.MANDATORY)
164 protected GroupService groupService;
165
alshabib3b1eadc2016-02-01 17:57:00 -0800166 protected McastListener listener = new InternalMulticastListener();
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000167
ke hanf1709e82016-08-12 10:48:17 +0800168 private InternalNetworkConfigListener configListener =
169 new InternalNetworkConfigListener();
alshabib3b1eadc2016-02-01 17:57:00 -0800170
Esin Karaman39b24852019-08-28 13:57:30 +0000171 private ConsistentMap<NextKey, NextContent> groups;
alshabib3b1eadc2016-02-01 17:57:00 -0800172
alshabib3b1eadc2016-02-01 17:57:00 -0800173 private ApplicationId appId;
ke hanf1709e82016-08-12 10:48:17 +0800174 private ApplicationId coreAppId;
Esin Karaman39b24852019-08-28 13:57:30 +0000175 private short mcastVlan = DEFAULT_MCAST_VLAN;
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000176 private VlanId mcastInnerVlan = VlanId.NONE;
alshabib3b1eadc2016-02-01 17:57:00 -0800177
Carmelo Cascone995fd682019-11-14 14:22:39 -0800178 /**
179 * Whether to use VLAN for multicast traffic.
180 **/
alshabib09069c92016-02-21 14:49:51 -0800181 private boolean vlanEnabled = DEFAULT_VLAN_ENABLED;
alshabibfc1cb032016-02-17 15:37:56 -0800182
Carmelo Cascone995fd682019-11-14 14:22:39 -0800183 /**
184 * Priority for multicast rules.
185 **/
alshabib3b1eadc2016-02-01 17:57:00 -0800186 private int priority = DEFAULT_PRIORITY;
187
ke hanf1709e82016-08-12 10:48:17 +0800188 private static final Class<McastConfig> CORD_MCAST_CONFIG_CLASS =
189 McastConfig.class;
190
191 private ConfigFactory<ApplicationId, McastConfig> cordMcastConfigFactory =
192 new ConfigFactory<ApplicationId, McastConfig>(
193 SubjectFactories.APP_SUBJECT_FACTORY, CORD_MCAST_CONFIG_CLASS, "multicast") {
194 @Override
195 public McastConfig createConfig() {
196 return new McastConfig();
197 }
198 };
Jonathan Hart28271642016-02-10 16:13:54 -0800199
Esin Karaman39b24852019-08-28 13:57:30 +0000200 // lock to synchronize local operations
201 private final Lock mcastLock = new ReentrantLock();
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000202
Esin Karaman39b24852019-08-28 13:57:30 +0000203 private void mcastLock() {
204 mcastLock.lock();
205 }
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000206
Esin Karaman39b24852019-08-28 13:57:30 +0000207 private void mcastUnlock() {
208 mcastLock.unlock();
209 }
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000210
Esin Karaman39b24852019-08-28 13:57:30 +0000211 private ExecutorService eventExecutor;
212
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200213 //Device listener to purge groups upon device disconnection.
214 private DeviceListener deviceListener = new InternalDeviceListener();
215
alshabib3b1eadc2016-02-01 17:57:00 -0800216 @Activate
Jonathan Hart435ffc42016-02-19 10:32:05 -0800217 public void activate(ComponentContext context) {
Jonathan Hartc3f84eb2016-02-19 12:44:36 -0800218 componentConfigService.registerProperties(getClass());
Jonathan Hart435ffc42016-02-19 10:32:05 -0800219 modified(context);
220
Charles Chanf867c4b2017-01-20 11:22:25 -0800221 appId = coreService.registerApplication(APP_NAME);
ke hanf1709e82016-08-12 10:48:17 +0800222 coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
Jonathan Hart28271642016-02-10 16:13:54 -0800223
Esin Karaman39b24852019-08-28 13:57:30 +0000224 eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("cord/mcast",
225 "events-mcast-%d", log));
226
227 KryoNamespace.Builder groupsKryo = new KryoNamespace.Builder()
228 .register(KryoNamespaces.API)
229 .register(NextKey.class)
230 .register(NextContent.class);
231 groups = storageService
232 .<NextKey, NextContent>consistentMapBuilder()
233 .withName("cord-mcast-groups-store")
234 .withSerializer(Serializer.using(groupsKryo.build("CordMcast-Groups")))
235 .build();
236
ke hanf1709e82016-08-12 10:48:17 +0800237 networkConfig.registerConfigFactory(cordMcastConfigFactory);
238 networkConfig.addListener(configListener);
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000239 if (mcastService != null) {
240 mcastService.addListener(listener);
241 mcastService.getRoutes().stream()
242 .map(r -> new ImmutablePair<>(r, mcastService.sinks(r)))
243 .filter(pair -> pair.getRight() != null && !pair.getRight().isEmpty())
244 .forEach(pair -> pair.getRight().forEach(sink -> addSink(pair.getLeft(),
245 sink)));
246 } else {
247 log.warn(MCAST_NOT_RUNNING);
248 }
ke hanf1709e82016-08-12 10:48:17 +0800249 McastConfig config = networkConfig.getConfig(coreAppId, CORD_MCAST_CONFIG_CLASS);
Esin Karaman39b24852019-08-28 13:57:30 +0000250 updateConfig(config);
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200251 deviceService.addListener(deviceListener);
alshabib3b1eadc2016-02-01 17:57:00 -0800252 log.info("Started");
253 }
254
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000255 @Modified
256 public void modified(ComponentContext context) {
257 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
258
259 String s = get(properties, VLAN_ENABLED);
260 vlanEnabled = isNullOrEmpty(s) ? DEFAULT_VLAN_ENABLED : Boolean.parseBoolean(s.trim());
261
262 try {
263 s = get(properties, PRIORITY);
264 priority = isNullOrEmpty(s) ? DEFAULT_PRIORITY : Integer.parseInt(s.trim());
265 } catch (NumberFormatException ne) {
266 log.error("Unable to parse configuration parameter for priority", ne);
267 priority = DEFAULT_PRIORITY;
268 }
Esin Karamane4890012020-04-19 11:58:54 +0000269 feedStatsServiceWithVlanConfigValues();
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000270 }
271
alshabib3b1eadc2016-02-01 17:57:00 -0800272 @Deactivate
273 public void deactivate() {
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200274 deviceService.removeListener(deviceListener);
Jonathan Hartc3f84eb2016-02-19 12:44:36 -0800275 componentConfigService.unregisterProperties(getClass(), false);
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000276 if (mcastService != null) {
277 mcastService.removeListener(listener);
278 }
ke hanf1709e82016-08-12 10:48:17 +0800279 networkConfig.removeListener(configListener);
ke han9590c812017-02-28 15:02:26 +0800280 networkConfig.unregisterConfigFactory(cordMcastConfigFactory);
Esin Karaman39b24852019-08-28 13:57:30 +0000281 eventExecutor.shutdown();
Andrea Campanella03652352020-05-06 16:12:00 +0200282 eventExecutor = null;
alshabib3b1eadc2016-02-01 17:57:00 -0800283 log.info("Stopped");
284 }
285
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000286 protected void bindSadisService(SadisService service) {
287 sadisService = service;
288 log.info("Sadis-service binds to onos.");
289 }
290
291 protected void unbindSadisService(SadisService service) {
292 sadisService = null;
293 log.info("Sadis-service unbinds from onos.");
294 }
295
296 protected void bindMcastRouteService(MulticastRouteService service) {
297 mcastService = service;
298 mcastService.addListener(listener);
299 log.info("Multicast route service binds to onos.");
300 }
301
302 protected void unbindMcastRouteService(MulticastRouteService service) {
303 service.removeListener(listener);
304 mcastService = null;
305 log.info("Multicast route service unbinds from onos.");
306 }
307
Esin Karamane4890012020-04-19 11:58:54 +0000308 /**
309 * Updates the stats service with current VLAN config values.
310 */
311 private void feedStatsServiceWithVlanConfigValues() {
312 cordMcastStatisticsService.setVlanValue(assignedVlan());
313 cordMcastStatisticsService.setInnerVlanValue(assignedInnerVlan());
314 }
315
Andrea Campanella03652352020-05-06 16:12:00 +0200316 private void clearGroups() {
Esin Karaman39b24852019-08-28 13:57:30 +0000317 mcastLock();
318 try {
319 groups.keySet().forEach(groupInfo -> {
Andrea Campanella03652352020-05-06 16:12:00 +0200320 NextContent next = groups.get(groupInfo).value();
Esin Karaman39b24852019-08-28 13:57:30 +0000321 if (!isLocalLeader(groupInfo.getDevice())) {
322 return;
323 }
Sonal Kasliwala0bbe6c2020-01-06 10:46:30 +0000324 if (next != null) {
Andrea Campanella03652352020-05-06 16:12:00 +0200325 //On Success of removing the fwd objective we remove also the group.
326 Consumer<Objective> onSuccess = (objective) -> {
327 log.debug("Successfully removed fwd objective for {} on {}, " +
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000328 "removing next objective {}", groupInfo.group,
329 groupInfo.getDevice(), next.getNextId());
Andrea Campanella03652352020-05-06 16:12:00 +0200330 eventExecutor.submit(() -> flowObjectiveService.next(groupInfo.getDevice(),
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000331 nextObject(next.getNextId(),
332 null,
333 NextType.Remove, groupInfo.group)));
Andrea Campanella03652352020-05-06 16:12:00 +0200334 };
335
336 ObjectiveContext context =
337 new DefaultObjectiveContext(onSuccess, (objective, error) ->
338 log.warn("Failed to remove {} on {}: {}",
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000339 groupInfo.group, next.getNextId(), error));
Sonal Kasliwala0bbe6c2020-01-06 10:46:30 +0000340 // remove the flow rule
Andrea Campanella03652352020-05-06 16:12:00 +0200341 flowObjectiveService.forward(groupInfo.getDevice(),
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000342 fwdObject(next.getNextId(),
343 groupInfo.group).remove(context));
Esin Karaman39b24852019-08-28 13:57:30 +0000344
Sonal Kasliwala0bbe6c2020-01-06 10:46:30 +0000345 }
Esin Karaman39b24852019-08-28 13:57:30 +0000346 });
Esin Karaman39b24852019-08-28 13:57:30 +0000347 } finally {
348 mcastUnlock();
349 }
350 }
351
352 private VlanId multicastVlan() {
353 return VlanId.vlanId(mcastVlan);
354 }
355
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000356 protected VlanId assignedVlan() {
Esin Karaman39b24852019-08-28 13:57:30 +0000357 return vlanEnabled ? multicastVlan() : VlanId.NONE;
ke han9590c812017-02-28 15:02:26 +0800358 }
359
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000360 protected VlanId assignedInnerVlan() {
361 return vlanEnabled ? mcastInnerVlan : VlanId.NONE;
362 }
363
alshabib3b1eadc2016-02-01 17:57:00 -0800364 private class InternalMulticastListener implements McastListener {
365 @Override
366 public void event(McastEvent event) {
Esin Karaman39b24852019-08-28 13:57:30 +0000367 eventExecutor.execute(() -> {
368 switch (event.type()) {
369 case ROUTE_ADDED:
370 case ROUTE_REMOVED:
371 case SOURCES_ADDED:
372 break;
373 case SINKS_ADDED:
374 addSinks(event);
375 break;
376 case SINKS_REMOVED:
377 removeSinks(event);
378 break;
379 default:
380 log.warn("Unknown mcast event {}", event.type());
381 }
382 });
383 }
384 }
385
386 /**
387 * Processes previous, and new sinks then finds the sinks to be removed.
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000388 *
Esin Karaman39b24852019-08-28 13:57:30 +0000389 * @param prevSinks the previous sinks to be evaluated
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000390 * @param newSinks the new sinks to be evaluated
Esin Karaman39b24852019-08-28 13:57:30 +0000391 * @returnt the set of the sinks to be removed
392 */
393 private Set<ConnectPoint> getSinksToBeRemoved(Map<HostId, Set<ConnectPoint>> prevSinks,
394 Map<HostId, Set<ConnectPoint>> newSinks) {
395 return getSinksToBeProcessed(prevSinks, newSinks);
396 }
397
398
399 /**
400 * Processes previous, and new sinks then finds the sinks to be added.
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000401 *
402 * @param newSinks the new sinks to be processed
Esin Karaman39b24852019-08-28 13:57:30 +0000403 * @param allPrevSinks all previous sinks
404 * @return the set of the sinks to be added
405 */
406 private Set<ConnectPoint> getSinksToBeAdded(Map<HostId, Set<ConnectPoint>> newSinks,
407 Map<HostId, Set<ConnectPoint>> allPrevSinks) {
408 return getSinksToBeProcessed(newSinks, allPrevSinks);
409 }
410
411 /**
412 * Gets single-homed sinks that are in set1 but not in set2.
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000413 *
Esin Karaman39b24852019-08-28 13:57:30 +0000414 * @param sinkSet1 the first sink map
415 * @param sinkSet2 the second sink map
416 * @return a set containing all the single-homed sinks found in set1 but not in set2
417 */
418 private Set<ConnectPoint> getSinksToBeProcessed(Map<HostId, Set<ConnectPoint>> sinkSet1,
419 Map<HostId, Set<ConnectPoint>> sinkSet2) {
420 final Set<ConnectPoint> sinksToBeProcessed = Sets.newHashSet();
421 sinkSet1.forEach(((hostId, connectPoints) -> {
422 if (HostId.NONE.equals(hostId)) {
423 //assume all connect points associated with HostId.NONE are single homed sinks
424 sinksToBeProcessed.addAll(connectPoints);
425 return;
426 }
427 }));
428 Set<ConnectPoint> singleHomedSinksOfSet2 = sinkSet2.get(HostId.NONE) == null ?
429 Sets.newHashSet() :
430 sinkSet2.get(HostId.NONE);
431 return Sets.difference(sinksToBeProcessed, singleHomedSinksOfSet2);
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000432 }
Esin Karaman39b24852019-08-28 13:57:30 +0000433
434
435 private void removeSinks(McastEvent event) {
436 mcastLock();
437 try {
438 Set<ConnectPoint> sinksToBeRemoved = getSinksToBeRemoved(event.prevSubject().sinks(),
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000439 event.subject().sinks());
Esin Karaman39b24852019-08-28 13:57:30 +0000440 sinksToBeRemoved.forEach(sink -> removeSink(event.subject().route().group(), sink));
441 } finally {
442 mcastUnlock();
443 }
444 }
445
446 private void removeSink(IpAddress group, ConnectPoint sink) {
447 if (!isLocalLeader(sink.deviceId())) {
448 log.debug("Not the leader of {}. Skip sink_removed event for the sink {} and group {}",
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000449 sink.deviceId(), sink, group);
Esin Karaman39b24852019-08-28 13:57:30 +0000450 return;
451 }
452
Esin Karaman996177c2020-03-05 13:21:09 +0000453 Optional<SubscriberAndDeviceInformation> oltInfo = getSubscriberAndDeviceInformation(sink.deviceId());
Esin Karaman39b24852019-08-28 13:57:30 +0000454
455 if (!oltInfo.isPresent()) {
456 log.warn("Unknown OLT device : {}", sink.deviceId());
457 return;
458 }
459
460 log.debug("Removing sink {} from the group {}", sink, group);
461
462 NextKey key = new NextKey(sink.deviceId(), group);
Esin Karamane9955542021-03-02 12:59:38 +0000463 if (groups.containsKey(key)) {
464 Versioned<NextContent> nextObj = groups.get(key);
Esin Karaman39b24852019-08-28 13:57:30 +0000465
Esin Karamane9955542021-03-02 12:59:38 +0000466 Set<PortNumber> outPorts = Sets.newHashSet(nextObj.value().getOutPorts());
Esin Karaman39b24852019-08-28 13:57:30 +0000467 outPorts.remove(sink.port());
468
469 if (outPorts.isEmpty()) {
Andrea Campanella03652352020-05-06 16:12:00 +0200470 log.debug("No more output ports for group {}, removing next and fwd objectives", group);
471
472 //On Success of removing the fwd objective we remove also the group.
473 Consumer<Objective> onSuccess = (objective) -> {
474 log.debug("Successfully removed fwd objective for {} on {}, " +
Esin Karamane9955542021-03-02 12:59:38 +0000475 "removing next objective {}", group, sink, nextObj.value().getNextId());
Andrea Campanella03652352020-05-06 16:12:00 +0200476 eventExecutor.execute(() -> {
477 //No port is needed since it's a remove Operation
Esin Karamane9955542021-03-02 12:59:38 +0000478 flowObjectiveService.next(sink.deviceId(), nextObject(nextObj.value().getNextId(),
Andrea Campanella03652352020-05-06 16:12:00 +0200479 null,
480 NextType.Remove, group));
481 });
482 };
483
Esin Karaman39b24852019-08-28 13:57:30 +0000484 // this is the last sink
Andrea Campanella03652352020-05-06 16:12:00 +0200485 ObjectiveContext context = new DefaultObjectiveContext(onSuccess,
Esin Karaman39b24852019-08-28 13:57:30 +0000486 (objective, error) -> log.warn("Failed to remove {} on {}: {}",
487 group, sink, error));
Esin Karamane9955542021-03-02 12:59:38 +0000488 ForwardingObjective fwdObj = fwdObject(nextObj.value().getNextId(), group).remove(context);
Esin Karaman39b24852019-08-28 13:57:30 +0000489 flowObjectiveService.forward(sink.deviceId(), fwdObj);
Esin Karamane9955542021-03-02 12:59:38 +0000490 // remove the whole entity if no out port exists in the port list
491 groups.remove(key);
Andrea Campanella03652352020-05-06 16:12:00 +0200492 } else {
493 log.debug("Group {} has remaining {} ports, removing just {} " +
494 "from it's sinks", group, outPorts, sink.port());
Esin Karamane9955542021-03-02 12:59:38 +0000495 flowObjectiveService.next(sink.deviceId(), nextObject(nextObj.value().getNextId(), sink.port(),
Andrea Campanella03652352020-05-06 16:12:00 +0200496 NextType.RemoveFromExisting, group));
Esin Karamane9955542021-03-02 12:59:38 +0000497 groups.put(key, new NextContent(nextObj.value().getNextId(), ImmutableSet.copyOf(outPorts)));
Esin Karaman39b24852019-08-28 13:57:30 +0000498 }
Esin Karamane9955542021-03-02 12:59:38 +0000499 }
Esin Karaman39b24852019-08-28 13:57:30 +0000500 }
501
502 private void addSinks(McastEvent event) {
503 mcastLock();
504 try {
505 Set<ConnectPoint> sinksToBeAdded = getSinksToBeAdded(event.subject().sinks(),
506 event.prevSubject().sinks());
507 sinksToBeAdded.forEach(sink -> addSink(event.subject().route(), sink));
508 } finally {
509 mcastUnlock();
510 }
511 }
512
513 private void addSink(McastRoute route, ConnectPoint sink) {
Arjun E Kabf9e6e2020-03-02 10:15:21 +0000514
Esin Karaman39b24852019-08-28 13:57:30 +0000515 if (!isLocalLeader(sink.deviceId())) {
516 log.debug("Not the leader of {}. Skip sink_added event for the sink {} and group {}",
517 sink.deviceId(), sink, route.group());
518 return;
519 }
520
Esin Karaman996177c2020-03-05 13:21:09 +0000521 Optional<SubscriberAndDeviceInformation> oltInfo = getSubscriberAndDeviceInformation(sink.deviceId());
Esin Karaman39b24852019-08-28 13:57:30 +0000522
523 if (!oltInfo.isPresent()) {
524 log.warn("Unknown OLT device : {}", sink.deviceId());
525 return;
526 }
527
528 log.debug("Adding sink {} to the group {}", sink, route.group());
529
530 NextKey key = new NextKey(sink.deviceId(), route.group());
531 NextObjective newNextObj;
532
533 boolean theFirstSinkOfGroup = false;
534 if (!groups.containsKey(key)) {
535 // First time someone request this mcast group via this device
536 Integer nextId = flowObjectiveService.allocateNextId();
537 newNextObj = nextObject(nextId, sink.port(), NextType.AddNew, route.group());
538 // Store the new port
539 groups.put(key, new NextContent(nextId, ImmutableSet.of(sink.port())));
540 theFirstSinkOfGroup = true;
541 } else {
542 // This device already serves some subscribers of this mcast group
543 Versioned<NextContent> nextObj = groups.get(key);
544 if (nextObj.value().getOutPorts().contains(sink.port())) {
545 log.info("Group {} already serves the sink connected to {}", route.group(), sink);
546 return;
547 }
548 newNextObj = nextObject(nextObj.value().getNextId(), sink.port(),
549 NextType.AddToExisting, route.group());
550 // add new port to the group
551 Set<PortNumber> outPorts = Sets.newHashSet(nextObj.value().getOutPorts());
552 outPorts.add(sink.port());
553 groups.put(key, new NextContent(newNextObj.id(), ImmutableSet.copyOf(outPorts)));
554 }
555
556 ObjectiveContext context = new DefaultObjectiveContext(
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000557 (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}, inner vlan {}",
Esin Karaman39b24852019-08-28 13:57:30 +0000558 route.group(), sink.deviceId(), sink.port().toLong(),
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000559 assignedVlan(), assignedInnerVlan()),
Esin Karaman39b24852019-08-28 13:57:30 +0000560 (objective, error) -> {
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000561 log.warn("Failed to add {} on {}/{}, vlan {}, inner vlan {}: {}",
Esin Karaman39b24852019-08-28 13:57:30 +0000562 route.group(), sink.deviceId(), sink.port().toLong(), assignedVlan(),
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000563 assignedInnerVlan(), error);
Esin Karaman39b24852019-08-28 13:57:30 +0000564 });
565
566 flowObjectiveService.next(sink.deviceId(), newNextObj);
567
568 if (theFirstSinkOfGroup) {
569 // create the necessary flow rule if this is the first sink request for the group
570 // on this device
571 flowObjectiveService.forward(sink.deviceId(), fwdObject(newNextObj.id(),
572 route.group()).add(context));
573 }
574 }
575
Esin Karaman996177c2020-03-05 13:21:09 +0000576 /**
577 * Fetches device information associated with the device serial number from SADIS.
578 *
579 * @param serialNumber serial number of a device
580 * @return device information; an empty Optional otherwise.
581 */
582 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(String serialNumber) {
583 long start = System.currentTimeMillis();
584 try {
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000585 if (sadisService == null) {
586 log.warn(SADIS_NOT_RUNNING);
587 return Optional.empty();
588 }
Esin Karaman996177c2020-03-05 13:21:09 +0000589 return Optional.ofNullable(sadisService.getSubscriberInfoService().get(serialNumber));
590 } finally {
591 if (log.isDebugEnabled()) {
592 // SADIS may call remote systems to fetch device data and this calls can take a long time.
593 // This measurement is just for monitoring these kinds of situations.
594 log.debug("Device fetched from SADIS. Elapsed {} msec", System.currentTimeMillis() - start);
595 }
596
597 }
598 }
599
600 /**
601 * Fetches device information associated with the device serial number from SADIS.
602 *
603 * @param deviceId device id
604 * @return device information; an empty Optional otherwise.
605 */
606 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(DeviceId deviceId) {
607 Device device = deviceService.getDevice(deviceId);
608 if (device == null || device.serialNumber() == null) {
609 return Optional.empty();
610 }
611 return getSubscriberAndDeviceInformation(device.serialNumber());
612 }
613
Esin Karaman39b24852019-08-28 13:57:30 +0000614 private class InternalNetworkConfigListener implements NetworkConfigListener {
615 @Override
616 public void event(NetworkConfigEvent event) {
617 eventExecutor.execute(() -> {
618 switch (event.type()) {
619
620 case CONFIG_ADDED:
621 case CONFIG_UPDATED:
622 if (event.configClass().equals(CORD_MCAST_CONFIG_CLASS)) {
623 McastConfig config = networkConfig.getConfig(coreAppId, CORD_MCAST_CONFIG_CLASS);
624 if (config != null) {
625 //TODO: Simply remove flows/groups, hosts will response period query
626 // and re-sent IGMP report, so the flows can be rebuild.
627 // However, better to remove and re-add mcast flow rules here
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000628 if (vlanEnabled && (mcastVlan != config.egressVlan().toShort() ||
629 !mcastInnerVlan.equals(config.egressInnerVlan()))) {
Esin Karaman39b24852019-08-28 13:57:30 +0000630 clearGroups();
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200631 groups.clear();
Esin Karaman39b24852019-08-28 13:57:30 +0000632 }
633 updateConfig(config);
634 }
635 }
636 break;
637 case CONFIG_REGISTERED:
638 case CONFIG_UNREGISTERED:
639 case CONFIG_REMOVED:
640 break;
641 default:
642 break;
643 }
644 });
645 }
646 }
647
648 private void updateConfig(McastConfig config) {
649 if (config == null) {
650 return;
651 }
652 log.debug("multicast config received: {}", config);
653
654 if (config.egressVlan() != null) {
655 mcastVlan = config.egressVlan().toShort();
656 }
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000657 if (config.egressInnerVlan() != null) {
658 mcastInnerVlan = config.egressInnerVlan();
659 }
Esin Karamane4890012020-04-19 11:58:54 +0000660 feedStatsServiceWithVlanConfigValues();
Esin Karaman39b24852019-08-28 13:57:30 +0000661 }
662
663 private class NextKey {
664 private DeviceId device;
665 private IpAddress group;
666
667 public NextKey(DeviceId deviceId, IpAddress groupAddress) {
668 device = deviceId;
669 group = groupAddress;
670 }
671
672 public DeviceId getDevice() {
673 return device;
674 }
675
676 public int hashCode() {
677 return Objects.hash(this.device, this.group);
678 }
679
680 public boolean equals(Object obj) {
681 if (this == obj) {
682 return true;
683 } else if (!(obj instanceof NextKey)) {
684 return false;
685 } else {
686 NextKey that = (NextKey) obj;
687 return this.getClass() == that.getClass() &&
688 Objects.equals(this.device, that.device) &&
689 Objects.equals(this.group, that.group);
690 }
691 }
692 }
693
694 private class NextContent {
695 private Integer nextId;
696 private Set<PortNumber> outPorts;
697
698 public NextContent(Integer nextId, Set<PortNumber> outPorts) {
699 this.nextId = nextId;
700 this.outPorts = outPorts;
701 }
702
703 public Integer getNextId() {
704 return nextId;
705 }
706
707 public Set<PortNumber> getOutPorts() {
708 return ImmutableSet.copyOf(outPorts);
709 }
710
711 public int hashCode() {
712 return Objects.hash(this.nextId, this.outPorts);
713 }
714
715 public boolean equals(Object obj) {
716 if (this == obj) {
717 return true;
718 } else if (!(obj instanceof NextContent)) {
719 return false;
720 } else {
721 NextContent that = (NextContent) obj;
722 return this.getClass() == that.getClass() &&
723 Objects.equals(this.nextId, that.nextId) &&
724 Objects.equals(this.outPorts, that.outPorts);
alshabib3b1eadc2016-02-01 17:57:00 -0800725 }
726 }
727 }
728
Ilayda Ozdemir2fccbce2021-02-23 15:36:47 +0000729 private enum NextType { AddNew, AddToExisting, Remove, RemoveFromExisting }
ke han9590c812017-02-28 15:02:26 +0800730
Esin Karaman39b24852019-08-28 13:57:30 +0000731 private NextObjective nextObject(Integer nextId, PortNumber port,
732 NextType nextType, IpAddress mcastIp) {
ke han9590c812017-02-28 15:02:26 +0800733
Esin Karaman39b24852019-08-28 13:57:30 +0000734 // Build the meta selector with the fwd objective info
735 TrafficSelector.Builder metadata = DefaultTrafficSelector.builder()
736 .matchIPDst(mcastIp.toIpPrefix());
737
738 if (vlanEnabled) {
739 metadata.matchVlanId(multicastVlan());
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000740
741 if (!mcastInnerVlan.equals(VlanId.NONE)) {
742 metadata.matchInnerVlanId(mcastInnerVlan);
743 }
Esin Karaman39b24852019-08-28 13:57:30 +0000744 }
745
Andrea Campanella03652352020-05-06 16:12:00 +0200746 DefaultNextObjective.Builder builder = DefaultNextObjective.builder()
ke han9590c812017-02-28 15:02:26 +0800747 .fromApp(appId)
ke han9590c812017-02-28 15:02:26 +0800748 .withType(NextObjective.Type.BROADCAST)
Esin Karaman39b24852019-08-28 13:57:30 +0000749 .withId(nextId)
750 .withMeta(metadata.build());
751
Andrea Campanella03652352020-05-06 16:12:00 +0200752 if (port == null && !nextType.equals(NextType.Remove)) {
753 log.error("Port can't be null with operation {}", nextType);
754 return null;
755 } else if (port != null && !nextType.equals(NextType.Remove)) {
756 builder.addTreatment(DefaultTrafficTreatment.builder().setOutput(port).build());
757 }
758
759 ObjectiveContext context = new ObjectiveContext() {
ke han9590c812017-02-28 15:02:26 +0800760 @Override
761 public void onSuccess(Objective objective) {
Andrea Campanella03652352020-05-06 16:12:00 +0200762 log.debug("Success for operation {} on Next Objective {}", objective.id(), nextType);
ke han9590c812017-02-28 15:02:26 +0800763 }
764
765 @Override
766 public void onError(Objective objective, ObjectiveError error) {
Esin Karaman39b24852019-08-28 13:57:30 +0000767 log.debug("Next Objective {} failed, because {}",
768 objective.id(),
769 error);
ke han9590c812017-02-28 15:02:26 +0800770 }
771 };
772
773 switch (nextType) {
774 case AddNew:
Andrea Campanella03652352020-05-06 16:12:00 +0200775 return builder.add(context);
ke han9590c812017-02-28 15:02:26 +0800776 case AddToExisting:
Andrea Campanella03652352020-05-06 16:12:00 +0200777 return builder.addToExisting(context);
ke han9590c812017-02-28 15:02:26 +0800778 case Remove:
Andrea Campanella03652352020-05-06 16:12:00 +0200779 return builder.remove(context);
ke han9590c812017-02-28 15:02:26 +0800780 case RemoveFromExisting:
Andrea Campanella03652352020-05-06 16:12:00 +0200781 return builder.removeFromExisting(context);
ke han9590c812017-02-28 15:02:26 +0800782 default:
783 return null;
784 }
785 }
786
Esin Karaman39b24852019-08-28 13:57:30 +0000787 private ForwardingObjective.Builder fwdObject(int nextId, IpAddress mcastIp) {
788 TrafficSelector.Builder mcast = DefaultTrafficSelector.builder()
789 .matchEthType(Ethernet.TYPE_IPV4)
790 .matchIPDst(mcastIp.toIpPrefix());
ke han9590c812017-02-28 15:02:26 +0800791
Esin Karaman39b24852019-08-28 13:57:30 +0000792 //build the meta selector
793 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
794 if (vlanEnabled) {
795 metabuilder.matchVlanId(multicastVlan());
Esin Karamanbb35a3b2020-03-18 13:53:24 +0000796
797 if (!mcastInnerVlan.equals(VlanId.NONE)) {
798 metabuilder.matchInnerVlanId(mcastInnerVlan);
799 }
Jonathan Hart718c0452016-02-18 15:56:22 -0800800 }
801
Esin Karaman39b24852019-08-28 13:57:30 +0000802 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder()
803 .fromApp(appId)
804 .nextStep(nextId)
805 .makePermanent()
806 .withFlag(ForwardingObjective.Flag.SPECIFIC)
807 .withPriority(priority)
808 .withSelector(mcast.build())
809 .withMeta(metabuilder.build());
alshabibfc1cb032016-02-17 15:37:56 -0800810
Esin Karaman39b24852019-08-28 13:57:30 +0000811 return fwdBuilder;
alshabibfc1cb032016-02-17 15:37:56 -0800812 }
813
Esin Karaman39b24852019-08-28 13:57:30 +0000814 // Custom-built function, when the device is not available we need a fallback mechanism
815 private boolean isLocalLeader(DeviceId deviceId) {
816 if (!mastershipService.isLocalMaster(deviceId)) {
817 // When the device is available we just check the mastership
818 if (deviceService.isAvailable(deviceId)) {
ke han9590c812017-02-28 15:02:26 +0800819 return false;
ke han9590c812017-02-28 15:02:26 +0800820 }
Esin Karaman39b24852019-08-28 13:57:30 +0000821 // Fallback with Leadership service - device id is used as topic
822 NodeId leader = leadershipService.runForLeadership(
823 deviceId.toString()).leaderNodeId();
824 // Verify if this node is the leader
825 return clusterService.getLocalNode().id().equals(leader);
ke han9590c812017-02-28 15:02:26 +0800826 }
Esin Karaman39b24852019-08-28 13:57:30 +0000827 return true;
ke han9590c812017-02-28 15:02:26 +0800828 }
Esin Karaman39b24852019-08-28 13:57:30 +0000829
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200830 private class InternalDeviceListener implements DeviceListener {
831
832 @Override
833 public void event(DeviceEvent event) {
834 eventExecutor.execute(() -> {
835 DeviceId devId = event.subject().id();
Andrea Campanella86bee262020-05-18 20:15:01 +0200836 if (!deviceService.isAvailable(devId) &&
837 isLocalLeader(event.subject().id())) {
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200838 if (deviceService.getPorts(devId).isEmpty()) {
839 log.info("Handling controlled device disconnection .. "
840 + "flushing all state for dev:{}", devId);
841 groupService.purgeGroupEntries(devId);
842 groups.keySet().iterator().forEachRemaining(groupInfo -> {
843 if (groupInfo.device.equals(devId)) {
844 log.debug("Removing next key {} from distributed mcast map", groupInfo.group);
845 groups.remove(groupInfo);
846 }
847 });
848 } else {
849 log.info("Disconnected device has available ports .. "
850 + "assuming temporary disconnection, "
851 + "retaining state for device {}", devId);
852 }
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200853 }
854 });
855
856 }
857
858 @Override
859 public boolean isRelevant(DeviceEvent event) {
Andrea Campanella86bee262020-05-18 20:15:01 +0200860 return event.type().equals(DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED);
Andrea Campanellac2f3ddd2020-05-18 11:09:11 +0200861 }
862 }
863
alshabib3b1eadc2016-02-01 17:57:00 -0800864}
ke hanf1709e82016-08-12 10:48:17 +0800865
ke han9590c812017-02-28 15:02:26 +0800866