blob: da9ae5597a7b847087b57ef6182a0fdc8096cee5 [file] [log] [blame]
/*
* 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.cli.net;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Streams;
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.apache.karaf.shell.api.action.Option;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.device.PortStatistics;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.ObjectiveTrackerService;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.net.intent.WorkPartitionService;
import org.onosproject.net.statistic.FlowStatisticService;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
@Service
@Command(scope = "onos", name = "intents-diagnosis",
description = "Diagnosis intents")
public class IntentsDiagnosisCommand extends AbstractShellCommand {
@Argument(index = 0, name = "key",
description = "Intent key",
required = false, multiValued = false)
@Completion(IntentKeyCompleter.class)
String key = null;
@Option(name = "-d", aliases = "--details", description = "printing intent details",
required = false, multiValued = false)
private boolean dump = false;
@Option(name = "-l", aliases = "--link", description = "printing local intentsByLink",
required = false, multiValued = false)
private boolean dumpIntentByLink = false;
private static final int MAX_INTENT_PATH = 100;
private static final String FIELD_INTENTS_BY_LINK = "intentsByLink";
@Override
protected void doExecute() {
print("intents-diagnosis");
ServiceRefs svcRefs = buildServiceRefs();
if (svcRefs == null) {
return;
}
try {
for (Intent intent : svcRefs.intentsService().getIntents()) {
if (key != null && !intent.key().toString().equals(key)) {
continue;
}
print("");
printIntentHdr(intent, svcRefs);
if (intent instanceof PointToPointIntent) {
diagnosisP2Pintent((PointToPointIntent) intent, svcRefs);
} else {
// TODO : it needs to implement other types of intent
print(" It doesn't support %s intent.", intent.getClass().getSimpleName());
}
}
if (dumpIntentByLink) {
dumpIntentsByLink(svcRefs);
}
} catch (Exception e) {
print("error: " + e);
}
}
private void printIntentHdr(Intent intent, ServiceRefs svcRefs) {
print("* intent key: %s", intent.key());
print(" - state: %s", svcRefs.intentsService().getIntentState(intent.key()));
dump(" - leader: %s %s", svcRefs.getWorkPartitionService().getLeader(intent.key(), Key::hash),
svcRefs.workPartitionService.isMine(intent.key(), Key::hash) ? "(Mine)" : "");
}
private void dumpIntentsByLink(ServiceRefs svcRefs) {
Set<Map.Entry<LinkKey, Key>> intentsByLink = getIntentsByLinkSet(svcRefs);
print("* intentsbylink:");
for (Map.Entry<LinkKey, Key> entry : intentsByLink) {
print(" - %s, Intents: %s ", entry.getKey(), entry.getValue());
}
}
private Set<Map.Entry<LinkKey, Key>> getIntentsByLinkSet(ServiceRefs svcRefs) {
try {
ObjectiveTrackerService objTracker = svcRefs.getObjectiveTrackerService();
// Utilizing reflection instead of adding new interface for getting intentsByLink
Field f = objTracker.getClass().getDeclaredField(FIELD_INTENTS_BY_LINK);
f.setAccessible(true);
SetMultimap<LinkKey, Key> intentsByLink = (SetMultimap<LinkKey, Key>) f.get(objTracker);
return ImmutableSet.copyOf(intentsByLink.entries());
} catch (NoSuchFieldException | IllegalAccessException ex) {
error("error: " + ex);
return ImmutableSet.of();
}
}
private void diagnosisP2Pintent(PointToPointIntent intent, ServiceRefs svcRefs) {
List<Intent> installableIntents = svcRefs.intentsService().getInstallableIntents(intent.key());
if (installableIntents.size() == 0) {
error("NO INSTALLABLE INTENTS");
return;
}
Set<String> notSupport = new HashSet<>();
for (Intent installable: installableIntents) {
if (installable instanceof FlowRuleIntent) {
checkP2PFlowRuleIntent(intent, (FlowRuleIntent) installable, svcRefs);
} else {
// TODO : it needs to implement other types of installables
notSupport.add(installable.getClass().getSimpleName());
}
}
if (notSupport.size() > 0) {
print(" It doesn't support %s.", notSupport);
}
}
private void checkP2PFlowRuleIntent(PointToPointIntent intent, FlowRuleIntent installable, ServiceRefs svcRefs) {
final Map<DeviceId, DeviceOnIntent> devs = createDevicesOnP2PIntent(intent, installable);
boolean errorOccurred = false;
// checking the number of links & CPs in P2P intent
for (DeviceOnIntent dev: devs.values()) {
if (dev.getIngressLinks().size() > 1) {
error("MULTIPLE NUMBER OF INGRESS LINKs on " + dev.deviceId()
+ ": " + dev.getIngressLinks());
errorOccurred = true;
}
if (dev.getIngressCps().size() > 1) {
error("MULTIPLE NUMBER OF INGRESS CONNECT POINTs on " + dev.deviceId()
+ ": " + dev.getIngressCps());
errorOccurred = true;
}
if (dev.getEgressLinks().size() > 1) {
error("MULTIPLE NUMBER OF EGRESS LINKs: on " + dev.deviceId()
+ ": " + dev.getEgressLinks());
errorOccurred = true;
}
if (dev.getEgressCps().size() > 1) {
error("MULTIPLE NUMBER OF EGRESS CONNECT POINTs: on " + dev.deviceId()
+ ": " + dev.getEgressCps());
errorOccurred = true;
}
}
ConnectPoint startCp = intent.filteredIngressPoint().connectPoint();
DeviceOnIntent startDev = devs.get(startCp.deviceId());
if (startDev == null) {
error("STARTING CONNECT POINT DEVICE: " + startCp.deviceId() + " is not on intent");
errorOccurred = true;
}
ConnectPoint endCp = intent.filteredEgressPoint().connectPoint();
DeviceOnIntent endDev = devs.get(endCp.deviceId());
if (endDev == null) {
error("END CONNECT POINT DEVICE: " + endCp.deviceId() + " is not on intent");
errorOccurred = true;
}
if (!errorOccurred) {
// Per device checking with path-order
DeviceOnIntent dev = startDev;
int i = 0;
for (; i < MAX_INTENT_PATH; i++) {
perDeviceChecking(dev, svcRefs);
// P2P intent has only 1 egress CP
ConnectPoint egressCp = dev.getEgressCps().stream().findFirst().orElse(null);
if (egressCp != null && Objects.equals(endCp, egressCp)) {
break;
}
// P2P intent has only 1 egress link
Link egressLink = dev.getEgressLinks().stream().findFirst().orElse(null);
if (egressLink == null) {
error("INVALID EGRESS LINK & CONNECT POINT for: " + dev);
errorOccurred = true;
break;
}
if (Objects.equals(egressLink.dst(), endCp)) {
break;
}
// P2P intent only 1 ingress link
dev = devs.values().stream()
.filter(nextDev -> Objects.equals(
egressLink, nextDev.getIngressLinks().stream().findFirst().orElse(null)))
.findAny().orElse(null);
if (dev == null) {
error("FAILED TO FIND NEXT DEV for: " + dev + ", LINK: " + egressLink);
errorOccurred = true;
break;
}
}
if (i == MAX_INTENT_PATH) {
error("MAX INTENT PATH WAS EXCEEDED");
errorOccurred = true;
}
}
if (errorOccurred) {
// Installable checking
dump("");
dump("ERROR OCCURRED. DO PER FLOW CHECKING");
perFlowRuleChecking(installable, svcRefs);
}
if (svcRefs.workPartitionService.isMine(intent.key(), Key::hash)) {
checkIntentsByLink(installable, svcRefs);
}
}
private void checkIntentsByLink(FlowRuleIntent installable, ServiceRefs svcRefs) {
Set<Map.Entry<LinkKey, Key>> intentsByLink = getIntentsByLinkSet(svcRefs);
installable.resources().forEach(
rsrc -> {
if (rsrc instanceof Link) {
Link link = (Link) rsrc;
LinkKey linkKey = LinkKey.linkKey(link);
intentsByLink.stream()
.filter(entry -> Objects.equals(entry.getKey(), linkKey)
&& Objects.equals(entry.getValue(), installable.key()))
.findAny()
.orElseGet(() -> {
error("FAILED TO FIND LINK(" + link + ") for intents: " + installable.key());
return null;
});
}
}
);
}
// TODO: It needs to consider FLowObjectiveIntent case
private void perDeviceChecking(DeviceOnIntent devOnIntent, ServiceRefs svcRefs) {
Collection<PortStatistics> portStats =
svcRefs.deviceService().getPortStatistics(devOnIntent.deviceId());
Collection<PortStatistics> portDeltaStats =
svcRefs.deviceService().getPortDeltaStatistics(devOnIntent.deviceId());
dump("");
dump(" ------------------------------------------------------------------------------------------");
Device device = svcRefs.deviceService.getDevice(devOnIntent.deviceId());
if (device == null) {
error("INVALID DEVICE for " + devOnIntent.deviceId());
return;
}
dump(" %s", getDeviceString(device));
dump(" %s", device.annotations());
devOnIntent.getIngressCps().stream()
.forEach(cp -> dumpCpStatistics(cp, portStats, portDeltaStats, "INGRESS", svcRefs));
Stream<FlowEntry> flowEntries = Streams.stream(svcRefs.flowService.getFlowEntries(devOnIntent.deviceId()));
devOnIntent.getFlowRules().stream()
.forEach(
intentFlowRule -> {
FlowEntry matchedEntry = flowEntries
.filter(entry -> Objects.equals(intentFlowRule.id(), entry.id()))
.findFirst().orElse(null);
if (matchedEntry == null) {
error("FAILED TO FIND FLOW ENTRY: for " + intentFlowRule);
return;
}
if (Objects.equals(intentFlowRule.selector(), matchedEntry.selector()) &&
Objects.equals(intentFlowRule.treatment(), matchedEntry.treatment())) {
dumpFlowEntry(matchedEntry, "FLOW ENTRY");
return;
}
error("INSTALLABLE-FLOW ENTRY mismatch");
dumpFlowRule(intentFlowRule, "INSTALLABLE");
dumpFlowEntry(matchedEntry, "FLOW ENTRY");
}
);
devOnIntent.getEgressCps().stream()
.forEach(
cp -> dumpCpStatistics(cp, portStats, portDeltaStats, "EGRESS", svcRefs)
);
}
// TODO: It needs to consider FLowObjectiveIntent case
private void perFlowRuleChecking(FlowRuleIntent installable, ServiceRefs svcRefs) {
installable.flowRules().forEach(
flowrule -> {
DeviceId devId = flowrule.deviceId();
if (devId == null) {
error("INVALID DEVICE ID for " + flowrule);
return;
}
Device dev = svcRefs.deviceService.getDevice(devId);
if (dev == null) {
error("INVALID DEVICE for " + flowrule);
return;
}
dump("");
dump(
" ------------------------------------------------------------------------------------------");
dump(" %s", getDeviceString(dev));
dump(" %s", dev.annotations());
svcRefs.flowService().getFlowEntries(devId)
.forEach(
entry -> {
dumpFlowRule(flowrule, "INSTALLABLE");
dumpFlowEntry(entry, "FLOW ENTRY");
if (!flowrule.selector().equals(entry.selector())) {
return;
}
if (flowrule.id().equals(entry.id()) &&
flowrule.treatment().equals(entry.treatment())) {
dumpFlowEntry(entry, "FLOW ENTRY");
return;
}
error("INSTALLABLE-FLOW ENTRY mismatch");
}
);
}
);
}
private Map<DeviceId, DeviceOnIntent> createDevicesOnP2PIntent(
PointToPointIntent intent, FlowRuleIntent flowRuleIntent) {
final Map<DeviceId, DeviceOnIntent> devMap = new HashMap<>();
flowRuleIntent.resources().forEach(
rsrc -> {
if (rsrc instanceof Link) {
Link link = (Link) rsrc;
ConnectPoint srcCp = link.src();
ConnectPoint dstCp = link.dst();
try {
DeviceOnIntent dev = devMap.computeIfAbsent(srcCp.deviceId(), DeviceOnIntent::new);
dev.addEgressLink(link);
dev = devMap.computeIfAbsent(dstCp.deviceId(), DeviceOnIntent::new);
dev.addIngressLink(link);
} catch (IllegalStateException e) {
print("error: " + e);
}
}
}
);
ConnectPoint startCp = intent.filteredIngressPoint().connectPoint();
DeviceOnIntent startDev = devMap.computeIfAbsent(startCp.deviceId(), DeviceOnIntent::new);
if (!startDev.hasIngressCp(startCp)) {
startDev.addIngressCp(startCp);
}
ConnectPoint endCp = intent.filteredEgressPoint().connectPoint();
DeviceOnIntent endDev = devMap.computeIfAbsent(endCp.deviceId(), DeviceOnIntent::new);
if (!endDev.hasEgressCp(endCp)) {
endDev.addEgessCp(endCp);
}
flowRuleIntent.flowRules().forEach(
flowRule -> {
DeviceId devId = flowRule.deviceId();
if (devId == null) {
error("INVALID DEVICE ID for " + flowRule);
return;
}
DeviceOnIntent dev = devMap.get(devId);
if (dev == null) {
error("DEVICE(" + devId + ") IS NOT ON INTENTS LINKS");
return;
}
dev.addFlowRule(flowRule);
}
);
return devMap;
}
private String getDeviceString(Device dev) {
StringBuilder buf = new StringBuilder();
if (dev != null) {
buf.append(String.format("Device: %s, ", dev.id()));
buf.append(String.format("%s, ", dev.type()));
buf.append(String.format("%s, ", dev.manufacturer()));
buf.append(String.format("%s, ", dev.hwVersion()));
buf.append(String.format("%s, ", dev.swVersion()));
if (dev instanceof DefaultDevice) {
DefaultDevice dfltDev = (DefaultDevice) dev;
if (dfltDev.driver() != null) {
buf.append(String.format("%s, ", dfltDev.driver().name()));
}
String channelId = dfltDev.annotations().value("channelId");
if (channelId != null) {
buf.append(String.format("%s, ", channelId));
}
}
}
return buf.toString();
}
private void dumpFlowRule(FlowRule rule, String hdr) {
dump(" " + hdr + ":");
dump(" - id=%s, priority=%d", rule.id(), rule.priority());
dump(" - %s", rule.selector());
dump(" - %s", rule.treatment());
}
private void dumpFlowEntry(FlowEntry entry, String hdr) {
dumpFlowRule(entry, hdr);
dump(" - packets=%d", entry.packets());
}
private void dumpCpStatistics(ConnectPoint cp, Collection<PortStatistics> devPortStats,
Collection<PortStatistics> devPortDeltaStats, String direction, ServiceRefs svcs) {
if (cp == null) {
return;
}
dump(" %s:", direction);
if (cp.port().isLogical()) {
dump(" - logical: device: %s, port: %s", cp.deviceId(), cp.port());
return;
}
Port port = svcs.deviceService.getPort(cp.deviceId(), cp.port());
if (port == null) {
return;
}
try {
devPortStats.stream()
.filter(stat -> stat.portNumber().equals(cp.port()))
.forEach(stat -> dump(" - stat : %s:", getPortStatStr(stat, port)));
} catch (IllegalStateException e) {
error("error: " + e);
return;
}
try {
devPortDeltaStats.stream()
.filter(stat -> stat.portNumber().equals(cp.port()))
.forEach(stat -> dump(" - delta : %s:", getPortStatStr(stat, port)));
} catch (IllegalStateException e) {
error("error: " + e);
}
}
private void dump(String format, Object... args) {
if (dump) {
print(format, args);
}
}
private String getPortStatStr(PortStatistics stat, Port port) {
final String portName = port.annotations().value(AnnotationKeys.PORT_NAME);
return String.format("port: %s(%s), ", stat.portNumber(), portName) +
String.format("enabled: %b, ", port.isEnabled()) +
String.format("pktRx: %d, ", stat.packetsReceived()) +
String.format("pktTx: %d, ", stat.packetsSent()) +
String.format("pktRxErr: %d, ", stat.packetsRxErrors()) +
String.format("pktTxErr: %d, ", stat.packetsTxErrors()) +
String.format("pktRxDrp: %d, ", stat.packetsRxDropped()) +
String.format("pktTxDrp: %d", stat.packetsTxDropped());
}
private static class DeviceOnIntent {
private final DeviceId devId;
private Collection<Link> ingressLinks = new ArrayList<>();
private Collection<Link> egressLinks = new ArrayList<>();
private Collection<ConnectPoint> ingressCps = new ArrayList<>();
private Collection<ConnectPoint> egressCps = new ArrayList<>();
private Collection<FlowRule> flowRules = new ArrayList<>();
public DeviceOnIntent(DeviceId devId) {
this.devId = devId;
}
public DeviceId deviceId() {
return devId;
}
public Collection<Link> getIngressLinks() {
return ingressLinks;
}
public Collection<Link> getEgressLinks() {
return egressLinks;
}
public void addIngressLink(Link link) {
ingressLinks.add(link);
addIngressCp(link.dst());
}
public void addEgressLink(Link link) {
egressLinks.add(link);
addEgessCp(link.src());
}
public void addIngressCp(ConnectPoint cp) {
ingressCps.add(cp);
}
public void addEgessCp(ConnectPoint cp) {
egressCps.add(cp);
}
public boolean hasIngressCp(final ConnectPoint cp) {
return ingressCps.stream().anyMatch(icp -> Objects.equals(icp, cp));
}
public boolean hasEgressCp(ConnectPoint cp) {
return egressCps.stream().anyMatch(ecp -> Objects.equals(ecp, cp));
}
public Collection<ConnectPoint> getIngressCps() {
return ingressCps;
}
public Collection<ConnectPoint> getEgressCps() {
return egressCps;
}
public Collection<FlowRule> getFlowRules() {
return flowRules;
}
public void addFlowRule(FlowRule flowRule) {
flowRules.add(flowRule);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.omitNullValues()
.add("devId", devId)
.add("ingressLinks", ingressLinks)
.add("egressLinks", egressLinks)
.add("flowRules", flowRules)
.toString();
}
}
private ServiceRefs buildServiceRefs() {
IntentService intentsService = get(IntentService.class);
if (intentsService == null) {
return null;
}
DeviceService deviceService = get(DeviceService.class);
if (deviceService == null) {
return null;
}
FlowStatisticService flowStatsService = get(FlowStatisticService.class);
if (flowStatsService == null) {
return null;
}
FlowRuleService flowService = get(FlowRuleService.class);
if (flowService == null) {
return null;
}
WorkPartitionService workPartitionService = get(WorkPartitionService.class);
if (workPartitionService == null) {
return null;
}
ObjectiveTrackerService objectiveTrackerService = get(ObjectiveTrackerService.class);
if (objectiveTrackerService == null) {
return null;
}
return new ServiceRefs(
intentsService,
deviceService,
flowService,
workPartitionService,
objectiveTrackerService
);
}
private static final class ServiceRefs {
private IntentService intentsService;
private DeviceService deviceService;
private FlowRuleService flowService;
private WorkPartitionService workPartitionService;
private ObjectiveTrackerService objectiveTrackerService;
private ServiceRefs(
IntentService intentsService,
DeviceService deviceService,
FlowRuleService flowService,
WorkPartitionService workPartitionService,
ObjectiveTrackerService objectiveTrackerService
) {
this.intentsService = intentsService;
this.deviceService = deviceService;
this.flowService = flowService;
this.workPartitionService = workPartitionService;
this.objectiveTrackerService = objectiveTrackerService;
}
public IntentService intentsService() {
return intentsService;
}
public DeviceService deviceService() {
return deviceService;
}
public FlowRuleService flowService() {
return flowService;
}
public WorkPartitionService getWorkPartitionService() {
return workPartitionService;
}
public ObjectiveTrackerService getObjectiveTrackerService() {
return objectiveTrackerService;
}
}
}