[ONOS-7639] intents-diagnosis CLI (initial implementation for P2P-flow rule intent)

Change-Id: Iba668809c6d2ad20fd86fb703f4d8e211acf2f14
diff --git a/cli/src/main/java/org/onosproject/cli/net/IntentsDiagnosisCommand.java b/cli/src/main/java/org/onosproject/cli/net/IntentsDiagnosisCommand.java
new file mode 100644
index 0000000..1518137
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/IntentsDiagnosisCommand.java
@@ -0,0 +1,694 @@
+/*
+ * 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.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.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;
+
+@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)
+    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 execute() {
+
+        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) throws Exception {
+        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) throws Exception {
+
+        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());
+    }
+
+    private void diagnosisP2Pintent(PointToPointIntent intent, ServiceRefs svcRefs) throws Exception {
+
+        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)
+            throws Exception {
+
+        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) throws Exception {
+
+        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);
+                            if (dev != null) {
+                                dev.addEgressLink(link);
+                            }
+                            dev = devMap.computeIfAbsent(dstCp.deviceId(), DeviceOnIntent::new);
+                            if (dev != null) {
+                                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);
+        }
+
+        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;
+        }
+    }
+
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 6ca9c5a..0633c44 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -303,6 +303,12 @@
             <action class="org.onosproject.cli.net.IntentsListCommand"/>
         </command>
         <command>
+            <action class="org.onosproject.cli.net.IntentsDiagnosisCommand"/>
+            <completers>
+                <ref component-id="intentKeyCompleter"/>
+            </completers>
+        </command>
+        <command>
             <action class="org.onosproject.cli.net.IntentListCompilers"/>
         </command>
         <command>