Get Diagnostics: Download logs and diagnostics data from SSVM, CPVM, Router (#3350)

* * Complete API implementation
* Complete UI integration
* Complete marvin test
* Complete Secondary storage GC background task

* improve UI labels

* slight reword and add another missing description

* improve download message clarity

* Address comments

* multiple fixes and cleanups

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>

* fix more bugs, let it return ip rule list in another log file

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>

* fix missing iprule bug

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>

* add support for ARCHIVE type of object to be linked/setup on secstorage

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>

* Fix retrieving files for Xenserver

* Update get_diagnostics_files.py

* Fix bug where executable scripts weren't handled

* Fixed error on script cmd generation

* Do not filter name for log files as it would override similar prefix script names

* Addressed code review comments

* log error instead of printstacktrace

* Treat script as executable and shell script

* Check missing script name case and write to output instead of catching exception

* Use shell = true instead of shlex to support any executable

* fix xenserver bug

* don't set dir permission for vmware

* Code review comments - refactoring

* Add check for possible NPE

* Remove unused imoprt after rebase

* Add better description for configs

Co-authored-by: Nicolas Vazquez <nicovazquez90@gmail.com>
Co-authored-by: Rohit Yadav <rohit@apache.org>
Co-authored-by: Anurag Awasthi <anurag.awasthi@shapeblue.com>
This commit is contained in:
Paul Angus 2020-01-15 10:38:33 +00:00 committed by Andrija Panic
parent cc093c61a1
commit be97470d83
43 changed files with 2482 additions and 87 deletions

View File

@ -19,5 +19,5 @@
package com.cloud.agent.api.to;
public enum DataObjectType {
VOLUME, SNAPSHOT, TEMPLATE
VOLUME, SNAPSHOT, TEMPLATE, ARCHIVE
}

View File

@ -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 class Storage {
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;

View File

@ -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 {

View File

@ -0,0 +1,157 @@
// 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 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 and files from system VMs",
since = "4.14.0.0",
authorized = {RoleType.Admin})
public class GetDiagnosticsDataCmd extends BaseAsyncCmd {
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<String> filesList;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public List<String> 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;
}
}

View File

@ -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;
}
}

View File

@ -18,12 +18,16 @@
//
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 {
String DIAGNOSTICS_DIRECTORY = "diagnostics";
Map<String, String> runDiagnosticsCommand(RunDiagnosticsCmd cmd);
String getDiagnosticsDataCommand(GetDiagnosticsDataCmd getDiagnosticsDataCmd);
}

View File

@ -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";
}

View File

@ -23,8 +23,11 @@ import java.io.IOException;
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 @@ public class VirtualRoutingResource {
} 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 @@ public class VirtualRoutingResource {
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()) {

View File

@ -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);
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<String> filesToRetrieveList;
private long timeout;
public PrepareFilesCommand(List<String> filesToRetrieve, long timeout) {
this.filesToRetrieveList = filesToRetrieve;
this.timeout = timeout;
}
public List<String> getFilesToRetrieveList() {
return filesToRetrieveList;
}
public long getTimeout() {
return timeout;
}
@Override
public boolean isQuery() {
return true;
}
}

View File

@ -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";

View File

@ -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 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.DiagnosticsService;
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;
@ResourceWrapper(handles = CopyToSecondaryStorageCommand.class)
public class LibvirtCopyToSecondaryStorageWrapper extends CommandWrapper<CopyToSecondaryStorageCommand, Answer, LibvirtComputingResource> {
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, DiagnosticsService.DIAGNOSTICS_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) {
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();
}
}
}

View File

@ -109,6 +109,8 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager {
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);

View File

@ -49,6 +49,9 @@ import javax.naming.ConfigurationException;
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.DiagnosticsService;
import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
@ -201,6 +204,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
}
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
@ -5612,4 +5616,67 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
}
/**
* 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 = 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 + File.separator + DiagnosticsService.DIAGNOSTICS_DIRECTORY;
final CopyToSecondaryStorageAnswer answer;
final String scpResult = callHostPlugin(conn, "vmops", "secureCopyToHost", "hostfilepath", dataDirectoryInSecondaryStore,
"srcip", vmIP, "srcfilepath", cmd.getFileName()).toLowerCase();
if (scpResult.contains("success")) {
answer = new CopyToSecondaryStorageAnswer(cmd, true, "File copied to secondary storage successfully.");
} else {
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 = "Exception caught zip file copy to secondary storage URI: " + secondaryStorageUrl + "Exception : " + e;
s_logger.error(msg, e);
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 = BASE_MOUNT_POINT_ON_REMOTE + 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 = 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)) {
String errMsg = "Could not umount secondary storage " + remoteDir + " on host " + localDir;
s_logger.warn(errMsg);
}
}
}

View File

@ -31,21 +31,6 @@ import java.util.Map;
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.PrimaryDataStoreTO;
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.Storage.ImageFormat;
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;
}

View File

@ -71,7 +71,7 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor {
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 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor {
}
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 class Xenserver625StorageProcessor extends XenServerStorageProcessor {
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 class Xenserver625StorageProcessor extends XenServerStorageProcessor {
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 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor {
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);

View File

@ -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<CopyToSecondaryStorageCommand, Answer, CitrixResourceBase> {
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);
}
}

View File

@ -203,6 +203,33 @@ 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])
util.pread2(['chmod', 'a+r', os.path.join(host_filepath, os.path.basename(src_filepath))])
txt = 'success#' + txt
except:
logging.error("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 +1587,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})

View File

@ -1377,6 +1377,20 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
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.
*/

View File

@ -0,0 +1,80 @@
//
// 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 void setDirFilePermissions(Path path) throws java.io.IOException {
Set<PosixFilePermission> 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;
}
}

View File

@ -17,33 +17,67 @@
// under the License.
package org.apache.cloudstack.diagnostics;
import static org.apache.cloudstack.diagnostics.DiagnosticsHelper.getTimeDifference;
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.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 {
public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService, Configurable {
private static final Logger LOGGER = Logger.getLogger(DiagnosticsServiceImpl.class);
@Inject
@ -54,6 +88,39 @@ 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;
// These 2 settings should require a restart of the management server
private static final ConfigKey<Boolean> EnableGarbageCollector = new ConfigKey<>("Advanced", Boolean.class,
"diagnostics.data.gc.enable", "true",
"Enable the garbage collector background task to delete old files from secondary storage.", false);
private static final ConfigKey<Integer> GarbageCollectionInterval = new ConfigKey<>("Advanced", Integer.class,
"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<Long> DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class,
"diagnostics.data.retrieval.timeout", "1800",
"Overall system VM script execution time out in seconds.", true);
private static final ConfigKey<Long> MaximumFileAgeforGarbageCollection = new ConfigKey<>("Advanced", Long.class,
"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<Double> DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class,
"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)
@ -92,13 +159,13 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ
Map<String, String> 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 for system vm: " + vmInstance + ", on remote host: " + vmInstance.getHostName());
}
}
@ -110,7 +177,6 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ
final Pattern pattern = Pattern.compile(regex);
return pattern.matcher(optionalArgs).find();
}
}
protected String prepareShellCmd(String cmdType, String ipAddress, String optionalParams) {
@ -126,10 +192,334 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ
}
}
private String zipFilesInSystemVm(VMInstanceVO vmInstance, List<String> optionalFilesList) {
List<String> fileList = getFileListToBeRetrieved(optionalFilesList, vmInstance);
if (CollectionUtils.isEmpty(fileList)) {
throw new CloudRuntimeException("Failed to generate diagnostics file list for retrieval.");
}
final Answer zipFilesAnswer = prepareDiagnosticsFilesInSystemVm(vmInstance, fileList);
if (zipFilesAnswer == null) {
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()));
}
return zipFilesAnswer.getDetails().replace("\n", "");
}
@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<String> optionalFilesList = cmd.getFilesList();
final VMInstanceVO vmInstance = getSystemVMInstance(vmId);
final DataStore store = getImageStore(vmInstance.getDataCenterId());
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);
}
/**
* 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<Boolean, String> 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<Boolean, String> copyResult;
if (vmInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
copyResult = copyToSecondaryStorageVMware(store, vmControlIp, fileToCopy);
} else {
copyResult = copyToSecondaryStorageNonVMware(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;
}
private void configureNetworkElementCommand(NetworkElementCommand cmd, VMInstanceVO vmInstance) {
Map<String, String> 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 prepareDiagnosticsFilesInSystemVm(VMInstanceVO vmInstance, List<String> 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);
final Answer fileCleanupAnswer = agentManager.easySend(vmInstance.getHostId(), cmd);
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()));
}
}
return fileCleanupAnswer;
}
/**
* 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<String> getFileListToBeRetrieved(List<String> optionalFileList, VMInstanceVO vmInstance) {
DiagnosticsFilesList fileListObject = DiagnosticsFilesListFactory.getDiagnosticsFilesList(optionalFileList, vmInstance);
List<String> fileList = new ArrayList<>();
if (fileListObject != null) {
fileList = fileListObject.generateFileList();
}
return fileList;
}
private Pair<Boolean, String> 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<Boolean, String> 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<Boolean, String> 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");
}
// Get ssvm from the zone to use for creating entity download URL
private VMInstanceVO getSecondaryStorageVmInZone(Long zoneId) {
List<VMInstanceVO> 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 DiskQuotaPercentageThreshold.value() 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<DataStore> 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.ZIP, 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<String, String> 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<String, Object> params) throws ConfigurationException {
if (EnableGarbageCollector.value()) {
backgroundPollManager.submitTask(new GCBackgroundTask(this));
}
return true;
}
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) {
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);
for (File file : fileList) {
if (file.isFile() && MaximumFileAgeforGarbageCollection.value() <= getTimeDifference(file)) {
boolean success = file.delete();
LOGGER.info(file.getName() + " delete status: " + success);
}
}
}
}
@Override
protected void runInContext() {
List<DataCenterVO> dcList = serviceImpl.dataCenterDao.listEnabledZones();
for (DataCenterVO vo: dcList) {
// Get All Image Stores in current running Zone
List<DataStore> storeList = serviceImpl.storeMgr.getImageStoresByScope(new ZoneScope(vo.getId()));
for (DataStore store : storeList) {
cleanupOldDiagnosticFiles(store);
}
}
}
@Override
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
public List<Class<?>> getCommands() {
List<Class<?>> 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,
SystemVMDefaultSupportedFiles,
RouterDefaultSupportedFiles
};
}
}

View File

@ -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.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.
* <p>
* 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<String> 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",
"List of supported diagnostics data file options for the CPVM and SSVM.", true);
ConfigKey<String> 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",
"List of supported diagnostics data file options for the domain router.", true);
List<String> generateFileList();
}

View File

@ -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.fileprocessor;
import java.util.Collections;
import java.util.List;
import com.cloud.vm.VirtualMachine;
public class DiagnosticsFilesListFactory {
public static DiagnosticsFilesList getDiagnosticsFilesList(List<String> dataTypeList, VirtualMachine vm) {
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 (DiagnosticsFilesList) Collections.emptyList();
}
}
}

View File

@ -0,0 +1,52 @@
//
// 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 java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
public class DomainRouterDiagnosticsFiles implements DiagnosticsFilesList {
// Optional parameters
private List<String> dataTypeList;
public DomainRouterDiagnosticsFiles(List<String> dataTypeList) {
this.dataTypeList = dataTypeList;
}
@Override
public List<String> generateFileList() {
List<String> filesList = new ArrayList<>();
if (CollectionUtils.isEmpty(dataTypeList)) {
filesList.addAll(Arrays.stream(RouterDefaultSupportedFiles.value().split(","))
.map(String :: trim)
.distinct()
.collect(Collectors.toList()));
} else {
filesList.addAll(dataTypeList);
}
return filesList;
}
}

View File

@ -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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
public class SystemVMDiagnosticsFiles implements DiagnosticsFilesList {
// Optional parameters
private List<String> dataTypeList;
public SystemVMDiagnosticsFiles(List<String> dataTypeList) {
this.dataTypeList = dataTypeList;
}
@Override
public List<String> generateFileList() {
List<String> filesList = new ArrayList<>();
if (CollectionUtils.isEmpty(dataTypeList)) {
filesList.addAll(Arrays.stream(SystemVMDefaultSupportedFiles.value().split(","))
.map(String :: trim)
.distinct()
.collect(Collectors.toList()));
} else {
filesList.addAll(dataTypeList);
}
return filesList;
}
}

View File

@ -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 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;
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;
}
}

View File

@ -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.ARCHIVE;
}
@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;
}
}

View File

@ -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);
}

View File

@ -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<String, String> storageMounts = new ConcurrentHashMap<>();
public static final ConfigKey<String> 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<String> 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<String> 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);
}
}

View File

@ -300,4 +300,12 @@
<bean id="directDownloadManager" class="org.apache.cloudstack.direct.download.DirectDownloadManagerImpl" />
<bean id="DiagnosticsService" class="org.apache.cloudstack.diagnostics.DiagnosticsServiceImpl" />
<bean id="storageLayer" class="com.cloud.storage.JavaStorageLayer" />
<bean id="nfsMountManager" class="org.apache.cloudstack.storage.NfsMountManagerImpl" >
<constructor-arg name="storage" ref="storageLayer" />
<constructor-arg name="timeout" value="10000" />
</bean>
</beans>

View File

@ -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 static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
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;
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 com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
@RunWith(MockitoJUnitRunner.class)
public class DiagnosticsFilesListFactoryTest {
private DomainRouterDiagnosticsFiles proxyDiagnosticFiles;
@Mock
private VMInstanceVO vmInstance;
@InjectMocks
private DiagnosticsFilesListFactory listFactory = new DiagnosticsFilesListFactory();
@Before
public void setUp() throws Exception {
Mockito.when(vmInstance.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
}
@After
public void tearDown() throws Exception {
Mockito.reset(vmInstance);
}
@Test
public void testgetDiagnosticsFilesListCpVmDataTypeList() {
List<String> dataTypeList = new ArrayList<>();
dataTypeList.add("/var/log/auth.log");
dataTypeList.add("/etc/dnsmasq.conf");
dataTypeList.add("iptables");
dataTypeList.add("ipaddr");
List<String> files = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(dataTypeList, vmInstance)).generateFileList();
assertEquals(files, dataTypeList);
}
@Test
public void testDiagnosticsFileListDefaultsRouter() {
List<String> filesList = Objects.requireNonNull(DiagnosticsFilesListFactory.getDiagnosticsFilesList(null, vmInstance)).generateFileList();
ConfigKey configKey = proxyDiagnosticFiles.RouterDefaultSupportedFiles;
String[] defaultFileArray = configKey.defaultValue().split(",");
assertEquals(filesList.size(), defaultFileArray.length);
}
}

View File

@ -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.Mock;
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<String, String> 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 class DiagnosticsServiceImplTest extends TestCase {
Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));
Map<String, String> detailsMap = diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
Map<String, String> 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 class DiagnosticsServiceImplTest extends TestCase {
@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<String, String> accessDetailsMap = new HashMap<>();
accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10");
@ -141,7 +142,7 @@ public class DiagnosticsServiceImplTest extends TestCase {
"4 packets transmitted, 0 packets received, 100% packet loss";
Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));
Map<String, String> detailsMap = diagnosticsService.runDiagnosticsCommand(diagnosticsCmd);
Map<String, String> detailsMap = serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd);
assertEquals(3, detailsMap.size());
assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR));
@ -151,46 +152,47 @@ public class DiagnosticsServiceImplTest extends TestCase {
@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<String, String> 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"));
}
}

View File

@ -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

View File

@ -0,0 +1,143 @@
#!/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 logging
import os
import re
import shlex
import subprocess as sp
import sys
import time
import zipfile
# 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 + '.zip'
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:
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:])
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 get_cmd(script):
if script is None or len(script) == 0:
return None
cmd = None
if script == 'iptables':
cmd = 'iptables-save'
elif script == 'ipaddr':
cmd = 'ip address'
elif script == 'iprule':
cmd = 'ip rule list'
elif script == 'iproute':
cmd = 'ip route show table all'
else:
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:
try:
cmd = get_cmd(script)
if cmd is None:
f.write('Unable to generate command for ' + script + ', perhaps missing file')
else:
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:
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__':
fileList = sys.argv[1:]
zip_files(fileList)

View File

@ -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 @@ from marvin.lib.common import (get_domain,
get_test_template,
list_ssvms,
list_routers)
from marvin.lib.utils import (cleanup_resources)
from nose.plugins.attrib import attr
@ -537,3 +538,197 @@ class TestRemoteDiagnostics(cloudstackTestCase):
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"
)

View File

@ -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 {

View File

@ -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;
}

View File

@ -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,8 @@ 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 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",
"label.gluster.volume":"Volume",
@ -2149,9 +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.ISO":"Please click <a href=\"#\">00000</a> to download ISO",
"message.download.template":"Please click <a href=\"#\">00000</a> to download template",
"message.download.volume":"Please click <a href=\"#\">00000</a> to download volume",
"message.download.diagnostics":"Please click the link to download the retrieved diagnostics:<p><a href=\"#\">00000</a>",
"message.download.ISO":"Please click the link to download the ISO:<p><a href=\"#\">00000</a>",
"message.download.template":"Please click the link to download the template:<p><a href=\"#\">00000</a>",
"message.download.volume":"Please click the link to download the volume:<p><a href=\"#\">00000</a>",
"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\".",

View File

@ -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: 'label.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: 'label.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: 'label.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");

View File

@ -58,6 +58,30 @@ public class SshHelper {
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 {