You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2018/07/13 11:28:47 UTC

[GitHub] rhtyd closed pull request #2721: api: Introducing a new diagnostics API command for system VMs for CloudStack admins

rhtyd closed pull request #2721: api: Introducing a new diagnostics API command for system VMs for CloudStack admins
URL: https://github.com/apache/cloudstack/pull/2721
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.travis.yml b/.travis.yml
index f4b52bae945..0474e7adb3d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -34,7 +34,7 @@ env:
   matrix:
     # Keep the TESTS sorted by name and grouped by type
     - TESTS="smoke/test_certauthority_root"
-    
+
     - TESTS="smoke/test_accounts
              smoke/test_affinity_groups
              smoke/test_affinity_groups_projects
@@ -43,6 +43,7 @@ env:
              smoke/test_deploy_vm_root_resize
              smoke/test_deploy_vm_with_userdata
              smoke/test_deploy_vms_with_varied_deploymentplanners
+             smoke/test_diagnostics
              smoke/test_disk_offerings
              smoke/test_dynamicroles
              smoke/test_global_settings
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 504b2149837..1a57313eaa7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -719,6 +719,11 @@
     public static final String LAST_ANNOTATED = "lastannotated";
     public static final String LDAP_DOMAIN = "ldapdomain";
 
+    public static final String STDOUT = "stdout";
+    public static final String STDERR = "stderr";
+    public static final String EXITCODE = "exitcode";
+    public static final String TARGET_ID = "targetid";
+
     public enum HostDetails {
         all, capacity, events, stats, min;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java
new file mode 100644
index 00000000000..bb1ddf57cb7
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java
@@ -0,0 +1,136 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.api.command.admin.diagnostics;
+
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.user.Account;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.RunDiagnosticsResponse;
+import org.apache.cloudstack.api.response.SystemVmResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.diagnostics.DiagnosticsService;
+import org.apache.cloudstack.diagnostics.DiagnosticsType;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.Map;
+
+@APICommand(name = RunDiagnosticsCmd.APINAME, responseObject = RunDiagnosticsResponse.class, entityType = {VirtualMachine.class},
+        responseHasSensitiveInfo = false,
+        requestHasSensitiveInfo = false,
+        description = "Execute network-utility command (ping/arping/tracert) on system VMs remotely",
+        authorized = {RoleType.Admin},
+        since = "4.12.0.0")
+public class RunDiagnosticsCmd extends BaseCmd {
+    private static final Logger LOGGER = Logger.getLogger(RunDiagnosticsCmd.class);
+    public static final String APINAME = "runDiagnostics";
+
+    @Inject
+    private DiagnosticsService diagnosticsService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.TARGET_ID, type = CommandType.UUID, required = true, entityType = SystemVmResponse.class,
+            validations = {ApiArgValidator.PositiveNumber},
+            description = "The ID of the system VM instance to diagnose")
+    private Long id;
+
+    @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, required = true,
+            validations = {ApiArgValidator.NotNullOrEmpty},
+            description = "The IP/Domain address to test connection to")
+    private String address;
+
+    @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true,
+            validations = {ApiArgValidator.NotNullOrEmpty},
+            description = "The system VM diagnostics type  valid options are: ping, traceroute, arping")
+    private String type;
+
+    @Parameter(name = ApiConstants.PARAMS, type = CommandType.STRING,
+            authorized = {RoleType.Admin},
+            description = "Additional command line options that apply for each command")
+    private String optionalArguments;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+    public Long getId() {
+        return id;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public DiagnosticsType getType() {
+        DiagnosticsType diagnosticsType = DiagnosticsType.getCommand(type);
+        if (diagnosticsType == null) {
+            throw new IllegalArgumentException(type + " Is not a valid diagnostics command type. ");
+        }
+        return diagnosticsType;
+    }
+
+    public String getOptionalArguments() {
+        return optionalArguments;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////////// Implementation //////////////////
+    /////////////////////////////////////////////////////
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Account account = CallContext.current().getCallingAccount();
+        if (account != null) {
+            return account.getId();
+        }
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException {
+        RunDiagnosticsResponse response = new RunDiagnosticsResponse();
+        try {
+            final Map<String, String> answerMap = diagnosticsService.runDiagnosticsCommand(this);
+            if (CollectionUtils.isNotEmpty(Collections.singleton(answerMap))) {
+                response.setStdout(answerMap.get(ApiConstants.STDOUT));
+                response.setStderr(answerMap.get(ApiConstants.STDERR));
+                response.setExitCode(answerMap.get(ApiConstants.EXITCODE));
+                response.setObjectName("diagnostics");
+                response.setResponseName(getCommandName());
+                this.setResponseObject(response);
+            }
+        } catch (final ServerApiException e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+        }
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RunDiagnosticsResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RunDiagnosticsResponse.java
new file mode 100644
index 00000000000..4c8a923613a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/RunDiagnosticsResponse.java
@@ -0,0 +1,67 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.cloud.vm.VirtualMachine;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+
+@EntityReference(value = VirtualMachine.class)
+public class RunDiagnosticsResponse extends BaseResponse {
+    @SerializedName(ApiConstants.STDOUT)
+    @Param(description = "the standard output from the command execution")
+    private String stdout;
+
+    @SerializedName(ApiConstants.STDERR)
+    @Param(description = "the standard error output from the command execution")
+    private String stderr;
+
+    @SerializedName(ApiConstants.EXITCODE)
+    @Param(description = "the command execution return code")
+    private String exitCode;
+
+    public String getStdout() {
+        return stdout;
+    }
+
+    public void setStdout(String stdout) {
+        this.stdout = stdout;
+    }
+
+    public String getStderr() {
+        return stderr;
+    }
+
+    public void setStderr(String stderr) {
+        this.stderr = stderr;
+    }
+
+    public String getExitCode() {
+        return exitCode;
+    }
+
+    public void setExitCode(String exitCode) {
+        this.exitCode = exitCode;
+    }
+
+}
diff --git a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java
new file mode 100644
index 00000000000..a9177af7e0c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java
@@ -0,0 +1,29 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd;
+
+import java.util.Map;
+
+public interface DiagnosticsService {
+
+    Map<String, String> runDiagnosticsCommand(RunDiagnosticsCmd cmd);
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java
new file mode 100644
index 00000000000..0e3a1dad2c6
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java
@@ -0,0 +1,42 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+public enum DiagnosticsType {
+    PING("ping"), TRACEROUTE("traceroute"), ARPING("arping");
+
+    private String value;
+
+    DiagnosticsType(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static DiagnosticsType getCommand(String cmd) {
+        for (DiagnosticsType type : DiagnosticsType.values()) {
+            if (type.value.equalsIgnoreCase(cmd)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java
index 838f0877918..2c75a78b1a3 100644
--- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java
+++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java
@@ -69,4 +69,5 @@
 
     public static final String VR_CFG = "vr_cfg.sh";
 
+    public static final String DIAGNOSTICS = "diagnostics.py";
 }
\ No newline at end of file
diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
index 0ffe8cc0ea2..112d9209349 100644
--- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
+++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
@@ -22,6 +22,9 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.channels.SocketChannel;
+
+import org.apache.cloudstack.diagnostics.DiagnosticsAnswer;
+import org.apache.cloudstack.diagnostics.DiagnosticsCommand;
 import org.joda.time.Duration;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -189,9 +192,11 @@ private Answer executeQueryCommand(NetworkElementCommand cmd) {
         } else if (cmd instanceof GetDomRVersionCmd) {
             return execute((GetDomRVersionCmd)cmd);
         } else if (cmd instanceof CheckS2SVpnConnectionsCommand) {
-            return execute((CheckS2SVpnConnectionsCommand) cmd);
+            return execute((CheckS2SVpnConnectionsCommand)cmd);
         } else if (cmd instanceof GetRouterAlertsCommand) {
             return execute((GetRouterAlertsCommand)cmd);
+        } else if (cmd instanceof DiagnosticsCommand) {
+            return execute((DiagnosticsCommand)cmd);
         } else {
             s_logger.error("Unknown query command in VirtualRoutingResource!");
             return Answer.createUnsupportedCommandAnswer(cmd);
@@ -292,6 +297,15 @@ private Answer execute(CheckRouterCommand cmd) {
         return new CheckRouterAnswer(cmd, result.getDetails(), true);
     }
 
+    private Answer execute(DiagnosticsCommand cmd) {
+        _eachTimeout = Duration.standardSeconds(NumbersUtil.parseInt("60", 60));
+        final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.DIAGNOSTICS, cmd.getSrciptArguments(), _eachTimeout);
+        if (!result.isSuccess()) {
+            return new DiagnosticsAnswer(cmd, false, result.getDetails());
+        }
+        return new DiagnosticsAnswer(cmd, result.isSuccess(), result.getDetails());
+    }
+
     private Answer execute(GetDomRVersionCmd cmd) {
         final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.VERSION, null);
         if (!result.isSuccess()) {
@@ -454,6 +468,6 @@ private Answer execute(AggregationControlCommand cmd) {
                 _vrAggregateCommandsSet.remove(routerName);
             }
         }
-        return new Answer(cmd, false, "Fail to recongize aggregation action " + action.toString());
+        return new Answer(cmd, false, "Fail to recognize aggregation action " + action.toString());
     }
 }
diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsAnswer.java b/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsAnswer.java
new file mode 100644
index 00000000000..006f0434ab3
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsAnswer.java
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.base.Strings;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DiagnosticsAnswer extends Answer {
+    public static final Logger LOGGER = Logger.getLogger(DiagnosticsAnswer.class);
+
+    public DiagnosticsAnswer(DiagnosticsCommand cmd, boolean result, String details) {
+        super(cmd, result, details);
+    }
+
+    public Map<String, String> getExecutionDetails() {
+        final Map<String, String> executionDetailsMap = new HashMap<>();
+        if (result == true && !Strings.isNullOrEmpty(details)) {
+            final String[] parseDetails = details.split("&&");
+            if (parseDetails.length >= 3) {
+                executionDetailsMap.put(ApiConstants.STDOUT, parseDetails[0].trim());
+                executionDetailsMap.put(ApiConstants.STDERR, parseDetails[1].trim());
+                executionDetailsMap.put(ApiConstants.EXITCODE, String.valueOf(parseDetails[2]).trim());
+            } else {
+                throw new CloudRuntimeException("Unsupported diagnostics command type supplied");
+            }
+        } else {
+            executionDetailsMap.put(ApiConstants.STDOUT, "");
+            executionDetailsMap.put(ApiConstants.STDERR, details);
+            executionDetailsMap.put(ApiConstants.EXITCODE, "-1");
+        }
+        return executionDetailsMap;
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsCommand.java b/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsCommand.java
new file mode 100644
index 00000000000..14d9da9ab17
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsCommand.java
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+import com.cloud.agent.api.routing.NetworkElementCommand;
+
+public class DiagnosticsCommand extends NetworkElementCommand {
+
+    private final String scriptArguments;
+    private final boolean executeInSequence;
+
+    public DiagnosticsCommand(String scriptArguments, boolean executeInSequence) {
+        this.scriptArguments = scriptArguments;
+        this.executeInSequence = executeInSequence;
+    }
+
+    public String getSrciptArguments() {
+        return scriptArguments;
+    }
+
+    @Override
+    public boolean isQuery() {
+        return true;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return this.executeInSequence;
+    }
+}
\ No newline at end of file
diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java
index 3a315509b49..6a9e70763a2 100644
--- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java
+++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManager.java
@@ -33,6 +33,7 @@
 import com.cloud.resource.AgentResourceBase;
 import com.cloud.simulator.MockHost;
 import com.cloud.utils.component.Manager;
+import org.apache.cloudstack.diagnostics.DiagnosticsCommand;
 
 public interface MockAgentManager extends Manager {
     public static final long DEFAULT_HOST_MEM_SIZE = 8 * 1024 * 1024 * 1024L; // 8G, unit of Mbytes
@@ -64,4 +65,6 @@ boolean handleSystemVMStart(long vmId, String privateIpAddress, String privateMa
     Answer maintain(MaintainCommand cmd);
 
     Answer checkNetworkCommand(CheckNetworkCommand cmd);
+
+    Answer runDiagnostics(DiagnosticsCommand cmd);
 }
diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java
index 9d1e4079d42..7af282724b2 100644
--- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java
+++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockAgentManagerImpl.java
@@ -16,29 +16,6 @@
 // under the License.
 package com.cloud.agent.manager;
 
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.PatternSyntaxException;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
-import org.apache.cloudstack.ca.SetupCertificateAnswer;
-import org.apache.cloudstack.ca.SetupCertificateCommand;
-import org.apache.cloudstack.ca.SetupKeyStoreCommand;
-import org.apache.cloudstack.ca.SetupKeystoreAnswer;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.log4j.Logger;
-import org.springframework.stereotype.Component;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckHealthCommand;
@@ -49,6 +26,7 @@
 import com.cloud.agent.api.HostStatsEntry;
 import com.cloud.agent.api.MaintainAnswer;
 import com.cloud.agent.api.PingTestCommand;
+import com.cloud.agent.api.routing.NetworkElementCommand;
 import com.cloud.api.commands.SimulatorAddSecondaryAgent;
 import com.cloud.dc.dao.HostPodDao;
 import com.cloud.exception.DiscoveryException;
@@ -73,6 +51,29 @@
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
+import org.apache.cloudstack.ca.SetupCertificateAnswer;
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
+import org.apache.cloudstack.ca.SetupKeystoreAnswer;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.diagnostics.DiagnosticsAnswer;
+import org.apache.cloudstack.diagnostics.DiagnosticsCommand;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.PatternSyntaxException;
 
 @Component
 public class MockAgentManagerImpl extends ManagerBase implements MockAgentManager {
@@ -481,6 +482,7 @@ public Answer setupCertificate(SetupCertificateCommand cmd) {
         return new SetupCertificateAnswer(true);
     }
 
+
     @Override
     public boolean start() {
         for (Discoverer discoverer : discoverers) {
@@ -520,6 +522,14 @@ public Answer checkNetworkCommand(CheckNetworkCommand cmd) {
         return new CheckNetworkAnswer(cmd, true, "Network Setup check by names is done");
     }
 
+    @Override
+    public Answer runDiagnostics(final DiagnosticsCommand cmd) {
+        final String vmInstance = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
+        final String[] args = cmd.getSrciptArguments().split(" ");
+        final String mockAnswer = String.format("%s %s executed in %s &&  && 0", args[0].toUpperCase(), args[1], vmInstance);
+        return new DiagnosticsAnswer(cmd, true, mockAnswer);
+    }
+
     public List<Discoverer> getDiscoverers() {
         return discoverers;
     }
diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java
index 729777312fb..29ad3cc4969 100644
--- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java
+++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java
@@ -26,6 +26,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.diagnostics.DiagnosticsCommand;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -293,7 +294,9 @@ public Answer simulate(final Command cmd, final String hostGuid) {
                 } else if (cmd instanceof PingTestCommand) {
                     answer = _mockAgentMgr.pingTest((PingTestCommand)cmd);
                 } else if (cmd instanceof SetupKeyStoreCommand) {
-                    answer = _mockAgentMgr.setupKeyStore((SetupKeyStoreCommand)cmd);
+                    answer = _mockAgentMgr.setupKeyStore((SetupKeyStoreCommand) cmd);
+                }else if (cmd instanceof DiagnosticsCommand) {
+                    answer = _mockAgentMgr.runDiagnostics((DiagnosticsCommand)cmd);
                 } else if (cmd instanceof SetupCertificateCommand) {
                     answer = _mockAgentMgr.setupCertificate((SetupCertificateCommand)cmd);
                 } else if (cmd instanceof PrepareForMigrationCommand) {
diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java
new file mode 100644
index 00000000000..a06b5bb1307
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java
@@ -0,0 +1,131 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.routing.NetworkElementCommand;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.google.common.base.Strings;
+import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService {
+    private static final Logger LOGGER = Logger.getLogger(DiagnosticsServiceImpl.class);
+
+    @Inject
+    private AgentManager agentManager;
+    @Inject
+    private VMInstanceDao instanceDao;
+    @Inject
+    private VirtualMachineManager vmManager;
+    @Inject
+    private NetworkOrchestrationService networkManager;
+
+    @Override
+    public Map<String, String> runDiagnosticsCommand(final RunDiagnosticsCmd cmd) {
+        final Long vmId = cmd.getId();
+        final String cmdType = cmd.getType().getValue();
+        final String ipAddress = cmd.getAddress();
+        final String optionalArguments = cmd.getOptionalArguments();
+        final VMInstanceVO vmInstance = instanceDao.findByIdTypes(vmId, VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.DomainRouter, VirtualMachine.Type.SecondaryStorageVm);
+
+        if (vmInstance == null) {
+            throw new InvalidParameterValueException("Unable to find a system vm with id " + vmId);
+        }
+        final Long hostId = vmInstance.getHostId();
+
+        if (hostId == null) {
+            throw new CloudRuntimeException("Unable to find host for virtual machine instance: " + vmInstance.getInstanceName());
+        }
+
+        final String shellCmd = prepareShellCmd(cmdType, ipAddress, optionalArguments);
+
+        if (Strings.isNullOrEmpty(shellCmd)) {
+            throw new IllegalArgumentException("Optional parameters contain unwanted characters: " + optionalArguments);
+        }
+
+        final Hypervisor.HypervisorType hypervisorType = vmInstance.getHypervisorType();
+
+        final DiagnosticsCommand command = new DiagnosticsCommand(shellCmd, vmManager.getExecuteInSequence(hypervisorType));
+        final Map<String, String> accessDetails = networkManager.getSystemVMAccessDetails(vmInstance);
+
+        if (Strings.isNullOrEmpty(accessDetails.get(NetworkElementCommand.ROUTER_IP))) {
+            throw new CloudRuntimeException("Unable to set system vm ControlIP for system vm with ID: " + vmId);
+        }
+
+        command.setAccessDetail(accessDetails);
+
+        Map<String, String> detailsMap;
+
+        final Answer answer = agentManager.easySend(hostId, command);
+
+        if (answer != null && (answer instanceof DiagnosticsAnswer)) {
+            detailsMap = ((DiagnosticsAnswer) answer).getExecutionDetails();
+            return detailsMap;
+        } else {
+            throw new CloudRuntimeException("Failed to execute diagnostics command on remote host: " + answer.getDetails());
+        }
+    }
+
+    protected boolean hasValidChars(String optionalArgs) {
+        if (Strings.isNullOrEmpty(optionalArgs)) {
+            return true;
+        } else {
+            final String regex = "^[\\w\\-\\s.]+$";
+            final Pattern pattern = Pattern.compile(regex);
+            return pattern.matcher(optionalArgs).find();
+        }
+
+    }
+
+    protected String prepareShellCmd(String cmdType, String ipAddress, String optionalParams) {
+        final String CMD_TEMPLATE = String.format("%s %s", cmdType, ipAddress);
+        if (Strings.isNullOrEmpty(optionalParams)) {
+            return CMD_TEMPLATE;
+        } else {
+            if (hasValidChars(optionalParams)) {
+                return String.format("%s %s", CMD_TEMPLATE, optionalParams);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<>();
+        cmdList.add(RunDiagnosticsCmd.class);
+        return cmdList;
+    }
+}
\ No newline at end of file
diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index c7715a866c4..2f67c4248d3 100644
--- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -298,4 +298,6 @@
     <bean id="indirectAgentLBService" class="org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl" />
 
     <bean id="directDownloadManager" class="org.apache.cloudstack.direct.download.DirectDownloadManagerImpl" />
+
+    <bean id="DiagnosticsService" class="org.apache.cloudstack.diagnostics.DiagnosticsServiceImpl" />
 </beans>
diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java
new file mode 100644
index 00000000000..d85c5434d9a
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java
@@ -0,0 +1,196 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.cloudstack.diagnostics;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.routing.NetworkElementCommand;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
+import junit.framework.TestCase;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DiagnosticsServiceImplTest extends TestCase {
+
+    @Mock
+    private AgentManager agentManager;
+    @Mock
+    private VMInstanceDao instanceDao;
+    @Mock
+    private RunDiagnosticsCmd diagnosticsCmd;
+    @Mock
+    private DiagnosticsCommand command;
+    @Mock
+    private VMInstanceVO instanceVO;
+    @Mock
+    private VirtualMachineManager vmManager;
+    @Mock
+    private NetworkOrchestrationService networkManager;
+
+    @InjectMocks
+    private DiagnosticsServiceImpl diagnosticsService = new DiagnosticsServiceImpl();
+
+    @Before
+    public void setUp() throws Exception {
+        Mockito.when(diagnosticsCmd.getId()).thenReturn(1L);
+        Mockito.when(diagnosticsCmd.getType()).thenReturn(DiagnosticsType.PING);
+        Mockito.when(instanceDao.findByIdTypes(Mockito.anyLong(), Mockito.any(VirtualMachine.Type.class),
+                Mockito.any(VirtualMachine.Type.class), Mockito.any(VirtualMachine.Type.class))).thenReturn(instanceVO);
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Mockito.reset(diagnosticsCmd);
+        Mockito.reset(agentManager);
+        Mockito.reset(instanceDao);
+        Mockito.reset(instanceVO);
+        Mockito.reset(command);
+    }
+
+    @Test
+    public void testRunDiagnosticsCommandTrue() throws Exception {
+        Mockito.when(diagnosticsCmd.getAddress()).thenReturn("8.8.8.8");
+        Map<String, String> accessDetailsMap = new HashMap<>();
+        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10");
+        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);
+        final String details = "PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=1 ttl=125 time=7.88 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=2 ttl=125 time=251 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=3 ttl=125 time=64.9 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=4 ttl=125 time=50.7 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=5 ttl=125 time=67.9 ms\n" +
+                "\n" +
+                "--- 8.8.8.8 ping statistics ---\n" +
+                "5 packets transmitted, 5 received, 0% packet loss, time 4003ms\n" +
+                "rtt min/avg/max/mdev = 7.881/88.587/251.410/84.191 ms&&\n" +
+                "&&\n" +
+                "0\n";
+
+        Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));
+
+        Map<String, String> detailsMap = diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
+
+        String stdout = "PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=1 ttl=125 time=7.88 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=2 ttl=125 time=251 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=3 ttl=125 time=64.9 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=4 ttl=125 time=50.7 ms\n" +
+                "64 bytes from 8.8.8.8: icmp_seq=5 ttl=125 time=67.9 ms\n" +
+                "\n" +
+                "--- 8.8.8.8 ping statistics ---\n" +
+                "5 packets transmitted, 5 received, 0% packet loss, time 4003ms\n" +
+                "rtt min/avg/max/mdev = 7.881/88.587/251.410/84.191 ms";
+
+        assertEquals(3, detailsMap.size());
+        assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR));
+        assertEquals("Mismatch between actual and expected EXITCODE", "0", detailsMap.get(ApiConstants.EXITCODE));
+        assertEquals("Mismatch between actual and expected STDOUT", stdout, detailsMap.get(ApiConstants.STDOUT));
+    }
+
+    @Test
+    public void testRunDiagnosticsCommandFalse() throws Exception {
+        Mockito.when(diagnosticsCmd.getAddress()).thenReturn("192.0.2.2");
+
+        Map<String, String> accessDetailsMap = new HashMap<>();
+        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10");
+        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);
+
+        String details = "PING 192.0.2.2 (192.0.2.2): 56 data bytes\n" +
+                "76 bytes from 213.130.48.253: Destination Net Unreachable\n" +
+                "--- 192.0.2.2 ping statistics ---\n" +
+                "4 packets transmitted, 0 packets received, 100% packet loss&&\n" +
+                "&&\n" +
+                "1\n";
+        String stdout = "PING 192.0.2.2 (192.0.2.2): 56 data bytes\n" +
+                "76 bytes from 213.130.48.253: Destination Net Unreachable\n" +
+                "--- 192.0.2.2 ping statistics ---\n" +
+                "4 packets transmitted, 0 packets received, 100% packet loss";
+        Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));
+
+        Map<String, String> detailsMap = diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
+
+        assertEquals(3, detailsMap.size());
+        assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR));
+        assertTrue("Mismatch between actual and expected EXITCODE", !detailsMap.get(ApiConstants.EXITCODE).equalsIgnoreCase("0"));
+        assertEquals("Mismatch between actual and expected STDOUT", stdout, detailsMap.get(ApiConstants.STDOUT));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRunDiagnosticsThrowsInvalidParamException() throws Exception {
+        Mockito.when(diagnosticsCmd.getAddress()).thenReturn("");
+        Mockito.when(instanceDao.findByIdTypes(Mockito.anyLong(), Mockito.any(VirtualMachine.Type.class),
+                Mockito.any(VirtualMachine.Type.class), Mockito.any(VirtualMachine.Type.class))).thenReturn(null);
+
+        diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVMControlIPisNull() throws Exception {
+        Mockito.when(diagnosticsCmd.getAddress()).thenReturn("0.42.42.42");
+
+        Map<String, String> accessDetailsMap = new HashMap<>();
+        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, null);
+        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);
+
+        diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
+    }
+
+    @Test
+    public void testInvalidCharsInParams() throws Exception {
+        assertFalse(diagnosticsService.hasValidChars("'\\''"));
+        assertFalse(diagnosticsService.hasValidChars("-I eth0 &"));
+        assertFalse(diagnosticsService.hasValidChars("-I eth0 ;"));
+        assertFalse(diagnosticsService.hasValidChars(" &2 > "));
+        assertFalse(diagnosticsService.hasValidChars(" &2 >> "));
+        assertFalse(diagnosticsService.hasValidChars(" | "));
+        assertFalse(diagnosticsService.hasValidChars("|"));
+        assertFalse(diagnosticsService.hasValidChars(","));
+    }
+
+    @Test
+    public void testValidCharsInParams() throws Exception {
+        assertTrue(diagnosticsService.hasValidChars(""));
+        assertTrue(diagnosticsService.hasValidChars("."));
+        assertTrue(diagnosticsService.hasValidChars(" "));
+        assertTrue(diagnosticsService.hasValidChars("-I eth0 www.google.com"));
+        assertTrue(diagnosticsService.hasValidChars(" "));
+        assertTrue(diagnosticsService.hasValidChars(" -I cloudbr0 --sport "));
+        assertTrue(diagnosticsService.hasValidChars(" --back -m20 "));
+        assertTrue(diagnosticsService.hasValidChars("-c 5 -4"));
+        assertTrue(diagnosticsService.hasValidChars("-c 5 -4 -AbDfhqUV"));
+    }
+}
\ No newline at end of file
diff --git a/systemvm/debian/opt/cloud/bin/diagnostics.py b/systemvm/debian/opt/cloud/bin/diagnostics.py
new file mode 100755
index 00000000000..477f99d9d3f
--- /dev/null
+++ b/systemvm/debian/opt/cloud/bin/diagnostics.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+import shlex
+import subprocess
+import sys
+
+
+def run_cmd(command):
+    if command is not None:
+        try:
+            p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            stdout, stderr = p.communicate()
+            return_code = p.returncode
+
+        except OSError as e:
+            stdout = "Check your command type"
+            stderr = "Exception occurred: %s" % e
+            return_code = 1
+
+        finally:
+            print('%s&&' % stdout.strip())
+            print('%s&&' % stderr.strip())
+            print('%s' % return_code)
+
+
+def get_command():
+    arguments = sys.argv
+    cmd = " ".join(arguments[1:])
+    cmd_type = sys.argv[1]
+
+    if cmd_type == 'ping':
+        if '-c' in arguments:
+            return cmd
+        else:
+            return cmd + " -c 4"
+
+    elif cmd_type == 'traceroute':
+        if '-m' in arguments:
+            return cmd
+        else:
+            return cmd + " -m 20"
+
+    elif cmd_type == 'arping':
+        if '-c' in arguments:
+            return cmd
+        else:
+            return cmd + " -c 4"
+
+    else:
+        return None
+
+
+if __name__ == "__main__":
+    command = get_command()
+    run_cmd(command)
diff --git a/test/integration/smoke/test_diagnostics.py b/test/integration/smoke/test_diagnostics.py
new file mode 100644
index 00000000000..6364d83eeee
--- /dev/null
+++ b/test/integration/smoke/test_diagnostics.py
@@ -0,0 +1,539 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+""" BVT tests for remote diagnostics of system VMs
+"""
+# Import Local Modules
+from marvin.codes import FAILED
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.cloudstackAPI import runDiagnostics
+from marvin.lib.utils import (cleanup_resources)
+from marvin.lib.base import (Account,
+                             ServiceOffering,
+                             VirtualMachine)
+from marvin.lib.common import (get_domain,
+                               get_zone,
+                               get_test_template,
+                               list_ssvms,
+                               list_routers)
+
+from nose.plugins.attrib import attr
+
+
+class TestRemoteDiagnostics(cloudstackTestCase):
+    """
+    Test remote diagnostics with system VMs and VR as root admin
+    """
+
+    @classmethod
+    def setUpClass(cls):
+
+        testClient = super(TestRemoteDiagnostics, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+
+        # Get Zone, Domain and templates
+        cls.domain = get_domain(cls.apiclient)
+        cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.services['mode'] = cls.zone.networktype
+        template = get_test_template(
+            cls.apiclient,
+            cls.zone.id,
+            cls.hypervisor
+        )
+        if template == FAILED:
+            cls.fail("get_test_template() failed to return template")
+
+        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
+
+        # Create an account, network, VM and IP addresses
+        cls.account = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            domainid=cls.domain.id
+        )
+        cls.service_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["tiny"]
+        )
+        cls.vm_1 = VirtualMachine.create(
+            cls.apiclient,
+            cls.services["virtual_machine"],
+            templateid=template.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            serviceofferingid=cls.service_offering.id
+        )
+        cls.cleanup = [
+            cls.account,
+            cls.service_offering
+        ]
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            cls.apiclient = super(
+                TestRemoteDiagnostics,
+                cls
+            ).getClsTestClient().getApiClient()
+            # Clean up, terminate the created templates
+            cleanup_resources(cls.apiclient, cls.cleanup)
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.hypervisor = self.testClient.getHypervisorInfo()
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_01_ping_in_vr_success(self):
+        '''
+        Test Ping command execution in VR
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on VR
+
+        list_router_response = list_routers(
+            self.apiclient,
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.assertEqual(
+            isinstance(list_router_response, list),
+            True,
+            "Check list response returns a valid list"
+        )
+        router = list_router_response[0]
+        self.debug('Starting the router with ID: %s' % router.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = router.id
+        cmd.ipaddress = '8.8.8.8'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Ping in VR')
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_02_ping_in_vr_failure(self):
+        '''
+        Test Ping command execution in VR
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on VR
+        # 2. Validate Ping command execution with a non-existent/pingable IP address
+
+        if self.hypervisor.lower() == 'simulator':
+            raise self.skipTest("Skipping negative test case for Simulator hypervisor")
+
+        list_router_response = list_routers(
+            self.apiclient,
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.assertEqual(
+            isinstance(list_router_response, list),
+            True,
+            "Check list response returns a valid list"
+        )
+        router = list_router_response[0]
+        self.debug('Starting the router with ID: %s' % router.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = router.id
+        cmd.ipaddress = '192.0.2.2'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertNotEqual(
+            '0',
+            cmd_response.exitcode,
+            'Check diagnostics command returns a non-zero exit code')
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_03_ping_in_ssvm_success(self):
+        '''
+        Test Ping command execution in SSVM
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on SSVM
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='secondarystoragevm',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        ssvm = list_ssvm_response[0]
+
+        self.debug('Setting up SSVM with ID %s' % ssvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = ssvm.id
+        cmd.ipaddress = '8.8.8.8'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Ping in SSVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_04_ping_in_ssvm_failure(self):
+        '''
+        Test Ping command execution in SSVM
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on SSVM
+        # 2. Validate Ping command execution with a non-existent/pingable IP address
+
+        if self.hypervisor.lower() == 'simulator':
+            raise self.skipTest("Skipping negative test case for Simulator hypervisor")
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='secondarystoragevm',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        ssvm = list_ssvm_response[0]
+
+        self.debug('Setting up SSVM with ID %s' % ssvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = ssvm.id
+        cmd.ipaddress = '192.0.2.2'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertNotEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Ping in SSVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_05_ping_in_cpvm_success(self):
+        '''
+        Test Ping command execution in CPVM
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on CPVM
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='consoleproxy',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        cpvm = list_ssvm_response[0]
+
+        self.debug('Setting up CPVM with ID %s' % cpvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = cpvm.id
+        cmd.ipaddress = '8.8.8.8'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Ping in CPVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_06_ping_in_cpvm_failure(self):
+        '''
+        Test Ping command execution in CPVM
+        '''
+
+        # Validate the following:
+        # 1. Ping command is executed remotely on CPVM
+        # 2. Validate Ping command execution with a non-existent/pingable IP address
+
+        if self.hypervisor.lower() == 'simulator':
+            raise self.skipTest("Skipping negative test case for Simulator hypervisor")
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='consoleproxy',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        cpvm = list_ssvm_response[0]
+
+        self.debug('Setting up CPVM with ID %s' % cpvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = cpvm.id
+        cmd.ipaddress = '192.0.2.2'
+        cmd.type = 'ping'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertNotEqual(
+            '0',
+            cmd_response.exitcode,
+            'Check diagnostics command returns a non-zero exit code'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_07_arping_in_vr(self):
+        '''
+        Test Arping command execution in VR
+        '''
+
+        # Validate the following:
+        # 1. Arping command is executed remotely on VR
+
+        list_router_response = list_routers(
+            self.apiclient,
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.assertEqual(
+            isinstance(list_router_response, list),
+            True,
+            "Check list response returns a valid list"
+        )
+        router = list_router_response[0]
+        self.debug('Starting the router with ID: %s' % router.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = router.id
+        cmd.ipaddress = router.gateway
+        cmd.type = 'arping'
+        cmd.params = "-I eth2"
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Arping in VR')
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_08_arping_in_ssvm(self):
+        '''
+        Test Arping command execution in SSVM
+        '''
+
+        # Validate the following:
+        # 1. Arping command is executed remotely on SSVM
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='secondarystoragevm',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        ssvm = list_ssvm_response[0]
+
+        self.debug('Setting up SSVM with ID %s' % ssvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = ssvm.id
+        cmd.ipaddress = ssvm.gateway
+        cmd.type = 'arping'
+        cmd.params = '-I eth2'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Arping in SSVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_09_arping_in_cpvm(self):
+        '''
+        Test Arping command execution in CPVM
+        '''
+
+        # Validate the following:
+        # 1. Arping command is executed remotely on CPVM
+
+        list_cpvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='secondarystoragevm',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_cpvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        cpvm = list_cpvm_response[0]
+
+        self.debug('Setting up CPVM with ID %s' % cpvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = cpvm.id
+        cmd.ipaddress = cpvm.gateway
+        cmd.type = 'arping'
+        cmd.params = '-I eth2'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Arping in CPVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_10_traceroute_in_vr(self):
+        '''
+        Test Arping command execution in VR
+        '''
+
+        # Validate the following:
+        # 1. Arping command is executed remotely on VR
+
+        list_router_response = list_routers(
+            self.apiclient,
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.assertEqual(
+            isinstance(list_router_response, list),
+            True,
+            "Check list response returns a valid list"
+        )
+        router = list_router_response[0]
+        self.debug('Starting the router with ID: %s' % router.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = router.id
+        cmd.ipaddress = '8.8.4.4'
+        cmd.type = 'traceroute'
+        cmd.params = "-m 10"
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Arping in VR')
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_11_traceroute_in_ssvm(self):
+        '''
+        Test Traceroute command execution in SSVM
+        '''
+
+        # Validate the following:
+        # 1. Traceroute command is executed remotely on SSVM
+
+        list_ssvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='secondarystoragevm',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_ssvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        ssvm = list_ssvm_response[0]
+
+        self.debug('Setting up SSVM with ID %s' % ssvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = ssvm.id
+        cmd.ipaddress = '8.8.4.4'
+        cmd.type = 'traceroute'
+        cmd.params = '-m 10'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Traceroute in SSVM'
+        )
+
+    @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true")
+    def test_12_traceroute_in_cpvm(self):
+        '''
+        Test Traceroute command execution in CPVMM
+        '''
+
+        # Validate the following:
+        # 1. Traceroute command is executed remotely on CPVM
+
+        list_cpvm_response = list_ssvms(
+            self.apiclient,
+            systemvmtype='consoleproxy',
+            state='Running',
+        )
+
+        self.assertEqual(
+            isinstance(list_cpvm_response, list),
+            True,
+            'Check list response returns a valid list'
+        )
+        cpvm = list_cpvm_response[0]
+
+        self.debug('Setting up CPVMM with ID %s' % cpvm.id)
+
+        cmd = runDiagnostics.runDiagnosticsCmd()
+        cmd.targetid = cpvm.id
+        cmd.ipaddress = '8.8.4.4'
+        cmd.type = 'traceroute'
+        cmd.params = '-m 10'
+        cmd_response = self.apiclient.runDiagnostics(cmd)
+
+        self.assertEqual(
+            '0',
+            cmd_response.exitcode,
+            'Failed to run remote Traceroute in CPVM'
+        )
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index a025efefcbc..f45f030ac40 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -190,7 +190,8 @@
     'CA': 'Certificate',
     'listElastistorInterface': 'Misc',
     'cloudian': 'Cloudian',
-    'Sioc' : 'Sioc'
+    'Sioc' : 'Sioc',
+    'Diagnostics': 'Diagnostics'
     }
 
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services