Yuta HIGUCHI

DatabaseService subsystem: add admin commands, etc.

Change-Id: I24124579f5e0b03ccbf35a03230ae5a7aff95f22
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
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 + */
16 +package org.onlab.onos.cli;
17 +
18 +import org.apache.karaf.shell.commands.Argument;
19 +import org.apache.karaf.shell.commands.Command;
20 +import org.onlab.onos.cluster.ControllerNode;
21 +import org.onlab.onos.cluster.DefaultControllerNode;
22 +import org.onlab.onos.cluster.NodeId;
23 +import org.onlab.onos.store.service.DatabaseAdminService;
24 +import org.onlab.packet.IpAddress;
25 +
26 +/**
27 + * Adds a new controller cluster node.
28 + */
29 +@Command(scope = "onos", name = "tablet-add",
30 + description = "Adds a new member to tablet")
31 +public class TabletAddCommand extends AbstractShellCommand {
32 +
33 + @Argument(index = 0, name = "nodeId", description = "Node ID",
34 + required = true, multiValued = false)
35 + String nodeId = null;
36 +
37 + // TODO context aware completer to get IP from ClusterService?
38 + @Argument(index = 1, name = "ip", description = "Node IP address",
39 + required = true, multiValued = false)
40 + String ip = null;
41 +
42 + @Argument(index = 2, name = "tcpPort", description = "Node TCP listen port",
43 + required = false, multiValued = false)
44 + int tcpPort = 9876;
45 +
46 + // TODO add tablet name argument when we support multiple tablets
47 +
48 + @Override
49 + protected void execute() {
50 + DatabaseAdminService service = get(DatabaseAdminService.class);
51 + ControllerNode node = new DefaultControllerNode(new NodeId(nodeId),
52 + IpAddress.valueOf(ip),
53 + tcpPort);
54 + service.addMember(node);
55 + }
56 +}
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
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 + */
16 +package org.onlab.onos.cli;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.ObjectMapper;
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
21 +
22 +import org.apache.karaf.shell.commands.Command;
23 +import org.onlab.onos.cluster.ClusterService;
24 +import org.onlab.onos.cluster.ControllerNode;
25 +import org.onlab.onos.store.service.DatabaseAdminService;
26 +
27 +import java.util.Collections;
28 +import java.util.List;
29 +
30 +import static com.google.common.collect.Lists.newArrayList;
31 +
32 +/**
33 + * Lists all controller cluster nodes.
34 + */
35 +@Command(scope = "onos", name = "tablet-member",
36 + description = "Lists all member nodes")
37 +public class TabletMemberCommand extends AbstractShellCommand {
38 +
39 + // TODO add tablet name argument when we support multiple tablets
40 +
41 + @Override
42 + protected void execute() {
43 + DatabaseAdminService service = get(DatabaseAdminService.class);
44 + ClusterService clusterService = get(ClusterService.class);
45 + List<ControllerNode> nodes = newArrayList(service.listMembers());
46 + Collections.sort(nodes, Comparators.NODE_COMPARATOR);
47 + if (outputJson()) {
48 + print("%s", json(service, nodes));
49 + } else {
50 + ControllerNode self = clusterService.getLocalNode();
51 + for (ControllerNode node : nodes) {
52 + print("id=%s, address=%s:%s %s",
53 + node.id(), node.ip(), node.tcpPort(),
54 + node.equals(self) ? "*" : "");
55 + }
56 + }
57 + }
58 +
59 + // Produces JSON structure.
60 + private JsonNode json(DatabaseAdminService service, List<ControllerNode> nodes) {
61 + ObjectMapper mapper = new ObjectMapper();
62 + ArrayNode result = mapper.createArrayNode();
63 + for (ControllerNode node : nodes) {
64 + result.add(mapper.createObjectNode()
65 + .put("id", node.id().toString())
66 + .put("ip", node.ip().toString())
67 + .put("tcpPort", node.tcpPort()));
68 + }
69 + return result;
70 + }
71 +
72 +}
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
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 + */
16 +package org.onlab.onos.cli;
17 +
18 +import org.apache.karaf.shell.commands.Argument;
19 +import org.apache.karaf.shell.commands.Command;
20 +import org.onlab.onos.cluster.ClusterService;
21 +import org.onlab.onos.cluster.ControllerNode;
22 +import org.onlab.onos.cluster.NodeId;
23 +import org.onlab.onos.store.service.DatabaseAdminService;
24 +
25 +/**
26 + * Removes a controller cluster node.
27 + */
28 +@Command(scope = "onos", name = "tablet-remove",
29 + description = "Removes a member from tablet")
30 +public class TabletRemoveCommand extends AbstractShellCommand {
31 +
32 + @Argument(index = 0, name = "nodeId", description = "Node ID",
33 + required = true, multiValued = false)
34 + String nodeId = null;
35 +
36 + // TODO add tablet name argument when we support multiple tablets
37 +
38 + @Override
39 + protected void execute() {
40 + DatabaseAdminService service = get(DatabaseAdminService.class);
41 + ClusterService clusterService = get(ClusterService.class);
42 + ControllerNode node = clusterService.getNode(new NodeId(nodeId));
43 + if (node != null) {
44 + service.removeMember(node);
45 + }
46 + }
47 +}
...@@ -20,6 +20,26 @@ ...@@ -20,6 +20,26 @@
20 <action class="org.onlab.onos.cli.SummaryCommand"/> 20 <action class="org.onlab.onos.cli.SummaryCommand"/>
21 </command> 21 </command>
22 <command> 22 <command>
23 + <action class="org.onlab.onos.cli.TabletMemberCommand"/>
24 + </command>
25 + <command>
26 + <action class="org.onlab.onos.cli.TabletAddCommand"/>
27 + <completers>
28 + <ref component-id="nodeIdCompleter"/>
29 + <null/>
30 + <null/>
31 + </completers>
32 + </command>
33 + <command>
34 + <action class="org.onlab.onos.cli.TabletRemoveCommand"/>
35 + <completers>
36 + <ref component-id="nodeIdCompleter"/>
37 + <null/>
38 + <null/>
39 + </completers>
40 + </command>
41 +
42 + <command>
23 <action class="org.onlab.onos.cli.NodesListCommand"/> 43 <action class="org.onlab.onos.cli.NodesListCommand"/>
24 </command> 44 </command>
25 <command> 45 <command>
......
1 package org.onlab.onos.store.service; 1 package org.onlab.onos.store.service;
2 2
3 +import java.util.Collection;
3 import java.util.List; 4 import java.util.List;
4 5
6 +import org.onlab.onos.cluster.ControllerNode;
7 +
5 /** 8 /**
6 * Service interface for running administrative tasks on a Database. 9 * Service interface for running administrative tasks on a Database.
7 */ 10 */
...@@ -32,4 +35,26 @@ public interface DatabaseAdminService { ...@@ -32,4 +35,26 @@ public interface DatabaseAdminService {
32 * Deletes all tables from the database. 35 * Deletes all tables from the database.
33 */ 36 */
34 public void dropAllTables(); 37 public void dropAllTables();
38 +
39 +
40 + /**
41 + * Add member to default Tablet.
42 + *
43 + * @param node to add
44 + */
45 + public void addMember(ControllerNode node);
46 +
47 + /**
48 + * Remove member from default Tablet.
49 + *
50 + * @param node node to remove
51 + */
52 + public void removeMember(ControllerNode node);
53 +
54 + /**
55 + * List members forming default Tablet.
56 + *
57 + * @return Copied collection of members forming default Tablet.
58 + */
59 + public Collection<ControllerNode> listMembers();
35 } 60 }
......
...@@ -169,7 +169,9 @@ public class ClusterMessagingProtocol ...@@ -169,7 +169,9 @@ public class ClusterMessagingProtocol
169 @Override 169 @Override
170 public ProtocolClient createClient(TcpMember member) { 170 public ProtocolClient createClient(TcpMember member) {
171 ControllerNode remoteNode = getControllerNode(member.host(), member.port()); 171 ControllerNode remoteNode = getControllerNode(member.host(), member.port());
172 - checkNotNull(remoteNode, "A valid controller node is expected"); 172 + checkNotNull(remoteNode,
173 + "A valid controller node is expected for %s:%s",
174 + member.host(), member.port());
173 return new ClusterMessagingProtocolClient( 175 return new ClusterMessagingProtocolClient(
174 clusterCommunicator, clusterService.getLocalNode(), remoteNode); 176 clusterCommunicator, clusterService.getLocalNode(), remoteNode);
175 } 177 }
......
...@@ -2,21 +2,30 @@ package org.onlab.onos.store.service.impl; ...@@ -2,21 +2,30 @@ package org.onlab.onos.store.service.impl;
2 2
3 import static org.slf4j.LoggerFactory.getLogger; 3 import static org.slf4j.LoggerFactory.getLogger;
4 4
5 +import java.io.File;
6 +import java.io.IOException;
5 import java.util.ArrayList; 7 import java.util.ArrayList;
6 import java.util.Arrays; 8 import java.util.Arrays;
9 +import java.util.Collection;
10 +import java.util.Collections;
11 +import java.util.HashSet;
7 import java.util.List; 12 import java.util.List;
13 +import java.util.Map;
14 +import java.util.Set;
8 import java.util.concurrent.CountDownLatch; 15 import java.util.concurrent.CountDownLatch;
9 import java.util.concurrent.TimeUnit; 16 import java.util.concurrent.TimeUnit;
10 17
11 import net.kuujo.copycat.Copycat; 18 import net.kuujo.copycat.Copycat;
12 import net.kuujo.copycat.StateMachine; 19 import net.kuujo.copycat.StateMachine;
13 import net.kuujo.copycat.cluster.ClusterConfig; 20 import net.kuujo.copycat.cluster.ClusterConfig;
21 +import net.kuujo.copycat.cluster.Member;
14 import net.kuujo.copycat.cluster.TcpCluster; 22 import net.kuujo.copycat.cluster.TcpCluster;
15 import net.kuujo.copycat.cluster.TcpClusterConfig; 23 import net.kuujo.copycat.cluster.TcpClusterConfig;
16 import net.kuujo.copycat.cluster.TcpMember; 24 import net.kuujo.copycat.cluster.TcpMember;
17 import net.kuujo.copycat.log.InMemoryLog; 25 import net.kuujo.copycat.log.InMemoryLog;
18 import net.kuujo.copycat.log.Log; 26 import net.kuujo.copycat.log.Log;
19 27
28 +import org.apache.commons.lang3.RandomUtils;
20 import org.apache.felix.scr.annotations.Activate; 29 import org.apache.felix.scr.annotations.Activate;
21 import org.apache.felix.scr.annotations.Component; 30 import org.apache.felix.scr.annotations.Component;
22 import org.apache.felix.scr.annotations.Deactivate; 31 import org.apache.felix.scr.annotations.Deactivate;
...@@ -27,6 +36,8 @@ import org.onlab.onos.cluster.ClusterEvent; ...@@ -27,6 +36,8 @@ import org.onlab.onos.cluster.ClusterEvent;
27 import org.onlab.onos.cluster.ClusterEventListener; 36 import org.onlab.onos.cluster.ClusterEventListener;
28 import org.onlab.onos.cluster.ClusterService; 37 import org.onlab.onos.cluster.ClusterService;
29 import org.onlab.onos.cluster.ControllerNode; 38 import org.onlab.onos.cluster.ControllerNode;
39 +import org.onlab.onos.cluster.DefaultControllerNode;
40 +import org.onlab.onos.cluster.NodeId;
30 import org.onlab.onos.store.service.DatabaseAdminService; 41 import org.onlab.onos.store.service.DatabaseAdminService;
31 import org.onlab.onos.store.service.DatabaseException; 42 import org.onlab.onos.store.service.DatabaseException;
32 import org.onlab.onos.store.service.DatabaseService; 43 import org.onlab.onos.store.service.DatabaseService;
...@@ -38,8 +49,12 @@ import org.onlab.onos.store.service.ReadResult; ...@@ -38,8 +49,12 @@ import org.onlab.onos.store.service.ReadResult;
38 import org.onlab.onos.store.service.WriteAborted; 49 import org.onlab.onos.store.service.WriteAborted;
39 import org.onlab.onos.store.service.WriteRequest; 50 import org.onlab.onos.store.service.WriteRequest;
40 import org.onlab.onos.store.service.WriteResult; 51 import org.onlab.onos.store.service.WriteResult;
52 +import org.onlab.packet.IpAddress;
41 import org.slf4j.Logger; 53 import org.slf4j.Logger;
42 54
55 +import com.google.common.collect.ImmutableList;
56 +import com.google.common.collect.Iterables;
57 +
43 /** 58 /**
44 * Strongly consistent and durable state management service based on 59 * Strongly consistent and durable state management service based on
45 * Copycat implementation of Raft consensus protocol. 60 * Copycat implementation of Raft consensus protocol.
...@@ -56,7 +71,19 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -56,7 +71,19 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
56 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 71 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
57 protected DatabaseProtocolService copycatMessagingProtocol; 72 protected DatabaseProtocolService copycatMessagingProtocol;
58 73
59 - public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log"; 74 + public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log_";
75 +
76 + // Current working dir seems to be /opt/onos/apache-karaf-3.0.2
77 + // TODO: Get the path to /opt/onos/config
78 + private static final String CONFIG_DIR = "../config";
79 +
80 + private static final String DEFAULT_MEMBER_FILE = "tablets.json";
81 +
82 + private static final String DEFAULT_TABLET = "default";
83 +
84 + // TODO: make this configurable
85 + // initial member configuration file path
86 + private String initialMemberConfig = DEFAULT_MEMBER_FILE;
60 87
61 private Copycat copycat; 88 private Copycat copycat;
62 private DatabaseClient client; 89 private DatabaseClient client;
...@@ -65,49 +92,75 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -65,49 +92,75 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
65 private ClusterConfig<TcpMember> clusterConfig; 92 private ClusterConfig<TcpMember> clusterConfig;
66 93
67 private CountDownLatch clusterEventLatch; 94 private CountDownLatch clusterEventLatch;
68 -
69 private ClusterEventListener clusterEventListener; 95 private ClusterEventListener clusterEventListener;
70 96
97 + private Map<String, Set<DefaultControllerNode>> tabletMembers;
98 +
99 + private boolean autoAddMember = false;
100 +
71 @Activate 101 @Activate
72 public void activate() { 102 public void activate() {
73 103
74 // TODO: Not every node should be part of the consensus ring. 104 // TODO: Not every node should be part of the consensus ring.
75 105
76 - final ControllerNode localNode = clusterService.getLocalNode(); 106 + // load tablet configuration
77 - TcpMember localMember = 107 + File file = new File(CONFIG_DIR, initialMemberConfig);
78 - new TcpMember( 108 + log.info("Loading config: {}", file.getAbsolutePath());
79 - localNode.ip().toString(), 109 + TabletDefinitionStore tabletDef = new TabletDefinitionStore(file);
80 - localNode.tcpPort()); 110 + try {
111 + tabletMembers = tabletDef.read();
112 + } catch (IOException e) {
113 + log.error("Failed to load tablet config {}", file);
114 + throw new IllegalStateException("Failed to load tablet config", e);
115 + }
81 116
117 + // load default tablet configuration and start copycat
82 clusterConfig = new TcpClusterConfig(); 118 clusterConfig = new TcpClusterConfig();
83 - clusterConfig.setLocalMember(localMember); 119 + Set<DefaultControllerNode> defaultMember = tabletMembers.get(DEFAULT_TABLET);
84 - 120 + if (defaultMember == null || defaultMember.isEmpty()) {
85 - List<TcpMember> remoteMembers = new ArrayList<>(clusterService.getNodes().size()); 121 + log.error("No member found in [{}] tablet configuration.",
122 + DEFAULT_TABLET);
123 + throw new IllegalStateException("No member found in tablet configuration");
86 124
87 - clusterEventLatch = new CountDownLatch(1); 125 + }
88 - clusterEventListener = new InternalClusterEventListener();
89 - clusterService.addListener(clusterEventListener);
90 126
91 - // note: from this point beyond, clusterConfig requires synchronization 127 + final ControllerNode localNode = clusterService.getLocalNode();
128 + TcpMember clientHandler = null;
129 + for (ControllerNode member : defaultMember) {
130 + final TcpMember tcpMember = new TcpMember(member.ip().toString(),
131 + member.tcpPort());
132 + if (localNode.equals(member)) {
133 + clientHandler = tcpMember;
134 + clusterConfig.setLocalMember(tcpMember);
135 + } else {
136 + clusterConfig.addRemoteMember(tcpMember);
137 + }
138 + }
92 139
93 - for (ControllerNode node : clusterService.getNodes()) { 140 + // TODO should be removed after DatabaseClient refactoring
94 - TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort()); 141 + if (clientHandler == null) {
95 - if (!member.equals(localMember)) { 142 + Set<TcpMember> members = clusterConfig.getMembers();
96 - remoteMembers.add(member); 143 + if (members.isEmpty()) {
144 + log.error("No member found in [{}] tablet configuration.",
145 + DEFAULT_TABLET);
146 + throw new IllegalStateException("No member found in tablet configuration");
97 } 147 }
148 + int position = RandomUtils.nextInt(0, members.size());
149 + clientHandler = Iterables.get(members, position);
98 } 150 }
99 151
100 - if (remoteMembers.isEmpty()) { 152 + // note: from this point beyond, clusterConfig requires synchronization
101 - log.info("This node is the only node in the cluster. " 153 + clusterEventLatch = new CountDownLatch(1);
102 - + "Waiting for others to show up."); 154 + clusterEventListener = new InternalClusterEventListener();
103 - // FIXME: hack trying to relax cases forming multiple consensus rings. 155 + clusterService.addListener(clusterEventListener);
104 - // add seed node configuration to avoid this
105 156
106 - // If the node is alone on it's own, wait some time 157 + if (clusterService.getNodes().size() < clusterConfig.getMembers().size()) {
107 - // hoping other will come up soon 158 + // current cluster size smaller then expected
108 try { 159 try {
109 if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) { 160 if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) {
110 - log.info("Starting as single node cluster"); 161 + log.info("Starting with {}/{} nodes cluster",
162 + clusterService.getNodes().size(),
163 + clusterConfig.getMembers().size());
111 } 164 }
112 } catch (InterruptedException e) { 165 } catch (InterruptedException e) {
113 log.info("Interrupted waiting for others", e); 166 log.info("Interrupted waiting for others", e);
...@@ -116,8 +169,6 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -116,8 +169,6 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
116 169
117 final TcpCluster cluster; 170 final TcpCluster cluster;
118 synchronized (clusterConfig) { 171 synchronized (clusterConfig) {
119 - clusterConfig.addRemoteMembers(remoteMembers);
120 -
121 // Create the cluster. 172 // Create the cluster.
122 cluster = new TcpCluster(clusterConfig); 173 cluster = new TcpCluster(clusterConfig);
123 } 174 }
...@@ -131,7 +182,8 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -131,7 +182,8 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
131 copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); 182 copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol);
132 copycat.start(); 183 copycat.start();
133 184
134 - client = new DatabaseClient(copycatMessagingProtocol.createClient(localMember)); 185 + // FIXME Redo DatabaseClient. Needs fall back mechanism etc.
186 + client = new DatabaseClient(copycatMessagingProtocol.createClient(clientHandler));
135 187
136 log.info("Started."); 188 log.info("Started.");
137 } 189 }
...@@ -233,22 +285,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -233,22 +285,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
233 final TcpMember tcpMember = new TcpMember(node.ip().toString(), 285 final TcpMember tcpMember = new TcpMember(node.ip().toString(),
234 node.tcpPort()); 286 node.tcpPort());
235 287
236 - log.trace("{}", event);
237 switch (event.type()) { 288 switch (event.type()) {
238 case INSTANCE_ACTIVATED: 289 case INSTANCE_ACTIVATED:
239 case INSTANCE_ADDED: 290 case INSTANCE_ADDED:
240 - log.info("{} was added to the cluster", tcpMember); 291 + if (autoAddMember) {
241 - synchronized (clusterConfig) { 292 + synchronized (clusterConfig) {
242 - clusterConfig.addRemoteMember(tcpMember); 293 + if (!clusterConfig.getMembers().contains(tcpMember)) {
294 + log.info("{} was automatically added to the cluster", tcpMember);
295 + clusterConfig.addRemoteMember(tcpMember);
296 + }
297 + }
243 } 298 }
244 break; 299 break;
245 case INSTANCE_DEACTIVATED: 300 case INSTANCE_DEACTIVATED:
246 case INSTANCE_REMOVED: 301 case INSTANCE_REMOVED:
247 - // FIXME to be replaced with admin interface 302 + if (autoAddMember) {
248 -// log.info("{} was removed from the cluster", tcpMember); 303 + Set<DefaultControllerNode> members
249 -// synchronized (clusterConfig) { 304 + = tabletMembers.getOrDefault(DEFAULT_TABLET,
250 -// clusterConfig.removeRemoteMember(tcpMember); 305 + Collections.emptySet());
251 -// } 306 + // remove only if not the initial members
307 + if (!members.contains(node)) {
308 + synchronized (clusterConfig) {
309 + if (clusterConfig.getMembers().contains(tcpMember)) {
310 + log.info("{} was automatically removed from the cluster", tcpMember);
311 + clusterConfig.removeRemoteMember(tcpMember);
312 + }
313 + }
314 + }
315 + }
252 break; 316 break;
253 default: 317 default:
254 break; 318 break;
...@@ -307,4 +371,58 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -307,4 +371,58 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
307 } 371 }
308 } 372 }
309 } 373 }
374 +
375 + @Override
376 + public void addMember(final ControllerNode node) {
377 + final TcpMember tcpMember = new TcpMember(node.ip().toString(),
378 + node.tcpPort());
379 + log.info("{} was added to the cluster", tcpMember);
380 + synchronized (clusterConfig) {
381 + clusterConfig.addRemoteMember(tcpMember);
382 + }
383 + }
384 +
385 + @Override
386 + public void removeMember(final ControllerNode node) {
387 + final TcpMember tcpMember = new TcpMember(node.ip().toString(),
388 + node.tcpPort());
389 + log.info("{} was removed from the cluster", tcpMember);
390 + synchronized (clusterConfig) {
391 + clusterConfig.removeRemoteMember(tcpMember);
392 + }
393 + }
394 +
395 + @Override
396 + public Collection<ControllerNode> listMembers() {
397 + if (copycat == null) {
398 + return ImmutableList.of();
399 + }
400 + Set<ControllerNode> members = new HashSet<>();
401 + for (Member member : copycat.cluster().members()) {
402 + if (member instanceof TcpMember) {
403 + final TcpMember tcpMember = (TcpMember) member;
404 + // TODO assuming tcpMember#host to be IP address,
405 + // but if not lookup DNS, etc. first
406 + IpAddress ip = IpAddress.valueOf(tcpMember.host());
407 + int tcpPort = tcpMember.port();
408 + NodeId id = getNodeIdFromIp(ip, tcpPort);
409 + if (id == null) {
410 + log.info("No NodeId found for {}:{}", ip, tcpPort);
411 + continue;
412 + }
413 + members.add(new DefaultControllerNode(id, ip, tcpPort));
414 + }
415 + }
416 + return members;
417 + }
418 +
419 + private NodeId getNodeIdFromIp(IpAddress ip, int tcpPort) {
420 + for (ControllerNode node : clusterService.getNodes()) {
421 + if (node.ip().equals(ip) &&
422 + node.tcpPort() == tcpPort) {
423 + return node.id();
424 + }
425 + }
426 + return null;
427 + }
310 } 428 }
......
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
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 + */
16 +package org.onlab.onos.store.service.impl;
17 +
18 +import static com.google.common.base.Preconditions.checkArgument;
19 +import static com.google.common.base.Preconditions.checkNotNull;
20 +import static org.slf4j.LoggerFactory.getLogger;
21 +
22 +import java.io.File;
23 +import java.io.IOException;
24 +import java.util.HashMap;
25 +import java.util.HashSet;
26 +import java.util.Iterator;
27 +import java.util.Map;
28 +import java.util.Map.Entry;
29 +import java.util.Set;
30 +
31 +import org.onlab.onos.cluster.DefaultControllerNode;
32 +import org.onlab.onos.cluster.NodeId;
33 +import org.onlab.packet.IpAddress;
34 +import org.slf4j.Logger;
35 +
36 +import com.fasterxml.jackson.core.JsonEncoding;
37 +import com.fasterxml.jackson.core.JsonFactory;
38 +import com.fasterxml.jackson.databind.JsonNode;
39 +import com.fasterxml.jackson.databind.ObjectMapper;
40 +import com.fasterxml.jackson.databind.node.ArrayNode;
41 +import com.fasterxml.jackson.databind.node.ObjectNode;
42 +
43 +/**
44 + * Allows for reading and writing tablet definition as a JSON file.
45 + */
46 +public class TabletDefinitionStore {
47 +
48 + private final Logger log = getLogger(getClass());
49 +
50 + private final File file;
51 +
52 + /**
53 + * Creates a reader/writer of the tablet definition file.
54 + *
55 + * @param filePath location of the definition file
56 + */
57 + public TabletDefinitionStore(String filePath) {
58 + file = new File(filePath);
59 + }
60 +
61 + /**
62 + * Creates a reader/writer of the tablet definition file.
63 + *
64 + * @param filePath location of the definition file
65 + */
66 + public TabletDefinitionStore(File filePath) {
67 + file = checkNotNull(filePath);
68 + }
69 +
70 + /**
71 + * Returns the Map from tablet name to set of initial member nodes.
72 + *
73 + * @return Map from tablet name to set of initial member nodes
74 + * @throws IOException when I/O exception of some sort has occurred.
75 + */
76 + public Map<String, Set<DefaultControllerNode>> read() throws IOException {
77 +
78 + final Map<String, Set<DefaultControllerNode>> tablets = new HashMap<>();
79 +
80 + final ObjectMapper mapper = new ObjectMapper();
81 + final ObjectNode tabletNodes = (ObjectNode) mapper.readTree(file);
82 + final Iterator<Entry<String, JsonNode>> fields = tabletNodes.fields();
83 + while (fields.hasNext()) {
84 + final Entry<String, JsonNode> next = fields.next();
85 + final Set<DefaultControllerNode> nodes = new HashSet<>();
86 + final Iterator<JsonNode> elements = next.getValue().elements();
87 + while (elements.hasNext()) {
88 + ObjectNode nodeDef = (ObjectNode) elements.next();
89 + nodes.add(new DefaultControllerNode(new NodeId(nodeDef.get("id").asText()),
90 + IpAddress.valueOf(nodeDef.get("ip").asText()),
91 + nodeDef.get("tcpPort").asInt(9876)));
92 + }
93 +
94 + tablets.put(next.getKey(), nodes);
95 + }
96 + return tablets;
97 + }
98 +
99 + /**
100 + * Updates the Map from tablet name to set of member nodes.
101 + *
102 + * @param tabletName name of the tablet to update
103 + * @param nodes set of initial member nodes
104 + * @throws IOException when I/O exception of some sort has occurred.
105 + */
106 + public void write(String tabletName, Set<DefaultControllerNode> nodes) throws IOException {
107 + checkNotNull(tabletName);
108 + checkArgument(tabletName.isEmpty(), "Tablet name cannot be empty");
109 + // TODO should validate if tabletName is allowed in JSON
110 +
111 + // load current
112 + Map<String, Set<DefaultControllerNode>> config;
113 + try {
114 + config = read();
115 + } catch (IOException e) {
116 + log.info("Reading tablet config failed, assuming empty definition.");
117 + config = new HashMap<>();
118 + }
119 + // update with specified
120 + config.put(tabletName, nodes);
121 +
122 + // write back to file
123 + final ObjectMapper mapper = new ObjectMapper();
124 + final ObjectNode tabletNodes = mapper.createObjectNode();
125 + for (Entry<String, Set<DefaultControllerNode>> tablet : config.entrySet()) {
126 + ArrayNode nodeDefs = mapper.createArrayNode();
127 + tabletNodes.set(tablet.getKey(), nodeDefs);
128 +
129 + for (DefaultControllerNode node : tablet.getValue()) {
130 + ObjectNode nodeDef = mapper.createObjectNode();
131 + nodeDef.put("id", node.id().toString())
132 + .put("ip", node.ip().toString())
133 + .put("tcpPort", node.tcpPort());
134 + nodeDefs.add(nodeDef);
135 + }
136 + }
137 + mapper.writeTree(new JsonFactory().createGenerator(file, JsonEncoding.UTF8),
138 + tabletNodes);
139 + }
140 +
141 +}
1 +onos-config command will copy files contained in this directory to ONOS instances according to cell definition
2 +
...@@ -25,3 +25,18 @@ ssh $remote " ...@@ -25,3 +25,18 @@ ssh $remote "
25 >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/system.properties 25 >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/system.properties
26 " 26 "
27 scp -q $CDEF_FILE $remote:$ONOS_INSTALL_DIR/config/ 27 scp -q $CDEF_FILE $remote:$ONOS_INSTALL_DIR/config/
28 +
29 +# Generate a default tablets.json from the ON* environment variables
30 +TDEF_FILE=/tmp/tablets.json
31 +echo "{ \"default\":[" > $TDEF_FILE
32 +for node in $(env | sort | egrep "OC[2-9]+" | cut -d= -f2); do
33 + echo " { \"id\": \"$node\", \"ip\": \"$node\", \"tcpPort\": 9876 }," >> $TDEF_FILE
34 +done
35 +echo " { \"id\": \"$OC1\", \"ip\": \"$OC1\", \"tcpPort\": 9876 }" >> $TDEF_FILE
36 +echo "]}" >> $TDEF_FILE
37 +scp -q $TDEF_FILE $remote:$ONOS_INSTALL_DIR/config/
38 +
39 +
40 +# copy tools/package/config/ to remote
41 +scp -qr ${ONOS_ROOT}/tools/package/config/ $remote:$ONOS_INSTALL_DIR/
42 +
......