Madan Jampani

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Showing 31 changed files with 1243 additions and 522 deletions
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 }
......
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 +
17 +/**
18 + * Definitions of events and messages pertaining to replication of flow entries.
19 + */
20 +package org.onlab.onos.store.flow;
...@@ -17,6 +17,7 @@ package org.onlab.onos.store.hz; ...@@ -17,6 +17,7 @@ package org.onlab.onos.store.hz;
17 17
18 import com.hazelcast.config.Config; 18 import com.hazelcast.config.Config;
19 import com.hazelcast.config.FileSystemXmlConfig; 19 import com.hazelcast.config.FileSystemXmlConfig;
20 +import com.hazelcast.config.MapConfig;
20 import com.hazelcast.core.Hazelcast; 21 import com.hazelcast.core.Hazelcast;
21 import com.hazelcast.core.HazelcastInstance; 22 import com.hazelcast.core.HazelcastInstance;
22 23
...@@ -46,6 +47,13 @@ public class StoreManager implements StoreService { ...@@ -46,6 +47,13 @@ public class StoreManager implements StoreService {
46 public void activate() { 47 public void activate() {
47 try { 48 try {
48 Config config = new FileSystemXmlConfig(HAZELCAST_XML_FILE); 49 Config config = new FileSystemXmlConfig(HAZELCAST_XML_FILE);
50 +
51 + MapConfig roles = config.getMapConfig("nodeRoles");
52 + roles.setAsyncBackupCount(MapConfig.MAX_BACKUP_COUNT - roles.getBackupCount());
53 +
54 + MapConfig terms = config.getMapConfig("terms");
55 + terms.setAsyncBackupCount(MapConfig.MAX_BACKUP_COUNT - terms.getBackupCount());
56 +
49 instance = Hazelcast.newHazelcastInstance(config); 57 instance = Hazelcast.newHazelcastInstance(config);
50 log.info("Started"); 58 log.info("Started");
51 } catch (FileNotFoundException e) { 59 } catch (FileNotFoundException e) {
......
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 +
17 +/**
18 + * Implementation of distributed intent store.
19 + */
20 +package org.onlab.onos.store.intent.impl;
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 +
17 +/**
18 + * Cluster messaging and distributed store serializers.
19 + */
20 +package org.onlab.onos.store.serializers.impl;
...@@ -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 }
......
1 package org.onlab.onos.store.service.impl; 1 package org.onlab.onos.store.service.impl;
2 2
3 -import java.util.Arrays; 3 +import static com.google.common.base.Preconditions.checkNotNull;
4 +
4 import java.util.List; 5 import java.util.List;
5 -import java.util.UUID;
6 import java.util.concurrent.CompletableFuture; 6 import java.util.concurrent.CompletableFuture;
7 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.ExecutionException;
8 8
9 -import net.kuujo.copycat.protocol.Response.Status; 9 +import net.kuujo.copycat.Copycat;
10 -import net.kuujo.copycat.protocol.SubmitRequest;
11 -import net.kuujo.copycat.protocol.SubmitResponse;
12 -import net.kuujo.copycat.spi.protocol.ProtocolClient;
13 10
14 import org.onlab.onos.store.service.DatabaseException; 11 import org.onlab.onos.store.service.DatabaseException;
15 import org.onlab.onos.store.service.ReadRequest; 12 import org.onlab.onos.store.service.ReadRequest;
...@@ -20,31 +17,17 @@ import org.onlab.onos.store.service.WriteRequest; ...@@ -20,31 +17,17 @@ import org.onlab.onos.store.service.WriteRequest;
20 */ 17 */
21 public class DatabaseClient { 18 public class DatabaseClient {
22 19
23 - private final ProtocolClient client; 20 + private final Copycat copycat;
24 -
25 - public DatabaseClient(ProtocolClient client) {
26 - this.client = client;
27 - }
28 21
29 - private static String nextId() { 22 + public DatabaseClient(Copycat copycat) {
30 - return UUID.randomUUID().toString(); 23 + this.copycat = checkNotNull(copycat);
31 } 24 }
32 25
33 public boolean createTable(String tableName) { 26 public boolean createTable(String tableName) {
34 27
35 - SubmitRequest request = 28 + CompletableFuture<Boolean> future = copycat.submit("createTable", tableName);
36 - new SubmitRequest(
37 - nextId(),
38 - "createTable",
39 - Arrays.asList(tableName));
40 - CompletableFuture<SubmitResponse> future = client.submit(request);
41 try { 29 try {
42 - final SubmitResponse submitResponse = future.get(); 30 + return future.get();
43 - if (submitResponse.status() == Status.OK) {
44 - return (boolean) submitResponse.result();
45 - } else {
46 - throw new DatabaseException(submitResponse.error());
47 - }
48 } catch (InterruptedException | ExecutionException e) { 31 } catch (InterruptedException | ExecutionException e) {
49 throw new DatabaseException(e); 32 throw new DatabaseException(e);
50 } 33 }
...@@ -52,17 +35,9 @@ public class DatabaseClient { ...@@ -52,17 +35,9 @@ public class DatabaseClient {
52 35
53 public void dropTable(String tableName) { 36 public void dropTable(String tableName) {
54 37
55 - SubmitRequest request = 38 + CompletableFuture<Void> future = copycat.submit("dropTable", tableName);
56 - new SubmitRequest(
57 - nextId(),
58 - "dropTable",
59 - Arrays.asList(tableName));
60 - CompletableFuture<SubmitResponse> future = client.submit(request);
61 try { 39 try {
62 - if (future.get().status() != Status.OK) { 40 + future.get();
63 - throw new DatabaseException(future.get().toString());
64 - }
65 -
66 } catch (InterruptedException | ExecutionException e) { 41 } catch (InterruptedException | ExecutionException e) {
67 throw new DatabaseException(e); 42 throw new DatabaseException(e);
68 } 43 }
...@@ -70,79 +45,39 @@ public class DatabaseClient { ...@@ -70,79 +45,39 @@ public class DatabaseClient {
70 45
71 public void dropAllTables() { 46 public void dropAllTables() {
72 47
73 - SubmitRequest request = 48 + CompletableFuture<Void> future = copycat.submit("dropAllTables");
74 - new SubmitRequest(
75 - nextId(),
76 - "dropAllTables",
77 - Arrays.asList());
78 - CompletableFuture<SubmitResponse> future = client.submit(request);
79 try { 49 try {
80 - if (future.get().status() != Status.OK) { 50 + future.get();
81 - throw new DatabaseException(future.get().toString());
82 - }
83 } catch (InterruptedException | ExecutionException e) { 51 } catch (InterruptedException | ExecutionException e) {
84 throw new DatabaseException(e); 52 throw new DatabaseException(e);
85 } 53 }
86 } 54 }
87 55
88 - @SuppressWarnings("unchecked")
89 public List<String> listTables() { 56 public List<String> listTables() {
90 57
91 - SubmitRequest request = 58 + CompletableFuture<List<String>> future = copycat.submit("listTables");
92 - new SubmitRequest(
93 - nextId(),
94 - "listTables",
95 - Arrays.asList());
96 - CompletableFuture<SubmitResponse> future = client.submit(request);
97 try { 59 try {
98 - final SubmitResponse submitResponse = future.get(); 60 + return future.get();
99 - if (submitResponse.status() == Status.OK) {
100 - return (List<String>) submitResponse.result();
101 - } else {
102 - throw new DatabaseException(submitResponse.error());
103 - }
104 } catch (InterruptedException | ExecutionException e) { 61 } catch (InterruptedException | ExecutionException e) {
105 throw new DatabaseException(e); 62 throw new DatabaseException(e);
106 } 63 }
107 } 64 }
108 65
109 - @SuppressWarnings("unchecked")
110 public List<InternalReadResult> batchRead(List<ReadRequest> requests) { 66 public List<InternalReadResult> batchRead(List<ReadRequest> requests) {
111 67
112 - SubmitRequest request = new SubmitRequest( 68 + CompletableFuture<List<InternalReadResult>> future = copycat.submit("read", requests);
113 - nextId(),
114 - "read",
115 - Arrays.asList(requests));
116 -
117 - CompletableFuture<SubmitResponse> future = client.submit(request);
118 try { 69 try {
119 - final SubmitResponse submitResponse = future.get(); 70 + return future.get();
120 - if (submitResponse.status() == Status.OK) {
121 - return (List<InternalReadResult>) submitResponse.result();
122 - } else {
123 - throw new DatabaseException(submitResponse.error());
124 - }
125 } catch (InterruptedException | ExecutionException e) { 71 } catch (InterruptedException | ExecutionException e) {
126 throw new DatabaseException(e); 72 throw new DatabaseException(e);
127 } 73 }
128 } 74 }
129 75
130 - @SuppressWarnings("unchecked")
131 public List<InternalWriteResult> batchWrite(List<WriteRequest> requests) { 76 public List<InternalWriteResult> batchWrite(List<WriteRequest> requests) {
132 77
133 - SubmitRequest request = new SubmitRequest( 78 + CompletableFuture<List<InternalWriteResult>> future = copycat.submit("write", requests);
134 - nextId(),
135 - "write",
136 - Arrays.asList(requests));
137 -
138 - CompletableFuture<SubmitResponse> future = client.submit(request);
139 try { 79 try {
140 - final SubmitResponse submitResponse = future.get(); 80 + return future.get();
141 - if (submitResponse.status() == Status.OK) {
142 - return (List<InternalWriteResult>) submitResponse.result();
143 - } else {
144 - throw new DatabaseException(submitResponse.error());
145 - }
146 } catch (InterruptedException | ExecutionException e) { 81 } catch (InterruptedException | ExecutionException e) {
147 throw new DatabaseException(e); 82 throw new DatabaseException(e);
148 } 83 }
......
...@@ -2,15 +2,23 @@ package org.onlab.onos.store.service.impl; ...@@ -2,15 +2,23 @@ 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;
...@@ -27,6 +35,8 @@ import org.onlab.onos.cluster.ClusterEvent; ...@@ -27,6 +35,8 @@ import org.onlab.onos.cluster.ClusterEvent;
27 import org.onlab.onos.cluster.ClusterEventListener; 35 import org.onlab.onos.cluster.ClusterEventListener;
28 import org.onlab.onos.cluster.ClusterService; 36 import org.onlab.onos.cluster.ClusterService;
29 import org.onlab.onos.cluster.ControllerNode; 37 import org.onlab.onos.cluster.ControllerNode;
38 +import org.onlab.onos.cluster.DefaultControllerNode;
39 +import org.onlab.onos.cluster.NodeId;
30 import org.onlab.onos.store.service.DatabaseAdminService; 40 import org.onlab.onos.store.service.DatabaseAdminService;
31 import org.onlab.onos.store.service.DatabaseException; 41 import org.onlab.onos.store.service.DatabaseException;
32 import org.onlab.onos.store.service.DatabaseService; 42 import org.onlab.onos.store.service.DatabaseService;
...@@ -38,8 +48,11 @@ import org.onlab.onos.store.service.ReadResult; ...@@ -38,8 +48,11 @@ import org.onlab.onos.store.service.ReadResult;
38 import org.onlab.onos.store.service.WriteAborted; 48 import org.onlab.onos.store.service.WriteAborted;
39 import org.onlab.onos.store.service.WriteRequest; 49 import org.onlab.onos.store.service.WriteRequest;
40 import org.onlab.onos.store.service.WriteResult; 50 import org.onlab.onos.store.service.WriteResult;
51 +import org.onlab.packet.IpAddress;
41 import org.slf4j.Logger; 52 import org.slf4j.Logger;
42 53
54 +import com.google.common.collect.ImmutableList;
55 +
43 /** 56 /**
44 * Strongly consistent and durable state management service based on 57 * Strongly consistent and durable state management service based on
45 * Copycat implementation of Raft consensus protocol. 58 * Copycat implementation of Raft consensus protocol.
...@@ -56,7 +69,19 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -56,7 +69,19 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
56 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 69 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
57 protected DatabaseProtocolService copycatMessagingProtocol; 70 protected DatabaseProtocolService copycatMessagingProtocol;
58 71
59 - public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log"; 72 + public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log_";
73 +
74 + // Current working dir seems to be /opt/onos/apache-karaf-3.0.2
75 + // TODO: Get the path to /opt/onos/config
76 + private static final String CONFIG_DIR = "../config";
77 +
78 + private static final String DEFAULT_MEMBER_FILE = "tablets.json";
79 +
80 + private static final String DEFAULT_TABLET = "default";
81 +
82 + // TODO: make this configurable
83 + // initial member configuration file path
84 + private String initialMemberConfig = DEFAULT_MEMBER_FILE;
60 85
61 private Copycat copycat; 86 private Copycat copycat;
62 private DatabaseClient client; 87 private DatabaseClient client;
...@@ -65,49 +90,61 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -65,49 +90,61 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
65 private ClusterConfig<TcpMember> clusterConfig; 90 private ClusterConfig<TcpMember> clusterConfig;
66 91
67 private CountDownLatch clusterEventLatch; 92 private CountDownLatch clusterEventLatch;
68 -
69 private ClusterEventListener clusterEventListener; 93 private ClusterEventListener clusterEventListener;
70 94
95 + private Map<String, Set<DefaultControllerNode>> tabletMembers;
96 +
97 + private boolean autoAddMember = false;
98 +
71 @Activate 99 @Activate
72 public void activate() { 100 public void activate() {
73 101
74 // TODO: Not every node should be part of the consensus ring. 102 // TODO: Not every node should be part of the consensus ring.
75 103
76 - final ControllerNode localNode = clusterService.getLocalNode(); 104 + // load tablet configuration
77 - TcpMember localMember = 105 + File file = new File(CONFIG_DIR, initialMemberConfig);
78 - new TcpMember( 106 + log.info("Loading config: {}", file.getAbsolutePath());
79 - localNode.ip().toString(), 107 + TabletDefinitionStore tabletDef = new TabletDefinitionStore(file);
80 - localNode.tcpPort()); 108 + try {
109 + tabletMembers = tabletDef.read();
110 + } catch (IOException e) {
111 + log.error("Failed to load tablet config {}", file);
112 + throw new IllegalStateException("Failed to load tablet config", e);
113 + }
81 114
115 + // load default tablet configuration and start copycat
82 clusterConfig = new TcpClusterConfig(); 116 clusterConfig = new TcpClusterConfig();
83 - clusterConfig.setLocalMember(localMember); 117 + Set<DefaultControllerNode> defaultMember = tabletMembers.get(DEFAULT_TABLET);
84 - 118 + if (defaultMember == null || defaultMember.isEmpty()) {
85 - List<TcpMember> remoteMembers = new ArrayList<>(clusterService.getNodes().size()); 119 + log.error("No member found in [{}] tablet configuration.",
86 - 120 + DEFAULT_TABLET);
87 - clusterEventLatch = new CountDownLatch(1); 121 + throw new IllegalStateException("No member found in tablet configuration");
88 - clusterEventListener = new InternalClusterEventListener();
89 - clusterService.addListener(clusterEventListener);
90 122
91 - // note: from this point beyond, clusterConfig requires synchronization 123 + }
92 124
93 - for (ControllerNode node : clusterService.getNodes()) { 125 + final ControllerNode localNode = clusterService.getLocalNode();
94 - TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort()); 126 + for (ControllerNode member : defaultMember) {
95 - if (!member.equals(localMember)) { 127 + final TcpMember tcpMember = new TcpMember(member.ip().toString(),
96 - remoteMembers.add(member); 128 + member.tcpPort());
129 + if (localNode.equals(member)) {
130 + clusterConfig.setLocalMember(tcpMember);
131 + } else {
132 + clusterConfig.addRemoteMember(tcpMember);
97 } 133 }
98 } 134 }
99 135
100 - if (remoteMembers.isEmpty()) { 136 + // note: from this point beyond, clusterConfig requires synchronization
101 - log.info("This node is the only node in the cluster. " 137 + clusterEventLatch = new CountDownLatch(1);
102 - + "Waiting for others to show up."); 138 + clusterEventListener = new InternalClusterEventListener();
103 - // FIXME: hack trying to relax cases forming multiple consensus rings. 139 + clusterService.addListener(clusterEventListener);
104 - // add seed node configuration to avoid this
105 140
106 - // If the node is alone on it's own, wait some time 141 + if (clusterService.getNodes().size() < clusterConfig.getMembers().size()) {
107 - // hoping other will come up soon 142 + // current cluster size smaller then expected
108 try { 143 try {
109 if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) { 144 if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) {
110 - log.info("Starting as single node cluster"); 145 + log.info("Starting with {}/{} nodes cluster",
146 + clusterService.getNodes().size(),
147 + clusterConfig.getMembers().size());
111 } 148 }
112 } catch (InterruptedException e) { 149 } catch (InterruptedException e) {
113 log.info("Interrupted waiting for others", e); 150 log.info("Interrupted waiting for others", e);
...@@ -116,8 +153,6 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -116,8 +153,6 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
116 153
117 final TcpCluster cluster; 154 final TcpCluster cluster;
118 synchronized (clusterConfig) { 155 synchronized (clusterConfig) {
119 - clusterConfig.addRemoteMembers(remoteMembers);
120 -
121 // Create the cluster. 156 // Create the cluster.
122 cluster = new TcpCluster(clusterConfig); 157 cluster = new TcpCluster(clusterConfig);
123 } 158 }
...@@ -125,16 +160,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -125,16 +160,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
125 160
126 161
127 StateMachine stateMachine = new DatabaseStateMachine(); 162 StateMachine stateMachine = new DatabaseStateMachine();
128 - // Chronicle + OSGi issue
129 - //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id());
130 - //Log consensusLog = new KryoRegisteredInMemoryLog();
131 Log consensusLog = new MapDBLog(LOG_FILE_PREFIX + localNode.id(), 163 Log consensusLog = new MapDBLog(LOG_FILE_PREFIX + localNode.id(),
132 ClusterMessagingProtocol.SERIALIZER); 164 ClusterMessagingProtocol.SERIALIZER);
133 165
134 copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); 166 copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol);
135 copycat.start(); 167 copycat.start();
136 168
137 - client = new DatabaseClient(copycatMessagingProtocol.createClient(localMember)); 169 + client = new DatabaseClient(copycat);
138 170
139 log.info("Started."); 171 log.info("Started.");
140 } 172 }
...@@ -236,21 +268,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -236,21 +268,34 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
236 final TcpMember tcpMember = new TcpMember(node.ip().toString(), 268 final TcpMember tcpMember = new TcpMember(node.ip().toString(),
237 node.tcpPort()); 269 node.tcpPort());
238 270
239 - log.trace("{}", event);
240 switch (event.type()) { 271 switch (event.type()) {
241 case INSTANCE_ACTIVATED: 272 case INSTANCE_ACTIVATED:
242 case INSTANCE_ADDED: 273 case INSTANCE_ADDED:
243 - log.info("{} was added to the cluster", tcpMember); 274 + if (autoAddMember) {
244 synchronized (clusterConfig) { 275 synchronized (clusterConfig) {
276 + if (!clusterConfig.getMembers().contains(tcpMember)) {
277 + log.info("{} was automatically added to the cluster", tcpMember);
245 clusterConfig.addRemoteMember(tcpMember); 278 clusterConfig.addRemoteMember(tcpMember);
246 } 279 }
280 + }
281 + }
247 break; 282 break;
248 case INSTANCE_DEACTIVATED: 283 case INSTANCE_DEACTIVATED:
249 case INSTANCE_REMOVED: 284 case INSTANCE_REMOVED:
250 - log.info("{} was removed from the cluster", tcpMember); 285 + if (autoAddMember) {
286 + Set<DefaultControllerNode> members
287 + = tabletMembers.getOrDefault(DEFAULT_TABLET,
288 + Collections.emptySet());
289 + // remove only if not the initial members
290 + if (!members.contains(node)) {
251 synchronized (clusterConfig) { 291 synchronized (clusterConfig) {
292 + if (clusterConfig.getMembers().contains(tcpMember)) {
293 + log.info("{} was automatically removed from the cluster", tcpMember);
252 clusterConfig.removeRemoteMember(tcpMember); 294 clusterConfig.removeRemoteMember(tcpMember);
253 } 295 }
296 + }
297 + }
298 + }
254 break; 299 break;
255 default: 300 default:
256 break; 301 break;
...@@ -309,4 +354,58 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -309,4 +354,58 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
309 } 354 }
310 } 355 }
311 } 356 }
357 +
358 + @Override
359 + public void addMember(final ControllerNode node) {
360 + final TcpMember tcpMember = new TcpMember(node.ip().toString(),
361 + node.tcpPort());
362 + log.info("{} was added to the cluster", tcpMember);
363 + synchronized (clusterConfig) {
364 + clusterConfig.addRemoteMember(tcpMember);
365 + }
366 + }
367 +
368 + @Override
369 + public void removeMember(final ControllerNode node) {
370 + final TcpMember tcpMember = new TcpMember(node.ip().toString(),
371 + node.tcpPort());
372 + log.info("{} was removed from the cluster", tcpMember);
373 + synchronized (clusterConfig) {
374 + clusterConfig.removeRemoteMember(tcpMember);
375 + }
376 + }
377 +
378 + @Override
379 + public Collection<ControllerNode> listMembers() {
380 + if (copycat == null) {
381 + return ImmutableList.of();
382 + }
383 + Set<ControllerNode> members = new HashSet<>();
384 + for (Member member : copycat.cluster().members()) {
385 + if (member instanceof TcpMember) {
386 + final TcpMember tcpMember = (TcpMember) member;
387 + // TODO assuming tcpMember#host to be IP address,
388 + // but if not lookup DNS, etc. first
389 + IpAddress ip = IpAddress.valueOf(tcpMember.host());
390 + int tcpPort = tcpMember.port();
391 + NodeId id = getNodeIdFromIp(ip, tcpPort);
392 + if (id == null) {
393 + log.info("No NodeId found for {}:{}", ip, tcpPort);
394 + continue;
395 + }
396 + members.add(new DefaultControllerNode(id, ip, tcpPort));
397 + }
398 + }
399 + return members;
400 + }
401 +
402 + private NodeId getNodeIdFromIp(IpAddress ip, int tcpPort) {
403 + for (ControllerNode node : clusterService.getNodes()) {
404 + if (node.ip().equals(ip) &&
405 + node.tcpPort() == tcpPort) {
406 + return node.id();
407 + }
408 + }
409 + return null;
410 + }
312 } 411 }
......
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 +}
...@@ -61,7 +61,13 @@ ...@@ -61,7 +61,13 @@
61 <group> 61 <group>
62 <title>Core Subsystems</title> 62 <title>Core Subsystems</title>
63 <packages> 63 <packages>
64 - org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.json:org.onlab.onos.json.*:org.onlab.onos.provider.host.impl:org.onlab.onos.provider.lldp.impl:org.onlab.onos.net.statistic.impl 64 + org.onlab.onos.impl:org.onlab.onos.core.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.net.resource.impl:org.onlab.onos.json:org.onlab.onos.json.*:org.onlab.onos.provider.host.impl:org.onlab.onos.provider.lldp.impl:org.onlab.onos.net.statistic.impl
65 + </packages>
66 + </group>
67 + <group>
68 + <title>Distributed Stores</title>
69 + <packages>
70 + org.onlab.onos.store.*
65 </packages> 71 </packages>
66 </group> 72 </group>
67 <group> 73 <group>
...@@ -92,12 +98,11 @@ ...@@ -92,12 +98,11 @@
92 <group> 98 <group>
93 <title>Test Instrumentation</title> 99 <title>Test Instrumentation</title>
94 <packages> 100 <packages>
95 - org.onlab.onos.metrics.*:org.onlab.onos.oecfg 101 + org.onlab.onos.metrics.*
96 </packages> 102 </packages>
97 </group> 103 </group>
98 </groups> 104 </groups>
99 - <excludePackageNames>org.onlab.thirdparty 105 + <excludePackageNames>org.onlab.thirdparty:org.onlab.onos.oecfg</excludePackageNames>
100 - </excludePackageNames>
101 </configuration> 106 </configuration>
102 </plugin> 107 </plugin>
103 </plugins> 108 </plugins>
......
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 +
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.onlab.api; 16 +package org.onlab.util;
17 17
18 /** 18 /**
19 * Represents condition where an item is not found or not available. 19 * Represents condition where an item is not found or not available.
......
...@@ -17,7 +17,7 @@ package org.onlab.onos.rest; ...@@ -17,7 +17,7 @@ package org.onlab.onos.rest;
17 17
18 import com.fasterxml.jackson.databind.ObjectMapper; 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 import com.fasterxml.jackson.databind.node.ObjectNode; 19 import com.fasterxml.jackson.databind.node.ObjectNode;
20 -import org.onlab.api.ItemNotFoundException; 20 +import org.onlab.util.ItemNotFoundException;
21 import org.onlab.onos.codec.CodecContext; 21 import org.onlab.onos.codec.CodecContext;
22 import org.onlab.onos.codec.CodecService; 22 import org.onlab.onos.codec.CodecService;
23 import org.onlab.onos.codec.JsonCodec; 23 import org.onlab.onos.codec.JsonCodec;
...@@ -69,7 +69,7 @@ public class AbstractWebResource extends BaseResource implements CodecContext { ...@@ -69,7 +69,7 @@ public class AbstractWebResource extends BaseResource implements CodecContext {
69 * @param message not found message 69 * @param message not found message
70 * @param <T> item type 70 * @param <T> item type
71 * @return item if not null 71 * @return item if not null
72 - * @throws org.onlab.api.ItemNotFoundException if item is null 72 + * @throws org.onlab.util.ItemNotFoundException if item is null
73 */ 73 */
74 protected <T> T nullIsNotFound(T item, String message) { 74 protected <T> T nullIsNotFound(T item, String message) {
75 if (item == null) { 75 if (item == null) {
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
15 */ 15 */
16 package org.onlab.onos.rest.exceptions; 16 package org.onlab.onos.rest.exceptions;
17 17
18 -import org.onlab.api.ItemNotFoundException; 18 +import org.onlab.util.ItemNotFoundException;
19 19
20 import javax.ws.rs.core.Response; 20 import javax.ws.rs.core.Response;
21 21
......
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.gui;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.ObjectMapper;
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
21 +import com.fasterxml.jackson.databind.node.ObjectNode;
22 +import org.onlab.onos.cluster.ClusterEvent;
23 +import org.onlab.onos.cluster.ClusterService;
24 +import org.onlab.onos.cluster.ControllerNode;
25 +import org.onlab.onos.cluster.NodeId;
26 +import org.onlab.onos.mastership.MastershipService;
27 +import org.onlab.onos.net.Annotations;
28 +import org.onlab.onos.net.ConnectPoint;
29 +import org.onlab.onos.net.DefaultEdgeLink;
30 +import org.onlab.onos.net.Device;
31 +import org.onlab.onos.net.DeviceId;
32 +import org.onlab.onos.net.EdgeLink;
33 +import org.onlab.onos.net.Host;
34 +import org.onlab.onos.net.HostId;
35 +import org.onlab.onos.net.HostLocation;
36 +import org.onlab.onos.net.Link;
37 +import org.onlab.onos.net.Path;
38 +import org.onlab.onos.net.device.DeviceEvent;
39 +import org.onlab.onos.net.device.DeviceService;
40 +import org.onlab.onos.net.host.HostEvent;
41 +import org.onlab.onos.net.host.HostService;
42 +import org.onlab.onos.net.intent.IntentService;
43 +import org.onlab.onos.net.link.LinkEvent;
44 +import org.onlab.onos.net.link.LinkService;
45 +import org.onlab.onos.net.provider.ProviderId;
46 +import org.onlab.osgi.ServiceDirectory;
47 +import org.onlab.packet.IpAddress;
48 +
49 +import java.util.Iterator;
50 +import java.util.Map;
51 +import java.util.Set;
52 +import java.util.concurrent.ConcurrentHashMap;
53 +
54 +import static com.google.common.base.Preconditions.checkNotNull;
55 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
56 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
57 +import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
58 +import static org.onlab.onos.net.PortNumber.portNumber;
59 +import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
60 +import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
61 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
62 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
63 +import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
64 +import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
65 +
66 +/**
67 + * Facility for creating messages bound for the topology viewer.
68 + */
69 +public abstract class TopologyMessages {
70 +
71 + private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
72 + private static final String COMPACT = "%s/%s-%s/%s";
73 +
74 + protected final ServiceDirectory directory;
75 + protected final ClusterService clusterService;
76 + protected final DeviceService deviceService;
77 + protected final LinkService linkService;
78 + protected final HostService hostService;
79 + protected final MastershipService mastershipService;
80 + protected final IntentService intentService;
81 +
82 + protected final ObjectMapper mapper = new ObjectMapper();
83 +
84 + // TODO: extract into an external & durable state; good enough for now and demo
85 + private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
86 +
87 + /**
88 + * Creates a messaging facility for creating messages for topology viewer.
89 + *
90 + * @param directory service directory
91 + */
92 + protected TopologyMessages(ServiceDirectory directory) {
93 + this.directory = checkNotNull(directory, "Directory cannot be null");
94 + clusterService = directory.get(ClusterService.class);
95 + deviceService = directory.get(DeviceService.class);
96 + linkService = directory.get(LinkService.class);
97 + hostService = directory.get(HostService.class);
98 + mastershipService = directory.get(MastershipService.class);
99 + intentService = directory.get(IntentService.class);
100 + }
101 +
102 + // Retrieves the payload from the specified event.
103 + protected ObjectNode payload(ObjectNode event) {
104 + return (ObjectNode) event.path("payload");
105 + }
106 +
107 + // Returns the specified node property as a number
108 + protected long number(ObjectNode node, String name) {
109 + return node.path(name).asLong();
110 + }
111 +
112 + // Returns the specified node property as a string.
113 + protected String string(ObjectNode node, String name) {
114 + return node.path(name).asText();
115 + }
116 +
117 + // Returns the specified node property as a string.
118 + protected String string(ObjectNode node, String name, String defaultValue) {
119 + return node.path(name).asText(defaultValue);
120 + }
121 +
122 + // Returns the specified set of IP addresses as a string.
123 + private String ip(Set<IpAddress> ipAddresses) {
124 + Iterator<IpAddress> it = ipAddresses.iterator();
125 + return it.hasNext() ? it.next().toString() : "unknown";
126 + }
127 +
128 + // Produces JSON structure from annotations.
129 + private JsonNode props(Annotations annotations) {
130 + ObjectNode props = mapper.createObjectNode();
131 + for (String key : annotations.keys()) {
132 + props.put(key, annotations.value(key));
133 + }
134 + return props;
135 + }
136 +
137 + // Produces an informational log message event bound to the client.
138 + protected ObjectNode info(long id, String message) {
139 + return message("info", id, message);
140 + }
141 +
142 + // Produces a warning log message event bound to the client.
143 + protected ObjectNode warning(long id, String message) {
144 + return message("warning", id, message);
145 + }
146 +
147 + // Produces an error log message event bound to the client.
148 + protected ObjectNode error(long id, String message) {
149 + return message("error", id, message);
150 + }
151 +
152 + // Produces a log message event bound to the client.
153 + private ObjectNode message(String severity, long id, String message) {
154 + return envelope("message", id,
155 + mapper.createObjectNode()
156 + .put("severity", severity)
157 + .put("message", message));
158 + }
159 +
160 + // Puts the payload into an envelope and returns it.
161 + protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
162 + ObjectNode event = mapper.createObjectNode();
163 + event.put("event", type);
164 + if (sid > 0) {
165 + event.put("sid", sid);
166 + }
167 + event.set("payload", payload);
168 + return event;
169 + }
170 +
171 + // Produces a cluster instance message to the client.
172 + protected ObjectNode instanceMessage(ClusterEvent event) {
173 + ControllerNode node = event.subject();
174 + ObjectNode payload = mapper.createObjectNode()
175 + .put("id", node.id().toString())
176 + .put("online", clusterService.getState(node.id()) == ACTIVE);
177 +
178 + ArrayNode labels = mapper.createArrayNode();
179 + labels.add(node.id().toString());
180 + labels.add(node.ip().toString());
181 +
182 + // Add labels, props and stuff the payload into envelope.
183 + payload.set("labels", labels);
184 + addMetaUi(node.id().toString(), payload);
185 +
186 + String type = (event.type() == INSTANCE_ADDED) ? "addInstance" :
187 + ((event.type() == INSTANCE_REMOVED) ? "removeInstance" : "updateInstance");
188 + return envelope(type, 0, payload);
189 + }
190 +
191 + // Produces a device event message to the client.
192 + protected ObjectNode deviceMessage(DeviceEvent event) {
193 + Device device = event.subject();
194 + ObjectNode payload = mapper.createObjectNode()
195 + .put("id", device.id().toString())
196 + .put("type", device.type().toString().toLowerCase())
197 + .put("online", deviceService.isAvailable(device.id()))
198 + .put("master", mastershipService.getMasterFor(device.id()).toString());
199 +
200 + // Generate labels: id, chassis id, no-label, optional-name
201 + ArrayNode labels = mapper.createArrayNode();
202 + labels.add(device.id().toString());
203 + labels.add(device.chassisId().toString());
204 + labels.add(""); // compact no-label view
205 + labels.add(device.annotations().value("name"));
206 +
207 + // Add labels, props and stuff the payload into envelope.
208 + payload.set("labels", labels);
209 + payload.set("props", props(device.annotations()));
210 + addMetaUi(device.id().toString(), payload);
211 +
212 + String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
213 + ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
214 + return envelope(type, 0, payload);
215 + }
216 +
217 + // Produces a link event message to the client.
218 + protected ObjectNode linkMessage(LinkEvent event) {
219 + Link link = event.subject();
220 + ObjectNode payload = mapper.createObjectNode()
221 + .put("id", compactLinkString(link))
222 + .put("type", link.type().toString().toLowerCase())
223 + .put("linkWidth", 2)
224 + .put("src", link.src().deviceId().toString())
225 + .put("srcPort", link.src().port().toString())
226 + .put("dst", link.dst().deviceId().toString())
227 + .put("dstPort", link.dst().port().toString());
228 + String type = (event.type() == LINK_ADDED) ? "addLink" :
229 + ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
230 + return envelope(type, 0, payload);
231 + }
232 +
233 + // Produces a host event message to the client.
234 + protected ObjectNode hostMessage(HostEvent event) {
235 + Host host = event.subject();
236 + ObjectNode payload = mapper.createObjectNode()
237 + .put("id", host.id().toString())
238 + .put("ingress", compactLinkString(edgeLink(host, true)))
239 + .put("egress", compactLinkString(edgeLink(host, false)));
240 + payload.set("cp", location(mapper, host.location()));
241 + payload.set("labels", labels(mapper, ip(host.ipAddresses()),
242 + host.mac().toString()));
243 + payload.set("props", props(host.annotations()));
244 + addMetaUi(host.id().toString(), payload);
245 +
246 + String type = (event.type() == HOST_ADDED) ? "addHost" :
247 + ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
248 + return envelope(type, 0, payload);
249 + }
250 +
251 + // Encodes the specified host location into a JSON object.
252 + private ObjectNode location(ObjectMapper mapper, HostLocation location) {
253 + return mapper.createObjectNode()
254 + .put("device", location.deviceId().toString())
255 + .put("port", location.port().toLong());
256 + }
257 +
258 + // Encodes the specified list of labels a JSON array.
259 + private ArrayNode labels(ObjectMapper mapper, String... labels) {
260 + ArrayNode json = mapper.createArrayNode();
261 + for (String label : labels) {
262 + json.add(label);
263 + }
264 + return json;
265 + }
266 +
267 + // Generates an edge link from the specified host location.
268 + private EdgeLink edgeLink(Host host, boolean ingress) {
269 + return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
270 + host.location(), ingress);
271 + }
272 +
273 + // Adds meta UI information for the specified object.
274 + private void addMetaUi(String id, ObjectNode payload) {
275 + ObjectNode meta = metaUi.get(id);
276 + if (meta != null) {
277 + payload.set("metaUi", meta);
278 + }
279 + }
280 +
281 + // Updates meta UI information for the specified object.
282 + protected void updateMetaUi(ObjectNode event) {
283 + ObjectNode payload = payload(event);
284 + metaUi.put(string(payload, "id"), payload);
285 + }
286 +
287 + // Returns device details response.
288 + protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
289 + Device device = deviceService.getDevice(deviceId);
290 + Annotations annot = device.annotations();
291 + int portCount = deviceService.getPorts(deviceId).size();
292 + NodeId master = mastershipService.getMasterFor(device.id());
293 + return envelope("showDetails", sid,
294 + json(deviceId.toString(),
295 + device.type().toString().toLowerCase(),
296 + new Prop("Name", annot.value("name")),
297 + new Prop("Vendor", device.manufacturer()),
298 + new Prop("H/W Version", device.hwVersion()),
299 + new Prop("S/W Version", device.swVersion()),
300 + new Prop("Serial Number", device.serialNumber()),
301 + new Separator(),
302 + new Prop("Latitude", annot.value("latitude")),
303 + new Prop("Longitude", annot.value("longitude")),
304 + new Prop("Ports", Integer.toString(portCount)),
305 + new Separator(),
306 + new Prop("Master", master.toString())));
307 + }
308 +
309 + // Returns host details response.
310 + protected ObjectNode hostDetails(HostId hostId, long sid) {
311 + Host host = hostService.getHost(hostId);
312 + Annotations annot = host.annotations();
313 + return envelope("showDetails", sid,
314 + json(hostId.toString(), "host",
315 + new Prop("MAC", host.mac().toString()),
316 + new Prop("IP", host.ipAddresses().toString()),
317 + new Separator(),
318 + new Prop("Latitude", annot.value("latitude")),
319 + new Prop("Longitude", annot.value("longitude"))));
320 + }
321 +
322 +
323 + // Produces a path message to the client.
324 + protected ObjectNode pathMessage(Path path) {
325 + ObjectNode payload = mapper.createObjectNode();
326 + ArrayNode links = mapper.createArrayNode();
327 + for (Link link : path.links()) {
328 + links.add(compactLinkString(link));
329 + }
330 +
331 + payload.set("links", links);
332 + return payload;
333 + }
334 +
335 + // Produces compact string representation of a link.
336 + private static String compactLinkString(Link link) {
337 + return String.format(COMPACT, link.src().elementId(), link.src().port(),
338 + link.dst().elementId(), link.dst().port());
339 + }
340 +
341 + // Produces JSON property details.
342 + private ObjectNode json(String id, String type, Prop... props) {
343 + ObjectMapper mapper = new ObjectMapper();
344 + ObjectNode result = mapper.createObjectNode()
345 + .put("id", id).put("type", type);
346 + ObjectNode pnode = mapper.createObjectNode();
347 + ArrayNode porder = mapper.createArrayNode();
348 + for (Prop p : props) {
349 + porder.add(p.key);
350 + pnode.put(p.key, p.value);
351 + }
352 + result.set("propOrder", porder);
353 + result.set("props", pnode);
354 + return result;
355 + }
356 +
357 + // Auxiliary key/value carrier.
358 + private class Prop {
359 + public final String key;
360 + public final String value;
361 +
362 + protected Prop(String key, String value) {
363 + this.key = key;
364 + this.value = value;
365 + }
366 + }
367 +
368 + // Auxiliary properties separator
369 + private class Separator extends Prop {
370 + protected Separator() {
371 + super("-", "");
372 + }
373 + }
374 +
375 +}
...@@ -15,158 +15,94 @@ ...@@ -15,158 +15,94 @@
15 */ 15 */
16 package org.onlab.onos.gui; 16 package org.onlab.onos.gui;
17 17
18 -import com.fasterxml.jackson.databind.JsonNode;
19 -import com.fasterxml.jackson.databind.ObjectMapper;
20 -import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.ObjectNode; 18 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import org.eclipse.jetty.websocket.WebSocket; 19 import org.eclipse.jetty.websocket.WebSocket;
20 +import org.onlab.onos.cluster.ClusterEvent;
21 +import org.onlab.onos.cluster.ClusterEventListener;
22 +import org.onlab.onos.cluster.ControllerNode;
23 import org.onlab.onos.core.ApplicationId; 23 import org.onlab.onos.core.ApplicationId;
24 import org.onlab.onos.core.CoreService; 24 import org.onlab.onos.core.CoreService;
25 import org.onlab.onos.mastership.MastershipEvent; 25 import org.onlab.onos.mastership.MastershipEvent;
26 import org.onlab.onos.mastership.MastershipListener; 26 import org.onlab.onos.mastership.MastershipListener;
27 -import org.onlab.onos.mastership.MastershipService;
28 -import org.onlab.onos.net.Annotations;
29 -import org.onlab.onos.net.ConnectPoint;
30 -import org.onlab.onos.net.DefaultEdgeLink;
31 import org.onlab.onos.net.Device; 27 import org.onlab.onos.net.Device;
32 -import org.onlab.onos.net.DeviceId;
33 -import org.onlab.onos.net.ElementId;
34 import org.onlab.onos.net.Host; 28 import org.onlab.onos.net.Host;
35 import org.onlab.onos.net.HostId; 29 import org.onlab.onos.net.HostId;
36 -import org.onlab.onos.net.HostLocation;
37 import org.onlab.onos.net.Link; 30 import org.onlab.onos.net.Link;
38 import org.onlab.onos.net.Path; 31 import org.onlab.onos.net.Path;
39 import org.onlab.onos.net.device.DeviceEvent; 32 import org.onlab.onos.net.device.DeviceEvent;
40 import org.onlab.onos.net.device.DeviceListener; 33 import org.onlab.onos.net.device.DeviceListener;
41 -import org.onlab.onos.net.device.DeviceService;
42 import org.onlab.onos.net.flow.DefaultTrafficSelector; 34 import org.onlab.onos.net.flow.DefaultTrafficSelector;
43 import org.onlab.onos.net.flow.DefaultTrafficTreatment; 35 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
44 import org.onlab.onos.net.host.HostEvent; 36 import org.onlab.onos.net.host.HostEvent;
45 import org.onlab.onos.net.host.HostListener; 37 import org.onlab.onos.net.host.HostListener;
46 -import org.onlab.onos.net.host.HostService;
47 import org.onlab.onos.net.intent.HostToHostIntent; 38 import org.onlab.onos.net.intent.HostToHostIntent;
48 import org.onlab.onos.net.intent.Intent; 39 import org.onlab.onos.net.intent.Intent;
49 import org.onlab.onos.net.intent.IntentEvent; 40 import org.onlab.onos.net.intent.IntentEvent;
50 import org.onlab.onos.net.intent.IntentId; 41 import org.onlab.onos.net.intent.IntentId;
51 import org.onlab.onos.net.intent.IntentListener; 42 import org.onlab.onos.net.intent.IntentListener;
52 -import org.onlab.onos.net.intent.IntentService;
53 import org.onlab.onos.net.intent.PathIntent; 43 import org.onlab.onos.net.intent.PathIntent;
54 import org.onlab.onos.net.link.LinkEvent; 44 import org.onlab.onos.net.link.LinkEvent;
55 import org.onlab.onos.net.link.LinkListener; 45 import org.onlab.onos.net.link.LinkListener;
56 -import org.onlab.onos.net.link.LinkService;
57 -import org.onlab.onos.net.provider.ProviderId;
58 -import org.onlab.onos.net.topology.PathService;
59 import org.onlab.osgi.ServiceDirectory; 46 import org.onlab.osgi.ServiceDirectory;
60 -import org.onlab.packet.IpAddress;
61 47
62 import java.io.IOException; 48 import java.io.IOException;
63 -import java.util.Iterator;
64 import java.util.List; 49 import java.util.List;
65 import java.util.Map; 50 import java.util.Map;
66 -import java.util.Set;
67 import java.util.concurrent.ConcurrentHashMap; 51 import java.util.concurrent.ConcurrentHashMap;
68 52
69 -import static com.google.common.base.Preconditions.checkNotNull; 53 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
70 import static org.onlab.onos.net.DeviceId.deviceId; 54 import static org.onlab.onos.net.DeviceId.deviceId;
71 import static org.onlab.onos.net.HostId.hostId; 55 import static org.onlab.onos.net.HostId.hostId;
72 -import static org.onlab.onos.net.PortNumber.portNumber;
73 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; 56 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
74 -import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
75 import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED; 57 import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
76 -import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
77 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; 58 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
78 -import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
79 59
80 /** 60 /**
81 * Web socket capable of interacting with the GUI topology view. 61 * Web socket capable of interacting with the GUI topology view.
82 */ 62 */
83 -public class TopologyWebSocket implements WebSocket.OnTextMessage { 63 +public class TopologyWebSocket
64 + extends TopologyMessages implements WebSocket.OnTextMessage {
84 65
85 private static final String APP_ID = "org.onlab.onos.gui"; 66 private static final String APP_ID = "org.onlab.onos.gui";
86 - private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
87 67
88 private final ApplicationId appId; 68 private final ApplicationId appId;
89 - private final ServiceDirectory directory;
90 -
91 - private final ObjectMapper mapper = new ObjectMapper();
92 69
93 private Connection connection; 70 private Connection connection;
94 71
95 - private final DeviceService deviceService; 72 + private final ClusterEventListener clusterListener = new InternalClusterListener();
96 - private final LinkService linkService;
97 - private final HostService hostService;
98 - private final MastershipService mastershipService;
99 - private final IntentService intentService;
100 -
101 private final DeviceListener deviceListener = new InternalDeviceListener(); 73 private final DeviceListener deviceListener = new InternalDeviceListener();
102 private final LinkListener linkListener = new InternalLinkListener(); 74 private final LinkListener linkListener = new InternalLinkListener();
103 private final HostListener hostListener = new InternalHostListener(); 75 private final HostListener hostListener = new InternalHostListener();
104 private final MastershipListener mastershipListener = new InternalMastershipListener(); 76 private final MastershipListener mastershipListener = new InternalMastershipListener();
105 private final IntentListener intentListener = new InternalIntentListener(); 77 private final IntentListener intentListener = new InternalIntentListener();
106 78
107 - // TODO: extract into an external & durable state; good enough for now and demo
108 - private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
109 -
110 // Intents that are being monitored for the GUI 79 // Intents that are being monitored for the GUI
111 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>(); 80 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
112 81
113 - private static final String COMPACT = "%s/%s-%s/%s";
114 -
115 -
116 /** 82 /**
117 * Creates a new web-socket for serving data to GUI topology view. 83 * Creates a new web-socket for serving data to GUI topology view.
118 * 84 *
119 * @param directory service directory 85 * @param directory service directory
120 */ 86 */
121 public TopologyWebSocket(ServiceDirectory directory) { 87 public TopologyWebSocket(ServiceDirectory directory) {
122 - this.directory = checkNotNull(directory, "Directory cannot be null"); 88 + super(directory);
123 - deviceService = directory.get(DeviceService.class);
124 - linkService = directory.get(LinkService.class);
125 - hostService = directory.get(HostService.class);
126 - mastershipService = directory.get(MastershipService.class);
127 - intentService = directory.get(IntentService.class);
128 -
129 appId = directory.get(CoreService.class).registerApplication(APP_ID); 89 appId = directory.get(CoreService.class).registerApplication(APP_ID);
130 } 90 }
131 91
132 @Override 92 @Override
133 public void onOpen(Connection connection) { 93 public void onOpen(Connection connection) {
134 this.connection = connection; 94 this.connection = connection;
135 - deviceService.addListener(deviceListener); 95 + addListeners();
136 - linkService.addListener(linkListener);
137 - hostService.addListener(hostListener);
138 - mastershipService.addListener(mastershipListener);
139 - intentService.addListener(intentListener);
140 96
97 + sendAllInstances();
141 sendAllDevices(); 98 sendAllDevices();
142 sendAllLinks(); 99 sendAllLinks();
143 sendAllHosts(); 100 sendAllHosts();
144 } 101 }
145 102
146 - private void sendAllHosts() {
147 - for (Host host : hostService.getHosts()) {
148 - sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
149 - }
150 - }
151 -
152 - private void sendAllDevices() {
153 - for (Device device : deviceService.getDevices()) {
154 - sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
155 - }
156 - }
157 -
158 - private void sendAllLinks() {
159 - for (Link link : linkService.getLinks()) {
160 - sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
161 - }
162 - }
163 -
164 @Override 103 @Override
165 public void onClose(int closeCode, String message) { 104 public void onClose(int closeCode, String message) {
166 - deviceService.removeListener(deviceListener); 105 + removeListeners();
167 - linkService.removeListener(linkListener);
168 - hostService.removeListener(hostListener);
169 - mastershipService.removeListener(mastershipListener);
170 } 106 }
171 107
172 @Override 108 @Override
...@@ -177,7 +113,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -177,7 +113,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
177 if (type.equals("showDetails")) { 113 if (type.equals("showDetails")) {
178 showDetails(event); 114 showDetails(event);
179 } else if (type.equals("updateMeta")) { 115 } else if (type.equals("updateMeta")) {
180 - updateMetaInformation(event); 116 + updateMetaUi(event);
181 } else if (type.equals("requestPath")) { 117 } else if (type.equals("requestPath")) {
182 createHostIntent(event); 118 createHostIntent(event);
183 } else if (type.equals("requestTraffic")) { 119 } else if (type.equals("requestTraffic")) {
...@@ -200,74 +136,32 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -200,74 +136,32 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
200 } 136 }
201 } 137 }
202 138
203 - // Retrieves the payload from the specified event. 139 + // Sends all controller nodes to the client as node-added messages.
204 - private ObjectNode payload(ObjectNode event) { 140 + private void sendAllInstances() {
205 - return (ObjectNode) event.path("payload"); 141 + for (ControllerNode node : clusterService.getNodes()) {
206 - } 142 + sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
207 -
208 - // Returns the specified node property as a number
209 - private long number(ObjectNode node, String name) {
210 - return node.path(name).asLong();
211 - }
212 -
213 - // Returns the specified node property as a string.
214 - private String string(ObjectNode node, String name) {
215 - return node.path(name).asText();
216 - }
217 -
218 - // Returns the specified node property as a string.
219 - private String string(ObjectNode node, String name, String defaultValue) {
220 - return node.path(name).asText(defaultValue);
221 } 143 }
222 -
223 - // Returns the specified set of IP addresses as a string.
224 - private String ip(Set<IpAddress> ipAddresses) {
225 - Iterator<IpAddress> it = ipAddresses.iterator();
226 - return it.hasNext() ? it.next().toString() : "unknown";
227 - }
228 -
229 - // Encodes the specified host location into a JSON object.
230 - private ObjectNode location(ObjectMapper mapper, HostLocation location) {
231 - return mapper.createObjectNode()
232 - .put("device", location.deviceId().toString())
233 - .put("port", location.port().toLong());
234 } 144 }
235 145
236 - // Encodes the specified list of labels a JSON array. 146 + // Sends all devices to the client as device-added messages.
237 - private ArrayNode labels(ObjectMapper mapper, String... labels) { 147 + private void sendAllDevices() {
238 - ArrayNode json = mapper.createArrayNode(); 148 + for (Device device : deviceService.getDevices()) {
239 - for (String label : labels) { 149 + sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
240 - json.add(label);
241 } 150 }
242 - return json;
243 } 151 }
244 152
245 - // Produces JSON structure from annotations. 153 + // Sends all links to the client as link-added messages.
246 - private JsonNode props(Annotations annotations) { 154 + private void sendAllLinks() {
247 - ObjectNode props = mapper.createObjectNode(); 155 + for (Link link : linkService.getLinks()) {
248 - for (String key : annotations.keys()) { 156 + sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
249 - props.put(key, annotations.value(key));
250 - }
251 - return props;
252 } 157 }
253 -
254 - // Produces a log message event bound to the client.
255 - private ObjectNode message(String severity, long id, String message) {
256 - return envelope("message", id,
257 - mapper.createObjectNode()
258 - .put("severity", severity)
259 - .put("message", message));
260 } 158 }
261 159
262 - // Puts the payload into an envelope and returns it. 160 + // Sends all hosts to the client as host-added messages.
263 - private ObjectNode envelope(String type, long sid, ObjectNode payload) { 161 + private void sendAllHosts() {
264 - ObjectNode event = mapper.createObjectNode(); 162 + for (Host host : hostService.getHosts()) {
265 - event.put("event", type); 163 + sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
266 - if (sid > 0) {
267 - event.put("sid", sid);
268 } 164 }
269 - event.set("payload", payload);
270 - return event;
271 } 165 }
272 166
273 // Sends back device or host details. 167 // Sends back device or host details.
...@@ -283,12 +177,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -283,12 +177,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
283 } 177 }
284 } 178 }
285 179
286 - // Updates device/host meta information.
287 - private void updateMetaInformation(ObjectNode event) {
288 - ObjectNode payload = payload(event);
289 - metaUi.put(string(payload, "id"), payload);
290 - }
291 -
292 // Creates host-to-host intent. 180 // Creates host-to-host intent.
293 private void createHostIntent(ObjectNode event) { 181 private void createHostIntent(ObjectNode event) {
294 ObjectNode payload = payload(event); 182 ObjectNode payload = payload(event);
...@@ -314,7 +202,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -314,7 +202,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
314 payload.put("traffic", true); 202 payload.put("traffic", true);
315 sendMessage(envelope("showPath", id, payload)); 203 sendMessage(envelope("showPath", id, payload));
316 } else { 204 } else {
317 - sendMessage(message("warn", id, "No path found")); 205 + sendMessage(warning(id, "No path found"));
318 } 206 }
319 } 207 }
320 208
...@@ -323,178 +211,35 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -323,178 +211,35 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
323 // TODO: implement this 211 // TODO: implement this
324 } 212 }
325 213
326 - // Finds the path between the specified devices.
327 - private ObjectNode findPath(DeviceId one, DeviceId two) {
328 - PathService pathService = directory.get(PathService.class);
329 - Set<Path> paths = pathService.getPaths(one, two);
330 - if (paths.isEmpty()) {
331 - return null;
332 - } else {
333 - return pathMessage(paths.iterator().next());
334 - }
335 - }
336 214
337 - // Produces a path message to the client. 215 + // Adds all internal listeners.
338 - private ObjectNode pathMessage(Path path) { 216 + private void addListeners() {
339 - ObjectNode payload = mapper.createObjectNode(); 217 + clusterService.addListener(clusterListener);
340 - ArrayNode links = mapper.createArrayNode(); 218 + deviceService.addListener(deviceListener);
341 - for (Link link : path.links()) { 219 + linkService.addListener(linkListener);
342 - links.add(compactLinkString(link)); 220 + hostService.addListener(hostListener);
221 + mastershipService.addListener(mastershipListener);
222 + intentService.addListener(intentListener);
343 } 223 }
344 224
345 - payload.set("links", links); 225 + // Removes all internal listeners.
346 - return payload; 226 + private void removeListeners() {
227 + clusterService.removeListener(clusterListener);
228 + deviceService.removeListener(deviceListener);
229 + linkService.removeListener(linkListener);
230 + hostService.removeListener(hostListener);
231 + mastershipService.removeListener(mastershipListener);
347 } 232 }
348 233
349 - /** 234 + // Cluster event listener.
350 - * Returns a compact string representing the given link. 235 + private class InternalClusterListener implements ClusterEventListener {
351 - * 236 + @Override
352 - * @param link infrastructure link 237 + public void event(ClusterEvent event) {
353 - * @return formatted link string 238 + sendMessage(instanceMessage(event));
354 - */
355 - public static String compactLinkString(Link link) {
356 - return String.format(COMPACT, link.src().elementId(), link.src().port(),
357 - link.dst().elementId(), link.dst().port());
358 - }
359 -
360 -
361 - // Produces a link event message to the client.
362 - private ObjectNode deviceMessage(DeviceEvent event) {
363 - Device device = event.subject();
364 - ObjectNode payload = mapper.createObjectNode()
365 - .put("id", device.id().toString())
366 - .put("type", device.type().toString().toLowerCase())
367 - .put("online", deviceService.isAvailable(device.id()));
368 -
369 - // Generate labels: id, chassis id, no-label, optional-name
370 - ArrayNode labels = mapper.createArrayNode();
371 - labels.add(device.id().toString());
372 - labels.add(device.chassisId().toString());
373 - labels.add(""); // compact no-label view
374 - labels.add(device.annotations().value("name"));
375 -
376 - // Add labels, props and stuff the payload into envelope.
377 - payload.set("labels", labels);
378 - payload.set("props", props(device.annotations()));
379 - addMetaUi(device.id(), payload);
380 -
381 - String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
382 - ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
383 - return envelope(type, 0, payload);
384 - }
385 -
386 - // Produces a link event message to the client.
387 - private ObjectNode linkMessage(LinkEvent event) {
388 - Link link = event.subject();
389 - ObjectNode payload = mapper.createObjectNode()
390 - .put("id", compactLinkString(link))
391 - .put("type", link.type().toString().toLowerCase())
392 - .put("linkWidth", 2)
393 - .put("src", link.src().deviceId().toString())
394 - .put("srcPort", link.src().port().toString())
395 - .put("dst", link.dst().deviceId().toString())
396 - .put("dstPort", link.dst().port().toString());
397 - String type = (event.type() == LINK_ADDED) ? "addLink" :
398 - ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
399 - return envelope(type, 0, payload);
400 - }
401 -
402 - // Produces a host event message to the client.
403 - private ObjectNode hostMessage(HostEvent event) {
404 - Host host = event.subject();
405 - ObjectNode payload = mapper.createObjectNode()
406 - .put("id", host.id().toString())
407 - .put("ingress", compactLinkString(edgeLink(host, true)))
408 - .put("egress", compactLinkString(edgeLink(host, false)));
409 - payload.set("cp", location(mapper, host.location()));
410 - payload.set("labels", labels(mapper, ip(host.ipAddresses()),
411 - host.mac().toString()));
412 - payload.set("props", props(host.annotations()));
413 - addMetaUi(host.id(), payload);
414 -
415 - String type = (event.type() == HOST_ADDED) ? "addHost" :
416 - ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
417 - return envelope(type, 0, payload);
418 - }
419 -
420 - private DefaultEdgeLink edgeLink(Host host, boolean ingress) {
421 - return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
422 - host.location(), ingress);
423 - }
424 -
425 - private void addMetaUi(ElementId id, ObjectNode payload) {
426 - ObjectNode meta = metaUi.get(id.toString());
427 - if (meta != null) {
428 - payload.set("metaUi", meta);
429 - }
430 - }
431 -
432 -
433 - // Returns device details response.
434 - private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
435 - Device device = deviceService.getDevice(deviceId);
436 - Annotations annot = device.annotations();
437 - int portCount = deviceService.getPorts(deviceId).size();
438 - return envelope("showDetails", sid,
439 - json(deviceId.toString(),
440 - device.type().toString().toLowerCase(),
441 - new Prop("Name", annot.value("name")),
442 - new Prop("Vendor", device.manufacturer()),
443 - new Prop("H/W Version", device.hwVersion()),
444 - new Prop("S/W Version", device.swVersion()),
445 - new Prop("Serial Number", device.serialNumber()),
446 - new Separator(),
447 - new Prop("Latitude", annot.value("latitude")),
448 - new Prop("Longitude", annot.value("longitude")),
449 - new Prop("Ports", Integer.toString(portCount))));
450 - }
451 -
452 - // Returns host details response.
453 - private ObjectNode hostDetails(HostId hostId, long sid) {
454 - Host host = hostService.getHost(hostId);
455 - Annotations annot = host.annotations();
456 - return envelope("showDetails", sid,
457 - json(hostId.toString(), "host",
458 - new Prop("MAC", host.mac().toString()),
459 - new Prop("IP", host.ipAddresses().toString()),
460 - new Separator(),
461 - new Prop("Latitude", annot.value("latitude")),
462 - new Prop("Longitude", annot.value("longitude"))));
463 - }
464 -
465 - // Produces JSON property details.
466 - private ObjectNode json(String id, String type, Prop... props) {
467 - ObjectMapper mapper = new ObjectMapper();
468 - ObjectNode result = mapper.createObjectNode()
469 - .put("id", id).put("type", type);
470 - ObjectNode pnode = mapper.createObjectNode();
471 - ArrayNode porder = mapper.createArrayNode();
472 - for (Prop p : props) {
473 - porder.add(p.key);
474 - pnode.put(p.key, p.value);
475 - }
476 - result.set("propOrder", porder);
477 - result.set("props", pnode);
478 - return result;
479 - }
480 -
481 - // Auxiliary key/value carrier.
482 - private class Prop {
483 - private final String key;
484 - private final String value;
485 -
486 - protected Prop(String key, String value) {
487 - this.key = key;
488 - this.value = value;
489 - }
490 - }
491 -
492 - private class Separator extends Prop {
493 - protected Separator() {
494 - super("-", "");
495 } 239 }
496 } 240 }
497 241
242 + // Device event listener.
498 private class InternalDeviceListener implements DeviceListener { 243 private class InternalDeviceListener implements DeviceListener {
499 @Override 244 @Override
500 public void event(DeviceEvent event) { 245 public void event(DeviceEvent event) {
...@@ -502,6 +247,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -502,6 +247,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
502 } 247 }
503 } 248 }
504 249
250 + // Link event listener.
505 private class InternalLinkListener implements LinkListener { 251 private class InternalLinkListener implements LinkListener {
506 @Override 252 @Override
507 public void event(LinkEvent event) { 253 public void event(LinkEvent event) {
...@@ -509,6 +255,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -509,6 +255,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
509 } 255 }
510 } 256 }
511 257
258 + // Host event listener.
512 private class InternalHostListener implements HostListener { 259 private class InternalHostListener implements HostListener {
513 @Override 260 @Override
514 public void event(HostEvent event) { 261 public void event(HostEvent event) {
...@@ -516,13 +263,15 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -516,13 +263,15 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
516 } 263 }
517 } 264 }
518 265
266 + // Mastership event listener.
519 private class InternalMastershipListener implements MastershipListener { 267 private class InternalMastershipListener implements MastershipListener {
520 @Override 268 @Override
521 public void event(MastershipEvent event) { 269 public void event(MastershipEvent event) {
522 - 270 + // TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same?
523 } 271 }
524 } 272 }
525 273
274 + // Intent event listener.
526 private class InternalIntentListener implements IntentListener { 275 private class InternalIntentListener implements IntentListener {
527 @Override 276 @Override
528 public void event(IntentEvent event) { 277 public void event(IntentEvent event) {
...@@ -539,5 +288,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -539,5 +288,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
539 } 288 }
540 } 289 }
541 } 290 }
291 +
542 } 292 }
543 293
......
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 6,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "512 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "80 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "removeLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "80 Gb"
13 + }
14 + }
15 +}
1 { 1 {
2 - "event": "doUiThing", 2 + "event": "noop",
3 "payload": { 3 "payload": {
4 "id": "xyyzy" 4 "id": "xyyzy"
5 } 5 }
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
11 "" 11 ""
12 ], 12 ],
13 "metaUi": { 13 "metaUi": {
14 - "x": 400, 14 + "x": 520,
15 - "y": 280 15 + "y": 350
16 } 16 }
17 } 17 }
18 } 18 }
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
11 "" 11 ""
12 ], 12 ],
13 "metaUi": { 13 "metaUi": {
14 - "x": 400, 14 + "x": 520,
15 - "y": 280 15 + "y": 350
16 } 16 }
17 } 17 }
18 } 18 }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 "dst": "of:0000ffffffff0008", 9 "dst": "of:0000ffffffff0008",
10 "dstPort": "20", 10 "dstPort": "20",
11 "props" : { 11 "props" : {
12 - "BW": "70 G" 12 + "BW": "70 Gb"
13 } 13 }
14 } 14 }
15 } 15 }
......
...@@ -10,13 +10,16 @@ ...@@ -10,13 +10,16 @@
10 "description": [ 10 "description": [
11 "1. add device [8] (offline)", 11 "1. add device [8] (offline)",
12 "2. add device [3] (offline)", 12 "2. add device [3] (offline)",
13 - "3. update device [8] (online)", 13 + "3. update device [8] (online, label3 change)",
14 - "4. update device [3] (online)", 14 + "4. update device [3] (online, label3 change)",
15 "5. add link [3] --> [8]", 15 "5. add link [3] --> [8]",
16 "6. add host (to [3])", 16 "6. add host (to [3])",
17 "7. add host (to [8])", 17 "7. add host (to [8])",
18 "8. update host[3] (IP now 10.0.0.13)", 18 "8. update host[3] (IP now 10.0.0.13)",
19 "9. update host[8] (IP now 10.0.0.17)", 19 "9. update host[8] (IP now 10.0.0.17)",
20 + "10. update link (increase width, update props)",
21 + "11. update link (reduce width, update props)",
22 + "12. remove link",
20 "" 23 ""
21 ] 24 ]
22 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -260,36 +260,6 @@ ...@@ -260,36 +260,6 @@
260 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden'); 260 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
261 } 261 }
262 262
263 - function updateDeviceLabel(d) {
264 - var label = niceLabel(deviceLabel(d)),
265 - node = d.el,
266 - box;
267 -
268 - node.select('text')
269 - .text(label)
270 - .style('opacity', 0)
271 - .transition()
272 - .style('opacity', 1);
273 -
274 - box = adjustRectToFitText(node);
275 -
276 - node.select('rect')
277 - .transition()
278 - .attr(box);
279 -
280 - node.select('image')
281 - .transition()
282 - .attr('x', box.x + config.icons.xoff)
283 - .attr('y', box.y + config.icons.yoff);
284 - }
285 -
286 - function updateHostLabel(d) {
287 - var label = hostLabel(d),
288 - host = d.el;
289 -
290 - host.select('text').text(label);
291 - }
292 -
293 function cycleLabels() { 263 function cycleLabels() {
294 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1) 264 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
295 ? 0 : deviceLabelIndex + 1; 265 ? 0 : deviceLabelIndex + 1;
...@@ -371,10 +341,10 @@ ...@@ -371,10 +341,10 @@
371 addLink: addLink, 341 addLink: addLink,
372 addHost: addHost, 342 addHost: addHost,
373 updateDevice: updateDevice, 343 updateDevice: updateDevice,
374 - updateLink: stillToImplement, 344 + updateLink: updateLink,
375 updateHost: updateHost, 345 updateHost: updateHost,
376 removeDevice: stillToImplement, 346 removeDevice: stillToImplement,
377 - removeLink: stillToImplement, 347 + removeLink: removeLink,
378 removeHost: stillToImplement, 348 removeHost: stillToImplement,
379 showPath: showPath 349 showPath: showPath
380 }; 350 };
...@@ -429,6 +399,18 @@ ...@@ -429,6 +399,18 @@
429 } 399 }
430 } 400 }
431 401
402 + function updateLink(data) {
403 + var link = data.payload,
404 + id = link.id,
405 + linkData = network.lookup[id];
406 + if (linkData) {
407 + $.extend(linkData, link);
408 + updateLinkState(linkData);
409 + } else {
410 + logicError('updateLink lookup fail. ID = "' + id + '"');
411 + }
412 + }
413 +
432 function updateHost(data) { 414 function updateHost(data) {
433 var host = data.payload, 415 var host = data.payload,
434 id = host.id, 416 id = host.id,
...@@ -441,6 +423,17 @@ ...@@ -441,6 +423,17 @@
441 } 423 }
442 } 424 }
443 425
426 + function removeLink(data) {
427 + var link = data.payload,
428 + id = link.id,
429 + linkData = network.lookup[id];
430 + if (linkData) {
431 + removeLinkElement(linkData);
432 + } else {
433 + logicError('removeLink lookup fail. ID = "' + id + '"');
434 + }
435 + }
436 +
444 function showPath(data) { 437 function showPath(data) {
445 var links = data.payload.links, 438 var links = data.payload.links,
446 s = [ data.event + "\n" + links.length ]; 439 s = [ data.event + "\n" + links.length ];
...@@ -483,74 +476,81 @@ ...@@ -483,74 +476,81 @@
483 return 'translate(' + x + ',' + y + ')'; 476 return 'translate(' + x + ',' + y + ')';
484 } 477 }
485 478
479 + function missMsg(what, id) {
480 + return '\n[' + what + '] "' + id + '" missing ';
481 + }
482 +
483 + function linkEndPoints(srcId, dstId) {
484 + var srcNode = network.lookup[srcId],
485 + dstNode = network.lookup[dstId],
486 + sMiss = !srcNode ? missMsg('src', srcId) : '',
487 + dMiss = !dstNode ? missMsg('dst', dstId) : '';
488 +
489 + if (sMiss || dMiss) {
490 + logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
491 + return null;
492 + }
493 + return {
494 + source: srcNode,
495 + target: dstNode,
496 + x1: srcNode.x,
497 + y1: srcNode.y,
498 + x2: dstNode.x,
499 + y2: dstNode.y
500 + };
501 + }
502 +
486 function createHostLink(host) { 503 function createHostLink(host) {
487 var src = host.id, 504 var src = host.id,
488 dst = host.cp.device, 505 dst = host.cp.device,
489 id = host.ingress, 506 id = host.ingress,
490 - srcNode = network.lookup[src], 507 + lnk = linkEndPoints(src, dst);
491 - dstNode = network.lookup[dst],
492 - lnk;
493 508
494 - if (!dstNode) { 509 + if (!lnk) {
495 - logicError('switch not on map for link\n\n' +
496 - 'src = ' + src + '\ndst = ' + dst);
497 return null; 510 return null;
498 } 511 }
499 512
500 - // Compose link ... 513 + // Synthesize link ...
501 - lnk = { 514 + $.extend(lnk, {
502 id: id, 515 id: id,
503 - source: srcNode,
504 - target: dstNode,
505 class: 'link', 516 class: 'link',
506 type: 'hostLink', 517 type: 'hostLink',
507 svgClass: 'link hostLink', 518 svgClass: 'link hostLink',
508 - x1: srcNode.x, 519 + linkWidth: 1
509 - y1: srcNode.y, 520 + });
510 - x2: dstNode.x,
511 - y2: dstNode.y,
512 - width: 1
513 - }
514 return lnk; 521 return lnk;
515 } 522 }
516 523
517 function createLink(link) { 524 function createLink(link) {
518 - // start with the link object as is 525 + var lnk = linkEndPoints(link.src, link.dst),
519 - var lnk = link, 526 + type = link.type;
520 - type = link.type, 527 +
521 - src = link.src, 528 + if (!lnk) {
522 - dst = link.dst,
523 - w = link.linkWidth,
524 - srcNode = network.lookup[src],
525 - dstNode = network.lookup[dst];
526 -
527 - if (!(srcNode && dstNode)) {
528 - logicError('nodes not on map for link\n\n' +
529 - 'src = ' + src + '\ndst = ' + dst);
530 return null; 529 return null;
531 } 530 }
532 531
533 - // Augment as needed... 532 + // merge in remaining data
534 - $.extend(lnk, { 533 + $.extend(lnk, link, {
535 - source: srcNode,
536 - target: dstNode,
537 class: 'link', 534 class: 'link',
538 - svgClass: type ? 'link ' + type : 'link', 535 + svgClass: type ? 'link ' + type : 'link'
539 - x1: srcNode.x,
540 - y1: srcNode.y,
541 - x2: dstNode.x,
542 - y2: dstNode.y,
543 - width: w
544 }); 536 });
545 return lnk; 537 return lnk;
546 } 538 }
547 539
548 - function linkWidth(w) { 540 + var widthRatio = 1.4,
549 - // w is number of links between nodes. Scale appropriately. 541 + linkScale = d3.scale.linear()
550 - // TODO: use a d3.scale (linear, log, ... ?) 542 + .domain([1, 12])
551 - return w * 1.2; 543 + .range([widthRatio, 12 * widthRatio])
544 + .clamp(true);
545 +
546 + function updateLinkWidth (d) {
547 + // TODO: watch out for .showPath/.showTraffic classes
548 + d.el.transition()
549 + .duration(1000)
550 + .attr('stroke-width', linkScale(d.linkWidth));
552 } 551 }
553 552
553 +
554 function updateLinks() { 554 function updateLinks() {
555 link = linkG.selectAll('.link') 555 link = linkG.selectAll('.link')
556 .data(network.links, function (d) { return d.id; }); 556 .data(network.links, function (d) { return d.id; });
...@@ -572,7 +572,7 @@ ...@@ -572,7 +572,7 @@
572 }) 572 })
573 .transition().duration(1000) 573 .transition().duration(1000)
574 .attr({ 574 .attr({
575 - 'stroke-width': function (d) { return linkWidth(d.width); }, 575 + 'stroke-width': function (d) { return linkScale(d.linkWidth); },
576 stroke: '#666' // TODO: remove explicit stroke, rather... 576 stroke: '#666' // TODO: remove explicit stroke, rather...
577 }); 577 });
578 578
...@@ -589,13 +589,20 @@ ...@@ -589,13 +589,20 @@
589 //link .foo() .bar() ... 589 //link .foo() .bar() ...
590 590
591 // operate on exiting links: 591 // operate on exiting links:
592 - // TODO: figure out how to remove the node 'g' AND its children 592 + // TODO: better transition (longer as a dashed, grey line)
593 link.exit() 593 link.exit()
594 + .attr({
595 + 'stroke-dasharray': '3, 3'
596 + })
597 + .style('opacity', 0.4)
594 .transition() 598 .transition()
595 - .duration(750) 599 + .duration(2000)
596 .attr({ 600 .attr({
597 - opacity: 0 601 + 'stroke-dasharray': '3, 12'
598 }) 602 })
603 + .transition()
604 + .duration(1000)
605 + .style('opacity', 0.0)
599 .remove(); 606 .remove();
600 } 607 }
601 608
...@@ -650,7 +657,6 @@ ...@@ -650,7 +657,6 @@
650 node.y = y || network.view.height() / 2; 657 node.y = y || network.view.height() / 2;
651 } 658 }
652 659
653 -
654 function iconUrl(d) { 660 function iconUrl(d) {
655 return 'img/' + d.type + '.png'; 661 return 'img/' + d.type + '.png';
656 } 662 }
...@@ -694,12 +700,48 @@ ...@@ -694,12 +700,48 @@
694 return (label && label.trim()) ? label : '.'; 700 return (label && label.trim()) ? label : '.';
695 } 701 }
696 702
703 + function updateDeviceLabel(d) {
704 + var label = niceLabel(deviceLabel(d)),
705 + node = d.el,
706 + box;
707 +
708 + node.select('text')
709 + .text(label)
710 + .style('opacity', 0)
711 + .transition()
712 + .style('opacity', 1);
713 +
714 + box = adjustRectToFitText(node);
715 +
716 + node.select('rect')
717 + .transition()
718 + .attr(box);
719 +
720 + node.select('image')
721 + .transition()
722 + .attr('x', box.x + config.icons.xoff)
723 + .attr('y', box.y + config.icons.yoff);
724 + }
725 +
726 + function updateHostLabel(d) {
727 + var label = hostLabel(d),
728 + host = d.el;
729 +
730 + host.select('text').text(label);
731 + }
732 +
697 function updateDeviceState(nodeData) { 733 function updateDeviceState(nodeData) {
698 nodeData.el.classed('online', nodeData.online); 734 nodeData.el.classed('online', nodeData.online);
699 updateDeviceLabel(nodeData); 735 updateDeviceLabel(nodeData);
700 // TODO: review what else might need to be updated 736 // TODO: review what else might need to be updated
701 } 737 }
702 738
739 + function updateLinkState(linkData) {
740 + updateLinkWidth(linkData);
741 + // TODO: review what else might need to be updated
742 + // update label, if showing
743 + }
744 +
703 function updateHostState(hostData) { 745 function updateHostState(hostData) {
704 updateHostLabel(hostData); 746 updateHostLabel(hostData);
705 // TODO: review what else might need to be updated 747 // TODO: review what else might need to be updated
...@@ -826,6 +868,25 @@ ...@@ -826,6 +868,25 @@
826 .remove(); 868 .remove();
827 } 869 }
828 870
871 + function find(id, array) {
872 + for (var idx = 0, n = array.length; idx < n; idx++) {
873 + if (array[idx].id === id) {
874 + return idx;
875 + }
876 + }
877 + return -1;
878 + }
879 +
880 + function removeLinkElement(linkData) {
881 + // remove from lookup cache
882 + delete network.lookup[linkData.id];
883 + // remove from links array
884 + var idx = find(linkData.id, network.links);
885 +
886 + network.links.splice(linkData.index, 1);
887 + // remove from SVG
888 + updateLinks();
889 + }
829 890
830 function tick() { 891 function tick() {
831 node.attr({ 892 node.attr({
......
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
132 com.fasterxml.jackson.databind.node, 132 com.fasterxml.jackson.databind.node,
133 com.google.common.base.*, 133 com.google.common.base.*,
134 org.eclipse.jetty.websocket.*, 134 org.eclipse.jetty.websocket.*,
135 - org.onlab.api.*, 135 + org.onlab.util.*,
136 org.onlab.osgi.*, 136 org.onlab.osgi.*,
137 org.onlab.packet.*, 137 org.onlab.packet.*,
138 org.onlab.rest.*, 138 org.onlab.rest.*,
......