Showing
3 changed files
with
300 additions
and
0 deletions
| 1 | +package org.onlab.onos.ccc; | ||
| 2 | + | ||
| 3 | +import com.google.common.collect.ImmutableSet; | ||
| 4 | +import org.apache.felix.scr.annotations.Activate; | ||
| 5 | +import org.apache.felix.scr.annotations.Component; | ||
| 6 | +import org.apache.felix.scr.annotations.Deactivate; | ||
| 7 | +import org.apache.felix.scr.annotations.Service; | ||
| 8 | +import org.onlab.nio.AcceptorLoop; | ||
| 9 | +import org.onlab.nio.IOLoop; | ||
| 10 | +import org.onlab.nio.MessageStream; | ||
| 11 | +import org.onlab.onos.cluster.ClusterEvent; | ||
| 12 | +import org.onlab.onos.cluster.ClusterStore; | ||
| 13 | +import org.onlab.onos.cluster.ClusterStoreDelegate; | ||
| 14 | +import org.onlab.onos.cluster.ControllerNode; | ||
| 15 | +import org.onlab.onos.cluster.DefaultControllerNode; | ||
| 16 | +import org.onlab.onos.cluster.NodeId; | ||
| 17 | +import org.onlab.onos.store.AbstractStore; | ||
| 18 | +import org.onlab.packet.IpPrefix; | ||
| 19 | +import org.slf4j.Logger; | ||
| 20 | +import org.slf4j.LoggerFactory; | ||
| 21 | + | ||
| 22 | +import java.io.IOException; | ||
| 23 | +import java.net.InetSocketAddress; | ||
| 24 | +import java.nio.channels.ByteChannel; | ||
| 25 | +import java.nio.channels.ServerSocketChannel; | ||
| 26 | +import java.util.ArrayList; | ||
| 27 | +import java.util.List; | ||
| 28 | +import java.util.Map; | ||
| 29 | +import java.util.Set; | ||
| 30 | +import java.util.concurrent.ConcurrentHashMap; | ||
| 31 | +import java.util.concurrent.ExecutorService; | ||
| 32 | +import java.util.concurrent.Executors; | ||
| 33 | + | ||
| 34 | +import static java.net.InetAddress.getByAddress; | ||
| 35 | +import static org.onlab.onos.cluster.ControllerNode.State; | ||
| 36 | +import static org.onlab.packet.IpPrefix.valueOf; | ||
| 37 | +import static org.onlab.util.Tools.namedThreads; | ||
| 38 | + | ||
| 39 | +/** | ||
| 40 | + * Distributed implementation of the cluster nodes store. | ||
| 41 | + */ | ||
| 42 | +@Component(immediate = true) | ||
| 43 | +@Service | ||
| 44 | +public class DistributedClusterStore | ||
| 45 | + extends AbstractStore<ClusterEvent, ClusterStoreDelegate> | ||
| 46 | + implements ClusterStore { | ||
| 47 | + | ||
| 48 | + private final Logger log = LoggerFactory.getLogger(getClass()); | ||
| 49 | + | ||
| 50 | + private static final long SELECT_TIMEOUT = 50; | ||
| 51 | + private static final int WORKERS = 3; | ||
| 52 | + private static final int COMM_BUFFER_SIZE = 16 * 1024; | ||
| 53 | + private static final int COMM_IDLE_TIME = 500; | ||
| 54 | + | ||
| 55 | + private DefaultControllerNode self; | ||
| 56 | + private final Map<NodeId, DefaultControllerNode> nodes = new ConcurrentHashMap<>(); | ||
| 57 | + private final Map<NodeId, State> states = new ConcurrentHashMap<>(); | ||
| 58 | + | ||
| 59 | + private final ExecutorService listenExecutor = | ||
| 60 | + Executors.newSingleThreadExecutor(namedThreads("onos-listen")); | ||
| 61 | + private final ExecutorService commExecutors = | ||
| 62 | + Executors.newFixedThreadPool(WORKERS, namedThreads("onos-cluster")); | ||
| 63 | + private final ExecutorService heartbeatExecutor = | ||
| 64 | + Executors.newSingleThreadExecutor(namedThreads("onos-heartbeat")); | ||
| 65 | + | ||
| 66 | + private ListenLoop listenLoop; | ||
| 67 | + private List<CommLoop> commLoops = new ArrayList<>(WORKERS); | ||
| 68 | + | ||
| 69 | + @Activate | ||
| 70 | + public void activate() { | ||
| 71 | + establishIdentity(); | ||
| 72 | + startCommunications(); | ||
| 73 | + startListening(); | ||
| 74 | + log.info("Started"); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + private void startCommunications() { | ||
| 78 | + for (int i = 0; i < WORKERS; i++) { | ||
| 79 | + try { | ||
| 80 | + CommLoop loop = new CommLoop(); | ||
| 81 | + commLoops.add(loop); | ||
| 82 | + commExecutors.execute(loop); | ||
| 83 | + } catch (IOException e) { | ||
| 84 | + log.warn("Unable to start comm IO loop", e); | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + // Starts listening for connections from peer cluster members. | ||
| 90 | + private void startListening() { | ||
| 91 | + try { | ||
| 92 | + listenLoop = new ListenLoop(self.ip(), self.tcpPort()); | ||
| 93 | + listenExecutor.execute(listenLoop); | ||
| 94 | + } catch (IOException e) { | ||
| 95 | + log.error("Unable to listen for cluster connections", e); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + // Establishes the controller's own identity. | ||
| 100 | + private void establishIdentity() { | ||
| 101 | + // For now rely on env. variable. | ||
| 102 | + IpPrefix ip = valueOf(System.getenv("ONOS_NIC")); | ||
| 103 | + self = new DefaultControllerNode(new NodeId(ip.toString()), ip); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + @Deactivate | ||
| 107 | + public void deactivate() { | ||
| 108 | + listenLoop.shutdown(); | ||
| 109 | + for (CommLoop loop : commLoops) { | ||
| 110 | + loop.shutdown(); | ||
| 111 | + } | ||
| 112 | + log.info("Stopped"); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + @Override | ||
| 116 | + public ControllerNode getLocalNode() { | ||
| 117 | + return self; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + @Override | ||
| 121 | + public Set<ControllerNode> getNodes() { | ||
| 122 | + ImmutableSet.Builder<ControllerNode> builder = ImmutableSet.builder(); | ||
| 123 | + return builder.addAll(nodes.values()).build(); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + @Override | ||
| 127 | + public ControllerNode getNode(NodeId nodeId) { | ||
| 128 | + return nodes.get(nodeId); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + @Override | ||
| 132 | + public State getState(NodeId nodeId) { | ||
| 133 | + State state = states.get(nodeId); | ||
| 134 | + return state == null ? State.INACTIVE : state; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + @Override | ||
| 138 | + public ControllerNode addNode(NodeId nodeId, IpPrefix ip, int tcpPort) { | ||
| 139 | + DefaultControllerNode node = new DefaultControllerNode(nodeId, ip, tcpPort); | ||
| 140 | + nodes.put(nodeId, node); | ||
| 141 | + return node; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + @Override | ||
| 145 | + public void removeNode(NodeId nodeId) { | ||
| 146 | + nodes.remove(nodeId); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + // Listens and accepts inbound connections from other cluster nodes. | ||
| 150 | + private class ListenLoop extends AcceptorLoop { | ||
| 151 | + ListenLoop(IpPrefix ip, int tcpPort) throws IOException { | ||
| 152 | + super(SELECT_TIMEOUT, new InetSocketAddress(getByAddress(ip.toOctets()), tcpPort)); | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + @Override | ||
| 156 | + protected void acceptConnection(ServerSocketChannel channel) throws IOException { | ||
| 157 | + | ||
| 158 | + } | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + private class CommLoop extends IOLoop<TLVMessage, TLVMessageStream> { | ||
| 162 | + CommLoop() throws IOException { | ||
| 163 | + super(SELECT_TIMEOUT); | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + @Override | ||
| 167 | + protected TLVMessageStream createStream(ByteChannel byteChannel) { | ||
| 168 | + return new TLVMessageStream(this, byteChannel, COMM_BUFFER_SIZE, COMM_IDLE_TIME); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + @Override | ||
| 172 | + protected void processMessages(List<TLVMessage> messages, MessageStream<TLVMessage> stream) { | ||
| 173 | + | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | +} |
| 1 | +package org.onlab.onos.ccc; | ||
| 2 | + | ||
| 3 | +import org.onlab.nio.AbstractMessage; | ||
| 4 | + | ||
| 5 | +import java.util.Objects; | ||
| 6 | + | ||
| 7 | +import static com.google.common.base.MoreObjects.toStringHelper; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * Base message for cluster-wide communications using TLVs. | ||
| 11 | + */ | ||
| 12 | +public class TLVMessage extends AbstractMessage { | ||
| 13 | + | ||
| 14 | + private final int type; | ||
| 15 | + private final Object data; | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Creates an immutable TLV message. | ||
| 19 | + * | ||
| 20 | + * @param type message type | ||
| 21 | + * @param length message length | ||
| 22 | + * @param data message data | ||
| 23 | + */ | ||
| 24 | + public TLVMessage(int type, int length, Object data) { | ||
| 25 | + this.length = length; | ||
| 26 | + this.type = type; | ||
| 27 | + this.data = data; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * Returns the message type indicator. | ||
| 32 | + * | ||
| 33 | + * @return message type | ||
| 34 | + */ | ||
| 35 | + public int type() { | ||
| 36 | + return type; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Returns the data object. | ||
| 41 | + * | ||
| 42 | + * @return message data | ||
| 43 | + */ | ||
| 44 | + public Object data() { | ||
| 45 | + return data; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public int hashCode() { | ||
| 50 | + return Objects.hash(type, data); | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + @Override | ||
| 54 | + public boolean equals(Object obj) { | ||
| 55 | + if (this == obj) { | ||
| 56 | + return true; | ||
| 57 | + } | ||
| 58 | + if (obj == null || getClass() != obj.getClass()) { | ||
| 59 | + return false; | ||
| 60 | + } | ||
| 61 | + final TLVMessage other = (TLVMessage) obj; | ||
| 62 | + return Objects.equals(this.type, other.type) && | ||
| 63 | + Objects.equals(this.data, other.data); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + @Override | ||
| 67 | + public String toString() { | ||
| 68 | + return toStringHelper(this).add("type", type).add("length", length).toString(); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | +} |
| 1 | +package org.onlab.onos.ccc; | ||
| 2 | + | ||
| 3 | +import org.onlab.nio.IOLoop; | ||
| 4 | +import org.onlab.nio.MessageStream; | ||
| 5 | + | ||
| 6 | +import java.nio.ByteBuffer; | ||
| 7 | +import java.nio.channels.ByteChannel; | ||
| 8 | + | ||
| 9 | +import static com.google.common.base.Preconditions.checkState; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Stream for transferring TLV messages between cluster members. | ||
| 13 | + */ | ||
| 14 | +public class TLVMessageStream extends MessageStream<TLVMessage> { | ||
| 15 | + | ||
| 16 | + private static final long MARKER = 0xfeedcafecafefeedL; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * Creates a message stream associated with the specified IO loop and | ||
| 20 | + * backed by the given byte channel. | ||
| 21 | + * | ||
| 22 | + * @param loop IO loop | ||
| 23 | + * @param byteChannel backing byte channel | ||
| 24 | + * @param bufferSize size of the backing byte buffers | ||
| 25 | + * @param maxIdleMillis maximum number of millis the stream can be idle | ||
| 26 | + */ | ||
| 27 | + protected TLVMessageStream(IOLoop<TLVMessage, ?> loop, ByteChannel byteChannel, | ||
| 28 | + int bufferSize, int maxIdleMillis) { | ||
| 29 | + super(loop, byteChannel, bufferSize, maxIdleMillis); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + protected TLVMessage read(ByteBuffer buffer) { | ||
| 34 | + long marker = buffer.getLong(); | ||
| 35 | + checkState(marker == MARKER, "Incorrect message marker"); | ||
| 36 | + | ||
| 37 | + int type = buffer.getInt(); | ||
| 38 | + int length = buffer.getInt(); | ||
| 39 | + | ||
| 40 | + // TODO: add deserialization hook here | ||
| 41 | + | ||
| 42 | + return new TLVMessage(type, length, null); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Override | ||
| 46 | + protected void write(TLVMessage message, ByteBuffer buffer) { | ||
| 47 | + buffer.putLong(MARKER); | ||
| 48 | + buffer.putInt(message.type()); | ||
| 49 | + buffer.putInt(message.length()); | ||
| 50 | + | ||
| 51 | + // TODO: add serialization hook here | ||
| 52 | + } | ||
| 53 | +} |
-
Please register or login to post a comment