-
Notifications
You must be signed in to change notification settings - Fork 7
Remote Diagnostics API #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
0658e24
bbe380d
836bcb1
67669db
95e8e57
17dc1e1
8cf67d3
c9d4458
259a459
cd5e623
bf01218
448fb44
1e60bdc
9b9cb89
72f7023
05483de
1df8855
1ff1298
6a09386
0c45874
289a4ed
e3f2bef
7645c07
9801b9f
149f422
97e560d
50d152f
93a3cfb
5571123
6a11767
2ddb21b
11a818b
2f5767c
80058c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // 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.ExecuteDiagnosticsResponse; | ||
| 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.log4j.Logger; | ||
|
|
||
| import javax.inject.Inject; | ||
| import java.util.Map; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| @APICommand(name = ExecuteDiagnosticsCmd.APINAME, responseObject = ExecuteDiagnosticsResponse.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 ExecuteDiagnosticsCmd extends BaseCmd { | ||
| private static final Logger LOGGER = Logger.getLogger(ExecuteDiagnosticsCmd.class); | ||
| public static final String APINAME = "executeDiagnostics"; | ||
|
|
||
| @Inject | ||
| private DiagnosticsService diagnosticsService; | ||
|
|
||
| ///////////////////////////////////////////////////// | ||
| //////////////// API parameters ///////////////////// | ||
| ///////////////////////////////////////////////////// | ||
| @Parameter(name = ApiConstants.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() { | ||
| final String EMPTY_STRING = ""; | ||
|
|
||
| if (optionalArguments == null || optionalArguments.isEmpty()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do this a lot as well, but it's better to use CollectionUtils.isEmpty()
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is not much difference in terms of performance, but adopted since it provides cleaner code |
||
| return EMPTY_STRING; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return "" is fine, than declare a variable and return that.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| } | ||
| final String regex = "^[\\w\\-\\s]+$"; | ||
| final Pattern pattern = Pattern.compile(regex); | ||
| final boolean hasInvalidChar = pattern.matcher(optionalArguments).find(); | ||
| if (!hasInvalidChar) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Write +/- unit tests for this validation method.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| throw new IllegalArgumentException("Optional parameters contain unwanted characters: " + optionalArguments); | ||
| } | ||
| 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 { | ||
| ExecuteDiagnosticsResponse response = new ExecuteDiagnosticsResponse(); | ||
| try { | ||
| final Map<String, String> answerMap = diagnosticsService.runDiagnosticsCommand(this); | ||
| if (answerMap != null || !answerMap.isEmpty()) { | ||
| response.setStdout(answerMap.get("STDOUT")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| response.setStderr(answerMap.get("STDERR")); | ||
| response.setExitCode(answerMap.get("EXITCODE")); | ||
| response.setObjectName("diagnostics results"); | ||
| response.setResponseName(getCommandName()); | ||
| this.setResponseObject(response); | ||
| } | ||
| } catch (ServerApiException e) { | ||
| LOGGER.error("Exception occurred while executing remote diagnostics command: ", e); | ||
| throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and wrapping a server exception with only INTERNAL_ERROR as message doesn't add much value. |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // | ||
| // 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.BaseResponse; | ||
| import org.apache.cloudstack.api.EntityReference; | ||
|
|
||
| @EntityReference(value = VirtualMachine.class) | ||
| public class ExecuteDiagnosticsResponse extends BaseResponse { | ||
| @SerializedName("STDOUT") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lowercase the api response keys to stdout, stderr, exitcode. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use api constant class for them, perhaps use the same keys in the class where the details map is created.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| @Param(description = "the standard output from the command execution") | ||
| private String stdout; | ||
|
|
||
| @SerializedName("STDERR") | ||
| @Param(description = "the standard error output from the command execution") | ||
| private String stderr; | ||
|
|
||
| @SerializedName("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; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // | ||
| // 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.exception.AgentUnavailableException; | ||
| import com.cloud.exception.InvalidParameterValueException; | ||
| import org.apache.cloudstack.api.command.admin.diagnostics.ExecuteDiagnosticsCmd; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| public interface DiagnosticsService { | ||
|
|
||
| Map<String, String> runDiagnosticsCommand(ExecuteDiagnosticsCmd cmd) throws AgentUnavailableException, InvalidParameterValueException; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,4 +69,6 @@ public class VRScripts { | |
|
|
||
| public static final String VR_CFG = "vr_cfg.sh"; | ||
|
|
||
| // New script for use with Remote Diagnostics API | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove comment.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| public static final String DIAGNOSTICS = "diagnostics.py"; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,29 +19,6 @@ | |
|
|
||
| package com.cloud.agent.resource.virtualnetwork; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.InetSocketAddress; | ||
| import java.nio.channels.SocketChannel; | ||
| import org.joda.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Queue; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.LinkedBlockingQueue; | ||
| import java.util.concurrent.locks.Lock; | ||
| import java.util.concurrent.locks.ReentrantLock; | ||
|
|
||
| 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.utils.security.KeyStoreUtils; | ||
| import org.apache.log4j.Logger; | ||
|
|
||
| import com.cloud.agent.api.Answer; | ||
| import com.cloud.agent.api.CheckRouterAnswer; | ||
| import com.cloud.agent.api.CheckRouterCommand; | ||
|
|
@@ -59,6 +36,29 @@ | |
| import com.cloud.utils.ExecutionResult; | ||
| import com.cloud.utils.NumbersUtil; | ||
| import com.cloud.utils.exception.CloudRuntimeException; | ||
| 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.diagnostics.DiagnosticsAnswer; | ||
| import org.apache.cloudstack.diagnostics.DiagnosticsCommand; | ||
| import org.apache.cloudstack.utils.security.KeyStoreUtils; | ||
| import org.apache.log4j.Logger; | ||
| import org.joda.time.Duration; | ||
|
|
||
| import javax.naming.ConfigurationException; | ||
| import java.io.IOException; | ||
| import java.net.InetSocketAddress; | ||
| import java.nio.channels.SocketChannel; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Queue; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.LinkedBlockingQueue; | ||
| import java.util.concurrent.locks.Lock; | ||
| import java.util.concurrent.locks.ReentrantLock; | ||
|
|
||
| /** | ||
| * VirtualNetworkResource controls and configures virtual networking | ||
|
|
@@ -185,13 +185,15 @@ private Answer execute(final SetupCertificateCommand cmd) { | |
|
|
||
| private Answer executeQueryCommand(NetworkElementCommand cmd) { | ||
| if (cmd instanceof CheckRouterCommand) { | ||
| return execute((CheckRouterCommand)cmd); | ||
| return execute((CheckRouterCommand) cmd); | ||
| } else if (cmd instanceof GetDomRVersionCmd) { | ||
| return execute((GetDomRVersionCmd)cmd); | ||
| return execute((GetDomRVersionCmd) cmd); | ||
| } else if (cmd instanceof CheckS2SVpnConnectionsCommand) { | ||
| return execute((CheckS2SVpnConnectionsCommand) cmd); | ||
| } else if (cmd instanceof GetRouterAlertsCommand) { | ||
| return execute((GetRouterAlertsCommand)cmd); | ||
| 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 +294,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)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add space in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved |
||
| final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.DIAGNOSTICS, cmd.getSrciptArguments(), _eachTimeout); | ||
| if (!result.isSuccess()) { | ||
| return new DiagnosticsAnswer(cmd, result.isSuccess(), "Diagnostics Command Execution failed: " + 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()) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you consider renaming this to
runDiagnostics. Ignore if already discussed with Paul and others.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, might be a bit late. It would require redoing and testing a lot of things i.e unit/marvin test cases etc.