diff --git a/.travis.yml b/.travis.yml index 2d323244f86..130e907c5cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,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 @@ -47,6 +47,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 class ApiConstants { 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 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 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 class VRScripts { 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 @@ package com.cloud.agent.resource.virtualnetwork; 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 @@ public class VirtualRoutingResource { } 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 @@ public class VirtualRoutingResource { 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 @@ public class VirtualRoutingResource { _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 getExecutionDetails() { + final Map 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.agent.api.PingTestCommand; 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 @@ public interface MockAgentManager extends Manager { 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.GetHostStatsCommand; 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.DB; 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 class MockAgentManagerImpl extends ManagerBase implements MockAgentManage return new SetupCertificateAnswer(true); } + @Override public boolean start() { for (Discoverer discoverer : discoverers) { @@ -520,6 +522,14 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage 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 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 java.util.Map; 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 class SimulatorManagerImpl extends ManagerBase implements SimulatorManage } 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 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 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 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> getCommands() { + List> 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 @@ + + 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 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 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 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 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 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 @@ known_categories = { 'CA': 'Certificate', 'listElastistorInterface': 'Misc', 'cloudian': 'Cloudian', - 'Sioc' : 'Sioc' + 'Sioc' : 'Sioc', + 'Diagnostics': 'Diagnostics' }