Charles Chan
Committed by Gerrit Code Review

CORD-394 Purge group/flow store when device goes offline

Stage 1: (this commit)
Add a component config purgeOnDisconnection, which is false by default.
When set to true, GroupManager and FlowManager will purge groups/flows
associated with a device when the device goes offline.

Stage 2: (upcoming commit)
Enable these configs in SegmentRoutingManager
Clean up group related information in SegmentRountingManager

Change-Id: I46d047d690d4641e030f6cdd084ce16ac02d8919
...@@ -107,6 +107,13 @@ public interface FlowRuleStore extends Store<FlowRuleBatchEvent, FlowRuleStoreDe ...@@ -107,6 +107,13 @@ public interface FlowRuleStore extends Store<FlowRuleBatchEvent, FlowRuleStoreDe
107 FlowRuleEvent pendingFlowRule(FlowEntry rule); 107 FlowRuleEvent pendingFlowRule(FlowEntry rule);
108 108
109 /** 109 /**
110 + * Removes all flow entries of given device from store.
111 + *
112 + * @param deviceId device id
113 + */
114 + void purgeFlowRule(DeviceId deviceId);
115 +
116 + /**
110 * Updates the flow table statistics of the specified device using 117 * Updates the flow table statistics of the specified device using
111 * the given statistics. 118 * the given statistics.
112 * 119 *
......
...@@ -118,6 +118,13 @@ public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> { ...@@ -118,6 +118,13 @@ public interface GroupStore extends Store<GroupEvent, GroupStoreDelegate> {
118 void removeGroupEntry(Group group); 118 void removeGroupEntry(Group group);
119 119
120 /** 120 /**
121 + * Removes all group entries of given device from store.
122 + *
123 + * @param deviceId device id
124 + */
125 + void purgeGroupEntry(DeviceId deviceId);
126 +
127 + /**
121 * A group entry that is present in switch but not in the store. 128 * A group entry that is present in switch but not in the store.
122 * 129 *
123 * @param group group entry 130 * @param group group entry
......
...@@ -274,6 +274,10 @@ public class SimpleFlowRuleStore ...@@ -274,6 +274,10 @@ public class SimpleFlowRuleStore
274 return null; 274 return null;
275 } 275 }
276 276
277 + public void purgeFlowRule(DeviceId deviceId) {
278 + flowEntries.remove(deviceId);
279 + }
280 +
277 @Override 281 @Override
278 public void storeBatch( 282 public void storeBatch(
279 FlowRuleBatchOperation operation) { 283 FlowRuleBatchOperation operation) {
......
...@@ -23,6 +23,8 @@ import java.util.Collection; ...@@ -23,6 +23,8 @@ import java.util.Collection;
23 import java.util.HashMap; 23 import java.util.HashMap;
24 import java.util.Iterator; 24 import java.util.Iterator;
25 import java.util.List; 25 import java.util.List;
26 +import java.util.Map;
27 +import java.util.Map.Entry;
26 import java.util.Optional; 28 import java.util.Optional;
27 import java.util.Set; 29 import java.util.Set;
28 import java.util.concurrent.ConcurrentHashMap; 30 import java.util.concurrent.ConcurrentHashMap;
...@@ -477,6 +479,19 @@ public class SimpleGroupStore ...@@ -477,6 +479,19 @@ public class SimpleGroupStore
477 } 479 }
478 480
479 @Override 481 @Override
482 + public void purgeGroupEntry(DeviceId deviceId) {
483 + Set<Map.Entry<GroupId, StoredGroupEntry>> entryPendingRemove =
484 + groupEntriesById.get(deviceId).entrySet();
485 +
486 + groupEntriesById.remove(deviceId);
487 + groupEntriesByKey.remove(deviceId);
488 +
489 + entryPendingRemove.forEach(entry -> {
490 + notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, entry.getValue()));
491 + });
492 + }
493 +
494 + @Override
480 public void deviceInitialAuditCompleted(DeviceId deviceId, 495 public void deviceInitialAuditCompleted(DeviceId deviceId,
481 boolean completed) { 496 boolean completed) {
482 synchronized (deviceAuditStatus) { 497 synchronized (deviceAuditStatus) {
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
16 package org.onosproject.store.trivial; 16 package org.onosproject.store.trivial;
17 17
18 import static org.junit.Assert.assertEquals; 18 import static org.junit.Assert.assertEquals;
19 +import static org.junit.Assert.assertThat;
20 +import static org.hamcrest.Matchers.is;
19 import static org.onosproject.net.DeviceId.deviceId; 21 import static org.onosproject.net.DeviceId.deviceId;
20 22
21 import java.util.ArrayList; 23 import java.util.ArrayList;
...@@ -199,6 +201,11 @@ public class SimpleGroupStoreTest { ...@@ -199,6 +201,11 @@ public class SimpleGroupStoreTest {
199 201
200 // Testing removeGroupEntry operation from southbound 202 // Testing removeGroupEntry operation from southbound
201 testRemoveGroupFromSB(currKey); 203 testRemoveGroupFromSB(currKey);
204 +
205 + // Testing removing all groups on the given device
206 + newKey = new DefaultGroupKey("group1".getBytes());
207 + testStoreAndGetGroup(newKey);
208 + testDeleteGroupOnDevice(newKey);
202 } 209 }
203 210
204 // Testing storeGroup operation 211 // Testing storeGroup operation
...@@ -376,6 +383,13 @@ public class SimpleGroupStoreTest { ...@@ -376,6 +383,13 @@ public class SimpleGroupStoreTest {
376 simpleGroupStore.unsetDelegate(deleteGroupDescDelegate); 383 simpleGroupStore.unsetDelegate(deleteGroupDescDelegate);
377 } 384 }
378 385
386 + // Testing deleteGroupDescription operation from northbound
387 + private void testDeleteGroupOnDevice(GroupKey currKey) {
388 + assertThat(simpleGroupStore.getGroupCount(D1), is(1));
389 + simpleGroupStore.purgeGroupEntry(D1);
390 + assertThat(simpleGroupStore.getGroupCount(D1), is(0));
391 + }
392 +
379 // Testing removeGroupEntry operation from southbound 393 // Testing removeGroupEntry operation from southbound
380 private void testRemoveGroupFromSB(GroupKey currKey) { 394 private void testRemoveGroupFromSB(GroupKey currKey) {
381 Group existingGroup = simpleGroupStore.getGroup(D1, currKey); 395 Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
15 */ 15 */
16 package org.onosproject.net.flow.impl; 16 package org.onosproject.net.flow.impl;
17 17
18 -import com.google.common.base.Strings;
19 import com.google.common.collect.ArrayListMultimap; 18 import com.google.common.collect.ArrayListMultimap;
20 import com.google.common.collect.Iterables; 19 import com.google.common.collect.Iterables;
21 import com.google.common.collect.Lists; 20 import com.google.common.collect.Lists;
...@@ -31,8 +30,9 @@ import org.apache.felix.scr.annotations.Property; ...@@ -31,8 +30,9 @@ import org.apache.felix.scr.annotations.Property;
31 import org.apache.felix.scr.annotations.Reference; 30 import org.apache.felix.scr.annotations.Reference;
32 import org.apache.felix.scr.annotations.ReferenceCardinality; 31 import org.apache.felix.scr.annotations.ReferenceCardinality;
33 import org.apache.felix.scr.annotations.Service; 32 import org.apache.felix.scr.annotations.Service;
34 -import org.onlab.util.Tools;
35 import org.onosproject.cfg.ComponentConfigService; 33 import org.onosproject.cfg.ComponentConfigService;
34 +import org.onosproject.net.device.DeviceEvent;
35 +import org.onosproject.net.device.DeviceListener;
36 import org.onosproject.net.provider.AbstractListenerProviderRegistry; 36 import org.onosproject.net.provider.AbstractListenerProviderRegistry;
37 import org.onosproject.core.ApplicationId; 37 import org.onosproject.core.ApplicationId;
38 import org.onosproject.core.CoreService; 38 import org.onosproject.core.CoreService;
...@@ -75,6 +75,7 @@ import java.util.concurrent.Executors; ...@@ -75,6 +75,7 @@ import java.util.concurrent.Executors;
75 import java.util.concurrent.atomic.AtomicBoolean; 75 import java.util.concurrent.atomic.AtomicBoolean;
76 76
77 import static com.google.common.base.Preconditions.checkNotNull; 77 import static com.google.common.base.Preconditions.checkNotNull;
78 +import static com.google.common.base.Strings.isNullOrEmpty;
78 import static org.onlab.util.Tools.groupedThreads; 79 import static org.onlab.util.Tools.groupedThreads;
79 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED; 80 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
80 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED; 81 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
...@@ -101,9 +102,14 @@ public class FlowRuleManager ...@@ -101,9 +102,14 @@ public class FlowRuleManager
101 label = "Allow flow rules in switch not installed by ONOS") 102 label = "Allow flow rules in switch not installed by ONOS")
102 private boolean allowExtraneousRules = ALLOW_EXTRANEOUS_RULES; 103 private boolean allowExtraneousRules = ALLOW_EXTRANEOUS_RULES;
103 104
105 + @Property(name = "purgeOnDisconnection", boolValue = false,
106 + label = "Purge entries associated with a device when the device goes offline")
107 + private boolean purgeOnDisconnection = false;
108 +
104 private final Logger log = getLogger(getClass()); 109 private final Logger log = getLogger(getClass());
105 110
106 private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate(); 111 private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
112 + private final DeviceListener deviceListener = new InternalDeviceListener();
107 113
108 protected ExecutorService deviceInstallers = 114 protected ExecutorService deviceInstallers =
109 Executors.newFixedThreadPool(32, groupedThreads("onos/flowservice", "device-installer-%d")); 115 Executors.newFixedThreadPool(32, groupedThreads("onos/flowservice", "device-installer-%d"));
...@@ -130,13 +136,12 @@ public class FlowRuleManager ...@@ -130,13 +136,12 @@ public class FlowRuleManager
130 136
131 @Activate 137 @Activate
132 public void activate(ComponentContext context) { 138 public void activate(ComponentContext context) {
139 + store.setDelegate(delegate);
140 + eventDispatcher.addSink(FlowRuleEvent.class, listenerRegistry);
141 + deviceService.addListener(deviceListener);
133 cfgService.registerProperties(getClass()); 142 cfgService.registerProperties(getClass());
134 idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC); 143 idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC);
135 -
136 modified(context); 144 modified(context);
137 -
138 - store.setDelegate(delegate);
139 - eventDispatcher.addSink(FlowRuleEvent.class, listenerRegistry);
140 log.info("Started"); 145 log.info("Started");
141 } 146 }
142 147
...@@ -152,18 +157,59 @@ public class FlowRuleManager ...@@ -152,18 +157,59 @@ public class FlowRuleManager
152 157
153 @Modified 158 @Modified
154 public void modified(ComponentContext context) { 159 public void modified(ComponentContext context) {
155 - if (context == null) { 160 + if (context != null) {
156 - return; 161 + readComponentConfiguration(context);
157 } 162 }
163 + }
158 164
165 + /**
166 + * Extracts properties from the component configuration context.
167 + *
168 + * @param context the component context
169 + */
170 + private void readComponentConfiguration(ComponentContext context) {
159 Dictionary<?, ?> properties = context.getProperties(); 171 Dictionary<?, ?> properties = context.getProperties();
172 + Boolean flag;
173 +
174 + flag = isPropertyEnabled(properties, "allowExtraneousRules");
175 + if (flag == null) {
176 + log.info("AllowExtraneousRules is not configured, " +
177 + "using current value of {}", allowExtraneousRules);
178 + } else {
179 + allowExtraneousRules = flag;
180 + log.info("Configured. AllowExtraneousRules is {}",
181 + allowExtraneousRules ? "enabled" : "disabled");
182 + }
160 183
161 - String s = Tools.get(properties, "allowExtraneousRules"); 184 + flag = isPropertyEnabled(properties, "purgeOnDisconnection");
162 - allowExtraneousRules = Strings.isNullOrEmpty(s) ? ALLOW_EXTRANEOUS_RULES : Boolean.valueOf(s); 185 + if (flag == null) {
186 + log.info("PurgeOnDisconnection is not configured, " +
187 + "using current value of {}", purgeOnDisconnection);
188 + } else {
189 + purgeOnDisconnection = flag;
190 + log.info("Configured. PurgeOnDisconnection is {}",
191 + purgeOnDisconnection ? "enabled" : "disabled");
192 + }
193 + }
163 194
164 - if (allowExtraneousRules) { 195 + /**
165 - log.info("Allowing flow rules not installed by ONOS"); 196 + * Check property name is defined and set to true.
197 + *
198 + * @param properties properties to be looked up
199 + * @param propertyName the name of the property to look up
200 + * @return value when the propertyName is defined or return null
201 + */
202 + private static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
203 + String propertyName) {
204 + Boolean value = null;
205 + try {
206 + String s = (String) properties.get(propertyName);
207 + value = isNullOrEmpty(s) ? null : s.trim().equals("true");
208 + } catch (ClassCastException e) {
209 + // No propertyName defined.
210 + value = null;
166 } 211 }
212 + return value;
167 } 213 }
168 214
169 @Override 215 @Override
...@@ -613,4 +659,23 @@ public class FlowRuleManager ...@@ -613,4 +659,23 @@ public class FlowRuleManager
613 checkPermission(FLOWRULE_READ); 659 checkPermission(FLOWRULE_READ);
614 return store.getTableStatistics(deviceId); 660 return store.getTableStatistics(deviceId);
615 } 661 }
662 +
663 + private class InternalDeviceListener implements DeviceListener {
664 + @Override
665 + public void event(DeviceEvent event) {
666 + switch (event.type()) {
667 + case DEVICE_REMOVED:
668 + case DEVICE_AVAILABILITY_CHANGED:
669 + DeviceId deviceId = event.subject().id();
670 + if (!deviceService.isAvailable(deviceId)) {
671 + if (purgeOnDisconnection) {
672 + store.purgeFlowRule(deviceId);
673 + }
674 + }
675 + break;
676 + default:
677 + break;
678 + }
679 + }
680 + }
616 } 681 }
......
...@@ -18,9 +18,12 @@ package org.onosproject.net.group.impl; ...@@ -18,9 +18,12 @@ package org.onosproject.net.group.impl;
18 import org.apache.felix.scr.annotations.Activate; 18 import org.apache.felix.scr.annotations.Activate;
19 import org.apache.felix.scr.annotations.Component; 19 import org.apache.felix.scr.annotations.Component;
20 import org.apache.felix.scr.annotations.Deactivate; 20 import org.apache.felix.scr.annotations.Deactivate;
21 +import org.apache.felix.scr.annotations.Modified;
22 +import org.apache.felix.scr.annotations.Property;
21 import org.apache.felix.scr.annotations.Reference; 23 import org.apache.felix.scr.annotations.Reference;
22 import org.apache.felix.scr.annotations.ReferenceCardinality; 24 import org.apache.felix.scr.annotations.ReferenceCardinality;
23 import org.apache.felix.scr.annotations.Service; 25 import org.apache.felix.scr.annotations.Service;
26 +import org.onosproject.cfg.ComponentConfigService;
24 import org.onosproject.net.provider.AbstractListenerProviderRegistry; 27 import org.onosproject.net.provider.AbstractListenerProviderRegistry;
25 import org.onosproject.core.ApplicationId; 28 import org.onosproject.core.ApplicationId;
26 import org.onosproject.net.DeviceId; 29 import org.onosproject.net.DeviceId;
...@@ -43,11 +46,14 @@ import org.onosproject.net.group.GroupStore; ...@@ -43,11 +46,14 @@ import org.onosproject.net.group.GroupStore;
43 import org.onosproject.net.group.GroupStore.UpdateType; 46 import org.onosproject.net.group.GroupStore.UpdateType;
44 import org.onosproject.net.group.GroupStoreDelegate; 47 import org.onosproject.net.group.GroupStoreDelegate;
45 import org.onosproject.net.provider.AbstractProviderService; 48 import org.onosproject.net.provider.AbstractProviderService;
49 +import org.osgi.service.component.ComponentContext;
46 import org.slf4j.Logger; 50 import org.slf4j.Logger;
47 51
48 import java.util.Collection; 52 import java.util.Collection;
49 import java.util.Collections; 53 import java.util.Collections;
54 +import java.util.Dictionary;
50 55
56 +import static com.google.common.base.Strings.isNullOrEmpty;
51 import static org.onosproject.security.AppGuard.checkPermission; 57 import static org.onosproject.security.AppGuard.checkPermission;
52 import static org.slf4j.LoggerFactory.getLogger; 58 import static org.slf4j.LoggerFactory.getLogger;
53 import static org.onosproject.security.AppPermission.Type.*; 59 import static org.onosproject.security.AppPermission.Type.*;
...@@ -75,21 +81,78 @@ public class GroupManager ...@@ -75,21 +81,78 @@ public class GroupManager
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected DeviceService deviceService; 82 protected DeviceService deviceService;
77 83
84 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 + protected ComponentConfigService cfgService;
86 +
87 + @Property(name = "purgeOnDisconnection", boolValue = false,
88 + label = "Purge entries associated with a device when the device goes offline")
89 + private boolean purgeOnDisconnection = false;
90 +
78 @Activate 91 @Activate
79 - public void activate() { 92 + public void activate(ComponentContext context) {
80 store.setDelegate(delegate); 93 store.setDelegate(delegate);
81 eventDispatcher.addSink(GroupEvent.class, listenerRegistry); 94 eventDispatcher.addSink(GroupEvent.class, listenerRegistry);
82 deviceService.addListener(deviceListener); 95 deviceService.addListener(deviceListener);
96 + cfgService.registerProperties(getClass());
97 + modified(context);
83 log.info("Started"); 98 log.info("Started");
84 } 99 }
85 100
86 @Deactivate 101 @Deactivate
87 public void deactivate() { 102 public void deactivate() {
103 + cfgService.unregisterProperties(getClass(), false);
88 store.unsetDelegate(delegate); 104 store.unsetDelegate(delegate);
89 eventDispatcher.removeSink(GroupEvent.class); 105 eventDispatcher.removeSink(GroupEvent.class);
90 log.info("Stopped"); 106 log.info("Stopped");
91 } 107 }
92 108
109 + @Modified
110 + public void modified(ComponentContext context) {
111 + if (context != null) {
112 + readComponentConfiguration(context);
113 + }
114 + }
115 +
116 + /**
117 + * Extracts properties from the component configuration context.
118 + *
119 + * @param context the component context
120 + */
121 + private void readComponentConfiguration(ComponentContext context) {
122 + Dictionary<?, ?> properties = context.getProperties();
123 + Boolean flag;
124 +
125 + flag = isPropertyEnabled(properties, "purgeOnDisconnection");
126 + if (flag == null) {
127 + log.info("PurgeOnDisconnection is not configured, " +
128 + "using current value of {}", purgeOnDisconnection);
129 + } else {
130 + purgeOnDisconnection = flag;
131 + log.info("Configured. PurgeOnDisconnection is {}",
132 + purgeOnDisconnection ? "enabled" : "disabled");
133 + }
134 + }
135 +
136 + /**
137 + * Check property name is defined and set to true.
138 + *
139 + * @param properties properties to be looked up
140 + * @param propertyName the name of the property to look up
141 + * @return value when the propertyName is defined or return null
142 + */
143 + private static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
144 + String propertyName) {
145 + Boolean value = null;
146 + try {
147 + String s = (String) properties.get(propertyName);
148 + value = isNullOrEmpty(s) ? null : s.trim().equals("true");
149 + } catch (ClassCastException e) {
150 + // No propertyName defined.
151 + value = null;
152 + }
153 + return value;
154 + }
155 +
93 /** 156 /**
94 * Create a group in the specified device with the provided parameters. 157 * Create a group in the specified device with the provided parameters.
95 * 158 *
...@@ -303,13 +366,17 @@ public class GroupManager ...@@ -303,13 +366,17 @@ public class GroupManager
303 switch (event.type()) { 366 switch (event.type()) {
304 case DEVICE_REMOVED: 367 case DEVICE_REMOVED:
305 case DEVICE_AVAILABILITY_CHANGED: 368 case DEVICE_AVAILABILITY_CHANGED:
306 - if (!deviceService.isAvailable(event.subject().id())) { 369 + DeviceId deviceId = event.subject().id();
370 + if (!deviceService.isAvailable(deviceId)) {
307 log.debug("Device {} became un available; clearing initial audit status", 371 log.debug("Device {} became un available; clearing initial audit status",
308 event.type(), event.subject().id()); 372 event.type(), event.subject().id());
309 store.deviceInitialAuditCompleted(event.subject().id(), false); 373 store.deviceInitialAuditCompleted(event.subject().id(), false);
374 +
375 + if (purgeOnDisconnection) {
376 + store.purgeGroupEntry(deviceId);
377 + }
310 } 378 }
311 break; 379 break;
312 -
313 default: 380 default:
314 break; 381 break;
315 } 382 }
......
...@@ -32,6 +32,7 @@ import org.junit.Before; ...@@ -32,6 +32,7 @@ import org.junit.Before;
32 import org.junit.Test; 32 import org.junit.Test;
33 import org.onlab.packet.MacAddress; 33 import org.onlab.packet.MacAddress;
34 import org.onlab.packet.MplsLabel; 34 import org.onlab.packet.MplsLabel;
35 +import org.onosproject.cfg.ComponentConfigAdapter;
35 import org.onosproject.core.ApplicationId; 36 import org.onosproject.core.ApplicationId;
36 import org.onosproject.core.DefaultApplicationId; 37 import org.onosproject.core.DefaultApplicationId;
37 import org.onosproject.core.DefaultGroupId; 38 import org.onosproject.core.DefaultGroupId;
...@@ -89,11 +90,12 @@ public class GroupManagerTest { ...@@ -89,11 +90,12 @@ public class GroupManagerTest {
89 mgr = new GroupManager(); 90 mgr = new GroupManager();
90 groupService = mgr; 91 groupService = mgr;
91 mgr.deviceService = new DeviceManager(); 92 mgr.deviceService = new DeviceManager();
93 + mgr.cfgService = new ComponentConfigAdapter();
92 mgr.store = new SimpleGroupStore(); 94 mgr.store = new SimpleGroupStore();
93 injectEventDispatcher(mgr, new TestEventDispatcher()); 95 injectEventDispatcher(mgr, new TestEventDispatcher());
94 providerRegistry = mgr; 96 providerRegistry = mgr;
95 97
96 - mgr.activate(); 98 + mgr.activate(null);
97 mgr.addListener(listener); 99 mgr.addListener(listener);
98 100
99 internalProvider = new TestGroupProvider(PID); 101 internalProvider = new TestGroupProvider(PID);
......
...@@ -611,6 +611,11 @@ public class NewDistributedFlowRuleStore ...@@ -611,6 +611,11 @@ public class NewDistributedFlowRuleStore
611 } 611 }
612 612
613 @Override 613 @Override
614 + public void purgeFlowRule(DeviceId deviceId) {
615 + flowTable.purgeFlowRule(deviceId);
616 + }
617 +
618 + @Override
614 public void batchOperationComplete(FlowRuleBatchEvent event) { 619 public void batchOperationComplete(FlowRuleBatchEvent event) {
615 //FIXME: need a per device pending response 620 //FIXME: need a per device pending response
616 NodeId nodeId = pendingResponses.remove(event.subject().batchId()); 621 NodeId nodeId = pendingResponses.remove(event.subject().batchId());
...@@ -827,6 +832,10 @@ public class NewDistributedFlowRuleStore ...@@ -827,6 +832,10 @@ public class NewDistributedFlowRuleStore
827 } 832 }
828 } 833 }
829 834
835 + public void purgeFlowRule(DeviceId deviceId) {
836 + flowEntries.remove(deviceId);
837 + }
838 +
830 private NodeId getBackupNode(DeviceId deviceId) { 839 private NodeId getBackupNode(DeviceId deviceId) {
831 List<NodeId> deviceStandbys = replicaInfoManager.getReplicaInfoFor(deviceId).backups(); 840 List<NodeId> deviceStandbys = replicaInfoManager.getReplicaInfoFor(deviceId).backups();
832 // pick the standby which is most likely to become next master 841 // pick the standby which is most likely to become next master
......
...@@ -67,8 +67,10 @@ import java.util.ArrayList; ...@@ -67,8 +67,10 @@ import java.util.ArrayList;
67 import java.util.Collection; 67 import java.util.Collection;
68 import java.util.Collections; 68 import java.util.Collections;
69 import java.util.HashMap; 69 import java.util.HashMap;
70 +import java.util.HashSet;
70 import java.util.Iterator; 71 import java.util.Iterator;
71 import java.util.List; 72 import java.util.List;
73 +import java.util.Map.Entry;
72 import java.util.Objects; 74 import java.util.Objects;
73 import java.util.Optional; 75 import java.util.Optional;
74 import java.util.Set; 76 import java.util.Set;
...@@ -845,6 +847,21 @@ public class DistributedGroupStore ...@@ -845,6 +847,21 @@ public class DistributedGroupStore
845 } 847 }
846 848
847 @Override 849 @Override
850 + public void purgeGroupEntry(DeviceId deviceId) {
851 + Set<Entry<GroupStoreKeyMapKey, StoredGroupEntry>> entryPendingRemove =
852 + new HashSet<>();
853 +
854 + groupStoreEntriesByKey.entrySet().stream()
855 + .filter(entry -> entry.getKey().deviceId().equals(deviceId))
856 + .forEach(entryPendingRemove::add);
857 +
858 + entryPendingRemove.forEach(entry -> {
859 + groupStoreEntriesByKey.remove(entry.getKey());
860 + notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, entry.getValue()));
861 + });
862 + }
863 +
864 + @Override
848 public void deviceInitialAuditCompleted(DeviceId deviceId, 865 public void deviceInitialAuditCompleted(DeviceId deviceId,
849 boolean completed) { 866 boolean completed) {
850 synchronized (deviceAuditStatus) { 867 synchronized (deviceAuditStatus) {
......
...@@ -71,8 +71,10 @@ public class DistributedGroupStoreTest { ...@@ -71,8 +71,10 @@ public class DistributedGroupStoreTest {
71 DeviceId deviceId2 = did("dev2"); 71 DeviceId deviceId2 = did("dev2");
72 GroupId groupId1 = new DefaultGroupId(1); 72 GroupId groupId1 = new DefaultGroupId(1);
73 GroupId groupId2 = new DefaultGroupId(2); 73 GroupId groupId2 = new DefaultGroupId(2);
74 + GroupId groupId3 = new DefaultGroupId(3);
74 GroupKey groupKey1 = new DefaultGroupKey("abc".getBytes()); 75 GroupKey groupKey1 = new DefaultGroupKey("abc".getBytes());
75 GroupKey groupKey2 = new DefaultGroupKey("def".getBytes()); 76 GroupKey groupKey2 = new DefaultGroupKey("def".getBytes());
77 + GroupKey groupKey3 = new DefaultGroupKey("ghi".getBytes());
76 78
77 TrafficTreatment treatment = 79 TrafficTreatment treatment =
78 DefaultTrafficTreatment.emptyTreatment(); 80 DefaultTrafficTreatment.emptyTreatment();
...@@ -97,6 +99,13 @@ public class DistributedGroupStoreTest { ...@@ -97,6 +99,13 @@ public class DistributedGroupStoreTest {
97 groupKey2, 99 groupKey2,
98 groupId2.id(), 100 groupId2.id(),
99 APP_ID); 101 APP_ID);
102 + GroupDescription groupDescription3 = new DefaultGroupDescription(
103 + deviceId2,
104 + GroupDescription.Type.INDIRECT,
105 + buckets,
106 + groupKey3,
107 + groupId3.id(),
108 + APP_ID);
100 109
101 DistributedGroupStore groupStoreImpl; 110 DistributedGroupStore groupStoreImpl;
102 GroupStore groupStore; 111 GroupStore groupStore;
...@@ -202,6 +211,30 @@ public class DistributedGroupStoreTest { ...@@ -202,6 +211,30 @@ public class DistributedGroupStoreTest {
202 } 211 }
203 212
204 /** 213 /**
214 + * Tests removing all groups on the given device.
215 + */
216 + @Test
217 + public void testRemoveGroupOnDevice() throws Exception {
218 + groupStore.deviceInitialAuditCompleted(deviceId1, true);
219 + assertThat(groupStore.deviceInitialAuditStatus(deviceId1), is(true));
220 + groupStore.deviceInitialAuditCompleted(deviceId2, true);
221 + assertThat(groupStore.deviceInitialAuditStatus(deviceId2), is(true));
222 +
223 + // Make sure the pending list starts out empty
224 + assertThat(auditPendingReqQueue.size(), is(0));
225 +
226 + groupStore.storeGroupDescription(groupDescription1);
227 + groupStore.storeGroupDescription(groupDescription2);
228 + groupStore.storeGroupDescription(groupDescription3);
229 + assertThat(groupStore.getGroupCount(deviceId1), is(1));
230 + assertThat(groupStore.getGroupCount(deviceId2), is(2));
231 +
232 + groupStore.purgeGroupEntry(deviceId2);
233 + assertThat(groupStore.getGroupCount(deviceId1), is(1));
234 + assertThat(groupStore.getGroupCount(deviceId2), is(0));
235 + }
236 +
237 + /**
205 * Tests adding and removing a group. 238 * Tests adding and removing a group.
206 */ 239 */
207 @Test 240 @Test
......