From 07a22304de82e67e27dba18893b9ee2be60bccf8 Mon Sep 17 00:00:00 2001 From: Dingane Hlaluku Date: Thu, 29 Nov 2018 11:45:10 +0200 Subject: [PATCH 01/25] * Complete API implementation * Complete UI integration * Complete marvin test * Complete Secondary storage GC background task --- .../cloud/agent/api/to/DataObjectType.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../diagnostics/GetDiagnosticsDataCmd.java | 156 +++++++ .../GetDiagnosticsDataResponse.java | 40 ++ .../diagnostics/DiagnosticsService.java | 6 +- .../resource/virtualnetwork/VRScripts.java | 2 + .../VirtualRoutingResource.java | 27 +- .../CopyToSecondaryStorageAnswer.java | 26 ++ .../CopyToSecondaryStorageCommand.java | 53 +++ .../diagnostics/DeleteFileInVrCommand.java | 36 ++ .../diagnostics/PrepareFilesAnswer.java | 27 ++ .../diagnostics/PrepareFilesCommand.java | 44 ++ .../resource/LibvirtComputingResource.java | 1 + .../LibvirtCopyToSecondaryStorageWrapper.java | 87 ++++ .../resource/CitrixResourceBase.java | 77 ++++ ...CoppyToSecondaryStorageCommandWrapper.java | 43 ++ .../java/com/cloud/server/StatsCollector.java | 14 + .../diagnostics/DiagnosticsHelper.java | 82 ++++ .../diagnostics/DiagnosticsServiceImpl.java | 404 +++++++++++++++++- .../ConsoleProxyDiagnosticFiles.java | 47 ++ .../fileprocessor/DiagnosticsFilesList.java | 50 +++ .../DiagnosticsFilesListFactory.java | 38 ++ .../DomainRouterDiagnosticsFiles.java | 48 +++ .../SecondaryStorageVmDiagnosticsFiles.java | 47 ++ .../diagnostics/to/DiagnosticsDataObject.java | 97 +++++ .../diagnostics/to/DiagnosticsDataTO.java | 60 +++ .../cloudstack/storage/NfsMountManager.java | 23 + .../storage/NfsMountManagerImpl.java | 203 +++++++++ .../spring-server-core-managers-context.xml | 8 + .../DiagnosticsFilesListFactoryTest.java | 83 ++++ .../DiagnosticsServiceImplTest.java | 92 ++-- systemvm/debian/opt/cloud/bin/cleanup.sh | 28 ++ .../opt/cloud/bin/get_diagnostics_files.py | 122 ++++++ test/integration/smoke/test_diagnostics.py | 203 ++++++++- ui/css/cloudstack3.css | 8 + ui/l10n/en.js | 3 + ui/scripts/system.js | 202 +++++++++ .../java/com/cloud/utils/ssh/SshHelper.java | 24 ++ 38 files changed, 2453 insertions(+), 61 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/diagnostics/GetDiagnosticsDataResponse.java create mode 100644 core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageAnswer.java create mode 100644 core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageCommand.java create mode 100644 core/src/main/java/org/apache/cloudstack/diagnostics/DeleteFileInVrCommand.java create mode 100644 core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesAnswer.java create mode 100644 core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesCommand.java create mode 100644 plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java create mode 100644 plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCoppyToSecondaryStorageCommandWrapper.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java create mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java create mode 100644 server/src/main/java/org/apache/cloudstack/storage/NfsMountManager.java create mode 100644 server/src/main/java/org/apache/cloudstack/storage/NfsMountManagerImpl.java create mode 100644 server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java create mode 100755 systemvm/debian/opt/cloud/bin/cleanup.sh create mode 100755 systemvm/debian/opt/cloud/bin/get_diagnostics_files.py diff --git a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java index 9addd7116ece..120f1875e4a8 100644 --- a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java +++ b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java @@ -19,5 +19,5 @@ package com.cloud.agent.api.to; public enum DataObjectType { - VOLUME, SNAPSHOT, TEMPLATE + VOLUME, SNAPSHOT, TEMPLATE, TAR } 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 e810760314cd..44c53f690fb8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -742,6 +742,7 @@ public class ApiConstants { public static final String STDERR = "stderr"; public static final String EXITCODE = "exitcode"; public static final String TARGET_ID = "targetid"; + public static final String FILES = "files"; public static final String VOLUME_IDS = "volumeids"; public enum HostDetails { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java new file mode 100644 index 000000000000..6f4e9c78f3e2 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java @@ -0,0 +1,156 @@ +// 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 java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiCommandJobType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SystemVmResponse; +import org.apache.cloudstack.api.response.diagnostics.GetDiagnosticsDataResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.diagnostics.DiagnosticsService; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.validator.routines.UrlValidator; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; + +@APICommand(name = GetDiagnosticsDataCmd.APINAME, + responseObject = GetDiagnosticsDataResponse.class, + entityType = {VirtualMachine.class}, + responseHasSensitiveInfo = false, + requestHasSensitiveInfo = false, + description = "Get diagnostics data files from system VMs", + since = "4.12.0.0", + authorized = {RoleType.Admin}) +public class GetDiagnosticsDataCmd extends BaseAsyncCmd { + private static final Logger LOGGER = Logger.getLogger(GetDiagnosticsDataCmd.class); + public static final String APINAME = "getDiagnosticsData"; + @Inject + private DiagnosticsService diagnosticsService; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.TARGET_ID, + type = BaseCmd.CommandType.UUID, + entityType = SystemVmResponse.class, + required = true, + validations = {ApiArgValidator.PositiveNumber}, + description = "The ID of the system VM instance to retrieve diagnostics data files from") + private Long id; + + @Parameter(name = ApiConstants.FILES, + type = BaseCmd.CommandType.LIST, + collectionType = BaseCmd.CommandType.STRING, + description = "A comma separated list of diagnostics data files to be retrieved. Defaults are taken from global settings if none has been provided.") + private List filesList; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + public Long getId() { + return id; + } + + public List getFilesList() { + return filesList; + } + + ///////////////////////////////////////////////////// + /////////////////// 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 { + try { + String downloadUrl = diagnosticsService.getDiagnosticsDataCommand(this); + UrlValidator urlValidator = new UrlValidator(); + if (StringUtils.isEmpty(downloadUrl)) { + throw new CloudRuntimeException("Failed to retrieve diagnostics files"); + } + GetDiagnosticsDataResponse response = new GetDiagnosticsDataResponse(); + if (urlValidator.isValid(downloadUrl)){ + response.setUrl(downloadUrl); + response.setObjectName("diagnostics"); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new CloudRuntimeException("failed to generate valid download url: " + downloadUrl); + } + } catch (ServerApiException e) { + throw new CloudRuntimeException("Internal exception caught while retrieving diagnostics files: ", e); + } + } + + @Override + public String getEventType() { + VirtualMachine.Type vmType = _entityMgr.findById(VirtualMachine.class, getId()).getType(); + String eventType = ""; + switch (vmType) { + case ConsoleProxy: + eventType = EventTypes.EVENT_PROXY_DIAGNOSTICS; + break; + case SecondaryStorageVm: + eventType = EventTypes.EVENT_SSVM_DIAGNOSTICS; + break; + case DomainRouter: + eventType = EventTypes.EVENT_ROUTER_DIAGNOSTICS; + break; + } + return eventType; + } + + @Override + public String getEventDescription() { + return "Getting diagnostics data files from system vm: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + } + + @Override + public ApiCommandJobType getInstanceType() { + return ApiCommandJobType.SystemVm; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/diagnostics/GetDiagnosticsDataResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/diagnostics/GetDiagnosticsDataResponse.java new file mode 100644 index 000000000000..4d6e674b5b3f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/diagnostics/GetDiagnosticsDataResponse.java @@ -0,0 +1,40 @@ +// 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.diagnostics; + +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 GetDiagnosticsDataResponse extends BaseResponse { + @SerializedName(ApiConstants.URL) + @Param(description = "Storage URL to download retrieve diagnostics data files") + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java index a9177af7e0c9..0eae41ddb0fe 100644 --- a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java +++ b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java @@ -18,12 +18,14 @@ // package org.apache.cloudstack.diagnostics; -import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd; - import java.util.Map; +import org.apache.cloudstack.api.command.admin.diagnostics.GetDiagnosticsDataCmd; +import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd; + public interface DiagnosticsService { Map runDiagnosticsCommand(RunDiagnosticsCmd cmd); + String getDiagnosticsDataCommand(GetDiagnosticsDataCmd getDiagnosticsDataCmd); } \ 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 2c75a78b1a38..b9d6487de561 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 @@ -70,4 +70,6 @@ public class VRScripts { public static final String VR_CFG = "vr_cfg.sh"; public static final String DIAGNOSTICS = "diagnostics.py"; + public static final String RETRIEVE_DIAGNOSTICS = "get_diagnostics_files.py"; + public static final String VR_FILE_CLEANUP = "cleanup.sh"; } \ 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 21372a17f8d4..191a62263f36 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 @@ -23,8 +23,11 @@ import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; +import org.apache.cloudstack.diagnostics.DeleteFileInVrCommand; import org.apache.cloudstack.diagnostics.DiagnosticsAnswer; import org.apache.cloudstack.diagnostics.DiagnosticsCommand; +import org.apache.cloudstack.diagnostics.PrepareFilesAnswer; +import org.apache.cloudstack.diagnostics.PrepareFilesCommand; import org.joda.time.Duration; import java.util.ArrayList; import java.util.HashMap; @@ -196,7 +199,11 @@ private Answer executeQueryCommand(NetworkElementCommand cmd) { } else if (cmd instanceof GetRouterAlertsCommand) { return execute((GetRouterAlertsCommand)cmd); } else if (cmd instanceof DiagnosticsCommand) { - return execute((DiagnosticsCommand)cmd); + return execute((DiagnosticsCommand) cmd); + } else if (cmd instanceof PrepareFilesCommand) { + return execute((PrepareFilesCommand) cmd); + } else if (cmd instanceof DeleteFileInVrCommand) { + return execute((DeleteFileInVrCommand)cmd); } else { s_logger.error("Unknown query command in VirtualRoutingResource!"); return Answer.createUnsupportedCommandAnswer(cmd); @@ -306,6 +313,24 @@ private Answer execute(DiagnosticsCommand cmd) { return new DiagnosticsAnswer(cmd, result.isSuccess(), result.getDetails()); } + private Answer execute(PrepareFilesCommand cmd) { + String fileList = String.join(" ", cmd.getFilesToRetrieveList()); + _eachTimeout = Duration.standardSeconds(cmd.getTimeout()); + final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.RETRIEVE_DIAGNOSTICS, fileList, _eachTimeout); + if (result.isSuccess()) { + return new PrepareFilesAnswer(cmd, true, result.getDetails()); + } + return new PrepareFilesAnswer(cmd, false, result.getDetails()); + } + + private Answer execute(DeleteFileInVrCommand cmd) { + ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.VR_FILE_CLEANUP, cmd.getFileName()); + if (result.isSuccess()) { + return new Answer(cmd, result.isSuccess(), result.getDetails()); + } + return new Answer(cmd, result.isSuccess(), result.getDetails()); + } + private Answer execute(GetDomRVersionCmd cmd) { final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.VERSION, null); if (!result.isSuccess()) { diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageAnswer.java b/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageAnswer.java new file mode 100644 index 000000000000..044eccbbc979 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageAnswer.java @@ -0,0 +1,26 @@ +// 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; + +public class CopyToSecondaryStorageAnswer extends Answer { + + public CopyToSecondaryStorageAnswer(CopyToSecondaryStorageCommand command, boolean success, String details) { + super(command, success, details); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageCommand.java b/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageCommand.java new file mode 100644 index 000000000000..8e76aad580f7 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/diagnostics/CopyToSecondaryStorageCommand.java @@ -0,0 +1,53 @@ +// 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.storage.command.StorageSubSystemCommand; + +public class CopyToSecondaryStorageCommand extends StorageSubSystemCommand { + private String secondaryStorageUrl; + private String systemVmIp; + private String fileName; + + public CopyToSecondaryStorageCommand(String secondaryStorageUrl, String systemVmIp, String fileName) { + this.secondaryStorageUrl = secondaryStorageUrl; + this.systemVmIp = systemVmIp; + this.fileName = fileName; + } + + public String getSecondaryStorageUrl() { + return secondaryStorageUrl; + } + + public String getSystemVmIp() { + return systemVmIp; + } + + public String getFileName() { + return fileName; + } + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public void setExecuteInSequence(boolean inSeq) { + + } +} diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/DeleteFileInVrCommand.java b/core/src/main/java/org/apache/cloudstack/diagnostics/DeleteFileInVrCommand.java new file mode 100644 index 000000000000..025168b6f093 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/diagnostics/DeleteFileInVrCommand.java @@ -0,0 +1,36 @@ +// 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 DeleteFileInVrCommand extends NetworkElementCommand { + private String fileName; + + public DeleteFileInVrCommand(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } + + @Override + public boolean isQuery() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesAnswer.java b/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesAnswer.java new file mode 100644 index 000000000000..784a84aa8aeb --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesAnswer.java @@ -0,0 +1,27 @@ +// 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; + +public class PrepareFilesAnswer extends Answer { + + public PrepareFilesAnswer(PrepareFilesCommand command, boolean success, String details) { + super(command, success, details); + } + +} diff --git a/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesCommand.java b/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesCommand.java new file mode 100644 index 000000000000..db65544f948e --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/diagnostics/PrepareFilesCommand.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 java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; + +public class PrepareFilesCommand extends NetworkElementCommand { + private List filesToRetrieveList; + private long timeout; + + public PrepareFilesCommand(List filesToRetrieve, long timeout) { + this.filesToRetrieveList = filesToRetrieve; + this.timeout = timeout; + } + + public List getFilesToRetrieveList() { + return filesToRetrieveList; + } + + public long getTimeout() { + return timeout; + } + + @Override + public boolean isQuery() { + return true; + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 8ee318fa8af4..76db243ee6b7 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -226,6 +226,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv public static final String SSHKEYSPATH = "/root/.ssh"; public static final String SSHPRVKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.cloud"; public static final String SSHPUBKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.pub.cloud"; + public static final String DEFAULTDOMRSSHPORT = "3922"; public static final String BASH_SCRIPT_PATH = "/bin/bash"; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java new file mode 100644 index 000000000000..a4e732f9613f --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java @@ -0,0 +1,87 @@ +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; +import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; +import org.apache.cloudstack.diagnostics.DiagnosticsHelper; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.ssh.SshHelper; + +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; + +@ResourceWrapper(handles = CopyToSecondaryStorageCommand.class) +public class LibvirtCopyToSecondaryStorageWrapper extends CommandWrapper { + public static final Logger LOGGER = Logger.getLogger(LibvirtCopyToSecondaryStorageWrapper.class); + + @Override + public Answer execute(CopyToSecondaryStorageCommand command, LibvirtComputingResource libvirtResource) { + + String diagnosticsZipFile = command.getFileName(); + String vmSshIp = command.getSystemVmIp(); + String secondaryStorageUrl = command.getSecondaryStorageUrl(); + + KVMStoragePoolManager storagePoolMgr = libvirtResource.getStoragePoolMgr(); + KVMStoragePool secondaryPool; + + boolean success; + + secondaryPool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl); + String mountPoint = secondaryPool.getLocalPath(); + + // /mnt/SecStorage/uuid/diagnostics_data + String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DiagnosticsHelper.DIAGNOSTICS_DATA_DIR); + try { + File dataDirectory = new File(dataDirectoryInSecondaryStore); + boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); + + // Modify directory file permissions + Path path = Paths.get(dataDirectory.getAbsolutePath()); + setDirFilePermissions(path); + if (existsInSecondaryStore) { + LOGGER.info(String.format("Copying %s from %s to secondary store %s", diagnosticsZipFile, vmSshIp, secondaryStorageUrl)); + int port = Integer.valueOf(LibvirtComputingResource.DEFAULTDOMRSSHPORT); + File permKey = new File(LibvirtComputingResource.SSHPRVKEYPATH); + SshHelper.scpFrom(vmSshIp, port, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsZipFile); + } + // Verify File copy to Secondary Storage + File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsZipFile.replace("/root", "")); + if (fileInSecondaryStore.exists()) { + return new CopyToSecondaryStorageAnswer(command, true, "File copied to secondary storage successfully"); + } else { + return new CopyToSecondaryStorageAnswer(command, false, "Zip file " + diagnosticsZipFile.replace("/root/", "") + "not found in secondary storage"); + } + + } catch (Exception e) { + return new CopyToSecondaryStorageAnswer(command, false, e.getMessage()); + } finally { + // unmount secondary storage from hypervisor host + secondaryPool.delete(); + } + } +} diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index 4117892b59c2..f50a1511361f 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.hypervisor.xenserver.resource; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -29,6 +31,8 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -49,6 +53,9 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; +import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; +import org.apache.cloudstack.diagnostics.DiagnosticsHelper; import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; @@ -5612,4 +5619,74 @@ public boolean attachConfigDriveToMigratedVm(Connection conn, String vmName, Str } + /** + * Get Diagnostics Data API + * Copy zip file from system vm and copy file directly to secondary storage + */ + public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecondaryStorageCommand cmd) { + String secondaryStorageUrl = cmd.getSecondaryStorageUrl(); + String vmIP = cmd.getSystemVmIp(); + String diagnosticsZipFile = cmd.getFileName(); + + String localDir = null; + boolean success; + + // Mount Secondary storage + String secondaryStorageMountPath = null; + try { + URI uri = new URI(secondaryStorageUrl); + secondaryStorageMountPath = uri.getHost() + ":" + uri.getPath(); + localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(secondaryStorageMountPath.getBytes()); + String mountPoint = mountNfs(conn, secondaryStorageMountPath, localDir); + if (org.apache.commons.lang.StringUtils.isBlank(mountPoint)) { + return new CopyToSecondaryStorageAnswer(cmd, false, "Could not mount secondary storage " + secondaryStorageMountPath + " on host " + localDir); + } + + String dataDirectoryInSecondaryStore = localDir + "/" + DiagnosticsHelper.DIAGNOSTICS_DATA_DIR; + File dataDirectory = new File(dataDirectoryInSecondaryStore); + boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); + + // Modify directory file permissions + Path path = Paths.get(dataDirectory.getAbsolutePath()); + setDirFilePermissions(path); + + if (existsInSecondaryStore) { + int port = 3922; + File permKey = new File("/root/.ssh/id_rsa.cloud"); + SshHelper.scpFrom(vmIP, port, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsZipFile); + } + + File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsZipFile.replace("/root", "")); + if (fileInSecondaryStore.exists()) { + return new CopyToSecondaryStorageAnswer(cmd, true, "File copied to secondary storage successfully."); + } else { + return new CopyToSecondaryStorageAnswer(cmd, false, "Zip file " + diagnosticsZipFile.replace("/root/", "") + "not found in secondary storage"); + } + } catch (Exception e) { + String msg = String.format("Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl, e); + s_logger.error(msg); + return new CopyToSecondaryStorageAnswer(cmd, false, msg); + } finally { + if (localDir != null) umountNfs(conn, secondaryStorageMountPath, localDir); + } + } + + private String mountNfs(Connection conn, String remoteDir, String localDir) { + if (localDir == null) { + localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); + } + return callHostPlugin(conn, "cloud-plugin-storage", "mountNfsSecondaryStorage", "localDir", localDir, "remoteDir", remoteDir); + } + + // Unmount secondary storage from host + private void umountNfs(Connection conn, String remoteDir, String localDir) { + if (localDir == null) { + localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); + } + String result = callHostPlugin(conn, "cloud-plugin-storage", "umountNfsSecondaryStorage", "localDir", localDir, "remoteDir", remoteDir); + if (org.apache.commons.lang.StringUtils.isBlank(result)) { + String errMsg = "Could not umount secondary storage " + remoteDir + " on host " + localDir; + s_logger.warn(errMsg); + } + } } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCoppyToSecondaryStorageCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCoppyToSecondaryStorageCommandWrapper.java new file mode 100644 index 000000000000..cacab0f75a69 --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCoppyToSecondaryStorageCommandWrapper.java @@ -0,0 +1,43 @@ +// +// 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 com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; + +import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.xensource.xenapi.Connection; + + +@ResourceWrapper(handles = CopyToSecondaryStorageCommand.class) +public class CitrixCoppyToSecondaryStorageCommandWrapper extends CommandWrapper { + public static final Logger LOGGER = Logger.getLogger(CitrixCoppyToSecondaryStorageCommandWrapper.class); + + @Override + public Answer execute(CopyToSecondaryStorageCommand cmd, CitrixResourceBase citrixResourceBase) { + final Connection conn = citrixResourceBase.getConnection(); + String msg = String.format("Copying diagnostics zip file %s from system vm %s to secondary storage %s", cmd.getFileName(), cmd.getSystemVmIp(), cmd.getSecondaryStorageUrl()); + LOGGER.debug(msg); + // Allow the hypervisor host to copy file from system VM to mounted secondary storage + return citrixResourceBase.copyDiagnosticsFileToSecondaryStorage(conn, cmd); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index ac1ae952caca..b2f244229e32 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -1377,6 +1377,20 @@ public long imageStoreCurrentFreeCapacity(DataStore imageStore) { return imageStoreStats != null ? Math.max(0, imageStoreStats.getCapacityBytes() - imageStoreStats.getByteUsed()) : 0; } + /** + * Calculates secondary storage disk capacity against a configurable threshold instead of the hardcoded default 95 % value + * @param imageStore secondary storage + * @param storeCapThreshold the threshold capacity for computing if secondary storage has enough space to accommodate the @this object + * @return + */ + public boolean imageStoreHasEnoughCapacity(DataStore imageStore, Double storeCapThreshold) { + StorageStats imageStoreStats = _storageStats.get(imageStore.getId()); + if (imageStoreStats != null && (imageStoreStats.getByteUsed() / (imageStoreStats.getCapacityBytes() * 1.0)) <= storeCapThreshold) { + return true; + } + return false; + } + /** * Sends VMs metrics to the configured graphite host. */ diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java new file mode 100644 index 000000000000..b7295ba909d7 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java @@ -0,0 +1,82 @@ +// +// 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 java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.utils.script.Script2; + +public class DiagnosticsHelper { + private static final Logger LOGGER = Logger.getLogger(DiagnosticsHelper.class); + + public static final String DIAGNOSTICS_DATA_DIR = "diagnostics_data"; + + public static void setDirFilePermissions(Path path) throws java.io.IOException { + Set perms = Files.readAttributes(path, PosixFileAttributes.class).permissions(); + perms.add(PosixFilePermission.OWNER_WRITE); + perms.add(PosixFilePermission.OWNER_READ); + perms.add(PosixFilePermission.OWNER_EXECUTE); + perms.add(PosixFilePermission.GROUP_WRITE); + perms.add(PosixFilePermission.GROUP_READ); + perms.add(PosixFilePermission.GROUP_EXECUTE); + perms.add(PosixFilePermission.OTHERS_WRITE); + perms.add(PosixFilePermission.OTHERS_READ); + perms.add(PosixFilePermission.OTHERS_EXECUTE); + Files.setPosixFilePermissions(path, perms); + } + + public static void umountSecondaryStorage(String mountPoint) { + if (StringUtils.isNotBlank(mountPoint)) { + Script2 umountCmd = new Script2("/bin/bash", LOGGER); + umountCmd.add("-c"); + String cmdLine = String.format("umount %s", mountPoint); + umountCmd.add(cmdLine); + umountCmd.execute(); + } + } + + public static Long getFileCreationTime(File file) throws IOException { + Path p = Paths.get(file.getAbsolutePath()); + BasicFileAttributes view = Files.getFileAttributeView(p, BasicFileAttributeView.class).readAttributes(); + FileTime fileTime = view.creationTime(); + return fileTime.toMillis(); + } + + public static Long getTimeDifference(File f) { + Long fileCreationTime = null; + try { + fileCreationTime = getFileCreationTime(f); + } catch (IOException e) { + LOGGER.error("File not found: " + e); + } + return (fileCreationTime != null) ? (System.currentTimeMillis() - fileCreationTime) / 1000 : 1L; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 21bb0a1889e5..fe7f18a758f2 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -17,33 +17,71 @@ // under the License. package org.apache.cloudstack.diagnostics; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.admin.diagnostics.GetDiagnosticsDataCmd; +import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd; +import org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList; +import org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesListFactory; +import org.apache.cloudstack.diagnostics.to.DiagnosticsDataObject; +import org.apache.cloudstack.diagnostics.to.DiagnosticsDataTO; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.poll.BackgroundPollManager; +import org.apache.cloudstack.poll.BackgroundPollTask; +import org.apache.cloudstack.storage.NfsMountManager; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.DataTO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.hypervisor.Hypervisor; +import com.cloud.server.StatsCollector; +import com.cloud.storage.ImageStoreDetailsUtil; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SshHelper; 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; -public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService { +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.getTimeDifference; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.umountSecondaryStorage; +import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.CpvmDefaultSupportedFiles; +import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.SsvmDefaultSupportedFiles; +import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.VrDefaultSupportedFiles; + +public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService, Configurable { private static final Logger LOGGER = Logger.getLogger(DiagnosticsServiceImpl.class); @Inject @@ -54,6 +92,34 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ private VirtualMachineManager vmManager; @Inject private NetworkOrchestrationService networkManager; + @Inject + private StatsCollector statsCollector; + @Inject + private DataStoreManager storeMgr; + @Inject + private BackgroundPollManager backgroundPollManager; + @Inject + private ImageStoreDetailsUtil imageStoreDetailsUtil; + @Inject + private NfsMountManager mountManager; + @Inject + private DataCenterDao dataCenterDao; + + // This 2 settings should require a restart of the management server? + private static final ConfigKey EnableGarbageCollector = new ConfigKey<>("Advanced", Boolean.class, + "diagnostics.data.gc.enable", "true", "enable the diagnostics data files garbage collector", true); + private static final ConfigKey GarbageCollectionInterval = new ConfigKey<>("Advanced", Integer.class, + "diagnostics.data.gc.interval", "86400", "garbage collection interval in seconds", true); + + // These are easily computed properties and need not need a restart of the management server + private static final ConfigKey DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class, + "diagnostics.data.retrieval.timeout", "3600", "overall data retrieval timeout in seconds", true); + private static final ConfigKey MaximumFileAgeforGarbageCollection = new ConfigKey<>("Advanced", Long.class, + "diagnostics.data.max.file.age", "86400", "maximum file age for garbage collection in seconds", true); + private static final ConfigKey DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class, + "diagnostics.data.disable.threshold", "0.95", "Minimum disk space percentage to initiate diagnostics file retrieval", true); + + private final static String DIAGNOSTICS_DATA_DIRECTORY = "diagnostics_data"; @Override @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "running diagnostics on system vm", async = true) @@ -92,13 +158,13 @@ public Map runDiagnosticsCommand(final RunDiagnosticsCmd cmd) { Map detailsMap; - final Answer answer = agentManager.easySend(hostId, command); + Answer answer = agentManager.easySend(hostId, command); - if (answer != null && (answer instanceof DiagnosticsAnswer)) { + if (answer != null) { detailsMap = ((DiagnosticsAnswer) answer).getExecutionDetails(); return detailsMap; } else { - throw new CloudRuntimeException("Failed to execute diagnostics command on remote host: " + answer.getDetails()); + throw new CloudRuntimeException("Failed to execute diagnostics command on remote host: " + vmInstance.getHostName()); } } @@ -110,7 +176,6 @@ protected boolean hasValidChars(String optionalArgs) { final Pattern pattern = Pattern.compile(regex); return pattern.matcher(optionalArgs).find(); } - } protected String prepareShellCmd(String cmdType, String ipAddress, String optionalParams) { @@ -126,10 +191,333 @@ protected String prepareShellCmd(String cmdType, String ipAddress, String option } } + @Override + @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "getting diagnostics files on system vm", async = true) + public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { + Long vmId = cmd.getId(); + List optionalFilesList = cmd.getFilesList(); + VMInstanceVO vmInstance = getSystemVMInstance(vmId); + long zoneId = vmInstance.getDataCenterId(); + + List fileList = getFileListToBeRetrieved(optionalFilesList, vmInstance); + + if (CollectionUtils.isEmpty(fileList)) { + throw new CloudRuntimeException("Failed to generate diagnostics file list for retrieval."); + } + + Long vmHostId = vmInstance.getHostId(); + + // Find Secondary Storage with enough Disk Quota in the current Zone + final DataStore store = getImageStore(vmInstance.getDataCenterId()); + + Answer zipFilesAnswer = zipDiagnosticsFilesInSystemVm(vmInstance, fileList); + + if (zipFilesAnswer == null) { + throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in VM %s", vmInstance.getUuid())); + } + + if (!zipFilesAnswer.getResult()) { + throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file file in VM %s due to %s", vmInstance.getUuid(), zipFilesAnswer.getDetails())); + } + + // Copy zip file from system VM to secondary storage + String zipFileInSystemVm = zipFilesAnswer.getDetails().replace("\n", ""); + Pair copyToSecondaryStorageResults = copyZipFileToSecondaryStorage(vmInstance, vmHostId, zipFileInSystemVm, store); + + // Send cleanup zip file cleanup command to system VM + Answer fileCleanupAnswer = deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); + if (fileCleanupAnswer == null) { + LOGGER.error(String.format("Failed to cleanup diagnostics zip file on vm: %s", vmInstance.getUuid())); + } else { + if (!fileCleanupAnswer.getResult()) { + LOGGER.error(String.format("Zip file cleanup for vm %s has failed with: %s", vmInstance.getUuid(), fileCleanupAnswer.getDetails())); + } + } + + if (!copyToSecondaryStorageResults.first()) { + throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to %s.", zipFileInSystemVm, store.getUri(), copyToSecondaryStorageResults.second())); + } + + // Now we need to create the file download URL + // Find ssvm of store + VMInstanceVO ssvm = getSecondaryStorageVmInZone(zoneId); + if (ssvm == null) { + throw new CloudRuntimeException("No ssvm found in Zone with ID: " + zoneId); + } + // Secondary Storage install path = "diagnostics_data/diagnostics_files_xxxx.tar + String installPath = DIAGNOSTICS_DATA_DIRECTORY + "/" + zipFileInSystemVm.replace("/root", ""); + return createFileDownloadUrl(store, ssvm.getHypervisorType(), installPath); + } + + /** + * Copy retrieved diagnostics zip file from system vm to secondary storage + * For VMware use the mgmt server, and for Xen/KVM use the hyperhost of the target VM + * The strategy is to mount secondary storage on mgmt server or host and scp directly to /mnt/SecStorage/diagnostics_data + * + * @param fileToCopy zip file in system vm to be copied + * @param store secondary storage to copy zip file to + */ + private Pair copyZipFileToSecondaryStorage(VMInstanceVO vmInstance, Long vmHostId, String fileToCopy, DataStore store) { + String vmControlIp = getVMSshIp(vmInstance); + if (StringUtils.isBlank(vmControlIp)) { + return new Pair<>(false, "Unable to find system vm ssh/control IP for vm with ID: " + vmInstance.getId()); + } + Pair copyResult; + if (vmInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) { + copyResult = orchestrateCopyToSecondaryStorageVMware(store, vmControlIp, fileToCopy); + } else { + copyResult = orchestrateCopyToSecondaryStorageNonVMware(store, vmControlIp, fileToCopy, vmHostId); + } + return copyResult; + } + + private void configureNetworkElementCommand(NetworkElementCommand cmd, VMInstanceVO vmInstance) { + Map accessDetails = networkManager.getSystemVMAccessDetails(vmInstance); + if (StringUtils.isBlank(accessDetails.get(NetworkElementCommand.ROUTER_IP))) { + throw new CloudRuntimeException("Unable to set system vm ControlIP for system vm with ID: " + vmInstance.getId()); + } + cmd.setAccessDetail(accessDetails); + } + + private Answer zipDiagnosticsFilesInSystemVm(VMInstanceVO vmInstance, List fileList) { + final PrepareFilesCommand cmd = new PrepareFilesCommand(fileList, DataRetrievalTimeout.value()); + configureNetworkElementCommand(cmd, vmInstance); + Answer answer = agentManager.easySend(vmInstance.getHostId(), cmd); + return answer; + } + + private Answer deleteDiagnosticsZipFileInsystemVm(VMInstanceVO vmInstance, String zipFileName) { + final DeleteFileInVrCommand cmd = new DeleteFileInVrCommand(zipFileName); + configureNetworkElementCommand(cmd, vmInstance); + return agentManager.easySend(vmInstance.getHostId(), cmd); + } + + /** + * Generate a list of diagnostics file to be retrieved depending on the system VM type + * + * @param optionalFileList Optional list of files that user may want to retrieve, empty by default + * @param vmInstance system VM instance, either SSVM, CPVM or VR + * @return a list of files to be retrieved for system VM, either generated from defaults depending on the VM type, or specified + * by the optional list param + */ + private List getFileListToBeRetrieved(List optionalFileList, VMInstanceVO vmInstance) { + DiagnosticsFilesList fileListObject = DiagnosticsFilesListFactory.getDiagnosticsFilesList(optionalFileList, vmInstance); + List fileList = new ArrayList<>(); + + if (fileListObject != null) { + fileList = fileListObject.generateFileList(); + } + return fileList; + } + + private Pair orchestrateCopyToSecondaryStorageNonVMware(final DataStore store, final String vmControlIp, String fileToCopy, Long vmHostId) { + CopyToSecondaryStorageCommand toSecondaryStorageCommand = new CopyToSecondaryStorageCommand(store.getUri(), vmControlIp, fileToCopy); + Answer copyToSecondaryAnswer = agentManager.easySend(vmHostId, toSecondaryStorageCommand); + Pair copyAnswer; + if (copyToSecondaryAnswer != null) { + copyAnswer = new Pair<>(copyToSecondaryAnswer.getResult(), copyToSecondaryAnswer.getDetails()); + } else { + copyAnswer = new Pair<>(false, "Diagnostics Zip file to secondary storage failed"); + } + return copyAnswer; + } + + private Pair orchestrateCopyToSecondaryStorageVMware(final DataStore store, final String vmSshIp, String diagnosticsFile) { + String mountPoint; + boolean success; + + Integer nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId()); + mountPoint = mountManager.getMountPoint(store.getUri(), nfsVersion); + if (StringUtils.isNotBlank(mountPoint)) { + LOGGER.info(String.format("Copying %s from %s to secondary store %s", diagnosticsFile, vmSshIp, store.getUri())); + + // dirIn/mnt/SecStorage/uuid/diagnostics_data + String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DIAGNOSTICS_DATA_DIRECTORY); + try { + File dataDirectory = new File(dataDirectoryInSecondaryStore); + boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); + + // Modify directory file permissions + Path path = Paths.get(dataDirectory.getAbsolutePath()); + setDirFilePermissions(path); + + if (existsInSecondaryStore) { + // scp from system VM to mounted sec storage directory + int port = 3922; + File permKey = new File("/var/cloudstack/management/.ssh/id_rsa"); + SshHelper.scpFrom(vmSshIp, port, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsFile); + } + // Verify File copy to Secondary Storage + File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsFile.replace("/root", "")); + success = fileInSecondaryStore.exists(); + } catch (Exception e) { + String msg = String.format("Exception caught during scp from %s to secondary store %s: ", vmSshIp, dataDirectoryInSecondaryStore); + LOGGER.error(msg); + return new Pair<>(false, msg); + } finally { + // umount secondary storage + umountSecondaryStorage(mountPoint); + } + } else { + return new Pair<>(false, "Failed to mount secondary storage:" + store.getName()); + } + return new Pair<>(success, "File copied to secondary storage successfully"); + } + + // Get ssvm from the zone to use for creating entity download URL + private VMInstanceVO getSecondaryStorageVmInZone(Long zoneId) { + List ssvm = instanceDao.listByZoneIdAndType(zoneId, VirtualMachine.Type.SecondaryStorageVm); + return (CollectionUtils.isEmpty(ssvm)) ? null : ssvm.get(0); + } + + /** + * Iterate through all Image stores in the current running zone and select any that has less than 95% disk usage + * + * @param zoneId of the current running zone + * @return a valid secondary storage with less than DiskQuotaPercentageThreshold set by global config + */ + private DataStore getImageStore(Long zoneId) { + List stores = storeMgr.getImageStoresByScope(new ZoneScope(zoneId)); + if (CollectionUtils.isEmpty(stores)) { + throw new CloudRuntimeException("No Secondary storage found in Zone with Id: " + zoneId); + } + DataStore imageStore = null; + for (DataStore store : stores) { + // Return image store if used percentage is less then threshold value set by global config diagnostics.data.disable.threshold + if (statsCollector.imageStoreHasEnoughCapacity(store, DiskQuotaPercentageThreshold.value())) { + imageStore = store; + break; + } + } + if (imageStore == null) { + throw new CloudRuntimeException("No suitable secondary storage found to retrieve diagnostics in Zone: " + zoneId); + } + return imageStore; + } + + // createEntityExtractUrl throws CloudRuntime exception in case of failure + private String createFileDownloadUrl(DataStore store, Hypervisor.HypervisorType hypervisorType, String filePath) { + // Get image store driver + ImageStoreEntity secStore = (ImageStoreEntity) store; + + //Create dummy TO with hyperType + DataTO dataTO = new DiagnosticsDataTO(hypervisorType, store.getTO()); + DataObject dataObject = new DiagnosticsDataObject(dataTO, store); + return secStore.createEntityExtractUrl(filePath, Storage.ImageFormat.TAR, dataObject); + } + + private VMInstanceVO getSystemVMInstance(Long vmId) { + VMInstanceVO vmInstance = instanceDao.findByIdTypes(vmId, VirtualMachine.Type.ConsoleProxy, + VirtualMachine.Type.DomainRouter, VirtualMachine.Type.SecondaryStorageVm); + if (vmInstance == null) { + String msg = String.format("Unable to find vm instance with id: %s", vmId); + LOGGER.error(msg); + throw new CloudRuntimeException("Diagnostics command execution failed, " + msg); + } + + final Long hostId = vmInstance.getHostId(); + if (hostId == null) { + throw new CloudRuntimeException("Unable to find host for virtual machine instance: " + vmInstance.getInstanceName()); + } + return vmInstance; + } + + private String getVMSshIp(final VMInstanceVO vmInstance) { + Map accessDetails = networkManager.getSystemVMAccessDetails(vmInstance); + String controlIP = accessDetails.get(NetworkElementCommand.ROUTER_IP); + if (StringUtils.isBlank(controlIP)) { + throw new CloudRuntimeException("Unable to find system vm ssh/control IP for vm with ID: " + vmInstance.getId()); + } + return controlIP; + } + + @Override + public boolean start() { + super.start(); + return true; + } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + if (EnableGarbageCollector.value()) { + backgroundPollManager.submitTask(new GCBackgroundTask(this)); + return true; + } + return false; + } + + public static final class GCBackgroundTask extends ManagedContextRunnable implements BackgroundPollTask { + private DiagnosticsServiceImpl serviceImpl; + + public GCBackgroundTask(DiagnosticsServiceImpl serviceImpl) { + this.serviceImpl = serviceImpl; + } + + private static void deleteOldDiagnosticsFiles(File directory, String storeName) { + File[] fileList = directory.listFiles(); + if (fileList != null) { + String msg = String.format("Found %s diagnostics files in store %s for garbage collection", fileList.length, storeName); + LOGGER.info(msg); + for (File file : fileList) { + if (file.isFile()) { + if (MaximumFileAgeforGarbageCollection.value() <= getTimeDifference(file)) { + boolean success = file.delete(); + LOGGER.info(file.getName() + " delete status: " + success); + } + } + } + } + } + + @Override + protected void runInContext() { + List dcList = serviceImpl.dataCenterDao.listEnabledZones(); + for (DataCenterVO vo: dcList) { + // Get All Image Stores in current running Zone + List storeList = serviceImpl.storeMgr.getImageStoresByScope(new ZoneScope(vo.getId())); + for (DataStore store : storeList) { + String mountPoint = null; + try { + mountPoint = serviceImpl.mountManager.getMountPoint(store.getUri(), null); + if (StringUtils.isNotBlank(mountPoint)) { + File directory = new File(mountPoint + "/" + DIAGNOSTICS_DATA_DIRECTORY); + if (directory.isDirectory()) { + deleteOldDiagnosticsFiles(directory, store.getName()); + } + } + } finally { + // umount secondary storage + umountSecondaryStorage(mountPoint); + } + } + + } + } + + @Override + public Long getDelay() { + // In Milliseconds + return GarbageCollectionInterval.value() * 1000L; + } + } + @Override public List> getCommands() { List> cmdList = new ArrayList<>(); cmdList.add(RunDiagnosticsCmd.class); + cmdList.add(GetDiagnosticsDataCmd.class); return cmdList; } + + @Override + public String getConfigComponentName() { + return DiagnosticsServiceImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{EnableGarbageCollector, DataRetrievalTimeout, + MaximumFileAgeforGarbageCollection, GarbageCollectionInterval, DiskQuotaPercentageThreshold, + SsvmDefaultSupportedFiles, VrDefaultSupportedFiles, CpvmDefaultSupportedFiles}; + } } \ No newline at end of file diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java new file mode 100644 index 000000000000..e41edeaa8a82 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java @@ -0,0 +1,47 @@ +// +// 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.fileprocessor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +public class ConsoleProxyDiagnosticFiles implements DiagnosticsFilesList { + // Optional parameters + private List dataTypeList; + + public ConsoleProxyDiagnosticFiles(List dataTypeList) { + this.dataTypeList = dataTypeList; + } + + @Override + public List generateFileList() { + List filesList = new ArrayList<>(); + + if (CollectionUtils.isEmpty(dataTypeList)) { + filesList.addAll(Arrays.asList(CpvmDefaultSupportedFiles.value().split(","))); + + } else { + filesList.addAll(dataTypeList); + } + return filesList; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java new file mode 100644 index 000000000000..e14b3e5d093d --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -0,0 +1,50 @@ +// 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.fileprocessor; + +import java.util.List; + +import org.apache.cloudstack.framework.config.ConfigKey; + +public interface DiagnosticsFilesList { + + /** + * Global configs below are used to set the diagnostics + * data types applicable for each system vm. + *

+ * the names wrapped in square brackets are for data types that need to first execute a script + * in the system vm and grab output for retrieval, e.g. the output from iptables-save is written to a file + * which will then be retrieved. + */ + ConfigKey SsvmDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, + "diagnostics.data.ssvm.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], /usr/local/cloud/systemvm/conf/agent.properties," + + " /usr/local/cloud/systemvm/conf/consoleproxy.properties, /var/log/cloud.log", + "List of supported diagnostics data file options for the ssvm", true); + + ConfigKey VrDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, + "diagnostics.data.vr.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], " + + "/etc/dnsmasq.conf, /etc/resolv.conf, /etc/haproxy.conf, /etc/hosts.conf, /etcdnsmaq-resolv.conf, /var/log/cloud.log, " + + "/var/log/routerServiceMonitor.log, /var/log/dnsmasq.log", + "List of supported diagnostics data file options for the VR", true); + + ConfigKey CpvmDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, + "diagnostics.data.cpvm.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], /usr/local/cloud/systemvm/conf/agent.properties, " + + "/usr/local/cloud/systemvm/conf/consoleproxy.properties, /var/log/cloud.log", + "List of supported diagnostics data file options for the cpvm", true); + + List generateFileList(); +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java new file mode 100644 index 000000000000..9a4fc3f1d70e --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java @@ -0,0 +1,38 @@ +// 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.fileprocessor; + +import java.util.List; + +import com.cloud.vm.VirtualMachine; + +public class DiagnosticsFilesListFactory { + + public static DiagnosticsFilesList getDiagnosticsFilesList(List dataTypeList, VirtualMachine vm) { + VirtualMachine.Type vmType = vm.getType(); + + if (vmType == VirtualMachine.Type.ConsoleProxy) { + return new ConsoleProxyDiagnosticFiles(dataTypeList); + } else if (vmType == VirtualMachine.Type.SecondaryStorageVm) { + return new SecondaryStorageVmDiagnosticsFiles(dataTypeList); + } else if (vmType == VirtualMachine.Type.DomainRouter) { + return new DomainRouterDiagnosticsFiles(dataTypeList); + } else { + return null; + } + } +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java new file mode 100644 index 000000000000..80fb8c02e5f6 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java @@ -0,0 +1,48 @@ +// +// 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.fileprocessor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +public class DomainRouterDiagnosticsFiles implements DiagnosticsFilesList { + // Optional parameters + private List dataTypeList; + + public DomainRouterDiagnosticsFiles(List dataTypeList) { + this.dataTypeList = dataTypeList; + } + + @Override + public List generateFileList() { + List filesList = new ArrayList<>(); + + if (CollectionUtils.isEmpty(dataTypeList)) { + filesList.addAll(Arrays.asList(VrDefaultSupportedFiles.value().split(","))); + + } else { + filesList.addAll(dataTypeList); + } + return filesList; + } + +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java new file mode 100644 index 000000000000..8d321f8d4ce9 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java @@ -0,0 +1,47 @@ +// +// 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.fileprocessor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +public class SecondaryStorageVmDiagnosticsFiles implements DiagnosticsFilesList { + // Optional parameters + private List dataTypeList; + + public SecondaryStorageVmDiagnosticsFiles(List dataTypeList) { + this.dataTypeList = dataTypeList; + } + + @Override + public List generateFileList() { + List filesList = new ArrayList<>(); + + if (CollectionUtils.isEmpty(dataTypeList)) { + filesList.addAll(Arrays.asList(SsvmDefaultSupportedFiles.value().split(","))); + + } else { + filesList.addAll(dataTypeList); + } + return filesList; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java new file mode 100644 index 000000000000..538cdc2a97eb --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java @@ -0,0 +1,97 @@ +// +// 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.to; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataTO; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; + +public class DiagnosticsDataObject implements DataObject { + private DataTO dataTO; + private DataStore dataStore; + + public DiagnosticsDataObject(DataTO dataTO, DataStore dataStore) { + this.dataTO = dataTO; + this.dataStore = dataStore; + } + + @Override + public long getId() { + return 0; + } + + @Override + public String getUri() { + return null; + } + + @Override + public DataTO getTO() { + return dataTO; + } + + @Override + public DataStore getDataStore() { + return dataStore; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public DataObjectType getType() { + return dataTO.getObjectType(); + } + + @Override + public String getUuid() { + return null; + } + + @Override + public boolean delete() { + return false; + } + + @Override + public void processEvent(ObjectInDataStoreStateMachine.Event event) { } + + @Override + public void processEvent(ObjectInDataStoreStateMachine.Event event, Answer answer) { + + } + + @Override + public void incRefCount() { } + + @Override + public void decRefCount() { + + } + + @Override + public Long getRefCount() { + return null; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java new file mode 100644 index 000000000000..7cadfa553f90 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java @@ -0,0 +1,60 @@ +// +// 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.to; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor; + +public class DiagnosticsDataTO implements DataTO { + private DataStoreTO dataStoreTO; + private Hypervisor.HypervisorType hypervisorType; + private String path; + private long id; + + public DiagnosticsDataTO(Hypervisor.HypervisorType hypervisorType, DataStoreTO dataStoreTO) { + this.hypervisorType = hypervisorType; + this.dataStoreTO = dataStoreTO; + } + + @Override + public DataObjectType getObjectType() { + return DataObjectType.TAR; + } + + @Override + public DataStoreTO getDataStore() { + return dataStoreTO; + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return hypervisorType; + } + + @Override + public String getPath() { + return path; + } + + @Override + public long getId() { + return id; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/storage/NfsMountManager.java b/server/src/main/java/org/apache/cloudstack/storage/NfsMountManager.java new file mode 100644 index 000000000000..a4e413ced9f0 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/storage/NfsMountManager.java @@ -0,0 +1,23 @@ +// +// 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.storage; + +public interface NfsMountManager { + + String getMountPoint(String storageUrl, Integer nfsVersion); +} diff --git a/server/src/main/java/org/apache/cloudstack/storage/NfsMountManagerImpl.java b/server/src/main/java/org/apache/cloudstack/storage/NfsMountManagerImpl.java new file mode 100644 index 000000000000..50ef13654512 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/storage/NfsMountManagerImpl.java @@ -0,0 +1,203 @@ +// +// 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.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.annotation.PreDestroy; + +import com.cloud.storage.StorageLayer; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +@Component +public class NfsMountManagerImpl implements NfsMountManager { + private static final Logger s_logger = Logger.getLogger(NfsMountManager.class); + + private StorageLayer storage; + private int timeout; + private final Random rand = new Random(System.currentTimeMillis()); + private final ConcurrentMap storageMounts = new ConcurrentHashMap<>(); + + public static final ConfigKey MOUNT_PARENT = new ConfigKey<>("Advanced", String.class, + "mount.parent", "/var/cloudstack/mnt", + "The mount point on the Management Server for Secondary Storage.", + true, ConfigKey.Scope.Global); + + public NfsMountManagerImpl(StorageLayer storage, int timeout) { + this.storage = storage; + this.timeout = timeout; + } + + public String getMountPoint(String storageUrl, Integer nfsVersion) { + String mountPoint = storageMounts.get(storageUrl); + if (mountPoint != null) { + return mountPoint; + } + + URI uri; + try { + uri = new URI(storageUrl); + } catch (URISyntaxException e) { + s_logger.error("Invalid storage URL format ", e); + throw new CloudRuntimeException("Unable to create mount point due to invalid storage URL format " + storageUrl); + } + + mountPoint = mount(uri.getHost() + ":" + uri.getPath(), MOUNT_PARENT.value(), nfsVersion); + if (mountPoint == null) { + s_logger.error("Unable to create mount point for " + storageUrl); + throw new CloudRuntimeException("Unable to create mount point for " + storageUrl); + } + + storageMounts.putIfAbsent(storageUrl, mountPoint); + return mountPoint; + } + + private String mount(String path, String parent, Integer nfsVersion) { + String mountPoint = setupMountPoint(parent); + if (mountPoint == null) { + s_logger.warn("Unable to create a mount point"); + return null; + } + + Script command = new Script(true, "mount", timeout, s_logger); + command.add("-t", "nfs"); + if (nfsVersion != null){ + command.add("-o", "vers=" + nfsVersion); + } + // command.add("-o", "soft,timeo=133,retrans=2147483647,tcp,acdirmax=0,acdirmin=0"); + if ("Mac OS X".equalsIgnoreCase(System.getProperty("os.name"))) { + command.add("-o", "resvport"); + } + command.add(path); + command.add(mountPoint); + String result = command.execute(); + if (result != null) { + s_logger.warn("Unable to mount " + path + " due to " + result); + deleteMountPath(mountPoint); + return null; + } + + // Change permissions for the mountpoint + Script script = new Script(true, "chmod", timeout, s_logger); + script.add("1777", mountPoint); + result = script.execute(); + if (result != null) { + s_logger.warn("Unable to set permissions for " + mountPoint + " due to " + result); + } + return mountPoint; + } + + private String setupMountPoint(String parent) { + String mountPoint = null; + for (int i = 0; i < 10; i++) { + String mntPt = parent + File.separator + String.valueOf(ManagementServerNode.getManagementServerId()) + "." + Integer.toHexString(rand.nextInt(Integer.MAX_VALUE)); + File file = new File(mntPt); + if (!file.exists()) { + if (storage.mkdir(mntPt)) { + mountPoint = mntPt; + break; + } + } + s_logger.error("Unable to create mount: " + mntPt); + } + + return mountPoint; + } + + private void umount(String localRootPath) { + if (!mountExists(localRootPath)) { + return; + } + Script command = new Script(true, "umount", timeout, s_logger); + command.add(localRootPath); + String result = command.execute(); + if (result != null) { + // Fedora Core 12 errors out with any -o option executed from java + String errMsg = "Unable to umount " + localRootPath + " due to " + result; + s_logger.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + deleteMountPath(localRootPath); + s_logger.debug("Successfully umounted " + localRootPath); + } + + private void deleteMountPath(String localRootPath) { + try { + Files.deleteIfExists(Paths.get(localRootPath)); + } catch (IOException e) { + s_logger.warn(String.format("unable to delete mount directory %s:%s.%n", localRootPath, e.getMessage())); + } + } + + private boolean mountExists(String localRootPath) { + Script script = new Script(true, "mount", timeout, s_logger); + ZfsPathParser parser = new ZfsPathParser(localRootPath); + script.execute(parser); + return parser.getPaths().stream().filter(s -> s.contains(localRootPath)).findAny().map(s -> true).orElse(false); + } + + public static class ZfsPathParser extends OutputInterpreter { + String _parent; + List paths = new ArrayList<>(); + + public ZfsPathParser(String parent) { + _parent = parent; + } + + @Override + public String interpret(BufferedReader reader) throws IOException { + String line; + while ((line = reader.readLine()) != null) { + paths.add(line); + } + return null; + } + + public List getPaths() { + return paths; + } + + @Override + public boolean drain() { + return true; + } + } + + @PreDestroy + public void destroy() { + s_logger.info("Clean up mounted NFS mount points used in current session."); + storageMounts.values().stream().forEach(this::umount); + } +} 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 2f67c4248d35..f3525cce6b19 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 @@ -300,4 +300,12 @@ + + + + + + + + diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java new file mode 100644 index 000000000000..7d39c26d4bbb --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java @@ -0,0 +1,83 @@ +// 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 java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; + +import org.apache.cloudstack.diagnostics.fileprocessor.ConsoleProxyDiagnosticFiles; +import org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesListFactory; +import org.apache.cloudstack.framework.config.ConfigKey; +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 static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class DiagnosticsFilesListFactoryTest { + + private ConsoleProxyDiagnosticFiles proxyDiagnosticFiles; + + @Mock + private VMInstanceVO vmInstance; + + @InjectMocks + private DiagnosticsFilesListFactory listFactory = new DiagnosticsFilesListFactory(); + + @Before + public void setUp() throws Exception { + Mockito.when(vmInstance.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy); + } + + @After + public void tearDown() throws Exception { + Mockito.reset(vmInstance); + } + + @Test + public void testgetDiagnosticsFilesListCpVmDataTypeList() { + List dataTypeList = new ArrayList<>(); + dataTypeList.add("/var/log/auth.log"); + dataTypeList.add("/etc/dnsmasq.conf"); + dataTypeList.add("[IPTABLES]"); + dataTypeList.add("[IFCONFIG]"); + + List files = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(dataTypeList, vmInstance)).generateFileList(); + + assertEquals(files, dataTypeList); + } + + @Test + public void testDiagnisticsFileListDefaultsCpvm() { + List filesList = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(null, vmInstance)).generateFileList(); + + ConfigKey configKey = proxyDiagnosticFiles.CpvmDefaultSupportedFiles; + String[] defaultFileArray = configKey.defaultValue().split(","); + + assertEquals(filesList.size(), defaultFileArray.length); + } +} \ No newline at end of file diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java index d85c5434d9a1..04a7e8a2b0d0 100644 --- a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java @@ -18,15 +18,9 @@ // 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 java.util.HashMap; +import java.util.Map; + import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -39,8 +33,16 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import java.util.HashMap; -import java.util.Map; +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; @RunWith(MockitoJUnitRunner.class) public class DiagnosticsServiceImplTest extends TestCase { @@ -50,40 +52,39 @@ public class DiagnosticsServiceImplTest extends TestCase { @Mock private VMInstanceDao instanceDao; @Mock - private RunDiagnosticsCmd diagnosticsCmd; + private RunDiagnosticsCmd runDiagnosticsCmd; @Mock private DiagnosticsCommand command; @Mock - private VMInstanceVO instanceVO; + private VMInstanceVO vmInstanceVO; @Mock private VirtualMachineManager vmManager; @Mock private NetworkOrchestrationService networkManager; @InjectMocks - private DiagnosticsServiceImpl diagnosticsService = new DiagnosticsServiceImpl(); + private DiagnosticsServiceImpl serviceImpl = new DiagnosticsServiceImpl(); @Before public void setUp() throws Exception { - Mockito.when(diagnosticsCmd.getId()).thenReturn(1L); - Mockito.when(diagnosticsCmd.getType()).thenReturn(DiagnosticsType.PING); + Mockito.when(runDiagnosticsCmd.getId()).thenReturn(1L); + Mockito.when(runDiagnosticsCmd.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); - + Mockito.any(VirtualMachine.Type.class), Mockito.any(VirtualMachine.Type.class))).thenReturn(vmInstanceVO); } @After public void tearDown() throws Exception { - Mockito.reset(diagnosticsCmd); + Mockito.reset(runDiagnosticsCmd); Mockito.reset(agentManager); Mockito.reset(instanceDao); - Mockito.reset(instanceVO); + Mockito.reset(vmInstanceVO); Mockito.reset(command); } @Test public void testRunDiagnosticsCommandTrue() throws Exception { - Mockito.when(diagnosticsCmd.getAddress()).thenReturn("8.8.8.8"); + Mockito.when(runDiagnosticsCmd.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); @@ -102,7 +103,7 @@ public void testRunDiagnosticsCommandTrue() throws Exception { Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details)); - Map detailsMap = diagnosticsService.runDiagnosticsCommand(diagnosticsCmd); + Map detailsMap = serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd); 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" + @@ -123,7 +124,7 @@ public void testRunDiagnosticsCommandTrue() throws Exception { @Test public void testRunDiagnosticsCommandFalse() throws Exception { - Mockito.when(diagnosticsCmd.getAddress()).thenReturn("192.0.2.2"); + Mockito.when(runDiagnosticsCmd.getAddress()).thenReturn("192.0.2.2"); Map accessDetailsMap = new HashMap<>(); accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10"); @@ -141,7 +142,7 @@ public void testRunDiagnosticsCommandFalse() throws Exception { "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); + Map detailsMap = serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd); assertEquals(3, detailsMap.size()); assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR)); @@ -151,46 +152,47 @@ public void testRunDiagnosticsCommandFalse() throws Exception { @Test(expected = InvalidParameterValueException.class) public void testRunDiagnosticsThrowsInvalidParamException() throws Exception { - Mockito.when(diagnosticsCmd.getAddress()).thenReturn(""); + Mockito.when(runDiagnosticsCmd.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); + serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd); } @Test(expected = CloudRuntimeException.class) public void testVMControlIPisNull() throws Exception { - Mockito.when(diagnosticsCmd.getAddress()).thenReturn("0.42.42.42"); + Mockito.when(runDiagnosticsCmd.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); + serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd); } @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(",")); + assertFalse(serviceImpl.hasValidChars("'\\''")); + assertFalse(serviceImpl.hasValidChars("-I eth0 &")); + assertFalse(serviceImpl.hasValidChars("-I eth0 ;")); + assertFalse(serviceImpl.hasValidChars(" &2 > ")); + assertFalse(serviceImpl.hasValidChars(" &2 >> ")); + assertFalse(serviceImpl.hasValidChars(" | ")); + assertFalse(serviceImpl.hasValidChars("|")); + assertFalse(serviceImpl.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")); + assertTrue(serviceImpl.hasValidChars("")); + assertTrue(serviceImpl.hasValidChars(".")); + assertTrue(serviceImpl.hasValidChars(" ")); + assertTrue(serviceImpl.hasValidChars("-I eth0 www.google.com")); + assertTrue(serviceImpl.hasValidChars(" ")); + assertTrue(serviceImpl.hasValidChars(" -I cloudbr0 --sport ")); + assertTrue(serviceImpl.hasValidChars(" --back -m20 ")); + assertTrue(serviceImpl.hasValidChars("-c 5 -4")); + assertTrue(serviceImpl.hasValidChars("-c 5 -4 -AbDfhqUV")); } + } \ No newline at end of file diff --git a/systemvm/debian/opt/cloud/bin/cleanup.sh b/systemvm/debian/opt/cloud/bin/cleanup.sh new file mode 100755 index 000000000000..d14877badbc6 --- /dev/null +++ b/systemvm/debian/opt/cloud/bin/cleanup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# 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. + +#rm -rf $@ && echo $? + +zip_file=$1 +if [ -e "$zip_file" ]; +then + rm -rf "$zip_file" + echo "Deleting diagnostics zip file $zip_file" +else + echo "File $zip_file not found in vm " +fi diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py new file mode 100755 index 000000000000..3f0b4db5dfcd --- /dev/null +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -0,0 +1,122 @@ +#!/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 zipfile +import sys +import time +import re +import subprocess as sp +import shlex +import os +import logging + +fileList = sys.argv[1:] + + +# Create zip archive and append files for retrieval +def zip_files(files): + fList = files + compression = zipfile.ZIP_DEFLATED + time_str = time.strftime("%Y%m%d-%H%M%S") + zf_name = '/root/diagnostics_files_' + time_str + '.tar' + zf = zipfile.ZipFile(zf_name, 'w', compression) + + ''' + Initialize 3 empty arrays to collect found files, non-existent files + and last one to collect temp files to be cleaned up when script exits + ''' + files_found_list = [] + files_not_found_list = [] + files_from_shell_commands = [] + + try: + for f in fList: + # [IPTABLES], [ROUE] and [IFCONFIG], remove square brackets + if '[' in f: + shell_script = re.sub(r'\W+', "", f).strip().lower() + f = execute_shell_script(shell_script) + files_from_shell_commands.append(f) + if os.path.isfile(f): + try: + zf.write(f, f[f.rfind('/') + 1:]) + except OSError or RuntimeError as e: + files_not_found_list.append(f) + else: + files_found_list.append(f) + finally: + cleanup(files_from_shell_commands) + generate_retrieved_files_txt(zf, files_found_list, files_not_found_list) + zf.close() + print zf_name + + +def execute_shell_script(script): + # Ex. iptables.log + outputfile = script + '.log' + + if script == 'iptables': + cmd = 'iptables-save' + elif script == 'ifconfig': + cmd = 'ifconfig' + elif script == 'route': + cmd = 'netstat -rn' + else: + cmd = script + with open(outputfile, 'wb', 0) as f: + try: + p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) + stdout, stderr = p.communicate() + return_code = p.returncode + if return_code is 0: + f.write(stdout) + else: + f.write(stderr) + except OSError as ex: + delete_tmp_file_cmd = 'rm -f %s' % outputfile + sp.check_call(shlex.split(delete_tmp_file_cmd)) + finally: + f.close() + return outputfile + + +def cleanup(file_list): + files = ' '.join(file_list) + cmd = 'rm -f %s' % files + try: + p = sp.Popen(shlex.split(cmd), stderr=sp.PIPE, stdout=sp.PIPE) + p.communicate() + except OSError as e: + logging.debug("Failed to execute bash command") + + +def generate_retrieved_files_txt(zip_file, files_found, files_not_found): + output_file = 'fileinfo.txt' + try: + with open(output_file, 'wb', 0) as man: + for i in files_found: + man.write(i + '\n') + for j in files_not_found: + man.write(j + 'File Not Found!!\n') + zip_file.write(output_file, output_file) + finally: + cleanup_cmd = "rm -f %s" % output_file + sp.check_call(shlex.split(cleanup_cmd)) + + +if __name__ == '__main__': + zip_files(fileList) diff --git a/test/integration/smoke/test_diagnostics.py b/test/integration/smoke/test_diagnostics.py index 6364d83eeee8..810dbb83093c 100644 --- a/test/integration/smoke/test_diagnostics.py +++ b/test/integration/smoke/test_diagnostics.py @@ -16,11 +16,12 @@ # under the License. """ BVT tests for remote diagnostics of system VMs """ +import urllib + +from marvin.cloudstackAPI import (runDiagnostics, getDiagnosticsData) +from marvin.cloudstackTestCase import cloudstackTestCase # 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) @@ -29,7 +30,7 @@ get_test_template, list_ssvms, list_routers) - +from marvin.lib.utils import (cleanup_resources) from nose.plugins.attrib import attr @@ -537,3 +538,197 @@ def test_12_traceroute_in_cpvm(self): cmd_response.exitcode, 'Failed to run remote Traceroute in CPVM' ) + + ''' + Add Get Diagnostics data BVT + ''' + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_13_retrieve_vr_default_files(self): + 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('Setting up VR with ID %s' % router.id) + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = router.id + + response = self.apiclient.getDiagnosticsData(cmd) + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) + + def check_url(self, url): + import urllib2 + try: + r = urllib.urlopen(url) + if r.code == 200: + return True + except urllib2.HTTPError: + return False + except urllib2.URLError: + return False + return True + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_14_retrieve_vr_one_file(self): + 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('Setting up VR with ID %s' % router.id) + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = router.id + cmd.type = "/var/log/cloud.log" + + response = self.apiclient.getDiagnosticsData(cmd) + + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_15_retrieve_ssvm_default_files(self): + 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 = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = ssvm.id + + response = self.apiclient.getDiagnosticsData(cmd) + + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_16_retrieve_ssvm_single_file(self): + 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 = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = ssvm.id + cmd.type = "/var/log/cloud.log" + + response = self.apiclient.getDiagnosticsData(cmd) + + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_17_retrieve_cpvm_default_files(self): + 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 CPVM with ID %s' % cpvm.id) + + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = cpvm.id + + response = self.apiclient.getDiagnosticsData(cmd) + + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") + def test_18_retrieve_cpvm_single_file(self): + 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 CPVM with ID %s' % cpvm.id) + + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = cpvm.id + cmd.type = "/var/log/cloud.log" + + response = self.apiclient.getDiagnosticsData(cmd) + + is_valid_url = self.check_url(response.url) + + self.assertEqual( + True, + is_valid_url, + msg="Failed to create valid download url response" + ) diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 18a70a2b89e4..676060221539 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12584,6 +12584,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -165px -704px; } +.retrieveDiagnostics .icon { + background-position: -35px -125px; +} + +.retrieveDiagnostics:hover .icon { + background-position: -35px -707px; +} + .enableOutOfBandManagement .icon { background-position: -138px -65px; } diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 4ce59e08599b..65ce76b153d1 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -243,6 +243,7 @@ var dictionary = { "label.action.force.reconnect.processing":"Reconnecting....", "label.action.generate.keys":"Generate Keys", "label.action.generate.keys.processing":"Generate Keys....", +"label.action.get.diagnostics":"Get Diagnostics Data", "label.action.list.nexusVswitch":"List Nexus 1000v", "label.action.lock.account":"Lock account", "label.action.lock.account.processing":"Locking account....", @@ -802,6 +803,7 @@ var dictionary = { "label.gateway":"Gateway", "label.general.alerts":"General Alerts", "label.generating.url":"Generating URL", +"label.get.diagnostics.files":"Files (comma separated list)", "label.globo.dns":"GloboDNS", "label.globo.dns.configuration":"GloboDNS Configuration", "label.gluster.volume":"Volume", @@ -2149,6 +2151,7 @@ var dictionary = { "message.disabling.network.offering":"Disabling network offering", "message.disabling.vpc.offering":"Disabling VPC offering", "message.disallowed.characters":"Disallowed characters: <,>", +"message.download.diagnostics":"Please click 00000 to download retrieved diagnostics", "message.download.ISO":"Please click 00000 to download ISO", "message.download.template":"Please click 00000 to download template", "message.download.volume":"Please click 00000 to download volume", diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 1a73e64f3df6..13598cb247ca 100755 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -3933,6 +3933,56 @@ } }, + retrieveDiagnostics: { + label: 'label.action.get.diagnostics', + messages: { + notification: function (args) { + return 'label.action.get.diagnostics'; + }, + complete: function(args) { + var url = args.url; + var htmlMsg = _l('message.download.diagnostics'); + var htmlMsg2 = htmlMsg.replace(/#/, url).replace(/00000/, url); + return htmlMsg2; + } + }, + createForm: { + title: 'label.action.get.diagnostics', + desc: '', + fields: { + files: { + label: 'label.get.diagnostics.files' + } + } + }, + action: function (args) { + $.ajax({ + url: createURL("getDiagnosticsData&targetid=" + args.context.routers[0].id + "&files=" + args.data.files), + dataType: "json", + async: true, + success: function(json) { + var jid = json.getdiagnosticsdataresponse.jobid; + args.response.success({ + _custom: { + jobId : jid, + getUpdatedItem: function (json) { + return json.queryasyncjobresultresponse.jobresult.diagnostics; + + }, + getActionFilter: function(){ + return systemvmActionfilter; + } + } + + }); + } + }); //end ajax + }, + notification: { + poll: pollAsyncJobResult + } + }, + viewConsole: { label: 'label.view.console', action: { @@ -8847,6 +8897,56 @@ } }, + retrieveDiagnostics: { + label: 'label.action.get.diagnostics', + messages: { + notification: function (args) { + return 'label.action.get.diagnostics'; + }, + complete: function(args) { + var url = args.url; + var htmlMsg = _l('message.download.diagnostics'); + var htmlMsg2 = htmlMsg.replace(/#/, url).replace(/00000/, url); + return htmlMsg2; + } + }, + createForm: { + title: 'label.action.get.diagnostics', + desc: '', + fields: { + files: { + label: 'label.get.diagnostics.files' + } + } + }, + action: function (args) { + $.ajax({ + url: createURL("getDiagnosticsData&targetid=" + args.context.systemVMs[0].id + "&files=" + args.data.files), + dataType: "json", + async: true, + success: function(json) { + var jid = json.getdiagnosticsdataresponse.jobid; + args.response.success({ + _custom: { + jobId : jid, + getUpdatedItem: function (json) { + return json.queryasyncjobresultresponse.jobresult.diagnostics; + + }, + getActionFilter: function(){ + return systemvmActionfilter; + } + } + + }); + } + }); //end ajax + }, + notification: { + poll: pollAsyncJobResult + } + }, + scaleUp: { label: 'label.change.service.offering', createForm: { @@ -10293,6 +10393,56 @@ } }, + retrieveDiagnostics: { + label: 'label.action.get.diagnostics', + messages: { + notification: function (args) { + return 'label.action.get.diagnostics'; + }, + complete: function(args) { + var url = args.url; + var htmlMsg = _l('message.download.diagnostics'); + var htmlMsg2 = htmlMsg.replace(/#/, url).replace(/00000/, url); + return htmlMsg2; + } + }, + createForm: { + title: 'label.action.get.diagnostics', + desc: '', + fields: { + files: { + label: 'label.get.diagnostics.files' + } + } + }, + action: function (args) { + $.ajax({ + url: createURL("getDiagnosticsData&targetid=" + args.context.routers[0].id + "&files=" + args.data.files), + dataType: "json", + async: true, + success: function(json) { + var jid = json.getdiagnosticsdataresponse.jobid; + args.response.success({ + _custom: { + jobId : jid, + getUpdatedItem: function (json) { + return json.queryasyncjobresultresponse.jobresult.diagnostics; + + }, + getActionFilter: function(){ + return systemvmActionfilter; + } + } + + }); + } + }); //end ajax + }, + notification: { + poll: pollAsyncJobResult + } + }, + scaleUp: { //*** Infrastructure > Virtual Routers > change service offering *** label: 'label.change.service.offering', createForm: { @@ -11643,6 +11793,56 @@ } }, + retrieveDiagnostics: { + label: 'label.action.get.diagnostics', + messages: { + notification: function (args) { + return 'label.action.get.diagnostics'; + }, + complete: function(args) { + var url = args.url; + var htmlMsg = _l('message.download.diagnostics'); + var htmlMsg2 = htmlMsg.replace(/#/, url).replace(/00000/, url); + return htmlMsg2; + } + }, + createForm: { + title: 'label.action.get.diagnostics', + desc: '', + fields: { + files: { + label: 'label.get.diagnostics.files' + } + } + }, + action: function (args) { + $.ajax({ + url: createURL("getDiagnosticsData&targetid=" + args.context.systemVMs[0].id + "&files=" + args.data.files), + dataType: "json", + async: true, + success: function(json) { + var jid = json.getdiagnosticsdataresponse.jobid; + args.response.success({ + _custom: { + jobId : jid, + getUpdatedItem: function (json) { + return json.queryasyncjobresultresponse.jobresult.diagnostics; + + }, + getActionFilter: function(){ + return systemvmActionfilter; + } + } + + }); + } + }); //end ajax + }, + notification: { + poll: pollAsyncJobResult + } + }, + scaleUp: { //*** Infrastructure > System VMs (consoleProxy or SSVM) > change service offering *** label: 'label.change.service.offering', createForm: { @@ -22072,6 +22272,7 @@ if (isAdmin()) { allowedActions.push("migrate"); allowedActions.push("diagnostics"); + allowedActions.push("retrieveDiagnostics"); } } else if (jsonObj.state == 'Stopped') { allowedActions.push("start"); @@ -22123,6 +22324,7 @@ if (isAdmin()) { allowedActions.push("migrate"); allowedActions.push("diagnostics"); + allowedActions.push("retrieveDiagnostics"); } } else if (jsonObj.state == 'Stopped') { allowedActions.push("start"); diff --git a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java index 88be57742257..042842064df9 100644 --- a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java +++ b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java @@ -58,6 +58,30 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St scpTo(host, port, user, pemKeyFile, password, remoteTargetDirectory, data, remoteFileName, fileMode, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT); } + public static void scpFrom(String host, int port, String user, File permKeyFile, String localTargetDirectory, String remoteTargetFile) throws Exception { + com.trilead.ssh2.Connection conn = null; + com.trilead.ssh2.SCPClient scpClient = null; + + try { + conn = new com.trilead.ssh2.Connection(host, port); + conn.connect(null, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT); + + if (!conn.authenticateWithPublicKey(user, permKeyFile, null)) { + String msg = "Failed to authentication SSH user " + user + " on host " + host; + s_logger.error(msg); + throw new Exception(msg); + } + scpClient = conn.createSCPClient(); + + scpClient.get(remoteTargetFile, localTargetDirectory); + + } finally { + if (conn != null) { + conn.close(); + } + } + } + public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String localFile, String fileMode, int connectTimeoutInMs, int kexTimeoutInMs) throws Exception { From b3b90b014bfd4a2af190b42aa9878eae20a3623e Mon Sep 17 00:00:00 2001 From: PaulAngus Date: Thu, 23 May 2019 09:39:33 +0100 Subject: [PATCH 02/25] improve UI labels --- ui/l10n/en.js | 3 ++- ui/scripts/system.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 65ce76b153d1..403e25af7bc9 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -803,7 +803,8 @@ var dictionary = { "label.gateway":"Gateway", "label.general.alerts":"General Alerts", "label.generating.url":"Generating URL", -"label.get.diagnostics.files":"Files (comma separated list)", +"label.get.diagnostics.desc":"If you wish to override the standard file returned enter them here, otherwise leave blank and press OK", +"label.get.diagnostics.files":"Alternate Files to Retrieve", "label.globo.dns":"GloboDNS", "label.globo.dns.configuration":"GloboDNS Configuration", "label.gluster.volume":"Volume", diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 13598cb247ca..a824abdf8dcb 100755 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -3948,7 +3948,7 @@ }, createForm: { title: 'label.action.get.diagnostics', - desc: '', + desc: 'label.get.diagnostics.desc', fields: { files: { label: 'label.get.diagnostics.files' @@ -10408,7 +10408,7 @@ }, createForm: { title: 'label.action.get.diagnostics', - desc: '', + desc: 'label.get.diagnostics.desc', fields: { files: { label: 'label.get.diagnostics.files' From aaa8dcf495cfa0a9e135e5a5c562ad6a414a4bd1 Mon Sep 17 00:00:00 2001 From: PaulAngus Date: Thu, 23 May 2019 10:46:57 +0100 Subject: [PATCH 03/25] slight reword and add another missing description --- ui/l10n/en.js | 2 +- ui/scripts/system.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 403e25af7bc9..0172cfd53b45 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -803,7 +803,7 @@ var dictionary = { "label.gateway":"Gateway", "label.general.alerts":"General Alerts", "label.generating.url":"Generating URL", -"label.get.diagnostics.desc":"If you wish to override the standard file returned enter them here, otherwise leave blank and press OK", +"label.get.diagnostics.desc":"If you wish to override the standard files returned, enter them here. Otherwise leave blank and press OK", "label.get.diagnostics.files":"Alternate Files to Retrieve", "label.globo.dns":"GloboDNS", "label.globo.dns.configuration":"GloboDNS Configuration", diff --git a/ui/scripts/system.js b/ui/scripts/system.js index a824abdf8dcb..2ae2f466043c 100755 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -11808,7 +11808,7 @@ }, createForm: { title: 'label.action.get.diagnostics', - desc: '', + desc: 'label.get.diagnostics.desc', fields: { files: { label: 'label.get.diagnostics.files' From 0a228b5a5b8cfb28da6e51eb9264657d9c11708f Mon Sep 17 00:00:00 2001 From: PaulAngus Date: Thu, 23 May 2019 15:32:53 +0100 Subject: [PATCH 04/25] improve download message clarity --- ui/l10n/en.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 0172cfd53b45..87deba8142b2 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -2152,10 +2152,10 @@ var dictionary = { "message.disabling.network.offering":"Disabling network offering", "message.disabling.vpc.offering":"Disabling VPC offering", "message.disallowed.characters":"Disallowed characters: <,>", -"message.download.diagnostics":"Please click 00000 to download retrieved diagnostics", -"message.download.ISO":"Please click 00000 to download ISO", -"message.download.template":"Please click 00000 to download template", -"message.download.volume":"Please click 00000 to download volume", +"message.download.diagnostics":"Please click the link to download the retrieved diagnostics:

00000", +"message.download.ISO":"Please click the link to download the ISO:

00000", +"message.download.template":"Please click the link to download the template:

00000", +"message.download.volume":"Please click the link to download the volume:

00000", "message.download.volume.confirm":"Please confirm that you want to download this volume.", "message.edit.account":"Edit (\"-1\" indicates no limit to the amount of resources create)", "message.edit.confirm":"Please confirm your changes before clicking \"Save\".", From db332d46b8db995ff5a3a283e7c37b02006430b3 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 10 Jun 2019 12:34:58 -0300 Subject: [PATCH 05/25] Address comments --- .../diagnostics/DiagnosticsServiceImpl.java | 22 +++++++++---------- ui/css/cloudstack3.css | 8 ------- ui/css/src/scss/components/action-icons.scss | 8 +++++++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index fe7f18a758f2..75ab1b5a6fe2 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -105,11 +105,11 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ @Inject private DataCenterDao dataCenterDao; - // This 2 settings should require a restart of the management server? + // These 2 settings should require a restart of the management server private static final ConfigKey EnableGarbageCollector = new ConfigKey<>("Advanced", Boolean.class, - "diagnostics.data.gc.enable", "true", "enable the diagnostics data files garbage collector", true); + "diagnostics.data.gc.enable", "true", "enable the diagnostics data files garbage collector", false); private static final ConfigKey GarbageCollectionInterval = new ConfigKey<>("Advanced", Integer.class, - "diagnostics.data.gc.interval", "86400", "garbage collection interval in seconds", true); + "diagnostics.data.gc.interval", "86400", "garbage collection interval in seconds", false); // These are easily computed properties and need not need a restart of the management server private static final ConfigKey DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class, @@ -217,14 +217,16 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { } if (!zipFilesAnswer.getResult()) { - throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file file in VM %s due to %s", vmInstance.getUuid(), zipFilesAnswer.getDetails())); + throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in VM %s due to: %s", vmInstance.getUuid(), zipFilesAnswer.getDetails())); } - // Copy zip file from system VM to secondary storage String zipFileInSystemVm = zipFilesAnswer.getDetails().replace("\n", ""); Pair copyToSecondaryStorageResults = copyZipFileToSecondaryStorage(vmInstance, vmHostId, zipFileInSystemVm, store); - // Send cleanup zip file cleanup command to system VM + if (!copyToSecondaryStorageResults.first()) { + throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to: %s.", zipFileInSystemVm, store.getUri(), copyToSecondaryStorageResults.second())); + } + Answer fileCleanupAnswer = deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); if (fileCleanupAnswer == null) { LOGGER.error(String.format("Failed to cleanup diagnostics zip file on vm: %s", vmInstance.getUuid())); @@ -234,10 +236,6 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { } } - if (!copyToSecondaryStorageResults.first()) { - throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to %s.", zipFileInSystemVm, store.getUri(), copyToSecondaryStorageResults.second())); - } - // Now we need to create the file download URL // Find ssvm of store VMInstanceVO ssvm = getSecondaryStorageVmInZone(zoneId); @@ -245,7 +243,7 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { throw new CloudRuntimeException("No ssvm found in Zone with ID: " + zoneId); } // Secondary Storage install path = "diagnostics_data/diagnostics_files_xxxx.tar - String installPath = DIAGNOSTICS_DATA_DIRECTORY + "/" + zipFileInSystemVm.replace("/root", ""); + String installPath = DIAGNOSTICS_DATA_DIRECTORY + File.separator + zipFileInSystemVm.replace("/root", ""); return createFileDownloadUrl(store, ssvm.getHypervisorType(), installPath); } @@ -371,7 +369,7 @@ private VMInstanceVO getSecondaryStorageVmInZone(Long zoneId) { } /** - * Iterate through all Image stores in the current running zone and select any that has less than 95% disk usage + * Iterate through all Image stores in the current running zone and select any that has less than DiskQuotaPercentageThreshold.value() disk usage * * @param zoneId of the current running zone * @return a valid secondary storage with less than DiskQuotaPercentageThreshold set by global config diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 676060221539..18a70a2b89e4 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12584,14 +12584,6 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -165px -704px; } -.retrieveDiagnostics .icon { - background-position: -35px -125px; -} - -.retrieveDiagnostics:hover .icon { - background-position: -35px -707px; -} - .enableOutOfBandManagement .icon { background-position: -138px -65px; } diff --git a/ui/css/src/scss/components/action-icons.scss b/ui/css/src/scss/components/action-icons.scss index 686b6e395bd2..6ed07a30be32 100644 --- a/ui/css/src/scss/components/action-icons.scss +++ b/ui/css/src/scss/components/action-icons.scss @@ -347,6 +347,14 @@ background-position: -165px -704px; } +.retrieveDiagnostics .icon { + background-position: -35px -125px; +} + +.retrieveDiagnostics:hover .icon { + background-position: -35px -707px; +} + .enableOutOfBandManagement .icon { background-position: -138px -65px; } From 56af34fd99a03a545390b87827b5b606cd413839 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 1 Jul 2019 16:59:08 +0530 Subject: [PATCH 06/25] multiple fixes and cleanups Signed-off-by: Rohit Yadav --- .../cloud/agent/api/to/DataObjectType.java | 2 +- .../diagnostics/GetDiagnosticsDataCmd.java | 11 ++-- .../diagnostics/DiagnosticsService.java | 2 + .../LibvirtCopyToSecondaryStorageWrapper.java | 8 +-- .../resource/CitrixResourceBase.java | 3 +- .../diagnostics/DiagnosticsHelper.java | 2 - .../diagnostics/DiagnosticsServiceImpl.java | 59 +++++++++---------- .../fileprocessor/DiagnosticsFilesList.java | 25 ++++---- .../DiagnosticsFilesListFactory.java | 12 ++-- .../DomainRouterDiagnosticsFiles.java | 3 +- .../SecondaryStorageVmDiagnosticsFiles.java | 47 --------------- ...les.java => SystemVMDiagnosticsFiles.java} | 7 +-- .../diagnostics/to/DiagnosticsDataObject.java | 16 ++--- .../diagnostics/to/DiagnosticsDataTO.java | 2 +- .../DiagnosticsFilesListFactoryTest.java | 22 +++---- .../opt/cloud/bin/get_diagnostics_files.py | 32 +++++----- ui/css/cloudstack3.css | 2 + 17 files changed, 100 insertions(+), 155 deletions(-) delete mode 100644 server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java rename server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/{ConsoleProxyDiagnosticFiles.java => SystemVMDiagnosticsFiles.java} (85%) diff --git a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java index 120f1875e4a8..26294cfbb223 100644 --- a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java +++ b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java @@ -19,5 +19,5 @@ package com.cloud.agent.api.to; public enum DataObjectType { - VOLUME, SNAPSHOT, TEMPLATE, TAR + VOLUME, SNAPSHOT, TEMPLATE, ARCHIVE } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java index 6f4e9c78f3e2..c673c79e819b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java @@ -35,7 +35,6 @@ import org.apache.cloudstack.diagnostics.DiagnosticsService; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.UrlValidator; -import org.apache.log4j.Logger; import com.cloud.event.EventTypes; import com.cloud.exception.InsufficientCapacityException; @@ -49,14 +48,15 @@ entityType = {VirtualMachine.class}, responseHasSensitiveInfo = false, requestHasSensitiveInfo = false, - description = "Get diagnostics data files from system VMs", - since = "4.12.0.0", + description = "Get diagnostics and files from system VMs", + since = "4.13.0.0", authorized = {RoleType.Admin}) public class GetDiagnosticsDataCmd extends BaseAsyncCmd { - private static final Logger LOGGER = Logger.getLogger(GetDiagnosticsDataCmd.class); public static final String APINAME = "getDiagnosticsData"; + @Inject private DiagnosticsService diagnosticsService; + ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @@ -74,10 +74,10 @@ public class GetDiagnosticsDataCmd extends BaseAsyncCmd { description = "A comma separated list of diagnostics data files to be retrieved. Defaults are taken from global settings if none has been provided.") private List filesList; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// + public Long getId() { return id; } @@ -89,6 +89,7 @@ public List getFilesList() { ///////////////////////////////////////////////////// /////////////////// Implementation ////////////////// ///////////////////////////////////////////////////// + @Override public String getCommandName() { return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; diff --git a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java index 0eae41ddb0fe..fb1d03b559b5 100644 --- a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java +++ b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsService.java @@ -25,6 +25,8 @@ public interface DiagnosticsService { + String DIAGNOSTICS_DIRECTORY = "diagnostics"; + Map runDiagnosticsCommand(RunDiagnosticsCmd cmd); String getDiagnosticsDataCommand(GetDiagnosticsDataCmd getDiagnosticsDataCmd); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java index a4e732f9613f..a6baa1c17855 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyToSecondaryStorageWrapper.java @@ -16,13 +16,15 @@ // under the License. package com.cloud.hypervisor.kvm.resource.wrapper; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; -import org.apache.cloudstack.diagnostics.DiagnosticsHelper; +import org.apache.cloudstack.diagnostics.DiagnosticsService; import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; @@ -33,8 +35,6 @@ import com.cloud.resource.ResourceWrapper; import com.cloud.utils.ssh.SshHelper; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; - @ResourceWrapper(handles = CopyToSecondaryStorageCommand.class) public class LibvirtCopyToSecondaryStorageWrapper extends CommandWrapper { public static final Logger LOGGER = Logger.getLogger(LibvirtCopyToSecondaryStorageWrapper.class); @@ -55,7 +55,7 @@ public Answer execute(CopyToSecondaryStorageCommand command, LibvirtComputingRes String mountPoint = secondaryPool.getLocalPath(); // /mnt/SecStorage/uuid/diagnostics_data - String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DiagnosticsHelper.DIAGNOSTICS_DATA_DIR); + String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DiagnosticsService.DIAGNOSTICS_DIRECTORY); try { File dataDirectory = new File(dataDirectoryInSecondaryStore); boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index f50a1511361f..c6f724d8b700 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -57,6 +57,7 @@ import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; import org.apache.cloudstack.diagnostics.DiagnosticsHelper; import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility; +import org.apache.cloudstack.diagnostics.DiagnosticsService; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.commons.collections.CollectionUtils; @@ -5642,7 +5643,7 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon return new CopyToSecondaryStorageAnswer(cmd, false, "Could not mount secondary storage " + secondaryStorageMountPath + " on host " + localDir); } - String dataDirectoryInSecondaryStore = localDir + "/" + DiagnosticsHelper.DIAGNOSTICS_DATA_DIR; + String dataDirectoryInSecondaryStore = localDir + "/" + DiagnosticsService.DIAGNOSTICS_DIRECTORY; File dataDirectory = new File(dataDirectoryInSecondaryStore); boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java index b7295ba909d7..282eee202cfc 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsHelper.java @@ -37,8 +37,6 @@ public class DiagnosticsHelper { private static final Logger LOGGER = Logger.getLogger(DiagnosticsHelper.class); - public static final String DIAGNOSTICS_DATA_DIR = "diagnostics_data"; - public static void setDirFilePermissions(Path path) throws java.io.IOException { Set perms = Files.readAttributes(path, PosixFileAttributes.class).permissions(); perms.add(PosixFilePermission.OWNER_WRITE); diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 75ab1b5a6fe2..57bb4a96d3e0 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -17,6 +17,12 @@ // under the License. package org.apache.cloudstack.diagnostics; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.getTimeDifference; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; +import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.umountSecondaryStorage; +import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.RouterDefaultSupportedFiles; +import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.SystemVMDefaultSupportedFiles; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -74,13 +80,6 @@ import com.cloud.vm.dao.VMInstanceDao; import com.google.common.base.Strings; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.getTimeDifference; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.umountSecondaryStorage; -import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.CpvmDefaultSupportedFiles; -import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.SsvmDefaultSupportedFiles; -import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.VrDefaultSupportedFiles; - public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService, Configurable { private static final Logger LOGGER = Logger.getLogger(DiagnosticsServiceImpl.class); @@ -113,13 +112,11 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ // These are easily computed properties and need not need a restart of the management server private static final ConfigKey DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class, - "diagnostics.data.retrieval.timeout", "3600", "overall data retrieval timeout in seconds", true); + "diagnostics.data.retrieval.timeout", "1800", "overall data retrieval timeout in seconds", true); private static final ConfigKey MaximumFileAgeforGarbageCollection = new ConfigKey<>("Advanced", Long.class, "diagnostics.data.max.file.age", "86400", "maximum file age for garbage collection in seconds", true); private static final ConfigKey DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class, - "diagnostics.data.disable.threshold", "0.95", "Minimum disk space percentage to initiate diagnostics file retrieval", true); - - private final static String DIAGNOSTICS_DATA_DIRECTORY = "diagnostics_data"; + "diagnostics.data.disable.threshold", "0.9", "Minimum disk space percentage to initiate diagnostics file retrieval", true); @Override @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "running diagnostics on system vm", async = true) @@ -205,29 +202,26 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { throw new CloudRuntimeException("Failed to generate diagnostics file list for retrieval."); } - Long vmHostId = vmInstance.getHostId(); - - // Find Secondary Storage with enough Disk Quota in the current Zone + final Long vmHostId = vmInstance.getHostId(); final DataStore store = getImageStore(vmInstance.getDataCenterId()); - - Answer zipFilesAnswer = zipDiagnosticsFilesInSystemVm(vmInstance, fileList); + final Answer zipFilesAnswer = prepareDiagnosticsFilesInSystemVm(vmInstance, fileList); if (zipFilesAnswer == null) { - throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in VM %s", vmInstance.getUuid())); + throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in the system VM %s", vmInstance.getUuid())); } if (!zipFilesAnswer.getResult()) { throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in VM %s due to: %s", vmInstance.getUuid(), zipFilesAnswer.getDetails())); } - String zipFileInSystemVm = zipFilesAnswer.getDetails().replace("\n", ""); + final String zipFileInSystemVm = zipFilesAnswer.getDetails().replace("\n", ""); Pair copyToSecondaryStorageResults = copyZipFileToSecondaryStorage(vmInstance, vmHostId, zipFileInSystemVm, store); if (!copyToSecondaryStorageResults.first()) { throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to: %s.", zipFileInSystemVm, store.getUri(), copyToSecondaryStorageResults.second())); } - Answer fileCleanupAnswer = deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); + final Answer fileCleanupAnswer = deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); if (fileCleanupAnswer == null) { LOGGER.error(String.format("Failed to cleanup diagnostics zip file on vm: %s", vmInstance.getUuid())); } else { @@ -240,10 +234,10 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { // Find ssvm of store VMInstanceVO ssvm = getSecondaryStorageVmInZone(zoneId); if (ssvm == null) { - throw new CloudRuntimeException("No ssvm found in Zone with ID: " + zoneId); + throw new CloudRuntimeException("No SSVM found in zone with ID: " + zoneId); } // Secondary Storage install path = "diagnostics_data/diagnostics_files_xxxx.tar - String installPath = DIAGNOSTICS_DATA_DIRECTORY + File.separator + zipFileInSystemVm.replace("/root", ""); + String installPath = DIAGNOSTICS_DIRECTORY + File.separator + zipFileInSystemVm.replace("/root", ""); return createFileDownloadUrl(store, ssvm.getHypervisorType(), installPath); } @@ -277,7 +271,7 @@ private void configureNetworkElementCommand(NetworkElementCommand cmd, VMInstanc cmd.setAccessDetail(accessDetails); } - private Answer zipDiagnosticsFilesInSystemVm(VMInstanceVO vmInstance, List fileList) { + private Answer prepareDiagnosticsFilesInSystemVm(VMInstanceVO vmInstance, List fileList) { final PrepareFilesCommand cmd = new PrepareFilesCommand(fileList, DataRetrievalTimeout.value()); configureNetworkElementCommand(cmd, vmInstance); Answer answer = agentManager.easySend(vmInstance.getHostId(), cmd); @@ -330,7 +324,7 @@ private Pair orchestrateCopyToSecondaryStorageVMware(final Data LOGGER.info(String.format("Copying %s from %s to secondary store %s", diagnosticsFile, vmSshIp, store.getUri())); // dirIn/mnt/SecStorage/uuid/diagnostics_data - String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DIAGNOSTICS_DATA_DIRECTORY); + String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DIAGNOSTICS_DIRECTORY); try { File dataDirectory = new File(dataDirectoryInSecondaryStore); boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); @@ -439,9 +433,8 @@ public boolean start() { public boolean configure(final String name, final Map params) throws ConfigurationException { if (EnableGarbageCollector.value()) { backgroundPollManager.submitTask(new GCBackgroundTask(this)); - return true; } - return false; + return true; } public static final class GCBackgroundTask extends ManagedContextRunnable implements BackgroundPollTask { @@ -452,7 +445,7 @@ public GCBackgroundTask(DiagnosticsServiceImpl serviceImpl) { } private static void deleteOldDiagnosticsFiles(File directory, String storeName) { - File[] fileList = directory.listFiles(); + final File[] fileList = directory.listFiles(); if (fileList != null) { String msg = String.format("Found %s diagnostics files in store %s for garbage collection", fileList.length, storeName); LOGGER.info(msg); @@ -478,7 +471,7 @@ protected void runInContext() { try { mountPoint = serviceImpl.mountManager.getMountPoint(store.getUri(), null); if (StringUtils.isNotBlank(mountPoint)) { - File directory = new File(mountPoint + "/" + DIAGNOSTICS_DATA_DIRECTORY); + File directory = new File(mountPoint + "/" + DIAGNOSTICS_DIRECTORY); if (directory.isDirectory()) { deleteOldDiagnosticsFiles(directory, store.getName()); } @@ -514,8 +507,14 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[]{EnableGarbageCollector, DataRetrievalTimeout, - MaximumFileAgeforGarbageCollection, GarbageCollectionInterval, DiskQuotaPercentageThreshold, - SsvmDefaultSupportedFiles, VrDefaultSupportedFiles, CpvmDefaultSupportedFiles}; + return new ConfigKey[]{ + EnableGarbageCollector, + DataRetrievalTimeout, + MaximumFileAgeforGarbageCollection, + GarbageCollectionInterval, + DiskQuotaPercentageThreshold, + SystemVMDefaultSupportedFiles, + RouterDefaultSupportedFiles + }; } } \ No newline at end of file diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java index e14b3e5d093d..a0180d1737cd 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -30,21 +30,18 @@ public interface DiagnosticsFilesList { * in the system vm and grab output for retrieval, e.g. the output from iptables-save is written to a file * which will then be retrieved. */ - ConfigKey SsvmDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.ssvm.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], /usr/local/cloud/systemvm/conf/agent.properties," + - " /usr/local/cloud/systemvm/conf/consoleproxy.properties, /var/log/cloud.log", - "List of supported diagnostics data file options for the ssvm", true); + ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, + "diagnostics.data.systemvm.defaults", "iptables, ipaddr, iproute, /etc/cloudstack-release, " + + "/usr/local/cloud/systemvm/conf/agent.properties, /usr/local/cloud/systemvm/conf/consoleproxy.properties, " + + "/var/log/cloud.log, /var/log/patchsystemvm.log /var/log/daemon.log", + "List of supported diagnostics data file options for the CPVM and SSVM.", true); - ConfigKey VrDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.vr.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], " + - "/etc/dnsmasq.conf, /etc/resolv.conf, /etc/haproxy.conf, /etc/hosts.conf, /etcdnsmaq-resolv.conf, /var/log/cloud.log, " + - "/var/log/routerServiceMonitor.log, /var/log/dnsmasq.log", - "List of supported diagnostics data file options for the VR", true); - - ConfigKey CpvmDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.cpvm.defaults", "[IPTABLES], [IFCONFIG], [ROUTE], /usr/local/cloud/systemvm/conf/agent.properties, " + - "/usr/local/cloud/systemvm/conf/consoleproxy.properties, /var/log/cloud.log", - "List of supported diagnostics data file options for the cpvm", true); + ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, + "diagnostics.data.router.defaults", "iptables, ipaddr, iproute, /etc/cloudstack-release, " + + "/etc/dnsmasq.conf, /etc/dhcphosts.txt, /etc/dhcpopts.txt, /etc/dnsmasq.d/cloud.conf, /etc/dnsmasq-resolv.conf, /var/lib/misc/dnsmasq.leases, /var/log/dnsmasq.log, " + + "/etc/hosts, /etc/resolv.conf, /etc/haproxy/haproxy.cfg, /var/log/haproxy.log, /etc/ipsec.d/l2tp.conf, /var/log/cloud.log, " + + "/var/log/routerServiceMonitor.log, /var/log/daemon.log", + "List of supported diagnostics data file options for the domain router.", true); List generateFileList(); } diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java index 9a4fc3f1d70e..b49da1d76b4c 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesListFactory.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.diagnostics.fileprocessor; +import java.util.Collections; import java.util.List; import com.cloud.vm.VirtualMachine; @@ -23,16 +24,13 @@ public class DiagnosticsFilesListFactory { public static DiagnosticsFilesList getDiagnosticsFilesList(List dataTypeList, VirtualMachine vm) { - VirtualMachine.Type vmType = vm.getType(); - - if (vmType == VirtualMachine.Type.ConsoleProxy) { - return new ConsoleProxyDiagnosticFiles(dataTypeList); - } else if (vmType == VirtualMachine.Type.SecondaryStorageVm) { - return new SecondaryStorageVmDiagnosticsFiles(dataTypeList); + final VirtualMachine.Type vmType = vm.getType(); + if (vmType == VirtualMachine.Type.ConsoleProxy || vmType == VirtualMachine.Type.SecondaryStorageVm) { + return new SystemVMDiagnosticsFiles(dataTypeList); } else if (vmType == VirtualMachine.Type.DomainRouter) { return new DomainRouterDiagnosticsFiles(dataTypeList); } else { - return null; + return (DiagnosticsFilesList) Collections.emptyList(); } } } diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java index 80fb8c02e5f6..da30e0e31722 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java @@ -37,8 +37,7 @@ public List generateFileList() { List filesList = new ArrayList<>(); if (CollectionUtils.isEmpty(dataTypeList)) { - filesList.addAll(Arrays.asList(VrDefaultSupportedFiles.value().split(","))); - + filesList.addAll(Arrays.asList(RouterDefaultSupportedFiles.value().split(","))); } else { filesList.addAll(dataTypeList); } diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java deleted file mode 100644 index 8d321f8d4ce9..000000000000 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SecondaryStorageVmDiagnosticsFiles.java +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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.fileprocessor; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.commons.collections.CollectionUtils; - -public class SecondaryStorageVmDiagnosticsFiles implements DiagnosticsFilesList { - // Optional parameters - private List dataTypeList; - - public SecondaryStorageVmDiagnosticsFiles(List dataTypeList) { - this.dataTypeList = dataTypeList; - } - - @Override - public List generateFileList() { - List filesList = new ArrayList<>(); - - if (CollectionUtils.isEmpty(dataTypeList)) { - filesList.addAll(Arrays.asList(SsvmDefaultSupportedFiles.value().split(","))); - - } else { - filesList.addAll(dataTypeList); - } - return filesList; - } -} diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java similarity index 85% rename from server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java rename to server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java index e41edeaa8a82..ccd27f964c73 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/ConsoleProxyDiagnosticFiles.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java @@ -24,11 +24,11 @@ import org.apache.commons.collections.CollectionUtils; -public class ConsoleProxyDiagnosticFiles implements DiagnosticsFilesList { +public class SystemVMDiagnosticsFiles implements DiagnosticsFilesList { // Optional parameters private List dataTypeList; - public ConsoleProxyDiagnosticFiles(List dataTypeList) { + public SystemVMDiagnosticsFiles(List dataTypeList) { this.dataTypeList = dataTypeList; } @@ -37,8 +37,7 @@ public List generateFileList() { List filesList = new ArrayList<>(); if (CollectionUtils.isEmpty(dataTypeList)) { - filesList.addAll(Arrays.asList(CpvmDefaultSupportedFiles.value().split(","))); - + filesList.addAll(Arrays.asList(SystemVMDefaultSupportedFiles.value().split(","))); } else { filesList.addAll(dataTypeList); } diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java index 538cdc2a97eb..7736e63a657a 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataObject.java @@ -17,14 +17,14 @@ // under the License. package org.apache.cloudstack.diagnostics.to; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.to.DataObjectType; -import com.cloud.agent.api.to.DataTO; - import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataTO; + public class DiagnosticsDataObject implements DataObject { private DataTO dataTO; private DataStore dataStore; @@ -75,19 +75,19 @@ public boolean delete() { } @Override - public void processEvent(ObjectInDataStoreStateMachine.Event event) { } + public void processEvent(ObjectInDataStoreStateMachine.Event event) { + } @Override public void processEvent(ObjectInDataStoreStateMachine.Event event, Answer answer) { - } @Override - public void incRefCount() { } + public void incRefCount() { + } @Override public void decRefCount() { - } @Override diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java index 7cadfa553f90..115ee718fbed 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java @@ -35,7 +35,7 @@ public DiagnosticsDataTO(Hypervisor.HypervisorType hypervisorType, DataStoreTO d @Override public DataObjectType getObjectType() { - return DataObjectType.TAR; + return DataObjectType.ARCHIVE; } @Override diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java index 7d39c26d4bbb..e0412db720fc 100644 --- a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java +++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java @@ -16,15 +16,14 @@ // under the License. package org.apache.cloudstack.diagnostics; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.List; import java.util.Objects; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; - -import org.apache.cloudstack.diagnostics.fileprocessor.ConsoleProxyDiagnosticFiles; import org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesListFactory; +import org.apache.cloudstack.diagnostics.fileprocessor.DomainRouterDiagnosticsFiles; import org.apache.cloudstack.framework.config.ConfigKey; import org.junit.After; import org.junit.Before; @@ -35,12 +34,13 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import static org.junit.Assert.assertEquals; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; @RunWith(MockitoJUnitRunner.class) public class DiagnosticsFilesListFactoryTest { - private ConsoleProxyDiagnosticFiles proxyDiagnosticFiles; + private DomainRouterDiagnosticsFiles proxyDiagnosticFiles; @Mock private VMInstanceVO vmInstance; @@ -50,7 +50,7 @@ public class DiagnosticsFilesListFactoryTest { @Before public void setUp() throws Exception { - Mockito.when(vmInstance.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy); + Mockito.when(vmInstance.getType()).thenReturn(VirtualMachine.Type.DomainRouter); } @After @@ -63,8 +63,8 @@ public void testgetDiagnosticsFilesListCpVmDataTypeList() { List dataTypeList = new ArrayList<>(); dataTypeList.add("/var/log/auth.log"); dataTypeList.add("/etc/dnsmasq.conf"); - dataTypeList.add("[IPTABLES]"); - dataTypeList.add("[IFCONFIG]"); + dataTypeList.add("iptables"); + dataTypeList.add("ipaddr"); List files = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(dataTypeList, vmInstance)).generateFileList(); @@ -72,10 +72,10 @@ public void testgetDiagnosticsFilesListCpVmDataTypeList() { } @Test - public void testDiagnisticsFileListDefaultsCpvm() { + public void testDiagnosticsFileListDefaultsRouter() { List filesList = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(null, vmInstance)).generateFileList(); - ConfigKey configKey = proxyDiagnosticFiles.CpvmDefaultSupportedFiles; + ConfigKey configKey = proxyDiagnosticFiles.RouterDefaultSupportedFiles; String[] defaultFileArray = configKey.defaultValue().split(","); assertEquals(filesList.size(), defaultFileArray.length); diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 3f0b4db5dfcd..b4736b06782b 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -16,16 +16,14 @@ # specific language governing permissions and limitations # under the License. -import zipfile -import sys -import time +import logging +import os import re -import subprocess as sp import shlex -import os -import logging - -fileList = sys.argv[1:] +import subprocess as sp +import sys +import time +import zipfile # Create zip archive and append files for retrieval @@ -33,7 +31,7 @@ def zip_files(files): fList = files compression = zipfile.ZIP_DEFLATED time_str = time.strftime("%Y%m%d-%H%M%S") - zf_name = '/root/diagnostics_files_' + time_str + '.tar' + zf_name = '/root/diagnostics_files_' + time_str + '.zip' zf = zipfile.ZipFile(zf_name, 'w', compression) ''' @@ -46,10 +44,8 @@ def zip_files(files): try: for f in fList: - # [IPTABLES], [ROUE] and [IFCONFIG], remove square brackets - if '[' in f: - shell_script = re.sub(r'\W+', "", f).strip().lower() - f = execute_shell_script(shell_script) + if f in ('iptables', 'ipaddr', 'iproute'): + f = execute_shell_script(f) files_from_shell_commands.append(f) if os.path.isfile(f): try: @@ -64,17 +60,16 @@ def zip_files(files): zf.close() print zf_name - def execute_shell_script(script): # Ex. iptables.log outputfile = script + '.log' if script == 'iptables': cmd = 'iptables-save' - elif script == 'ifconfig': - cmd = 'ifconfig' - elif script == 'route': - cmd = 'netstat -rn' + elif script == 'ipaddr': + cmd = 'ip address' + elif script == 'iproute': + cmd = 'ip rule list && ip route show table all' else: cmd = script with open(outputfile, 'wb', 0) as f: @@ -119,4 +114,5 @@ def generate_retrieved_files_txt(zip_file, files_found, files_not_found): if __name__ == '__main__': + fileList = sys.argv[1:] zip_files(fileList) diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 18a70a2b89e4..6831890ad863 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -12421,12 +12421,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it background-position: -69px -677px; } +.retrieveDiagnostics .icon, .downloadVolume .icon, .downloadTemplate .icon, .downloadISO .icon { background-position: -35px -125px; } +.retrieveDiagnostics:hover .icon, .downloadVolume:hover .icon, .downloadTemplate:hover .icon, .downloadISO:hover .icon { From 150d8d12a15f5009db839c0e960675e769264e51 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 1 Jul 2019 17:21:17 +0530 Subject: [PATCH 07/25] fix more bugs, let it return ip rule list in another log file Signed-off-by: Rohit Yadav --- api/src/main/java/com/cloud/storage/Storage.java | 5 +++-- .../cloudstack/diagnostics/DiagnosticsServiceImpl.java | 2 +- .../diagnostics/fileprocessor/DiagnosticsFilesList.java | 4 ++-- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java index 9093dc34f148..82bc5f6d4e5a 100644 --- a/api/src/main/java/com/cloud/storage/Storage.java +++ b/api/src/main/java/com/cloud/storage/Storage.java @@ -16,11 +16,11 @@ // under the License. package com.cloud.storage; -import org.apache.commons.lang.NotImplementedException; - import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang.NotImplementedException; + public class Storage { public static enum ImageFormat { QCOW2(true, true, false, "qcow2"), @@ -33,6 +33,7 @@ public static enum ImageFormat { VMDK(true, true, false, "vmdk"), VDI(true, true, false, "vdi"), TAR(false, false, false, "tar"), + ZIP(false, false, false, "zip"), DIR(false, false, false, "dir"); private final boolean supportThinProvisioning; diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 57bb4a96d3e0..554ee803f23f 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -395,7 +395,7 @@ private String createFileDownloadUrl(DataStore store, Hypervisor.HypervisorType //Create dummy TO with hyperType DataTO dataTO = new DiagnosticsDataTO(hypervisorType, store.getTO()); DataObject dataObject = new DiagnosticsDataObject(dataTO, store); - return secStore.createEntityExtractUrl(filePath, Storage.ImageFormat.TAR, dataObject); + return secStore.createEntityExtractUrl(filePath, Storage.ImageFormat.ZIP, dataObject); } private VMInstanceVO getSystemVMInstance(Long vmId) { diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java index a0180d1737cd..b1954a32bb96 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -31,13 +31,13 @@ public interface DiagnosticsFilesList { * which will then be retrieved. */ ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.systemvm.defaults", "iptables, ipaddr, iproute, /etc/cloudstack-release, " + + "diagnostics.data.systemvm.defaults", "iptables, ipaddr, iprule, iproute, /etc/cloudstack-release, " + "/usr/local/cloud/systemvm/conf/agent.properties, /usr/local/cloud/systemvm/conf/consoleproxy.properties, " + "/var/log/cloud.log, /var/log/patchsystemvm.log /var/log/daemon.log", "List of supported diagnostics data file options for the CPVM and SSVM.", true); ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.router.defaults", "iptables, ipaddr, iproute, /etc/cloudstack-release, " + + "diagnostics.data.router.defaults", "iptables, ipaddr, iprule, iproute, /etc/cloudstack-release, " + "/etc/dnsmasq.conf, /etc/dhcphosts.txt, /etc/dhcpopts.txt, /etc/dnsmasq.d/cloud.conf, /etc/dnsmasq-resolv.conf, /var/lib/misc/dnsmasq.leases, /var/log/dnsmasq.log, " + "/etc/hosts, /etc/resolv.conf, /etc/haproxy/haproxy.cfg, /var/log/haproxy.log, /etc/ipsec.d/l2tp.conf, /var/log/cloud.log, " + "/var/log/routerServiceMonitor.log, /var/log/daemon.log", diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index b4736b06782b..37e0d7df33b5 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -68,8 +68,10 @@ def execute_shell_script(script): cmd = 'iptables-save' elif script == 'ipaddr': cmd = 'ip address' + elif script == 'iprule': + cmd = 'ip rule list' elif script == 'iproute': - cmd = 'ip rule list && ip route show table all' + cmd = 'ip route show table all' else: cmd = script with open(outputfile, 'wb', 0) as f: From 1303acc182ebb37e1a00110be1106d6f37df6085 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 1 Jul 2019 17:38:43 +0530 Subject: [PATCH 08/25] fix missing iprule bug Signed-off-by: Rohit Yadav --- .../diagnostics/fileprocessor/DiagnosticsFilesList.java | 2 +- .../fileprocessor/DomainRouterDiagnosticsFiles.java | 7 ++++++- .../fileprocessor/SystemVMDiagnosticsFiles.java | 6 +++++- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java index b1954a32bb96..e05f0d2de4cc 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -33,7 +33,7 @@ public interface DiagnosticsFilesList { ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, "diagnostics.data.systemvm.defaults", "iptables, ipaddr, iprule, iproute, /etc/cloudstack-release, " + "/usr/local/cloud/systemvm/conf/agent.properties, /usr/local/cloud/systemvm/conf/consoleproxy.properties, " + - "/var/log/cloud.log, /var/log/patchsystemvm.log /var/log/daemon.log", + "/var/log/cloud.log, /var/log/patchsystemvm.log, /var/log/daemon.log", "List of supported diagnostics data file options for the CPVM and SSVM.", true); ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java index da30e0e31722..b50c4faab28b 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DomainRouterDiagnosticsFiles.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; @@ -37,7 +38,11 @@ public List generateFileList() { List filesList = new ArrayList<>(); if (CollectionUtils.isEmpty(dataTypeList)) { - filesList.addAll(Arrays.asList(RouterDefaultSupportedFiles.value().split(","))); + filesList.addAll(Arrays.stream(RouterDefaultSupportedFiles.value().split(",")) + .map(String :: trim) + .distinct() + .collect(Collectors.toList())); + } else { filesList.addAll(dataTypeList); } diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java index ccd27f964c73..4e123bf7d386 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/SystemVMDiagnosticsFiles.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; @@ -37,7 +38,10 @@ public List generateFileList() { List filesList = new ArrayList<>(); if (CollectionUtils.isEmpty(dataTypeList)) { - filesList.addAll(Arrays.asList(SystemVMDefaultSupportedFiles.value().split(","))); + filesList.addAll(Arrays.stream(SystemVMDefaultSupportedFiles.value().split(",")) + .map(String :: trim) + .distinct() + .collect(Collectors.toList())); } else { filesList.addAll(dataTypeList); } diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 37e0d7df33b5..79410137b92c 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -44,7 +44,7 @@ def zip_files(files): try: for f in fList: - if f in ('iptables', 'ipaddr', 'iproute'): + if f in ('iptables', 'ipaddr', 'iprule', 'iproute'): f = execute_shell_script(f) files_from_shell_commands.append(f) if os.path.isfile(f): From 12f920a28e1473f6c6a197e61f13f4a02c725730 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 3 Jul 2019 13:38:33 +0530 Subject: [PATCH 09/25] add support for ARCHIVE type of object to be linked/setup on secstorage Signed-off-by: Rohit Yadav --- .../hypervisor/vmware/manager/VmwareStorageManagerImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java index b73d25050c06..97b5088ccae8 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java @@ -109,6 +109,8 @@ public boolean execute(VmwareHostService hostService, CreateEntityDownloadURLCom newPath = createOvaForVolume((VolumeObjectTO)data, timeout); } else if (data.getObjectType() == DataObjectType.TEMPLATE) { newPath = createOvaForTemplate((TemplateObjectTO)data, timeout); + } else if (data.getObjectType() == DataObjectType.ARCHIVE) { + newPath = cmd.getInstallPath(); } if (newPath != null) { cmd.setInstallPath(newPath); From 528ea7fac8467300735f40eeaa4f6a330f3d8021 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Fri, 26 Jul 2019 16:46:41 +0530 Subject: [PATCH 10/25] Fix retrieving files for Xenserver --- .../resource/CitrixResourceBase.java | 29 +++++++------------ scripts/vm/hypervisor/xenserver/vmops | 29 ++++++++++++++++++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index c6f724d8b700..97bafe1ba198 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -31,8 +31,6 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -5644,27 +5642,20 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon } String dataDirectoryInSecondaryStore = localDir + "/" + DiagnosticsService.DIAGNOSTICS_DIRECTORY; - File dataDirectory = new File(dataDirectoryInSecondaryStore); - boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); + final CopyToSecondaryStorageAnswer answer; + final String scpResult = callHostPlugin(conn, "vmops", "secureCopyToHost", "hostfilepath", dataDirectoryInSecondaryStore, + "srcip", vmIP, "srcfilepath", cmd.getFileName()).toLowerCase(); - // Modify directory file permissions - Path path = Paths.get(dataDirectory.getAbsolutePath()); - setDirFilePermissions(path); - - if (existsInSecondaryStore) { - int port = 3922; - File permKey = new File("/root/.ssh/id_rsa.cloud"); - SshHelper.scpFrom(vmIP, port, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsZipFile); - } - - File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsZipFile.replace("/root", "")); - if (fileInSecondaryStore.exists()) { - return new CopyToSecondaryStorageAnswer(cmd, true, "File copied to secondary storage successfully."); + if (scpResult.contains("success")) { + answer = new CopyToSecondaryStorageAnswer(cmd, true, "File copied to secondary storage successfully."); } else { - return new CopyToSecondaryStorageAnswer(cmd, false, "Zip file " + diagnosticsZipFile.replace("/root/", "") + "not found in secondary storage"); + answer = new CopyToSecondaryStorageAnswer(cmd, false, "Zip file " + diagnosticsZipFile.replace("/root/", "") + "could not be copied to secondary storage due to " + scpResult); } + umountNfs(conn, secondaryStorageMountPath, localDir); + localDir = null; + return answer; } catch (Exception e) { - String msg = String.format("Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl, e); + String msg = "Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl + "Exception : " + e; s_logger.error(msg); return new CopyToSecondaryStorageAnswer(cmd, false, msg); } finally { diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index d87edff38822..a04b3508eb0b 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -203,6 +203,32 @@ def createFile(session, args): return txt +@echo +def secureCopyToHost(session, args): + host_filepath = args['hostfilepath'] + src_ip = args['srcip'] + src_filepath = args['srcfilepath'] + src_target = "root@" + src_ip + ":" + src_filepath + # Make any directories as needed + if not os.path.isdir(host_filepath): + try: + os.makedirs(host_filepath) + except OSError, (errno, strerror): + if not os.path.isdir(host_filepath): + errMsg = "OSError while creating " + host_filepath + " with errno: " + str(errno) + " and strerr: " + strerror + logging.debug(errMsg) + return "fail# Cannot create the directory to copy file to " + host_filepath + + # Copy file to created directory + txt="" + try: + txt = util.pread2(['scp','-P','3922','-q','-o','StrictHostKeyChecking=no','-i','/root/.ssh/id_rsa.cloud', src_target, host_filepath]) + txt = 'success#' + txt + except: + logging.debug("failed to scp source target " + src_target + " to host at file path " + host_filepath) + txt = 'fail#' + txt + return txt + @echo def createFileInDomr(session, args): src_filepath = args['srcfilepath'] @@ -1560,4 +1586,5 @@ if __name__ == "__main__": "setLinkLocalIP":setLinkLocalIP, "cleanup_rules":cleanup_rules, "createFileInDomr":createFileInDomr, - "kill_copy_process":kill_copy_process}) + "kill_copy_process":kill_copy_process, + "secureCopyToHost":secureCopyToHost}) From c7c317fd9ea05b51d9f9eb0b2c3a930bfd12b267 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 29 Jul 2019 17:12:29 +0530 Subject: [PATCH 11/25] Update get_diagnostics_files.py --- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 79410137b92c..5f284992172a 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -60,6 +60,7 @@ def zip_files(files): zf.close() print zf_name + def execute_shell_script(script): # Ex. iptables.log outputfile = script + '.log' From e46e61ffb473526aaa8d3c945d7616a784eb30ad Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Mon, 9 Dec 2019 14:53:33 +0530 Subject: [PATCH 12/25] Fix bug where executable scripts weren't handled --- .../opt/cloud/bin/get_diagnostics_files.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 5f284992172a..00457f7d8aa5 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -44,9 +44,16 @@ def zip_files(files): try: for f in fList: + f = f.strip() + if f in ('iptables', 'ipaddr', 'iprule', 'iproute'): f = execute_shell_script(f) files_from_shell_commands.append(f) + + if len(f) > 3 and f.startswith('[') and f.endswith(']'): + f = execute_shell_script(f[1:-1]) + files_from_shell_commands.append(f) + if os.path.isfile(f): try: zf.write(f, f[f.rfind('/') + 1:]) @@ -60,10 +67,17 @@ def zip_files(files): zf.close() print zf_name +def get_log_file_name(script): + prefix = script + if script.find("."): + prefix = script.split('.')[0] + + return prefix + '.log' + def execute_shell_script(script): - # Ex. iptables.log - outputfile = script + '.log' + script = script.strip() + outputfile = get_log_file_name(script) if script == 'iptables': cmd = 'iptables-save' @@ -74,7 +88,7 @@ def execute_shell_script(script): elif script == 'iproute': cmd = 'ip route show table all' else: - cmd = script + cmd = 'sh /opt/cloud/bin' + script with open(outputfile, 'wb', 0) as f: try: p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) From 7e1f9a2b3bbcba26be28422a10a5699aabf64fae Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 10 Dec 2019 14:04:55 +0530 Subject: [PATCH 13/25] Fixed error on script cmd generation --- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 00457f7d8aa5..c6393c62218c 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -88,7 +88,7 @@ def execute_shell_script(script): elif script == 'iproute': cmd = 'ip route show table all' else: - cmd = 'sh /opt/cloud/bin' + script + cmd = 'sh /opt/cloud/bin/' + script with open(outputfile, 'wb', 0) as f: try: p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) From 1b17672f82eebc315b5a9104af088780d831dca8 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 10 Dec 2019 15:32:30 +0530 Subject: [PATCH 14/25] Do not filter name for log files as it would override similar prefix script names --- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index c6393c62218c..e14fcc77d833 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -67,17 +67,10 @@ def zip_files(files): zf.close() print zf_name -def get_log_file_name(script): - prefix = script - if script.find("."): - prefix = script.split('.')[0] - - return prefix + '.log' - def execute_shell_script(script): script = script.strip() - outputfile = get_log_file_name(script) + outputfile = script + '.log' if script == 'iptables': cmd = 'iptables-save' From d528b82f7a6fe55352c815033db5f780c785fd04 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 17 Dec 2019 13:49:47 +0530 Subject: [PATCH 15/25] Addressed code review comments --- .../diagnostics/GetDiagnosticsDataCmd.java | 2 +- .../resource/CitrixResourceBase.java | 10 ++-- .../resource/XenServerStorageProcessor.java | 30 +++++----- .../Xenserver625StorageProcessor.java | 10 ++-- .../diagnostics/DiagnosticsServiceImpl.java | 57 +++++++++++-------- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java index c673c79e819b..dc058ff0a28b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java @@ -49,7 +49,7 @@ responseHasSensitiveInfo = false, requestHasSensitiveInfo = false, description = "Get diagnostics and files from system VMs", - since = "4.13.0.0", + since = "4.14.0.0", authorized = {RoleType.Admin}) public class GetDiagnosticsDataCmd extends BaseAsyncCmd { public static final String APINAME = "getDiagnosticsData"; diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index 97bafe1ba198..5153109b52fd 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -207,6 +207,7 @@ public String toString() { } private final static int BASE_TO_CONVERT_BYTES_INTO_KILOBYTES = 1024; + private final static String BASE_MOUNT_POINT_ON_REMOTE = "/var/cloud_mount/"; private static final XenServerConnectionPool ConnPool = XenServerConnectionPool.getInstance(); // static min values for guests on xenserver @@ -5635,13 +5636,13 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon try { URI uri = new URI(secondaryStorageUrl); secondaryStorageMountPath = uri.getHost() + ":" + uri.getPath(); - localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(secondaryStorageMountPath.getBytes()); + localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(secondaryStorageMountPath.getBytes()); String mountPoint = mountNfs(conn, secondaryStorageMountPath, localDir); if (org.apache.commons.lang.StringUtils.isBlank(mountPoint)) { return new CopyToSecondaryStorageAnswer(cmd, false, "Could not mount secondary storage " + secondaryStorageMountPath + " on host " + localDir); } - String dataDirectoryInSecondaryStore = localDir + "/" + DiagnosticsService.DIAGNOSTICS_DIRECTORY; + String dataDirectoryInSecondaryStore = localDir + File.separator + DiagnosticsService.DIAGNOSTICS_DIRECTORY; final CopyToSecondaryStorageAnswer answer; final String scpResult = callHostPlugin(conn, "vmops", "secureCopyToHost", "hostfilepath", dataDirectoryInSecondaryStore, "srcip", vmIP, "srcfilepath", cmd.getFileName()).toLowerCase(); @@ -5657,6 +5658,7 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon } catch (Exception e) { String msg = "Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl + "Exception : " + e; s_logger.error(msg); + e.printStackTrace(); return new CopyToSecondaryStorageAnswer(cmd, false, msg); } finally { if (localDir != null) umountNfs(conn, secondaryStorageMountPath, localDir); @@ -5665,7 +5667,7 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon private String mountNfs(Connection conn, String remoteDir, String localDir) { if (localDir == null) { - localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); + localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(remoteDir.getBytes()); } return callHostPlugin(conn, "cloud-plugin-storage", "mountNfsSecondaryStorage", "localDir", localDir, "remoteDir", remoteDir); } @@ -5673,7 +5675,7 @@ private String mountNfs(Connection conn, String remoteDir, String localDir) { // Unmount secondary storage from host private void umountNfs(Connection conn, String remoteDir, String localDir) { if (localDir == null) { - localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); + localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(remoteDir.getBytes()); } String result = callHostPlugin(conn, "cloud-plugin-storage", "umountNfsSecondaryStorage", "localDir", localDir, "remoteDir", remoteDir); if (org.apache.commons.lang.StringUtils.isBlank(result)) { diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java index fc72e79b074f..458b7d6377a2 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java @@ -31,21 +31,6 @@ import java.util.Set; import java.util.UUID; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.log4j.Logger; -import org.apache.xmlrpc.XmlRpcException; - -import com.google.common.annotations.VisibleForTesting; -import com.xensource.xenapi.Connection; -import com.xensource.xenapi.SR; -import com.xensource.xenapi.Types; -import com.xensource.xenapi.Types.BadServerResponse; -import com.xensource.xenapi.Types.VmPowerState; -import com.xensource.xenapi.Types.XenAPIException; -import com.xensource.xenapi.VBD; -import com.xensource.xenapi.VDI; -import com.xensource.xenapi.VM; - import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachCommand; @@ -67,6 +52,9 @@ import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; import com.cloud.agent.api.Answer; import com.cloud.agent.api.to.DataObjectType; @@ -86,12 +74,24 @@ import com.cloud.storage.resource.StorageProcessor; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.storage.S3.ClientOptions; +import com.google.common.annotations.VisibleForTesting; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.SR; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.Types.BadServerResponse; +import com.xensource.xenapi.Types.VmPowerState; +import com.xensource.xenapi.Types.XenAPIException; +import com.xensource.xenapi.VBD; +import com.xensource.xenapi.VDI; +import com.xensource.xenapi.VM; public class XenServerStorageProcessor implements StorageProcessor { private static final Logger s_logger = Logger.getLogger(XenServerStorageProcessor.class); protected CitrixResourceBase hypervisorResource; protected String BaseMountPointOnHost = "/var/run/cloud_mount"; + protected final static String BASE_MOUNT_POINT_ON_REMOTE = "/var/cloud_mount/"; + public XenServerStorageProcessor(final CitrixResourceBase resource) { hypervisorResource = resource; } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java index ddafc1598743..a2c8b708bf3a 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java @@ -71,7 +71,7 @@ public Xenserver625StorageProcessor(final CitrixResourceBase resource) { private void mountNfs(Connection conn, String remoteDir, String localDir) { if (localDir == null) { - localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); + localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(remoteDir.getBytes()); } String result = hypervisorResource.callHostPluginAsync(conn, "cloud-plugin-storage", "mountNfsSecondaryStorage", 100 * 1000, "localDir", localDir, "remoteDir", remoteDir); if (StringUtils.isBlank(result)) { @@ -241,7 +241,7 @@ protected void unplugPbd(Connection conn, PBD pbd) { } protected SR createFileSr(Connection conn, String remotePath, String dir) { - String localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remotePath.getBytes()); + String localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(remotePath.getBytes()); mountNfs(conn, remotePath, localDir); return createFileSR(conn, localDir + "/" + dir); } @@ -563,7 +563,7 @@ public Answer backupSnapshot(final CopyCommand cmd) { SR snapshotSr = null; Task task = null; try { - final String localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(secondaryStorageMountPath.getBytes()); + final String localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(secondaryStorageMountPath.getBytes()); mountNfs(conn, secondaryStorageMountPath, localDir); final boolean result = makeDirectory(conn, localDir + "/" + folder); if (!result) { @@ -1074,7 +1074,7 @@ public Answer createTemplateFromSnapshot(final CopyCommand cmd) { srcSr = createFileSr(conn, srcUri.getHost() + ":" + srcUri.getPath(), srcDir); final String destNfsPath = destUri.getHost() + ":" + destUri.getPath(); - final String localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(destNfsPath.getBytes()); + final String localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(destNfsPath.getBytes()); mountNfs(conn, destUri.getHost() + ":" + destUri.getPath(), localDir); makeDirectory(conn, localDir + "/" + destDir); @@ -1216,7 +1216,7 @@ private Answer createTemplateFromSnapshot2(final CopyCommand cmd) { srcSr = hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName, chapInitiatorUsername, chapInitiatorSecret, false, srType, true); final String destNfsPath = destUri.getHost() + ":" + destUri.getPath(); - final String localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(destNfsPath.getBytes()); + final String localDir = BASE_MOUNT_POINT_ON_REMOTE + UUID.nameUUIDFromBytes(destNfsPath.getBytes()); mountNfs(conn, destNfsPath, localDir); makeDirectory(conn, localDir + "/" + destDir); diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 554ee803f23f..936382ea45a6 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -161,7 +161,7 @@ public Map runDiagnosticsCommand(final RunDiagnosticsCmd cmd) { detailsMap = ((DiagnosticsAnswer) answer).getExecutionDetails(); return detailsMap; } else { - throw new CloudRuntimeException("Failed to execute diagnostics command on remote host: " + vmInstance.getHostName()); + throw new CloudRuntimeException("Failed to execute diagnostics command for system vm: " + vmInstance + ", on remote host: " + vmInstance.getHostName()); } } @@ -188,22 +188,13 @@ protected String prepareShellCmd(String cmdType, String ipAddress, String option } } - @Override - @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "getting diagnostics files on system vm", async = true) - public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { - Long vmId = cmd.getId(); - List optionalFilesList = cmd.getFilesList(); - VMInstanceVO vmInstance = getSystemVMInstance(vmId); - long zoneId = vmInstance.getDataCenterId(); - + private String zipFilesInSystemVm(VMInstanceVO vmInstance, List optionalFilesList) { List fileList = getFileListToBeRetrieved(optionalFilesList, vmInstance); if (CollectionUtils.isEmpty(fileList)) { throw new CloudRuntimeException("Failed to generate diagnostics file list for retrieval."); } - final Long vmHostId = vmInstance.getHostId(); - final DataStore store = getImageStore(vmInstance.getDataCenterId()); final Answer zipFilesAnswer = prepareDiagnosticsFilesInSystemVm(vmInstance, fileList); if (zipFilesAnswer == null) { @@ -214,28 +205,30 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { throw new CloudRuntimeException(String.format("Failed to generate diagnostics zip file in VM %s due to: %s", vmInstance.getUuid(), zipFilesAnswer.getDetails())); } - final String zipFileInSystemVm = zipFilesAnswer.getDetails().replace("\n", ""); - Pair copyToSecondaryStorageResults = copyZipFileToSecondaryStorage(vmInstance, vmHostId, zipFileInSystemVm, store); + return zipFilesAnswer.getDetails().replace("\n", ""); + } - if (!copyToSecondaryStorageResults.first()) { - throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to: %s.", zipFileInSystemVm, store.getUri(), copyToSecondaryStorageResults.second())); - } + @Override + @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "getting diagnostics files on system vm", async = true) + public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) { + final Long vmId = cmd.getId(); + final List optionalFilesList = cmd.getFilesList(); + final VMInstanceVO vmInstance = getSystemVMInstance(vmId); + final DataStore store = getImageStore(vmInstance.getDataCenterId()); - final Answer fileCleanupAnswer = deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); - if (fileCleanupAnswer == null) { - LOGGER.error(String.format("Failed to cleanup diagnostics zip file on vm: %s", vmInstance.getUuid())); - } else { - if (!fileCleanupAnswer.getResult()) { - LOGGER.error(String.format("Zip file cleanup for vm %s has failed with: %s", vmInstance.getUuid(), fileCleanupAnswer.getDetails())); - } - } + final String zipFileInSystemVm = zipFilesInSystemVm(vmInstance, optionalFilesList); + final Long vmHostId = vmInstance.getHostId(); + copyZipFileToSecondaryStorage(vmInstance, vmHostId, zipFileInSystemVm, store); + deleteDiagnosticsZipFileInsystemVm(vmInstance, zipFileInSystemVm); // Now we need to create the file download URL // Find ssvm of store + final long zoneId = vmInstance.getDataCenterId(); VMInstanceVO ssvm = getSecondaryStorageVmInZone(zoneId); if (ssvm == null) { throw new CloudRuntimeException("No SSVM found in zone with ID: " + zoneId); } + // Secondary Storage install path = "diagnostics_data/diagnostics_files_xxxx.tar String installPath = DIAGNOSTICS_DIRECTORY + File.separator + zipFileInSystemVm.replace("/root", ""); return createFileDownloadUrl(store, ssvm.getHypervisorType(), installPath); @@ -260,6 +253,11 @@ private Pair copyZipFileToSecondaryStorage(VMInstanceVO vmInsta } else { copyResult = orchestrateCopyToSecondaryStorageNonVMware(store, vmControlIp, fileToCopy, vmHostId); } + + if (!copyResult.first()) { + throw new CloudRuntimeException(String.format("Failed to copy %s to secondary storage %s due to: %s.", fileToCopy, store.getUri(), copyResult.second())); + } + return copyResult; } @@ -281,7 +279,16 @@ private Answer prepareDiagnosticsFilesInSystemVm(VMInstanceVO vmInstance, List Date: Tue, 17 Dec 2019 15:25:09 +0530 Subject: [PATCH 16/25] log error instead of printstacktrace --- .../hypervisor/xenserver/resource/CitrixResourceBase.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index 5153109b52fd..d284aececa13 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -5657,8 +5657,7 @@ public Answer copyDiagnosticsFileToSecondaryStorage(Connection conn, CopyToSecon return answer; } catch (Exception e) { String msg = "Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl + "Exception : " + e; - s_logger.error(msg); - e.printStackTrace(); + s_logger.error(msg, e); return new CopyToSecondaryStorageAnswer(cmd, false, msg); } finally { if (localDir != null) umountNfs(conn, secondaryStorageMountPath, localDir); From ac7a6f99f32756cf24f8eceae9ac24fdc94bd8db Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 24 Dec 2019 15:34:14 +0530 Subject: [PATCH 17/25] Treat script as executable and shell script --- .../opt/cloud/bin/get_diagnostics_files.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index e14fcc77d833..9f24639113a9 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -68,10 +68,11 @@ def zip_files(files): print zf_name -def execute_shell_script(script): - script = script.strip() - outputfile = script + '.log' +def get_cmd(script): + if script is None or len(script) == 0: + return None + cmd = None if script == 'iptables': cmd = 'iptables-save' elif script == 'ipaddr': @@ -81,8 +82,19 @@ def execute_shell_script(script): elif script == 'iproute': cmd = 'ip route show table all' else: - cmd = 'sh /opt/cloud/bin/' + script + cmd = '/opt/cloud/bin/' + script + if not os.path.isfile(cmd.split(' ')[0]): + cmd = None + + return cmd + + +def execute_shell_script(script): + script = script.strip() + outputfile = script + '.log' + with open(outputfile, 'wb', 0) as f: + cmd = get_cmd(script) try: p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = p.communicate() From 9d2671de23513c58daac1294a03adfc22782cef6 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 24 Dec 2019 15:49:27 +0530 Subject: [PATCH 18/25] Check missing script name case and write to output instead of catching exception --- .../opt/cloud/bin/get_diagnostics_files.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index 9f24639113a9..a776f98bcced 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -94,15 +94,18 @@ def execute_shell_script(script): outputfile = script + '.log' with open(outputfile, 'wb', 0) as f: - cmd = get_cmd(script) try: - p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) - stdout, stderr = p.communicate() - return_code = p.returncode - if return_code is 0: - f.write(stdout) + cmd = get_cmd(script) + if cmd is None: + f.write('Unable to generate command for ' + script + ', perhaps missing file') else: - f.write(stderr) + p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) + stdout, stderr = p.communicate() + return_code = p.returncode + if return_code is 0: + f.write(stdout) + else: + f.write(stderr) except OSError as ex: delete_tmp_file_cmd = 'rm -f %s' % outputfile sp.check_call(shlex.split(delete_tmp_file_cmd)) From f8818d79f0955cf5138f95a886de218fc392a69d Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Mon, 30 Dec 2019 15:17:26 +0530 Subject: [PATCH 19/25] Use shell = true instead of shlex to support any executable --- systemvm/debian/opt/cloud/bin/get_diagnostics_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py index a776f98bcced..b95dfb5420c1 100755 --- a/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py +++ b/systemvm/debian/opt/cloud/bin/get_diagnostics_files.py @@ -99,7 +99,7 @@ def execute_shell_script(script): if cmd is None: f.write('Unable to generate command for ' + script + ', perhaps missing file') else: - p = sp.Popen(shlex.split(cmd), stdout=sp.PIPE, stderr=sp.PIPE) + p = sp.Popen(cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = p.communicate() return_code = p.returncode if return_code is 0: From 6d27758e41c379adaa14d50219d03295cd30bb72 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Thu, 2 Jan 2020 23:51:16 +0530 Subject: [PATCH 20/25] fix xenserver bug --- scripts/vm/hypervisor/xenserver/vmops | 3 ++- .../apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index a04b3508eb0b..dd03ded95927 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -223,9 +223,10 @@ def secureCopyToHost(session, args): txt="" try: txt = util.pread2(['scp','-P','3922','-q','-o','StrictHostKeyChecking=no','-i','/root/.ssh/id_rsa.cloud', src_target, host_filepath]) + util.pread2(['chmod', 'a+r', os.path.join(host_filepath, os.path.basename(src_filepath))]) txt = 'success#' + txt except: - logging.debug("failed to scp source target " + src_target + " to host at file path " + host_filepath) + logging.error("failed to scp source target " + src_target + " to host at file path " + host_filepath) txt = 'fail#' + txt return txt diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 936382ea45a6..4265d707265b 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -351,7 +351,7 @@ private Pair orchestrateCopyToSecondaryStorageVMware(final Data success = fileInSecondaryStore.exists(); } catch (Exception e) { String msg = String.format("Exception caught during scp from %s to secondary store %s: ", vmSshIp, dataDirectoryInSecondaryStore); - LOGGER.error(msg); + LOGGER.error(msg, e); return new Pair<>(false, msg); } finally { // umount secondary storage From 4cea9b0dc6fac47b353be8de46fdae051251854f Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Fri, 3 Jan 2020 13:35:48 +0530 Subject: [PATCH 21/25] don't set dir permission for vmware --- .../cloudstack/diagnostics/DiagnosticsServiceImpl.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 4265d707265b..df13a530d5a0 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -18,14 +18,11 @@ package org.apache.cloudstack.diagnostics; import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.getTimeDifference; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.umountSecondaryStorage; import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.RouterDefaultSupportedFiles; import static org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesList.SystemVMDefaultSupportedFiles; import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -336,10 +333,6 @@ private Pair orchestrateCopyToSecondaryStorageVMware(final Data File dataDirectory = new File(dataDirectoryInSecondaryStore); boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); - // Modify directory file permissions - Path path = Paths.get(dataDirectory.getAbsolutePath()); - setDirFilePermissions(path); - if (existsInSecondaryStore) { // scp from system VM to mounted sec storage directory int port = 3922; From bdc29aa0c178daaba2c12c713f1138133a8b9a57 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Wed, 8 Jan 2020 13:52:34 +0530 Subject: [PATCH 22/25] Code review comments - refactoring --- .../diagnostics/DiagnosticsServiceImpl.java | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index df13a530d5a0..232d9330af7b 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -246,9 +246,9 @@ private Pair copyZipFileToSecondaryStorage(VMInstanceVO vmInsta } Pair copyResult; if (vmInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) { - copyResult = orchestrateCopyToSecondaryStorageVMware(store, vmControlIp, fileToCopy); + copyResult = copyToSecondaryStorageVMware(store, vmControlIp, fileToCopy); } else { - copyResult = orchestrateCopyToSecondaryStorageNonVMware(store, vmControlIp, fileToCopy, vmHostId); + copyResult = copyToSecondaryStorageNonVMware(store, vmControlIp, fileToCopy, vmHostId); } if (!copyResult.first()) { @@ -306,7 +306,7 @@ private List getFileListToBeRetrieved(List optionalFileList, VMI return fileList; } - private Pair orchestrateCopyToSecondaryStorageNonVMware(final DataStore store, final String vmControlIp, String fileToCopy, Long vmHostId) { + private Pair copyToSecondaryStorageNonVMware(final DataStore store, final String vmControlIp, String fileToCopy, Long vmHostId) { CopyToSecondaryStorageCommand toSecondaryStorageCommand = new CopyToSecondaryStorageCommand(store.getUri(), vmControlIp, fileToCopy); Answer copyToSecondaryAnswer = agentManager.easySend(vmHostId, toSecondaryStorageCommand); Pair copyAnswer; @@ -318,41 +318,37 @@ private Pair orchestrateCopyToSecondaryStorageNonVMware(final D return copyAnswer; } - private Pair orchestrateCopyToSecondaryStorageVMware(final DataStore store, final String vmSshIp, String diagnosticsFile) { - String mountPoint; - boolean success; - - Integer nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId()); - mountPoint = mountManager.getMountPoint(store.getUri(), nfsVersion); - if (StringUtils.isNotBlank(mountPoint)) { - LOGGER.info(String.format("Copying %s from %s to secondary store %s", diagnosticsFile, vmSshIp, store.getUri())); - - // dirIn/mnt/SecStorage/uuid/diagnostics_data - String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DIAGNOSTICS_DIRECTORY); - try { - File dataDirectory = new File(dataDirectoryInSecondaryStore); - boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); - - if (existsInSecondaryStore) { - // scp from system VM to mounted sec storage directory - int port = 3922; - File permKey = new File("/var/cloudstack/management/.ssh/id_rsa"); - SshHelper.scpFrom(vmSshIp, port, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsFile); - } - // Verify File copy to Secondary Storage - File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsFile.replace("/root", "")); - success = fileInSecondaryStore.exists(); - } catch (Exception e) { - String msg = String.format("Exception caught during scp from %s to secondary store %s: ", vmSshIp, dataDirectoryInSecondaryStore); - LOGGER.error(msg, e); - return new Pair<>(false, msg); - } finally { - // umount secondary storage - umountSecondaryStorage(mountPoint); - } - } else { + private Pair copyToSecondaryStorageVMware(final DataStore store, final String vmSshIp, String diagnosticsFile) { + LOGGER.info(String.format("Copying %s from %s to secondary store %s", diagnosticsFile, vmSshIp, store.getUri())); + boolean success = false; + String mountPoint = mountManager.getMountPoint(store.getUri(), imageStoreDetailsUtil.getNfsVersion(store.getId())); + if (StringUtils.isBlank(mountPoint)) { + LOGGER.error("Failed to generate mount point for copying to secondary storage for " + store.getName()); return new Pair<>(false, "Failed to mount secondary storage:" + store.getName()); } + + // dirIn/mnt/SecStorage/uuid/diagnostics_data + String dataDirectoryInSecondaryStore = String.format("%s/%s", mountPoint, DIAGNOSTICS_DIRECTORY); + try { + File dataDirectory = new File(dataDirectoryInSecondaryStore); + boolean existsInSecondaryStore = dataDirectory.exists() || dataDirectory.mkdir(); + if (existsInSecondaryStore) { + // scp from system VM to mounted sec storage directory + File permKey = new File("/var/cloudstack/management/.ssh/id_rsa"); + SshHelper.scpFrom(vmSshIp, 3922, "root", permKey, dataDirectoryInSecondaryStore, diagnosticsFile); + } + + // Verify File copy to Secondary Storage + File fileInSecondaryStore = new File(dataDirectoryInSecondaryStore + diagnosticsFile.replace("/root", "")); + success = fileInSecondaryStore.exists(); + } catch (Exception e) { + String msg = String.format("Exception caught during scp from %s to secondary store %s: ", vmSshIp, dataDirectoryInSecondaryStore); + LOGGER.error(msg, e); + return new Pair<>(false, msg); + } finally { + umountSecondaryStorage(mountPoint); + } + return new Pair<>(success, "File copied to secondary storage successfully"); } From 179d1ae9344e765bfde1915bd2dbfdab2dd30bb3 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Wed, 8 Jan 2020 20:49:17 +0530 Subject: [PATCH 23/25] Add check for possible NPE --- .../diagnostics/DiagnosticsServiceImpl.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 232d9330af7b..1ff9c8194c57 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -446,11 +446,9 @@ private static void deleteOldDiagnosticsFiles(File directory, String storeName) String msg = String.format("Found %s diagnostics files in store %s for garbage collection", fileList.length, storeName); LOGGER.info(msg); for (File file : fileList) { - if (file.isFile()) { - if (MaximumFileAgeforGarbageCollection.value() <= getTimeDifference(file)) { - boolean success = file.delete(); - LOGGER.info(file.getName() + " delete status: " + success); - } + if (file.isFile() && MaximumFileAgeforGarbageCollection.value() <= getTimeDifference(file)) { + boolean success = file.delete(); + LOGGER.info(file.getName() + " delete status: " + success); } } } @@ -463,21 +461,8 @@ protected void runInContext() { // Get All Image Stores in current running Zone List storeList = serviceImpl.storeMgr.getImageStoresByScope(new ZoneScope(vo.getId())); for (DataStore store : storeList) { - String mountPoint = null; - try { - mountPoint = serviceImpl.mountManager.getMountPoint(store.getUri(), null); - if (StringUtils.isNotBlank(mountPoint)) { - File directory = new File(mountPoint + "/" + DIAGNOSTICS_DIRECTORY); - if (directory.isDirectory()) { - deleteOldDiagnosticsFiles(directory, store.getName()); - } - } - } finally { - // umount secondary storage - umountSecondaryStorage(mountPoint); - } + cleanupOldDiagnosticFiles(store); } - } } @@ -486,6 +471,23 @@ public Long getDelay() { // In Milliseconds return GarbageCollectionInterval.value() * 1000L; } + + private void cleanupOldDiagnosticFiles(DataStore store) { + String mountPoint = null; + try { + mountPoint = serviceImpl.mountManager.getMountPoint(store.getUri(), null); + if (StringUtils.isNotBlank(mountPoint)) { + File directory = new File(mountPoint + File.separator + DIAGNOSTICS_DIRECTORY); + if (directory.isDirectory()) { + deleteOldDiagnosticsFiles(directory, store.getName()); + } + } + } finally { + if (StringUtils.isNotBlank(mountPoint)) { + umountSecondaryStorage(mountPoint); + } + } + } } @Override From de809ae25149b4d752f840ae967a3fd38ad9b316 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Thu, 9 Jan 2020 14:30:47 +0530 Subject: [PATCH 24/25] Remove unused imoprt after rebase --- .../hypervisor/xenserver/resource/CitrixResourceBase.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index d284aececa13..ea168d5275f1 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -16,8 +16,6 @@ // under the License. package com.cloud.hypervisor.xenserver.resource; -import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.setDirFilePermissions; - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -53,9 +51,8 @@ import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; -import org.apache.cloudstack.diagnostics.DiagnosticsHelper; -import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility; import org.apache.cloudstack.diagnostics.DiagnosticsService; +import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.commons.collections.CollectionUtils; From b3d46d832d8d306e269247f640aa2c3be1b8092c Mon Sep 17 00:00:00 2001 From: Anurag Awasthi Date: Tue, 14 Jan 2020 20:00:03 +0530 Subject: [PATCH 25/25] Add better description for configs --- .../diagnostics/DiagnosticsServiceImpl.java | 17 ++++++++++++----- .../fileprocessor/DiagnosticsFilesList.java | 14 +++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 1ff9c8194c57..49ad2159698f 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -103,17 +103,24 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ // These 2 settings should require a restart of the management server private static final ConfigKey EnableGarbageCollector = new ConfigKey<>("Advanced", Boolean.class, - "diagnostics.data.gc.enable", "true", "enable the diagnostics data files garbage collector", false); + "diagnostics.data.gc.enable", "true", + "Enable the garbage collector background task to delete old files from secondary storage.", false); private static final ConfigKey GarbageCollectionInterval = new ConfigKey<>("Advanced", Integer.class, - "diagnostics.data.gc.interval", "86400", "garbage collection interval in seconds", false); + "diagnostics.data.gc.interval", "86400", + "The interval at which the garbage collector background tasks in seconds", false); // These are easily computed properties and need not need a restart of the management server private static final ConfigKey DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class, - "diagnostics.data.retrieval.timeout", "1800", "overall data retrieval timeout in seconds", true); + "diagnostics.data.retrieval.timeout", "1800", + "Overall system VM script execution time out in seconds.", true); private static final ConfigKey MaximumFileAgeforGarbageCollection = new ConfigKey<>("Advanced", Long.class, - "diagnostics.data.max.file.age", "86400", "maximum file age for garbage collection in seconds", true); + "diagnostics.data.max.file.age", "86400", + "Sets the maximum time in seconds a file can stay in secondary storage before it is deleted.", true); private static final ConfigKey DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class, - "diagnostics.data.disable.threshold", "0.9", "Minimum disk space percentage to initiate diagnostics file retrieval", true); + "diagnostics.data.disable.threshold", "0.9", + "Sets the secondary storage disk utilisation percentage for file retrieval. " + + "Used to look for suitable secondary storage with enough space, otherwise an exception is " + + "thrown when no secondary store is found.", true); @Override @ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "running diagnostics on system vm", async = true) diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java index e05f0d2de4cc..cd9baa9f5d28 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -31,16 +31,16 @@ public interface DiagnosticsFilesList { * which will then be retrieved. */ ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.systemvm.defaults", "iptables, ipaddr, iprule, iproute, /etc/cloudstack-release, " + - "/usr/local/cloud/systemvm/conf/agent.properties, /usr/local/cloud/systemvm/conf/consoleproxy.properties, " + - "/var/log/cloud.log, /var/log/patchsystemvm.log, /var/log/daemon.log", + "diagnostics.data.systemvm.defaults", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + + "/usr/local/cloud/systemvm/conf/agent.properties,/usr/local/cloud/systemvm/conf/consoleproxy.properties," + + "/var/log/cloud.log,/var/log/patchsystemvm.log,/var/log/daemon.log", "List of supported diagnostics data file options for the CPVM and SSVM.", true); ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.router.defaults", "iptables, ipaddr, iprule, iproute, /etc/cloudstack-release, " + - "/etc/dnsmasq.conf, /etc/dhcphosts.txt, /etc/dhcpopts.txt, /etc/dnsmasq.d/cloud.conf, /etc/dnsmasq-resolv.conf, /var/lib/misc/dnsmasq.leases, /var/log/dnsmasq.log, " + - "/etc/hosts, /etc/resolv.conf, /etc/haproxy/haproxy.cfg, /var/log/haproxy.log, /etc/ipsec.d/l2tp.conf, /var/log/cloud.log, " + - "/var/log/routerServiceMonitor.log, /var/log/daemon.log", + "diagnostics.data.router.defaults", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + + "/etc/dnsmasq.conf,/etc/dhcphosts.txt,/etc/dhcpopts.txt,/etc/dnsmasq.d/cloud.conf,/etc/dnsmasq-resolv.conf,/var/lib/misc/dnsmasq.leases,/var/log/dnsmasq.log," + + "/etc/hosts,/etc/resolv.conf,/etc/haproxy/haproxy.cfg,/var/log/haproxy.log,/etc/ipsec.d/l2tp.conf,/var/log/cloud.log," + + "/var/log/routerServiceMonitor.log,/var/log/daemon.log", "List of supported diagnostics data file options for the domain router.", true); List generateFileList();