Relinquish mastership, tests, and few modifications to trivial MastershipStore
Change-Id: Iae29de010f13cb3ee02bcb316510cc254d5756fc
Showing
6 changed files
with
171 additions
and
29 deletions
... | @@ -64,4 +64,14 @@ public interface MastershipStore extends Store<MastershipEvent, MastershipStoreD | ... | @@ -64,4 +64,14 @@ public interface MastershipStore extends Store<MastershipEvent, MastershipStoreD |
64 | * @return the current master's ID and the term value for device, or null | 64 | * @return the current master's ID and the term value for device, or null |
65 | */ | 65 | */ |
66 | MastershipTerm getTermFor(DeviceId deviceId); | 66 | MastershipTerm getTermFor(DeviceId deviceId); |
67 | + | ||
68 | + /** | ||
69 | + * Revokes a controller instance's mastership over a device and hands | ||
70 | + * over mastership to another controller instance. | ||
71 | + * | ||
72 | + * @param nodeId the controller instance identifier | ||
73 | + * @param deviceId device to revoke mastership for | ||
74 | + * @return a mastership event | ||
75 | + */ | ||
76 | + MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId); | ||
67 | } | 77 | } | ... | ... |
... | @@ -72,9 +72,20 @@ public class MastershipManager | ... | @@ -72,9 +72,20 @@ public class MastershipManager |
72 | checkNotNull(nodeId, NODE_ID_NULL); | 72 | checkNotNull(nodeId, NODE_ID_NULL); |
73 | checkNotNull(deviceId, DEVICE_ID_NULL); | 73 | checkNotNull(deviceId, DEVICE_ID_NULL); |
74 | checkNotNull(role, ROLE_NULL); | 74 | checkNotNull(role, ROLE_NULL); |
75 | - //TODO figure out appropriate action for non-MASTER roles, if we even set those | 75 | + |
76 | + MastershipRole current = store.getRole(nodeId, deviceId); | ||
77 | + if (role.equals(current)) { | ||
78 | + return; | ||
79 | + } else { | ||
80 | + MastershipEvent event = null; | ||
76 | if (role.equals(MastershipRole.MASTER)) { | 81 | if (role.equals(MastershipRole.MASTER)) { |
77 | - MastershipEvent event = store.setMaster(nodeId, deviceId); | 82 | + //current was STANDBY, wanted MASTER |
83 | + event = store.setMaster(nodeId, deviceId); | ||
84 | + } else { | ||
85 | + //current was MASTER, wanted STANDBY | ||
86 | + event = store.unsetMaster(nodeId, deviceId); | ||
87 | + } | ||
88 | + | ||
78 | if (event != null) { | 89 | if (event != null) { |
79 | post(event); | 90 | post(event); |
80 | } | 91 | } |
... | @@ -90,7 +101,18 @@ public class MastershipManager | ... | @@ -90,7 +101,18 @@ public class MastershipManager |
90 | @Override | 101 | @Override |
91 | public void relinquishMastership(DeviceId deviceId) { | 102 | public void relinquishMastership(DeviceId deviceId) { |
92 | checkNotNull(deviceId, DEVICE_ID_NULL); | 103 | checkNotNull(deviceId, DEVICE_ID_NULL); |
93 | - // FIXME: add method to store to give up mastership and trigger new master selection process | 104 | + |
105 | + MastershipRole role = store.getRole( | ||
106 | + clusterService.getLocalNode().id(), deviceId); | ||
107 | + if (!role.equals(MastershipRole.MASTER)) { | ||
108 | + return; | ||
109 | + } | ||
110 | + | ||
111 | + MastershipEvent event = store.unsetMaster( | ||
112 | + clusterService.getLocalNode().id(), deviceId); | ||
113 | + if (event != null) { | ||
114 | + post(event); | ||
115 | + } | ||
94 | } | 116 | } |
95 | 117 | ||
96 | @Override | 118 | @Override |
... | @@ -159,10 +181,6 @@ public class MastershipManager | ... | @@ -159,10 +181,6 @@ public class MastershipManager |
159 | break; | 181 | break; |
160 | case INSTANCE_REMOVED: | 182 | case INSTANCE_REMOVED: |
161 | case INSTANCE_DEACTIVATED: | 183 | case INSTANCE_DEACTIVATED: |
162 | - for (DeviceId d : getDevicesOf(event.subject().id())) { | ||
163 | - //this method should be an admin iface? | ||
164 | - relinquishMastership(d); | ||
165 | - } | ||
166 | break; | 184 | break; |
167 | default: | 185 | default: |
168 | log.warn("unknown cluster event {}", event); | 186 | log.warn("unknown cluster event {}", event); | ... | ... |
... | @@ -204,8 +204,11 @@ public class DeviceManager | ... | @@ -204,8 +204,11 @@ public class DeviceManager |
204 | checkNotNull(deviceId, DEVICE_ID_NULL); | 204 | checkNotNull(deviceId, DEVICE_ID_NULL); |
205 | checkValidity(); | 205 | checkValidity(); |
206 | DeviceEvent event = store.markOffline(deviceId); | 206 | DeviceEvent event = store.markOffline(deviceId); |
207 | + | ||
208 | + //we're no longer capable of mastership. | ||
207 | if (event != null) { | 209 | if (event != null) { |
208 | log.info("Device {} disconnected", deviceId); | 210 | log.info("Device {} disconnected", deviceId); |
211 | + mastershipService.relinquishMastership(deviceId); | ||
209 | post(event); | 212 | post(event); |
210 | } | 213 | } |
211 | } | 214 | } | ... | ... |
... | @@ -19,6 +19,7 @@ import org.onlab.onos.net.trivial.impl.SimpleMastershipStore; | ... | @@ -19,6 +19,7 @@ import org.onlab.onos.net.trivial.impl.SimpleMastershipStore; |
19 | import org.onlab.packet.IpPrefix; | 19 | import org.onlab.packet.IpPrefix; |
20 | 20 | ||
21 | import static org.junit.Assert.assertEquals; | 21 | import static org.junit.Assert.assertEquals; |
22 | +import static org.junit.Assert.assertNull; | ||
22 | import static org.onlab.onos.net.MastershipRole.*; | 23 | import static org.onlab.onos.net.MastershipRole.*; |
23 | 24 | ||
24 | /** | 25 | /** |
... | @@ -65,7 +66,24 @@ public class MastershipManagerTest { | ... | @@ -65,7 +66,24 @@ public class MastershipManagerTest { |
65 | 66 | ||
66 | @Test | 67 | @Test |
67 | public void relinquishMastership() { | 68 | public void relinquishMastership() { |
68 | - //TODO | 69 | + //no backups - should turn to standby and no master for device |
70 | + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); | ||
71 | + assertEquals("wrong role:", MASTER, mgr.getLocalRole(DEV_MASTER)); | ||
72 | + mgr.relinquishMastership(DEV_MASTER); | ||
73 | + assertNull("wrong master:", mgr.getMasterFor(DEV_OTHER)); | ||
74 | + assertEquals("wrong role:", STANDBY, mgr.getLocalRole(DEV_MASTER)); | ||
75 | + | ||
76 | + //not master, nothing should happen | ||
77 | + mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY); | ||
78 | + mgr.relinquishMastership(DEV_OTHER); | ||
79 | + assertNull("wrong role:", mgr.getMasterFor(DEV_OTHER)); | ||
80 | + | ||
81 | + //provide NID_OTHER as backup and relinquish | ||
82 | + mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER); | ||
83 | + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER)); | ||
84 | + mgr.setRole(NID_OTHER, DEV_MASTER, STANDBY); | ||
85 | + mgr.relinquishMastership(DEV_MASTER); | ||
86 | + assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_MASTER)); | ||
69 | } | 87 | } |
70 | 88 | ||
71 | @Test | 89 | @Test | ... | ... |
... | @@ -122,4 +122,10 @@ public class DistributedMastershipStore | ... | @@ -122,4 +122,10 @@ public class DistributedMastershipStore |
122 | return null; | 122 | return null; |
123 | } | 123 | } |
124 | 124 | ||
125 | + @Override | ||
126 | + public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) { | ||
127 | + // TODO Auto-generated method stub | ||
128 | + return null; | ||
129 | + } | ||
130 | + | ||
125 | } | 131 | } | ... | ... |
... | @@ -5,6 +5,7 @@ import static org.slf4j.LoggerFactory.getLogger; | ... | @@ -5,6 +5,7 @@ import static org.slf4j.LoggerFactory.getLogger; |
5 | import java.util.Collections; | 5 | import java.util.Collections; |
6 | import java.util.HashMap; | 6 | import java.util.HashMap; |
7 | import java.util.HashSet; | 7 | import java.util.HashSet; |
8 | +import java.util.List; | ||
8 | import java.util.Map; | 9 | import java.util.Map; |
9 | import java.util.Set; | 10 | import java.util.Set; |
10 | import java.util.concurrent.atomic.AtomicInteger; | 11 | import java.util.concurrent.atomic.AtomicInteger; |
... | @@ -26,6 +27,8 @@ import org.onlab.onos.store.AbstractStore; | ... | @@ -26,6 +27,8 @@ import org.onlab.onos.store.AbstractStore; |
26 | import org.onlab.packet.IpPrefix; | 27 | import org.onlab.packet.IpPrefix; |
27 | import org.slf4j.Logger; | 28 | import org.slf4j.Logger; |
28 | 29 | ||
30 | +import com.google.common.collect.Lists; | ||
31 | + | ||
29 | import static org.onlab.onos.cluster.MastershipEvent.Type.*; | 32 | import static org.onlab.onos.cluster.MastershipEvent.Type.*; |
30 | 33 | ||
31 | /** | 34 | /** |
... | @@ -47,8 +50,10 @@ public class SimpleMastershipStore | ... | @@ -47,8 +50,10 @@ public class SimpleMastershipStore |
47 | 50 | ||
48 | //devices mapped to their masters, to emulate multiple nodes | 51 | //devices mapped to their masters, to emulate multiple nodes |
49 | protected final Map<DeviceId, NodeId> masterMap = new HashMap<>(); | 52 | protected final Map<DeviceId, NodeId> masterMap = new HashMap<>(); |
53 | + //emulate backups | ||
54 | + protected final Map<DeviceId, List<NodeId>> backupMap = new HashMap<>(); | ||
55 | + //terms | ||
50 | protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>(); | 56 | protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>(); |
51 | - protected final Set<NodeId> masters = new HashSet<>(); | ||
52 | 57 | ||
53 | @Activate | 58 | @Activate |
54 | public void activate() { | 59 | public void activate() { |
... | @@ -63,24 +68,38 @@ public class SimpleMastershipStore | ... | @@ -63,24 +68,38 @@ public class SimpleMastershipStore |
63 | @Override | 68 | @Override |
64 | public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) { | 69 | public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) { |
65 | 70 | ||
66 | - NodeId node = masterMap.get(deviceId); | 71 | + NodeId current = masterMap.get(deviceId); |
67 | - if (node == null) { | 72 | + List<NodeId> backups = backupMap.get(deviceId); |
73 | + | ||
74 | + if (current == null) { | ||
75 | + if (backups == null) { | ||
76 | + //add new mapping to everything | ||
68 | synchronized (this) { | 77 | synchronized (this) { |
69 | masterMap.put(deviceId, nodeId); | 78 | masterMap.put(deviceId, nodeId); |
79 | + backups = Lists.newLinkedList(); | ||
80 | + backupMap.put(deviceId, backups); | ||
70 | termMap.put(deviceId, new AtomicInteger()); | 81 | termMap.put(deviceId, new AtomicInteger()); |
71 | } | 82 | } |
72 | - return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | ||
73 | - } | ||
74 | - | ||
75 | - if (node.equals(nodeId)) { | ||
76 | - return null; | ||
77 | } else { | 83 | } else { |
84 | + //set master to new node and remove from backups if there | ||
78 | synchronized (this) { | 85 | synchronized (this) { |
79 | masterMap.put(deviceId, nodeId); | 86 | masterMap.put(deviceId, nodeId); |
87 | + backups.remove(nodeId); | ||
80 | termMap.get(deviceId).incrementAndGet(); | 88 | termMap.get(deviceId).incrementAndGet(); |
81 | - return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | ||
82 | } | 89 | } |
83 | } | 90 | } |
91 | + } else if (current.equals(nodeId)) { | ||
92 | + return null; | ||
93 | + } else { | ||
94 | + //add current to backup, set master to new node | ||
95 | + masterMap.put(deviceId, nodeId); | ||
96 | + backups.add(current); | ||
97 | + backups.remove(nodeId); | ||
98 | + termMap.get(deviceId).incrementAndGet(); | ||
99 | + } | ||
100 | + | ||
101 | + updateStandby(nodeId, deviceId); | ||
102 | + return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | ||
84 | } | 103 | } |
85 | 104 | ||
86 | @Override | 105 | @Override |
... | @@ -106,29 +125,97 @@ public class SimpleMastershipStore | ... | @@ -106,29 +125,97 @@ public class SimpleMastershipStore |
106 | 125 | ||
107 | @Override | 126 | @Override |
108 | public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { | 127 | public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { |
109 | - NodeId node = masterMap.get(deviceId); | 128 | + NodeId current = masterMap.get(deviceId); |
110 | - MastershipRole role; | 129 | + List<NodeId> backups = backupMap.get(deviceId); |
111 | - if (node != null) { | 130 | + |
112 | - if (node.equals(nodeId)) { | 131 | + if (current == null) { |
113 | - role = MastershipRole.MASTER; | 132 | + //masterMap or backup doesn't contain device. Say new node is MASTER |
114 | - } else { | 133 | + if (backups == null) { |
115 | - role = MastershipRole.STANDBY; | 134 | + synchronized (this) { |
135 | + masterMap.put(deviceId, nodeId); | ||
136 | + backups = Lists.newLinkedList(); | ||
137 | + backupMap.put(deviceId, backups); | ||
138 | + termMap.put(deviceId, new AtomicInteger()); | ||
139 | + } | ||
140 | + updateStandby(nodeId, deviceId); | ||
141 | + return MastershipRole.MASTER; | ||
142 | + } | ||
143 | + | ||
144 | + //device once existed, but got removed, and is now getting a backup. | ||
145 | + if (!backups.contains(nodeId)) { | ||
146 | + synchronized (this) { | ||
147 | + backups.add(nodeId); | ||
148 | + termMap.put(deviceId, new AtomicInteger()); | ||
116 | } | 149 | } |
150 | + updateStandby(nodeId, deviceId); | ||
151 | + } | ||
152 | + | ||
153 | + } else if (current.equals(nodeId)) { | ||
154 | + return MastershipRole.MASTER; | ||
117 | } else { | 155 | } else { |
118 | - //masterMap doesn't contain it. | 156 | + //once created, a device never has a null backups list. |
119 | - role = MastershipRole.MASTER; | 157 | + if (!backups.contains(nodeId)) { |
120 | - masterMap.put(deviceId, nodeId); | 158 | + //we must have requested STANDBY setting |
159 | + synchronized (this) { | ||
160 | + backups.add(nodeId); | ||
161 | + termMap.put(deviceId, new AtomicInteger()); | ||
162 | + } | ||
163 | + updateStandby(nodeId, deviceId); | ||
164 | + } | ||
121 | } | 165 | } |
122 | - return role; | 166 | + |
167 | + return MastershipRole.STANDBY; | ||
123 | } | 168 | } |
124 | 169 | ||
125 | @Override | 170 | @Override |
126 | public MastershipTerm getTermFor(DeviceId deviceId) { | 171 | public MastershipTerm getTermFor(DeviceId deviceId) { |
127 | - if (masterMap.get(deviceId) == null) { | 172 | + if ((masterMap.get(deviceId) == null) || |
173 | + (termMap.get(deviceId) == null)) { | ||
128 | return null; | 174 | return null; |
129 | } | 175 | } |
130 | return MastershipTerm.of( | 176 | return MastershipTerm.of( |
131 | masterMap.get(deviceId), termMap.get(deviceId).get()); | 177 | masterMap.get(deviceId), termMap.get(deviceId).get()); |
132 | } | 178 | } |
133 | 179 | ||
180 | + @Override | ||
181 | + public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) { | ||
182 | + NodeId node = masterMap.get(deviceId); | ||
183 | + | ||
184 | + //TODO case where node is completely removed from the cluster? | ||
185 | + if (node.equals(nodeId)) { | ||
186 | + synchronized (this) { | ||
187 | + //pick new node. | ||
188 | + List<NodeId> backups = backupMap.get(deviceId); | ||
189 | + | ||
190 | + //no backups, so device is hosed | ||
191 | + if (backups.isEmpty()) { | ||
192 | + masterMap.remove(deviceId); | ||
193 | + backups.add(nodeId); | ||
194 | + return null; | ||
195 | + } | ||
196 | + NodeId backup = backups.remove(0); | ||
197 | + masterMap.put(deviceId, backup); | ||
198 | + backups.add(nodeId); | ||
199 | + return new MastershipEvent(MASTER_CHANGED, deviceId, backup); | ||
200 | + } | ||
201 | + } | ||
202 | + return null; | ||
203 | + } | ||
204 | + | ||
205 | + //add node as STANDBY to maps un-scalably. | ||
206 | + private void updateStandby(NodeId nodeId, DeviceId deviceId) { | ||
207 | + for (Map.Entry<DeviceId, List<NodeId>> e : backupMap.entrySet()) { | ||
208 | + DeviceId dev = e.getKey(); | ||
209 | + if (dev.equals(deviceId)) { | ||
210 | + continue; | ||
211 | + } | ||
212 | + synchronized (this) { | ||
213 | + List<NodeId> nodes = e.getValue(); | ||
214 | + if (!nodes.contains(nodeId)) { | ||
215 | + nodes.add(nodeId); | ||
216 | + } | ||
217 | + } | ||
218 | + } | ||
219 | + } | ||
220 | + | ||
134 | } | 221 | } | ... | ... |
-
Please register or login to post a comment