CLI options: flows (remove), intents (remove/purge; details)
Change-Id: Id084c28451389a46826eced30f03dcce2c1afe86
diff --git a/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
index bd7a246..7cbd586 100644
--- a/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/FlowsListCommand.java
@@ -24,7 +24,6 @@
import org.apache.karaf.shell.commands.Option;
import org.onlab.util.StringFilter;
import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.utils.Comparators;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Device;
@@ -34,7 +33,11 @@
import org.onosproject.net.flow.FlowEntry.FlowEntryState;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.utils.Comparators;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -93,10 +96,15 @@
private boolean countOnly = false;
@Option(name = "-f", aliases = "--filter",
- description = "Filter flows by specific key",
+ description = "Filter flows by specific keyword",
required = false, multiValued = true)
private List<String> filter = new ArrayList<>();
+ @Option(name = "-r", aliases = "--remove",
+ description = "Remove flows by specific keyword",
+ required = false, multiValued = false)
+ private String remove = null;
+
private Predicate<FlowEntry> predicate = TRUE_PREDICATE;
private StringFilter contentFilter;
@@ -112,6 +120,22 @@
SortedMap<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service, coreService);
+ // Remove flows
+ if (remove != null) {
+ flows.values().forEach(flowList -> {
+ if (!remove.isEmpty()) {
+ filter.add(remove);
+ contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
+ }
+ if (!filter.isEmpty() || (remove != null && !remove.isEmpty())) {
+ flowList = filterFlows(flowList);
+ this.removeFlowsInteractive(flowList, service, coreService);
+ }
+ });
+ return;
+ }
+
+ // Show flows
if (outputJson()) {
print("%s", json(flows.keySet(), flows));
} else {
@@ -120,6 +144,36 @@
}
/**
+ * Removes the flows passed as argument after confirmation is provided
+ * for each of them.
+ * If no explicit confirmation is provided, the flow is not removed.
+ *
+ * @param flows list of flows to remove
+ * @param flowService FlowRuleService object
+ * @param coreService CoreService object
+ */
+ public void removeFlowsInteractive(Iterable<FlowEntry> flows,
+ FlowRuleService flowService, CoreService coreService) {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ flows.forEach(flow -> {
+ ApplicationId appId = coreService.getAppId(flow.appId());
+ System.out.print(String.format("Id=%s, AppId=%s. Remove? [y/N]: ",
+ flow.id(), appId != null ? appId.name() : "<none>"));
+ String response;
+ try {
+ response = br.readLine();
+ response = response.trim().replace("\n", "");
+ if (response.equals("y")) {
+ flowService.removeFlowRules(flow);
+ }
+ } catch (IOException e) {
+ response = "";
+ }
+ print(response);
+ });
+ }
+
+ /**
* Produces a JSON array of flows grouped by the each device.
*
* @param devices collection of devices to group flow by
@@ -213,6 +267,17 @@
}
/**
+ * Filter a given list of flows based on the existing content filter.
+ *
+ * @param flows list of flows to filter
+ * @return further filtered list of flows
+ */
+ private List<FlowEntry> filterFlows(List<FlowEntry> flows) {
+ return flows.stream().
+ filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+ }
+
+ /**
* Prints flows.
*
* @param d the device
@@ -221,8 +286,7 @@
*/
protected void printFlows(Device d, List<FlowEntry> flows,
CoreService coreService) {
- List<FlowEntry> filteredFlows = flows.stream().
- filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+ List<FlowEntry> filteredFlows = filterFlows(flows);
boolean empty = filteredFlows == null || filteredFlows.isEmpty();
print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : filteredFlows.size());
if (empty || countOnly) {
@@ -271,5 +335,4 @@
builder.append("]");
return builder.toString();
}
-
}
diff --git a/cli/src/main/java/org/onosproject/cli/net/IntentDetailsCommand.java b/cli/src/main/java/org/onosproject/cli/net/IntentDetailsCommand.java
index 1cd0a1b..8bc87fe 100644
--- a/cli/src/main/java/org/onosproject/cli/net/IntentDetailsCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/IntentDetailsCommand.java
@@ -45,8 +45,17 @@
@Override
protected void execute() {
- if (idsStr != null) {
- ids = idsStr.stream()
+ detailIntents(idsStr);
+ }
+
+ /**
+ * Print detailed data for intents, given a list of IDs.
+ *
+ * @param intentsIds List of intent IDs
+ */
+ public void detailIntents(List<String> intentsIds) {
+ if (intentsIds != null) {
+ ids = intentsIds.stream()
.map(IntentId::valueOf)
.collect(Collectors.toSet());
}
@@ -54,8 +63,8 @@
IntentService service = get(IntentService.class);
Tools.stream(service.getIntentData())
- .filter(this::filter)
- .forEach(this::printIntentData);
+ .filter(this::filter)
+ .forEach(this::printIntentData);
}
private boolean filter(IntentData data) {
diff --git a/cli/src/main/java/org/onosproject/cli/net/IntentRemoveCommand.java b/cli/src/main/java/org/onosproject/cli/net/IntentRemoveCommand.java
index ea2c67c..741de20 100644
--- a/cli/src/main/java/org/onosproject/cli/net/IntentRemoveCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/IntentRemoveCommand.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.cli.net;
+import com.google.common.collect.ImmutableList;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
@@ -22,12 +23,15 @@
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.intent.Intent;
-import org.onosproject.net.intent.IntentState;
-import org.onosproject.net.intent.IntentService;
-import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.EnumSet;
import java.util.Objects;
@@ -70,8 +74,72 @@
@Override
protected void execute() {
IntentService intentService = get(IntentService.class);
- CoreService coreService = get(CoreService.class);
+ removeIntent(intentService.getIntents(),
+ applicationIdString, keyString,
+ purgeAfterRemove, sync);
+ }
+ /**
+ * Purges the intents passed as argument.
+ *
+ * @param intents list of intents to purge
+ */
+ private void purgeIntents(Iterable<Intent> intents) {
+ IntentService intentService = get(IntentService.class);
+ this.purgeAfterRemove = true;
+ removeIntentsByAppId(intentService, intents, null);
+ }
+
+ /**
+ * Purges the intents passed as argument after confirmation is provided
+ * for each of them.
+ * If no explicit confirmation is provided, the intent is not purged.
+ *
+ * @param intents list of intents to purge
+ */
+ public void purgeIntentsInteractive(Iterable<Intent> intents) {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ intents.forEach(intent -> {
+ System.out.print(String.format("Id=%s, Key=%s, AppId=%s. Remove? [y/N]: ",
+ intent.id(), intent.key(), intent.appId().name()));
+ String response;
+ try {
+ response = br.readLine();
+ response = response.trim().replace("\n", "");
+ if (response.equals("y")) {
+ this.purgeIntents(ImmutableList.of(intent));
+ }
+ } catch (IOException e) {
+ response = "";
+ }
+ print(response);
+ });
+ }
+
+ /**
+ * Removes the intents passed as argument, assuming these
+ * belong to the application's ID provided (if any) and
+ * contain a key string.
+ *
+ * If an application ID is provided, it will be used to further
+ * filter the intents to be removed.
+ *
+ * @param intents list of intents to remove
+ * @param applicationIdString application ID to filter intents
+ * @param keyString string to filter intents
+ * @param purgeAfterRemove states whether the intents should be also purged
+ * @param sync states whether the cli should wait for the operation to finish
+ * before returning
+ */
+ private void removeIntent(Iterable<Intent> intents,
+ String applicationIdString, String keyString,
+ boolean purgeAfterRemove, boolean sync) {
+ IntentService intentService = get(IntentService.class);
+ CoreService coreService = get(CoreService.class);
+ this.applicationIdString = applicationIdString;
+ this.keyString = keyString;
+ this.purgeAfterRemove = purgeAfterRemove;
+ this.sync = sync;
if (purgeAfterRemove || sync) {
print("Using \"sync\" to remove/purge intents - this may take a while...");
print("Check \"summary\" to see remove/purge progress.");
@@ -87,11 +155,7 @@
}
if (isNullOrEmpty(keyString)) {
- for (Intent intent : intentService.getIntents()) {
- if (intent.appId().equals(appId)) {
- removeIntent(intentService, intent);
- }
- }
+ removeIntentsByAppId(intentService, intents, appId);
} else {
final Key key;
@@ -111,6 +175,32 @@
}
}
+ /**
+ * Removes the intents passed as argument.
+ *
+ * If an application ID is provided, it will be used to further
+ * filter the intents to be removed.
+ *
+ * @param intentService IntentService object
+ * @param intents intents to remove
+ * @param appId application ID to filter intents
+ */
+ private void removeIntentsByAppId(IntentService intentService,
+ Iterable<Intent> intents,
+ ApplicationId appId) {
+ for (Intent intent : intents) {
+ if (appId == null || intent.appId().equals(appId)) {
+ removeIntent(intentService, intent);
+ }
+ }
+ }
+
+ /**
+ * Removes the intent passed as argument.
+ *
+ * @param intentService IntentService object
+ * @param intent intent to remove
+ */
private void removeIntent(IntentService intentService, Intent intent) {
IntentListener listener = null;
Key key = intent.key();
diff --git a/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
index cb48396..e90041f 100644
--- a/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/IntentsListCommand.java
@@ -56,7 +56,6 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
/**
* Lists the inventory of intents and their states.
@@ -155,11 +154,21 @@
required = false, multiValued = false)
private boolean pending = false;
+ @Option(name = "-d", aliases = "--details",
+ description = "Show details for intents, filtered by ID",
+ required = false, multiValued = true)
+ private List<String> intentIds = new ArrayList<>();
+
@Option(name = "-f", aliases = "--filter",
description = "Filter intents by specific keyword",
required = false, multiValued = true)
private List<String> filter = new ArrayList<>();
+ @Option(name = "-r", aliases = "--remove",
+ description = "Remove and purge intents by specific keyword",
+ required = false, multiValued = false)
+ private String remove = null;
+
private StringFilter contentFilter;
private IntentService service;
@@ -175,6 +184,25 @@
intents = service.getIntents();
}
+ // Remove intents
+ if (remove != null && !remove.isEmpty()) {
+ filter.add(remove);
+ contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
+ IntentRemoveCommand intentRemoveCmd = new IntentRemoveCommand();
+ if (!remove.isEmpty()) {
+ intentRemoveCmd.purgeIntentsInteractive(filterIntents(service));
+ }
+ return;
+ }
+
+ // Show detailed intents
+ if (!intentIds.isEmpty()) {
+ IntentDetailsCommand intentDetailsCmd = new IntentDetailsCommand();
+ intentDetailsCmd.detailIntents(intentIds);
+ return;
+ }
+
+ // Show brief intents
if (intentsSummary || miniSummary) {
Map<String, IntentSummary> summarized = summarize(intents);
if (outputJson()) {
@@ -197,6 +225,7 @@
return;
}
+ // JSON or default output
if (outputJson()) {
print("%s", json(intents));
} else {
@@ -213,6 +242,27 @@
}
/**
+ * Filter a given list of intents based on the existing content filter.
+ *
+ * @param service IntentService object
+ * @return further filtered list of intents
+ */
+ private List<Intent> filterIntents(IntentService service) {
+ return filterIntents(service.getIntents());
+ }
+
+ /**
+ * Filter a given list of intents based on the existing content filter.
+ *
+ * @param intents Iterable of intents
+ * @return further filtered list of intents
+ */
+ private List<Intent> filterIntents(Iterable<Intent> intents) {
+ return Tools.stream(intents)
+ .filter(i -> contentFilter.filter(i)).collect(Collectors.toList());
+ }
+
+ /**
* Internal local class to keep track of a single type Intent summary.
*/
private class IntentSummary {
@@ -246,7 +296,9 @@
IntentSummary(Intent intent) {
// remove "Intent" from intentType label
this(intentType(intent));
- update(service.getIntentState(intent.key()));
+ if (contentFilter.filter(intent)) {
+ update(service.getIntentState(intent.key()));
+ }
}
// for identity element, when reducing
@@ -412,7 +464,7 @@
*/
private Map<String, IntentSummary> summarize(Iterable<Intent> intents) {
Map<String, List<Intent>> perIntent = Tools.stream(intents)
- .collect(Collectors.groupingBy(i -> intentType(i)));
+ .collect(Collectors.groupingBy(IntentsListCommand::intentType));
List<IntentSummary> collect = perIntent.values().stream()
.map(il ->
@@ -571,10 +623,9 @@
private JsonNode json(Iterable<Intent> intents) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
- StreamSupport.stream(intents.spliterator(), false)
+ Tools.stream(intents)
.filter(intent -> contentFilter.filter(jsonForEntity(intent, Intent.class).toString()))
.forEach(intent -> result.add(jsonForEntity(intent, Intent.class)));
return result;
}
-
}