Madan Jampani

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

Showing 31 changed files with 677 additions and 180 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 }
......
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
......
This diff is collapsed. Click to expand it.
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.*,
......