/* * Copyright 2014-present Open Networking Laboratory * * 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.cli.net; 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 com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.apache.karaf.shell.commands.Command; import org.apache.karaf.shell.commands.Option; import org.onlab.util.StringFilter; import org.onlab.util.Tools; import org.onosproject.cli.AbstractShellCommand; import org.onosproject.net.ConnectPoint; import org.onosproject.net.FilteredConnectPoint; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.criteria.Criterion; import org.onosproject.net.intent.ConnectivityIntent; import org.onosproject.net.intent.HostToHostIntent; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.IntentService; import org.onosproject.net.intent.IntentState; import org.onosproject.net.intent.LinkCollectionIntent; import org.onosproject.net.intent.MultiPointToSinglePointIntent; import org.onosproject.net.intent.OpticalCircuitIntent; import org.onosproject.net.intent.OpticalConnectivityIntent; import org.onosproject.net.intent.OpticalOduIntent; import org.onosproject.net.intent.PathIntent; import org.onosproject.net.intent.PointToPointIntent; import org.onosproject.net.intent.SinglePointToMultiPointIntent; import static com.google.common.base.MoreObjects.firstNonNull; import static java.lang.String.format; import static org.apache.commons.lang3.text.WordUtils.uncapitalize; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Lists the inventory of intents and their states. */ @Command(scope = "onos", name = "intents", description = "Lists the inventory of intents and their states") public class IntentsListCommand extends AbstractShellCommand { // Color codes and style private static final String BOLD = "\u001B[1m"; private static final String RESET = "\u001B[0m"; // Messages and string formatter private static final String APP_ID = BOLD + "Application Id:" + RESET + " %s"; private static final String COMMON_SELECTOR = BOLD + "Common ingress " + "selector:" + RESET + " %s"; private static final String CP = BOLD + "Connect Point:" + RESET + " %s"; private static final String CONSTRAINTS = BOLD + "Constraints:" + RESET + " %s"; private static final String DST = BOLD + "Destination " + RESET; private static final String EGRESS = BOLD + "Egress "; private static final String FILTERED_CPS = "connect points and individual selectors" + RESET; private static final String HOST = "host:" + RESET + " %s"; private static final String ID = BOLD + "Id:" + RESET + " %s"; private static final String INHERITED = "Inherited"; private static final String INGRESS = BOLD + "Ingress "; private static final String INDENTATION = " -> "; private static final String INSTALLABLE = BOLD + "Installable:" + RESET + " %s"; private static final String KEY = BOLD + "Key:" + RESET + " %s"; private static final String RESOURCES = BOLD + "Resources:" + RESET + " %s"; private static final String SELECTOR = BOLD + "Selector:" + RESET + " %s"; private static final String SEPARATOR = StringUtils.repeat("-", 172); private static final String SPACE = " "; private static final String SRC = BOLD + "Source "; private static final String STATE = BOLD + "State:" + RESET + " %s"; private static final String TREATMENT = BOLD + "Treatment:" + RESET + " %s"; private static final String TYPE = BOLD + "Intent type:" + RESET + " %s"; /** * {@value #SUMMARY_TITLES}. */ private static final String SUMMARY_TITLES = BOLD + format( "\n%1s%21s%14s%14s%14s%14s%14s%14s%14s%14s%14s%14s", "Intent type", "Total", "Installed", "Withdrawn", "Failed", "InstallReq", "Compiling", "Installing", "Recompiling", "WithdrawReq", "Withdrawing", "UnknownState") + RESET; @Option(name = "-i", aliases = "--installable", description = "Output Installable Intents", required = false, multiValued = false) private boolean showInstallable = false; @Option(name = "-s", aliases = "--summary", description = "Intents summary", required = false, multiValued = false) private boolean intentsSummary = false; @Option(name = "-m", aliases = "--mini-summary", description = "Intents mini summary", required = false, multiValued = false) private boolean miniSummary = false; @Option(name = "-p", aliases = "--pending", description = "Show information about pending intents", 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; @Override protected void execute() { service = get(IntentService.class); contentFilter = new StringFilter(filter, StringFilter.Strategy.AND); Iterable<Intent> intents; if (pending) { intents = service.getPending(); } else { 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()) { ObjectNode summaries = mapper().createObjectNode(); summarized.forEach((n, s) -> summaries.set(uncapitalize(n), s.json(mapper()))); print("%s", summaries); } else if (miniSummary) { StringBuilder builder = new StringBuilder(); builder.append(summarized.remove("All").miniSummary()); summarized.values().forEach(s -> builder.append(s.miniSummary())); print("%s", builder.toString()); } else { StringBuilder builder = new StringBuilder(); builder.append(SUMMARY_TITLES); builder.append('\n').append(SEPARATOR); builder.append(summarized.remove("All").summary()); summarized.values().forEach(s -> builder.append(s.summary())); print("%s", builder.toString()); } return; } // JSON or default output if (outputJson()) { print("%s", json(intents)); } else { for (Intent intent : intents) { IntentState state = service.getIntentState(intent.key()); StringBuilder intentFormat = fullFormat(intent, state); StringBuilder detailsIntentFormat = detailsFormat(intent, state); String formatted = intentFormat.append(detailsIntentFormat).toString(); if (contentFilter.filter(formatted)) { print("%s\n", formatted); } } } } /** * 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 { private final String intentType; private int total = 0; private int installReq = 0; private int compiling = 0; private int installing = 0; private int installed = 0; private int recompiling = 0; private int withdrawReq = 0; private int withdrawing = 0; private int withdrawn = 0; private int failed = 0; private int unknownState = 0; /** * Creates empty {@link IntentSummary} for specified {@code intentType}. * * @param intentType the string describing the Intent type */ IntentSummary(String intentType) { this.intentType = intentType; } /** * Creates {@link IntentSummary} initialized with given {@code intent}. * * @param intent to initialize with */ IntentSummary(Intent intent) { // remove "Intent" from intentType label this(intentType(intent)); if (contentFilter.filter(intent)) { update(service.getIntentState(intent.key())); } } // for identity element, when reducing IntentSummary() { this.intentType = null; } /** * Updates the Intent Summary. * * @param intentState the state of the intent */ void update(IntentState intentState) { total++; switch (intentState) { case INSTALL_REQ: installReq++; break; case COMPILING: compiling++; break; case INSTALLING: installing++; break; case INSTALLED: installed++; break; case RECOMPILING: recompiling++; break; case WITHDRAW_REQ: withdrawReq++; break; case WITHDRAWING: withdrawing++; break; case WITHDRAWN: withdrawn++; break; case FAILED: failed++; break; default: unknownState++; break; } } /** * Prints the Intent Summary. * */ StringBuilder summary() { StringBuilder builder = new StringBuilder(); builder.append(format( "\n%1s%s%14d%14d%14d%14d%14d%14d%14d%14d%14d%14d", BOLD + intentType + RESET, Strings.padStart(String.valueOf(total), (32 - intentType.length()), ' '), installed, withdrawn, failed, installReq, compiling, installing, recompiling, withdrawReq, withdrawing, unknownState)); builder.append('\n').append(SEPARATOR); return builder; } StringBuilder miniSummary() { StringBuilder builder = new StringBuilder(); builder.append(BOLD).append(intentType).append(RESET) .append(" (").append(total).append(')').append('\n'); builder.append('\t') .append("installed: ").append(installed).append(' ') .append("withdrawn: ").append(withdrawn).append(' ') .append("failed: ").append(failed) .append('\n'); builder.append('\t') .append("compiling: ").append(compiling).append(' ') .append("installing: ").append(installing).append(' ') .append("recompiling: ").append(recompiling).append(' ') .append("withdrawing: ").append(withdrawing) .append('\n'); builder.append('\t') .append("installReq: ").append(installReq).append(' ') .append("withdrawReq: ").append(withdrawReq).append(' ') .append("unknownState: ").append(unknownState) .append('\n') .append('\n'); return builder; } /** * Gets the JSON representation of the Intent Summary. * * @param mapper the object mapper * @return the JSON representation of the Intent Summary */ JsonNode json(ObjectMapper mapper) { ObjectNode result = mapper.createObjectNode() .put("total", total) .put("installed", installed) .put("failed", failed) .put("installReq", installReq) .put("installing", installing) .put("compiling", compiling) .put("recompiling", recompiling) .put("withdrawReq", withdrawReq) .put("withdrawing", withdrawing) .put("withdrawn", withdrawn) .put("unknownState", unknownState); return result; } } /** * Merges 2 {@link IntentSummary} together. * * @param a element to merge * @param b element to merge * @return merged {@link IntentSummary} */ IntentSummary merge(IntentSummary a, IntentSummary b) { IntentSummary m = new IntentSummary(firstNonNull(a.intentType, b.intentType)); m.total = a.total + b.total; m.installReq = a.installReq + b.installReq; m.compiling = a.compiling + b.compiling; m.installing = a.installing + b.installing; m.installed = a.installed + b.installed; m.recompiling = a.recompiling + b.recompiling; m.withdrawing = a.withdrawing + b.withdrawing; m.withdrawReq = a.withdrawReq + b.withdrawReq; m.withdrawn = a.withdrawn + b.withdrawn; m.failed = a.failed + b.failed; m.unknownState = a.unknownState + b.unknownState; return m; } /** * Returns IntentType string. * * @param intent input * @return IntentType string */ private static String intentType(Intent intent) { return intent.getClass().getSimpleName().replace("Intent", ""); } /** * Build summary of intents per intent type. * * @param intents to summarize * @return summaries per Intent type */ private Map<String, IntentSummary> summarize(Iterable<Intent> intents) { Map<String, List<Intent>> perIntent = Tools.stream(intents) .collect(Collectors.groupingBy(IntentsListCommand::intentType)); List<IntentSummary> collect = perIntent.values().stream() .map(il -> il.stream() .map(IntentSummary::new) .reduce(new IntentSummary(), this::merge) ).collect(Collectors.toList()); Map<String, IntentSummary> summaries = new HashMap<>(); // individual collect.forEach(is -> summaries.put(is.intentType, is)); // all summarised summaries.put("All", collect.stream() .reduce(new IntentSummary("All"), this::merge)); return summaries; } /** * Returns detailed information text about a specific intent. * * @param intent to print * @param state of intent * @return detailed information or "" if {@code state} was null */ private StringBuilder detailsFormat(Intent intent, IntentState state) { StringBuilder builder = new StringBuilder(); if (state == null) { return builder; } if (!intent.resources().isEmpty()) { builder.append('\n').append(format(RESOURCES, intent.resources())); } if (intent instanceof ConnectivityIntent) { ConnectivityIntent ci = (ConnectivityIntent) intent; if (!ci.selector().criteria().isEmpty()) { builder.append('\n').append(format(COMMON_SELECTOR, formatSelector(ci.selector()))); } if (!ci.treatment().allInstructions().isEmpty()) { builder.append('\n').append(format(TREATMENT, ci.treatment().allInstructions())); } if (ci.constraints() != null && !ci.constraints().isEmpty()) { builder.append('\n').append(format(CONSTRAINTS, ci.constraints())); } } if (intent instanceof HostToHostIntent) { HostToHostIntent pi = (HostToHostIntent) intent; builder.append('\n').append(format(SRC + HOST, pi.one())); builder.append('\n').append(format(DST + HOST, pi.two())); } else if (intent instanceof PointToPointIntent) { PointToPointIntent pi = (PointToPointIntent) intent; builder.append('\n').append(formatFilteredCps(Sets.newHashSet(pi.filteredIngressPoint()), INGRESS)); builder.append('\n').append(formatFilteredCps(Sets.newHashSet(pi.filteredEgressPoint()), EGRESS)); } else if (intent instanceof MultiPointToSinglePointIntent) { MultiPointToSinglePointIntent pi = (MultiPointToSinglePointIntent) intent; builder.append('\n').append(formatFilteredCps(pi.filteredIngressPoints(), INGRESS)); builder.append('\n').append(formatFilteredCps(Sets.newHashSet(pi.filteredEgressPoint()), EGRESS)); } else if (intent instanceof SinglePointToMultiPointIntent) { SinglePointToMultiPointIntent pi = (SinglePointToMultiPointIntent) intent; builder.append('\n').append(formatFilteredCps(Sets.newHashSet(pi.filteredIngressPoint()), INGRESS)); builder.append('\n').append(formatFilteredCps(pi.filteredEgressPoints(), EGRESS)); } else if (intent instanceof PathIntent) { PathIntent pi = (PathIntent) intent; builder.append(format("path=%s, cost=%f", pi.path().links(), pi.path().cost())); } else if (intent instanceof LinkCollectionIntent) { LinkCollectionIntent li = (LinkCollectionIntent) intent; builder.append('\n').append(format("links=%s", li.links())); builder.append('\n').append(format(CP, li.egressPoints())); } else if (intent instanceof OpticalCircuitIntent) { OpticalCircuitIntent ci = (OpticalCircuitIntent) intent; builder.append('\n').append(format("src=%s, dst=%s", ci.getSrc(), ci.getDst())); builder.append('\n').append(format("signal type=%s", ci.getSignalType())); builder.append('\n').append(format("bidirectional=%s", ci.isBidirectional())); } else if (intent instanceof OpticalConnectivityIntent) { OpticalConnectivityIntent ci = (OpticalConnectivityIntent) intent; builder.append('\n').append(format("src=%s, dst=%s", ci.getSrc(), ci.getDst())); builder.append('\n').append(format("signal type=%s", ci.getSignalType())); builder.append('\n').append(format("bidirectional=%s", ci.isBidirectional())); } else if (intent instanceof OpticalOduIntent) { OpticalOduIntent ci = (OpticalOduIntent) intent; builder.append('\n').append(format("src=%s, dst=%s", ci.getSrc(), ci.getDst())); builder.append('\n').append(format("signal type=%s", ci.getSignalType())); builder.append('\n').append(format("bidirectional=%s", ci.isBidirectional())); } List<Intent> installable = service.getInstallableIntents(intent.key()); installable.stream().filter(i -> contentFilter.filter(i)); if (showInstallable && installable != null && !installable.isEmpty()) { builder.append('\n').append(format(INSTALLABLE, installable)); } return builder; } /* * Prints out a formatted string, given a list of connect points. */ private StringBuilder formatFilteredCps(Set<FilteredConnectPoint> fCps, String prefix) { StringBuilder builder = new StringBuilder(); builder.append(prefix); builder.append(FILTERED_CPS); fCps.forEach(fCp -> builder.append('\n').append(formatFilteredCp(fCp))); return builder; } /* * Prints out a formatted string, given a filtered connect point. */ private StringBuilder formatFilteredCp(FilteredConnectPoint fCp) { ConnectPoint connectPoint = fCp.connectPoint(); TrafficSelector selector = fCp.trafficSelector(); StringBuilder builder = new StringBuilder(); builder.append(INDENTATION).append(format(CP, connectPoint)); builder.append(SPACE).append(format(SELECTOR, formatSelector(selector))); return builder; } /* * Prints out a formatted string, given a traffic selector */ private StringBuilder formatSelector(TrafficSelector ts) { StringBuilder builder = new StringBuilder(); List<Criterion> criteria = Lists.newArrayList(ts.criteria()); if (criteria == null || criteria.isEmpty()) { builder.append(INHERITED); return builder; } criteria.forEach(c -> { builder.append(c.toString()); if (criteria.indexOf(c) < criteria.size() - 1) { builder.append(", "); } }); return builder; } /* * Prints information about the intent state, given an intent. */ private StringBuilder fullFormat(Intent intent, IntentState state) { StringBuilder builder = new StringBuilder(); builder.append(format(ID, intent.id())); if (state != null) { builder.append('\n').append(format(STATE, state)); } builder.append('\n').append(format(KEY, intent.key())); builder.append('\n').append(format(TYPE, intent.getClass().getSimpleName())); builder.append('\n').append(format(APP_ID, intent.appId().name())); return builder; } /* * Produces a JSON array from the intents specified. */ private JsonNode json(Iterable<Intent> intents) { ObjectMapper mapper = new ObjectMapper(); ArrayNode result = mapper.createArrayNode(); Tools.stream(intents) .filter(intent -> contentFilter.filter(jsonForEntity(intent, Intent.class).toString())) .forEach(intent -> result.add(jsonForEntity(intent, Intent.class))); return result; } }