Relinquish mastership, tests, and few modifications to trivial MastershipStore
Change-Id: Iae29de010f13cb3ee02bcb316510cc254d5756fc
Showing
6 changed files
with
178 additions
and
36 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 | - if (role.equals(MastershipRole.MASTER)) { | 76 | + MastershipRole current = store.getRole(nodeId, deviceId); |
| 77 | - MastershipEvent event = store.setMaster(nodeId, deviceId); | 77 | + if (role.equals(current)) { |
| 78 | + return; | ||
| 79 | + } else { | ||
| 80 | + MastershipEvent event = null; | ||
| 81 | + if (role.equals(MastershipRole.MASTER)) { | ||
| 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); |
| 68 | - synchronized (this) { | 73 | + |
| 69 | - masterMap.put(deviceId, nodeId); | 74 | + if (current == null) { |
| 70 | - termMap.put(deviceId, new AtomicInteger()); | 75 | + if (backups == null) { |
| 76 | + //add new mapping to everything | ||
| 77 | + synchronized (this) { | ||
| 78 | + masterMap.put(deviceId, nodeId); | ||
| 79 | + backups = Lists.newLinkedList(); | ||
| 80 | + backupMap.put(deviceId, backups); | ||
| 81 | + termMap.put(deviceId, new AtomicInteger()); | ||
| 82 | + } | ||
| 83 | + } else { | ||
| 84 | + //set master to new node and remove from backups if there | ||
| 85 | + synchronized (this) { | ||
| 86 | + masterMap.put(deviceId, nodeId); | ||
| 87 | + backups.remove(nodeId); | ||
| 88 | + termMap.get(deviceId).incrementAndGet(); | ||
| 89 | + } | ||
| 71 | } | 90 | } |
| 72 | - return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | 91 | + } else if (current.equals(nodeId)) { |
| 73 | - } | ||
| 74 | - | ||
| 75 | - if (node.equals(nodeId)) { | ||
| 76 | return null; | 92 | return null; |
| 77 | } else { | 93 | } else { |
| 78 | - synchronized (this) { | 94 | + //add current to backup, set master to new node |
| 79 | - masterMap.put(deviceId, nodeId); | 95 | + masterMap.put(deviceId, nodeId); |
| 80 | - termMap.get(deviceId).incrementAndGet(); | 96 | + backups.add(current); |
| 81 | - return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | 97 | + backups.remove(nodeId); |
| 82 | - } | 98 | + termMap.get(deviceId).incrementAndGet(); |
| 83 | } | 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; | ||
| 116 | } | 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()); | ||
| 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