[WIP] Upgrade ONOS to karaf version 4.2.1
Change-Id: I7cd40c995bdf1c80f94b1895fb3344e32404c7fa
diff --git a/cli2/BUCK b/cli2/BUCK
new file mode 100644
index 0000000..3fedcf4
--- /dev/null
+++ b/cli2/BUCK
@@ -0,0 +1,16 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:JACKSON',
+ '//lib:METRICS',
+ '//lib:org.apache.karaf.shell.console',
+ '//incubator/api:onos-incubator-api',
+ '//incubator/net:onos-incubator-net',
+ '//utils/rest:onlab-rest',
+ '//core/common:onos-core-common',
+]
+
+osgi_jar (
+ deps = COMPILE_DEPS,
+ visibility = ['PUBLIC'],
+)
+
diff --git a/cli2/BUILD b/cli2/BUILD
new file mode 100644
index 0000000..2a8cd3b
--- /dev/null
+++ b/cli2/BUILD
@@ -0,0 +1,12 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + METRICS + [
+ "@org_apache_karaf_shell_core//jar",
+ "//incubator/api:onos-incubator-api",
+ "//incubator/net:onos-incubator-net",
+ "//utils/rest:onlab-rest",
+ "//core/common:onos-core-common",
+]
+
+osgi_jar(
+ visibility = ["//visibility:public"],
+ deps = COMPILE_DEPS,
+)
diff --git a/cli2/src/main/java/org/onosproject/cli2/AbstractChoicesCompleter.java b/cli2/src/main/java/org/onosproject/cli2/AbstractChoicesCompleter.java
new file mode 100644
index 0000000..7082ece
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/AbstractChoicesCompleter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Abstraction of a completer with preset choices.
+ */
+public abstract class AbstractChoicesCompleter extends AbstractCompleter {
+
+ protected abstract List<String> choices();
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+ SortedSet<String> strings = delegate.getStrings();
+ choices().forEach(strings::add);
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/AbstractCompleter.java b/cli2/src/main/java/org/onosproject/cli2/AbstractCompleter.java
new file mode 100644
index 0000000..3f10abc
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/AbstractCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.impl.action.command.ArgumentCompleter;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+
+/**
+ * Abstract argument completer.
+ */
+public abstract class AbstractCompleter implements Completer {
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/AbstractShellCommand.java b/cli2/src/main/java/org/onosproject/cli2/AbstractShellCommand.java
new file mode 100644
index 0000000..0046e03
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/AbstractShellCommand.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.Action;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Annotations;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.DefaultAnnotations;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Base abstraction of Karaf shell commands.
+ */
+public abstract class AbstractShellCommand implements Action, CodecContext {
+
+ @Option(name = "-j", aliases = "--json", description = "Output JSON",
+ required = false, multiValued = false)
+ private boolean json = false;
+
+ /**
+ * Returns the reference to the implementation of the specified service.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return service implementation
+ * @throws org.onlab.osgi.ServiceNotFoundException if service is unavailable
+ */
+ public static <T> T get(Class<T> serviceClass) {
+ return DefaultServiceDirectory.getService(serviceClass);
+ }
+
+ /**
+ * Returns application ID for the CLI.
+ *
+ * @return command-line application identifier
+ */
+ protected ApplicationId appId() {
+ return get(CoreService.class)
+ .registerApplication("org.onosproject.cli");
+ }
+
+ /**
+ * Prints the arguments using the specified format.
+ *
+ * @param format format string; see {@link String#format}
+ * @param args arguments
+ */
+ public void print(String format, Object... args) {
+ System.out.println(String.format(format, args));
+ }
+
+ /**
+ * Prints the arguments using the specified format to error stream.
+ *
+ * @param format format string; see {@link String#format}
+ * @param args arguments
+ */
+ public void error(String format, Object... args) {
+ System.err.println(String.format(format, args));
+ }
+
+ /**
+ * Produces a string image of the specified key/value annotations.
+ *
+ * @param annotations key/value annotations
+ * @return string image with ", k1=v1, k2=v2, ..." pairs
+ */
+ public static String annotations(Annotations annotations) {
+ if (annotations == null) {
+ annotations = DefaultAnnotations.EMPTY;
+ }
+ StringBuilder sb = new StringBuilder();
+ Set<String> keys = new TreeSet<>(annotations.keys());
+ for (String key : keys) {
+ sb.append(", ").append(key).append('=').append(annotations.value(key));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Produces a string image of the specified key/value annotations.
+ * Excludes the keys in the given Set.
+ *
+ * @param annotations key/value annotations
+ * @param excludedKeys keys not to add in the resulting string
+ * @return string image with ", k1=v1, k2=v2, ..." pairs
+ */
+ public static String annotations(Annotations annotations, Set<String> excludedKeys) {
+ StringBuilder sb = new StringBuilder();
+ Set<String> keys = new TreeSet<>(annotations.keys());
+ keys.removeAll(excludedKeys);
+ for (String key : keys) {
+ sb.append(", ").append(key).append('=').append(annotations.value(key));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Produces a JSON object from the specified key/value annotations.
+ *
+ * @param mapper ObjectMapper to use while converting to JSON
+ * @param annotations key/value annotations
+ * @return JSON object
+ */
+ public static ObjectNode annotations(ObjectMapper mapper, Annotations annotations) {
+ ObjectNode result = mapper.createObjectNode();
+ for (String key : annotations.keys()) {
+ result.put(key, annotations.value(key));
+ }
+ return result;
+ }
+
+ /**
+ * Indicates whether JSON format should be output.
+ *
+ * @return true if JSON is requested
+ */
+ protected boolean outputJson() {
+ return json;
+ }
+
+ @Override
+ public Object execute() throws Exception {
+ try {
+ doExecute();
+ } catch (ServiceNotFoundException e) {
+ error(e.getMessage());
+ }
+ return null;
+ }
+
+ protected void doExecute() throws Exception {
+ try {
+ execute();
+ } catch (ServiceNotFoundException e) {
+ error(e.getMessage());
+ }
+ }
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ return get(CodecService.class).getCodec(entityClass);
+ }
+
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return get(serviceClass);
+ }
+
+ /**
+ * Generates a Json representation of an object.
+ *
+ * @param entity object to generate JSON for
+ * @param entityClass class to format with - this chooses which codec to use
+ * @param <T> Type of the object being formatted
+ * @return JSON object representation
+ */
+ public <T> ObjectNode jsonForEntity(T entity, Class<T> entityClass) {
+ return codec(entityClass).encode(entity, this);
+ }
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/CliComponent.java b/cli2/src/main/java/org/onosproject/cli2/CliComponent.java
new file mode 100644
index 0000000..e5af520
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/CliComponent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.onosproject.core.CoreService;
+
+/**
+ * OSGI Component for the ONOS CLI.
+ */
+
+@Component(immediate = true)
+public class CliComponent {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Activate
+ public void activate() {
+ coreService
+ .registerApplication("org.onosproject.cli");
+ }
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/MastersListCommand.java b/cli2/src/main/java/org/onosproject/cli2/MastersListCommand.java
new file mode 100644
index 0000000..ae24484
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/MastersListCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.utils.Comparators;
+
+import java.util.Collections;
+import java.util.List;
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Lists device mastership information.
+ */
+@Command(scope = "onos", name = "masters",
+ description = "Lists device mastership information")
+@Service
+public class MastersListCommand extends AbstractShellCommand {
+
+ @Override
+ protected void doExecute() {
+ ClusterService service = get(ClusterService.class);
+ MastershipService mastershipService = get(MastershipService.class);
+ DeviceService deviceService = get(DeviceService.class);
+ List<ControllerNode> nodes = newArrayList(service.getNodes());
+ Collections.sort(nodes, Comparators.NODE_COMPARATOR);
+
+ if (outputJson()) {
+ print("%s", json(service, mastershipService, nodes));
+ } else {
+ for (ControllerNode node : nodes) {
+ List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
+ ids.removeIf(did -> deviceService.getDevice(did) == null);
+ Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
+ print("%s: %d devices", node.id(), ids.size());
+ for (DeviceId deviceId : ids) {
+ print(" %s", deviceId);
+ }
+ }
+ }
+ }
+
+ // Produces JSON structure.
+ private JsonNode json(ClusterService service, MastershipService mastershipService,
+ List<ControllerNode> nodes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (ControllerNode node : nodes) {
+ List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
+ result.add(mapper.createObjectNode()
+ .put("id", node.id().toString())
+ .put("size", ids.size())
+ .set("devices", json(mapper, ids)));
+ }
+ return result;
+ }
+
+ /**
+ * Produces a JSON array containing the specified device identifiers.
+ *
+ * @param mapper object mapper
+ * @param ids collection of device identifiers
+ * @return JSON array
+ */
+ public static JsonNode json(ObjectMapper mapper, Iterable<DeviceId> ids) {
+ ArrayNode result = mapper.createArrayNode();
+ for (DeviceId deviceId : ids) {
+ result.add(deviceId.toString());
+ }
+ return result;
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/NodeAddCommand.java b/cli2/src/main/java/org/onosproject/cli2/NodeAddCommand.java
new file mode 100644
index 0000000..99f94bb
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/NodeAddCommand.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Adds a new controller cluster node.
+ */
+@Service
+@Command(scope = "onos", name = "add-node",
+ description = "Adds a new controller cluster node")
+public class NodeAddCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "nodeId", description = "Node ID",
+ required = true, multiValued = false)
+ String nodeId = null;
+
+ @Argument(index = 1, name = "ip", description = "Node IP address",
+ required = true, multiValued = false)
+ String ip = null;
+
+ @Argument(index = 2, name = "tcpPort", description = "Node TCP listen port",
+ required = false, multiValued = false)
+ int tcpPort = DefaultControllerNode.DEFAULT_PORT;
+
+ @Override
+ protected void doExecute() {
+ ClusterAdminService service = get(ClusterAdminService.class);
+ service.addNode(new NodeId(nodeId), IpAddress.valueOf(ip), tcpPort);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/NodeIdCompleter.java b/cli2/src/main/java/org/onosproject/cli2/NodeIdCompleter.java
new file mode 100644
index 0000000..0438f76
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/NodeIdCompleter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Node ID completer.
+ */
+@Service
+public class NodeIdCompleter implements Completer {
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+
+ // Fetch our service and feed it's offerings to the string completer
+ ClusterService service = AbstractShellCommand.get(ClusterService.class);
+ Iterator<ControllerNode> it = service.getNodes().iterator();
+ SortedSet<String> strings = delegate.getStrings();
+ while (it.hasNext()) {
+ strings.add(it.next().id().toString());
+ }
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/NodeRemoveCommand.java b/cli2/src/main/java/org/onosproject/cli2/NodeRemoveCommand.java
new file mode 100644
index 0000000..80dd25d
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/NodeRemoveCommand.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Removes a controller cluster node.
+ */
+@Service
+@Command(scope = "onos", name = "remove-node",
+ description = "Removes a new controller cluster node")
+public class NodeRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "nodeId", description = "Node ID",
+ required = true, multiValued = false)
+ @Completion(NodeIdCompleter.class)
+ String nodeId = null;
+
+ @Override
+ protected void doExecute() {
+ ClusterAdminService service = get(ClusterAdminService.class);
+ service.removeNode(new NodeId(nodeId));
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/NodesListCommand.java b/cli2/src/main/java/org/onosproject/cli2/NodesListCommand.java
new file mode 100644
index 0000000..fbd6778
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/NodesListCommand.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.core.Version;
+import org.onosproject.utils.Comparators;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Lists all controller cluster nodes.
+ */
+@Service
+@Command(scope = "onos", name = "nodes",
+ description = "Lists all controller cluster nodes")
+public class NodesListCommand extends AbstractShellCommand {
+
+ private static final String FMT = "id=%s, address=%s:%s, state=%s, version=%s, updated=%s %s";
+
+ @Override
+ protected void doExecute() {
+ ClusterAdminService service = get(ClusterAdminService.class);
+ List<ControllerNode> nodes = newArrayList(service.getNodes());
+ Collections.sort(nodes, Comparators.NODE_COMPARATOR);
+ if (outputJson()) {
+ print("%s", json(service, nodes));
+ } else {
+ ControllerNode self = service.getLocalNode();
+ for (ControllerNode node : nodes) {
+ String timeAgo = service.localStatus(node.id());
+ Version version = service.getVersion(node.id());
+ print(FMT, node.id(), node.ip(), node.tcpPort(),
+ service.getState(node.id()),
+ version == null ? "unknown" : version,
+ timeAgo,
+ node.equals(self) ? "*" : "");
+ }
+ }
+ }
+
+ // Produces JSON structure.
+ private JsonNode json(ClusterAdminService service, List<ControllerNode> nodes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ ControllerNode self = service.getLocalNode();
+ for (ControllerNode node : nodes) {
+ ControllerNode.State nodeState = service.getState(node.id());
+ Version nodeVersion = service.getVersion(node.id());
+ ObjectNode newNode = mapper.createObjectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put("tcpPort", node.tcpPort())
+ .put("self", node.equals(self));
+ if (nodeState != null) {
+ newNode.put("state", nodeState.toString());
+ }
+ if (nodeVersion != null) {
+ newNode.put("version", nodeVersion.toString());
+ }
+ result.add(newNode);
+ }
+ return result;
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/SummaryCommand.java b/cli2/src/main/java/org/onosproject/cli2/SummaryCommand.java
new file mode 100644
index 0000000..86e75be
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/SummaryCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterMetadataService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.Version;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.topology.TopologyService;
+
+import java.util.Set;
+
+/**
+ * Provides summary of ONOS model.
+ */
+@Command(scope = "onos", name = "summary",
+ description = "Provides summary of ONOS model")
+@Service
+public class SummaryCommand extends AbstractShellCommand {
+
+ /**
+ * Count the active ONOS controller nodes.
+ *
+ * @param nodes set of all of the controller nodes in the cluster
+ * @return count of active nodes
+ */
+ private long activeNodes(Set<ControllerNode> nodes) {
+ ClusterService clusterService = get(ClusterService.class);
+
+ return nodes.stream()
+ .map(node -> clusterService.getState(node.id()))
+ .filter(nodeState -> nodeState.isActive())
+ .count();
+ }
+
+ @Override
+ protected void doExecute() {
+ IpAddress nodeIp = get(ClusterService.class).getLocalNode().ip();
+ Version version = get(CoreService.class).version();
+ long numNodes = activeNodes(get(ClusterService.class).getNodes());
+ int numDevices = get(DeviceService.class).getDeviceCount();
+ int numLinks = get(LinkService.class).getLinkCount();
+ int numHosts = get(HostService.class).getHostCount();
+ int numScc = get(TopologyService.class).currentTopology().clusterCount();
+ int numFlows = get(FlowRuleService.class).getFlowRuleCount();
+ long numIntents = get(IntentService.class).getIntentCount();
+ String clusterId = get(ClusterMetadataService.class).getClusterMetadata().getName();
+
+ if (outputJson()) {
+ print("%s", new ObjectMapper().createObjectNode()
+ .put("node", nodeIp.toString())
+ .put("version", version.toString())
+ .put("clusterId", clusterId)
+ .put("nodes", numNodes)
+ .put("devices", numDevices)
+ .put("links", numLinks)
+ .put("hosts", numHosts)
+ .put("SCC(s)", numScc)
+ .put("flows", numFlows)
+ .put("intents", numIntents));
+ } else {
+ print("node=%s, version=%s clusterId=%s", nodeIp, version, clusterId);
+ print("nodes=%d, devices=%d, links=%d, hosts=%d, SCC(s)=%s, flows=%d, intents=%d",
+ numNodes, numDevices, numLinks, numHosts, numScc, numFlows, numIntents);
+ }
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommand.java b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommand.java
new file mode 100644
index 0000000..35afe52
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommand.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2.app;
+
+import com.google.common.io.ByteStreams;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.cli2.AbstractShellCommand;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Manages application inventory.
+ */
+@Service
+@Command(scope = "onos", name = "app",
+ description = "Manages application inventory")
+public class ApplicationCommand extends AbstractShellCommand {
+
+ static final String INSTALL = "install";
+ static final String UNINSTALL = "uninstall";
+ static final String ACTIVATE = "activate";
+ static final String DEACTIVATE = "deactivate";
+ static final String DOWNLOAD = "download";
+
+ @Argument(index = 0, name = "command",
+ description = "Command name (install|activate|deactivate|uninstall|download)",
+ required = true, multiValued = false)
+ @Completion(ApplicationCommandCompleter.class)
+ String command = null;
+
+ @Argument(index = 1, name = "names", description = "Application name(s) or URL(s)",
+ required = true, multiValued = true)
+ @Completion(ApplicationNameCompleter.class)
+ String[] names = null;
+
+ @Override
+ protected void doExecute() {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ if (command.equals(INSTALL)) {
+ for (String name : names) {
+ if (!installApp(service, name)) {
+ return;
+ }
+ }
+
+ } else if (command.equals(DOWNLOAD)) {
+ for (String name : names) {
+ if (!downloadApp(service, name)) {
+ return;
+ }
+ }
+ } else {
+ for (String name : names) {
+ if (!manageApp(service, name)) {
+ return;
+ }
+ }
+ }
+ return;
+ }
+
+ // Installs the application from input of the specified URL
+ private boolean installApp(ApplicationAdminService service, String url) {
+ try {
+ if ("-".equals(url)) {
+ service.install(System.in);
+ } else {
+ service.install(new URL(url).openStream());
+ }
+ } catch (IOException e) {
+ error("Unable to get URL: %s", url);
+ return false;
+ }
+ return true;
+ }
+
+ // Downloads the application bits to the standard output.
+ private boolean downloadApp(ApplicationAdminService service, String name) {
+ try {
+ ByteStreams.copy(service.getApplicationArchive(service.getId(name)),
+ System.out);
+ } catch (IOException e) {
+ error("Unable to download bits for application %s", name);
+ return false;
+ }
+ return true;
+ }
+
+ // Manages the specified application.
+ private boolean manageApp(ApplicationAdminService service, String name) {
+ ApplicationId appId = service.getId(name);
+ if (appId == null) {
+ List<Application> matches = service.getApplications().stream()
+ .filter(app -> app.id().name().matches(".*\\." + name + "$"))
+ .collect(Collectors.toList());
+
+ if (matches.size() == 1) {
+ // Found match
+ appId = matches.iterator().next().id();
+ } else if (!matches.isEmpty()) {
+ print("Did you mean one of: %s",
+ matches.stream()
+ .map(Application::id)
+ .map(ApplicationId::name)
+ .collect(Collectors.toList()));
+ return false;
+ }
+ }
+ if (appId == null) {
+ print("No such application: %s", name);
+ return false;
+ }
+
+ String action;
+ if (command.equals(UNINSTALL)) {
+ service.uninstall(appId);
+ action = "Uninstalled";
+ } else if (command.equals(ACTIVATE)) {
+ service.activate(appId);
+ action = "Activated";
+ } else if (command.equals(DEACTIVATE)) {
+ service.deactivate(appId);
+ action = "Deactivated";
+ } else {
+ print("Unsupported command: %s", command);
+ return false;
+ }
+ print("%s %s", action, appId.name());
+ return true;
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommandCompleter.java b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommandCompleter.java
new file mode 100644
index 0000000..f4d3782
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationCommandCompleter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2.app;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli2.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.cli2.app.ApplicationCommand.*;
+
+/**
+ * Application command completer.
+ */
+@Service
+public class ApplicationCommandCompleter extends AbstractChoicesCompleter {
+ @Override
+ public List<String> choices() {
+ return ImmutableList.of(INSTALL, UNINSTALL, ACTIVATE, DEACTIVATE, DOWNLOAD);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/app/ApplicationNameCompleter.java b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationNameCompleter.java
new file mode 100644
index 0000000..5f002ff
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/app/ApplicationNameCompleter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cli2.app;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.cli2.AbstractCompleter;
+import org.onosproject.core.Application;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+import static java.util.Arrays.asList;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+import static org.onosproject.cli2.AbstractShellCommand.get;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Application name completer.
+ */
+@Service
+public class ApplicationNameCompleter extends AbstractCompleter {
+ private static final Logger log = getLogger(ApplicationNameCompleter.class);
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+
+ // Command name is the second argument.
+ String cmd = commandLine.getArguments()[1];
+ log.info("Command is {}", cmd);
+
+ // Grab apps already on the command (to prevent tab-completed duplicates)
+ // FIXME: This does not work.
+// final Set previousApps;
+// if (list.getArguments().length > 2) {
+// previousApps = Sets.newHashSet(
+// Arrays.copyOfRange(list.getArguments(), 2, list.getArguments().length));
+// } else {
+// previousApps = Collections.emptySet();
+// }
+
+ // Fetch our service and feed it's offerings to the string completer
+ ApplicationService service = get(ApplicationService.class);
+ Iterator<Application> it = service.getApplications().iterator();
+ SortedSet<String> strings = delegate.getStrings();
+ int c = 0;
+ log.info("Processing apps");
+ while (it.hasNext()) {
+ Application app = it.next();
+ log.info("app #{} is {}", c, app.id().name());
+ c++;
+ ApplicationState state = service.getState(app.id());
+// if (previousApps.contains(app.id().name())) {
+// continue;
+// }
+ if ("uninstall".equals(cmd) || "download".equals(cmd) ||
+ ("activate".equals(cmd) && state == INSTALLED) ||
+ ("deactivate".equals(cmd) && state == ACTIVE)) {
+ strings.add(app.id().name());
+ }
+ }
+
+ // add unique suffix to candidates, if user has something in buffer
+ log.info("Command line buffer {} position {}", commandLine.getBuffer(), commandLine.getBufferPosition());
+ if (!commandLine.getBuffer().endsWith(cmd)) {
+ List<String> suffixCandidates = strings.stream()
+ // remove onos common prefix
+ .map(full -> full.replaceFirst("org\\.onosproject\\.", ""))
+ // a.b.c -> [c, b.c, a.b.c]
+ .flatMap(appName -> {
+ List<String> suffixes = new ArrayList<>();
+ Deque<String> frags = new ArrayDeque<>();
+ // a.b.c -> [c, b, a] -> [c, b.c, a.b.c]
+ Lists.reverse(asList(appName.split("\\."))).forEach(frag -> {
+ frags.addFirst(frag);
+ suffixes.add(frags.stream().collect(Collectors.joining(".")));
+ });
+ return suffixes.stream();
+ })
+ // convert to occurrence map
+ .collect(Collectors.groupingBy(e -> e, Collectors.counting()))
+ .entrySet().stream()
+ // only accept unique suffix
+ .filter(e -> e.getValue() == 1L)
+ .map(Entry::getKey)
+ .collect(Collectors.toList());
+
+ delegate.getStrings().addAll(suffixCandidates);
+ }
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/cli2/src/main/java/org/onosproject/cli2/package-info.java b/cli2/src/main/java/org/onosproject/cli2/package-info.java
new file mode 100644
index 0000000..2531a52
--- /dev/null
+++ b/cli2/src/main/java/org/onosproject/cli2/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Administrative console command-line extensions.
+ */
+package org.onosproject.cli;