mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge remote-tracking branch 'apache/4.20'
This commit is contained in:
commit
1c1dad977e
@ -26,6 +26,7 @@ import org.apache.cloudstack.api.ApiErrorCode;
|
|||||||
import org.apache.cloudstack.api.BaseCmd;
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
import org.apache.cloudstack.api.Parameter;
|
import org.apache.cloudstack.api.Parameter;
|
||||||
import org.apache.cloudstack.api.ServerApiException;
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.api.response.BackupScheduleResponse;
|
||||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
import org.apache.cloudstack.backup.BackupManager;
|
import org.apache.cloudstack.backup.BackupManager;
|
||||||
@ -54,10 +55,16 @@ public class DeleteBackupScheduleCmd extends BaseCmd {
|
|||||||
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||||
type = CommandType.UUID,
|
type = CommandType.UUID,
|
||||||
entityType = UserVmResponse.class,
|
entityType = UserVmResponse.class,
|
||||||
required = true,
|
|
||||||
description = "ID of the VM")
|
description = "ID of the VM")
|
||||||
private Long vmId;
|
private Long vmId;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = BackupScheduleResponse.class,
|
||||||
|
description = "ID of the schedule",
|
||||||
|
since = "4.20.1")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////////// Accessors ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -66,6 +73,9 @@ public class DeleteBackupScheduleCmd extends BaseCmd {
|
|||||||
return vmId;
|
return vmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////// API Implementation///////////////////
|
/////////////// API Implementation///////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -73,7 +83,7 @@ public class DeleteBackupScheduleCmd extends BaseCmd {
|
|||||||
@Override
|
@Override
|
||||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||||
try {
|
try {
|
||||||
boolean result = backupManager.deleteBackupSchedule(getVmId());
|
boolean result = backupManager.deleteBackupSchedule(this);
|
||||||
if (result) {
|
if (result) {
|
||||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||||
response.setResponseName(getCommandName());
|
response.setResponseName(getCommandName());
|
||||||
|
|||||||
@ -57,10 +57,6 @@ public class BackupRepositoryResponse extends BaseResponse {
|
|||||||
@Param(description = "backup type")
|
@Param(description = "backup type")
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
@SerializedName(ApiConstants.MOUNT_OPTIONS)
|
|
||||||
@Param(description = "mount options for the backup repository")
|
|
||||||
private String mountOptions;
|
|
||||||
|
|
||||||
@SerializedName(ApiConstants.CAPACITY_BYTES)
|
@SerializedName(ApiConstants.CAPACITY_BYTES)
|
||||||
@Param(description = "capacity of the backup repository")
|
@Param(description = "capacity of the backup repository")
|
||||||
private Long capacityBytes;
|
private Long capacityBytes;
|
||||||
@ -112,14 +108,6 @@ public class BackupRepositoryResponse extends BaseResponse {
|
|||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMountOptions() {
|
|
||||||
return mountOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMountOptions(String mountOptions) {
|
|
||||||
this.mountOptions = mountOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProviderName() {
|
public String getProviderName() {
|
||||||
return providerName;
|
return providerName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import com.cloud.exception.ResourceAllocationException;
|
|||||||
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
|
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
|
||||||
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
|
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
|
||||||
import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd;
|
import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd;
|
||||||
import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd;
|
import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd;
|
||||||
import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd;
|
import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd;
|
||||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
@ -192,10 +193,10 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes VM backup schedule for a VM
|
* Deletes VM backup schedule for a VM
|
||||||
* @param vmId
|
* @param cmd
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean deleteBackupSchedule(Long vmId);
|
boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates backup of a VM
|
* Creates backup of a VM
|
||||||
|
|||||||
@ -1473,7 +1473,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
}
|
}
|
||||||
if (!BooleanUtils.toBoolean(EnableKVMAutoEnableDisable.valueIn(host.getClusterId()))) {
|
if (!BooleanUtils.toBoolean(EnableKVMAutoEnableDisable.valueIn(host.getClusterId()))) {
|
||||||
logger.debug("{} is disabled for the cluster {}, cannot process the health check result " +
|
logger.debug("{} is disabled for the cluster {}, cannot process the health check result " +
|
||||||
"received for the host {}", EnableKVMAutoEnableDisable.key(), host.getClusterId(), host);
|
"received for {}", EnableKVMAutoEnableDisable.key(), host.getClusterId(), host);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6071,6 +6071,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||||||
@Override
|
@Override
|
||||||
public Map<Long, Boolean> getDiskOfferingSuitabilityForVm(long vmId, List<Long> diskOfferingIds) {
|
public Map<Long, Boolean> getDiskOfferingSuitabilityForVm(long vmId, List<Long> diskOfferingIds) {
|
||||||
VMInstanceVO vm = _vmDao.findById(vmId);
|
VMInstanceVO vm = _vmDao.findById(vmId);
|
||||||
|
if (userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm);
|
VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm);
|
||||||
Pair<Long, Long> clusterAndHost = findClusterAndHostIdForVm(vm, false);
|
Pair<Long, Long> clusterAndHost = findClusterAndHostIdForVm(vm, false);
|
||||||
Long clusterId = clusterAndHost.first();
|
Long clusterId = clusterAndHost.first();
|
||||||
|
|||||||
@ -114,6 +114,7 @@ Requires: iproute
|
|||||||
Requires: ipset
|
Requires: ipset
|
||||||
Requires: perl
|
Requires: perl
|
||||||
Requires: rsync
|
Requires: rsync
|
||||||
|
Requires: cifs-utils
|
||||||
Requires: (python3-libvirt or python3-libvirt-python)
|
Requires: (python3-libvirt or python3-libvirt-python)
|
||||||
Requires: (qemu-img or qemu-tools)
|
Requires: (qemu-img or qemu-tools)
|
||||||
Requires: qemu-kvm
|
Requires: qemu-kvm
|
||||||
|
|||||||
@ -219,6 +219,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
|
|||||||
restoreCommand.setBackupPath(backup.getExternalId());
|
restoreCommand.setBackupPath(backup.getExternalId());
|
||||||
restoreCommand.setBackupRepoType(backupRepository.getType());
|
restoreCommand.setBackupRepoType(backupRepository.getType());
|
||||||
restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
|
restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
|
||||||
|
restoreCommand.setMountOptions(backupRepository.getMountOptions());
|
||||||
restoreCommand.setVmName(vm.getName());
|
restoreCommand.setVmName(vm.getName());
|
||||||
restoreCommand.setVolumePaths(getVolumePaths(volumes));
|
restoreCommand.setVolumePaths(getVolumePaths(volumes));
|
||||||
restoreCommand.setVmExists(vm.getRemoved() == null);
|
restoreCommand.setVmExists(vm.getRemoved() == null);
|
||||||
@ -287,6 +288,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
|
|||||||
restoreCommand.setVmName(vmNameAndState.first());
|
restoreCommand.setVmName(vmNameAndState.first());
|
||||||
restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID)));
|
restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID)));
|
||||||
restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT));
|
restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT));
|
||||||
|
restoreCommand.setMountOptions(backupRepository.getMountOptions());
|
||||||
restoreCommand.setVmExists(null);
|
restoreCommand.setVmExists(null);
|
||||||
restoreCommand.setVmState(vmNameAndState.second());
|
restoreCommand.setVmState(vmNameAndState.second());
|
||||||
restoreCommand.setRestoreVolumeUUID(volumeUuid);
|
restoreCommand.setRestoreVolumeUUID(volumeUuid);
|
||||||
@ -372,9 +374,13 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
|
|||||||
Long vmBackupSize = 0L;
|
Long vmBackupSize = 0L;
|
||||||
Long vmBackupProtectedSize = 0L;
|
Long vmBackupProtectedSize = 0L;
|
||||||
for (final Backup backup: backupDao.listByVmId(null, vm.getId())) {
|
for (final Backup backup: backupDao.listByVmId(null, vm.getId())) {
|
||||||
|
if (Objects.nonNull(backup.getSize())) {
|
||||||
vmBackupSize += backup.getSize();
|
vmBackupSize += backup.getSize();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(backup.getProtectedSize())) {
|
||||||
vmBackupProtectedSize += backup.getProtectedSize();
|
vmBackupProtectedSize += backup.getProtectedSize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Backup.Metric vmBackupMetric = new Backup.Metric(vmBackupSize,vmBackupProtectedSize);
|
Backup.Metric vmBackupMetric = new Backup.Metric(vmBackupSize,vmBackupProtectedSize);
|
||||||
LOG.debug("Metrics for VM {} is [backup size: {}, data size: {}].", vm, vmBackupMetric.getBackupSize(), vmBackupMetric.getDataSize());
|
LOG.debug("Metrics for VM {} is [backup size: {}, data size: {}].", vm, vmBackupMetric.getBackupSize(), vmBackupMetric.getDataSize());
|
||||||
metrics.put(vm, vmBackupMetric);
|
metrics.put(vm, vmBackupMetric);
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import com.cloud.agent.api.GetVmIpAddressCommand;
|
|||||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
import com.cloud.resource.CommandWrapper;
|
import com.cloud.resource.CommandWrapper;
|
||||||
import com.cloud.resource.ResourceWrapper;
|
import com.cloud.resource.ResourceWrapper;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.net.NetUtils;
|
import com.cloud.utils.net.NetUtils;
|
||||||
import com.cloud.utils.script.Script;
|
import com.cloud.utils.script.Script;
|
||||||
|
|
||||||
@ -34,6 +35,26 @@ import com.cloud.utils.script.Script;
|
|||||||
public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<GetVmIpAddressCommand, Answer, LibvirtComputingResource> {
|
public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<GetVmIpAddressCommand, Answer, LibvirtComputingResource> {
|
||||||
|
|
||||||
|
|
||||||
|
static String virsh_path = null;
|
||||||
|
static String virt_win_reg_path = null;
|
||||||
|
static String grep_path = null;
|
||||||
|
static String awk_path = null;
|
||||||
|
static String sed_path = null;
|
||||||
|
static String virt_ls_path = null;
|
||||||
|
static String virt_cat_path = null;
|
||||||
|
static String tail_path = null;
|
||||||
|
|
||||||
|
static void init() {
|
||||||
|
virt_ls_path = Script.getExecutableAbsolutePath("virt-ls");
|
||||||
|
virt_cat_path = Script.getExecutableAbsolutePath("virt-cat");
|
||||||
|
virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg");
|
||||||
|
tail_path = Script.getExecutableAbsolutePath("tail");
|
||||||
|
grep_path = Script.getExecutableAbsolutePath("grep");
|
||||||
|
awk_path = Script.getExecutableAbsolutePath("awk");
|
||||||
|
sed_path = Script.getExecutableAbsolutePath("sed");
|
||||||
|
virsh_path = Script.getExecutableAbsolutePath("virsh");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Answer execute(final GetVmIpAddressCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
public Answer execute(final GetVmIpAddressCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||||
String ip = null;
|
String ip = null;
|
||||||
@ -42,65 +63,113 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<Ge
|
|||||||
if (!NetUtils.verifyDomainNameLabel(vmName, true)) {
|
if (!NetUtils.verifyDomainNameLabel(vmName, true)) {
|
||||||
return new Answer(command, result, ip);
|
return new Answer(command, result, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
String sanitizedVmName = sanitizeBashCommandArgument(vmName);
|
String sanitizedVmName = sanitizeBashCommandArgument(vmName);
|
||||||
String networkCidr = command.getVmNetworkCidr();
|
String networkCidr = command.getVmNetworkCidr();
|
||||||
List<String[]> commands = new ArrayList<>();
|
|
||||||
final String virt_ls_path = Script.getExecutableAbsolutePath("virt-ls");
|
ip = ipFromDomIf(sanitizedVmName, networkCidr);
|
||||||
final String virt_cat_path = Script.getExecutableAbsolutePath("virt-cat");
|
|
||||||
final String virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg");
|
if (ip == null) {
|
||||||
final String tail_path = Script.getExecutableAbsolutePath("tail");
|
|
||||||
final String grep_path = Script.getExecutableAbsolutePath("grep");
|
|
||||||
final String awk_path = Script.getExecutableAbsolutePath("awk");
|
|
||||||
final String sed_path = Script.getExecutableAbsolutePath("sed");
|
|
||||||
if(!command.isWindows()) {
|
if(!command.isWindows()) {
|
||||||
//List all dhcp lease files inside guestVm
|
ip = ipFromDhcpLeaseFile(sanitizedVmName, networkCidr);
|
||||||
|
} else {
|
||||||
|
ip = ipFromWindowsRegistry(sanitizedVmName, networkCidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ip != null){
|
||||||
|
result = true;
|
||||||
|
logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip);
|
||||||
|
} else {
|
||||||
|
logger.warn("GetVmIp: "+ vmName + " IP not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Answer(command, result, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ipFromDomIf(String sanitizedVmName, String networkCidr) {
|
||||||
|
String ip = null;
|
||||||
|
List<String[]> commands = new ArrayList<>();
|
||||||
|
commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"});
|
||||||
|
Pair<Integer,String> response = executePipedCommands(commands, 0);
|
||||||
|
if (response != null) {
|
||||||
|
String output = response.second();
|
||||||
|
String[] lines = output.split("\n");
|
||||||
|
for (String line : lines) {
|
||||||
|
if (line.contains("ipv4")) {
|
||||||
|
String[] parts = line.split(" ");
|
||||||
|
String[] ipParts = parts[parts.length-1].split("/");
|
||||||
|
if (ipParts.length > 1) {
|
||||||
|
if (NetUtils.isIpWithInCidrRange(ipParts[0], networkCidr)) {
|
||||||
|
ip = ipParts[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName);
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ipFromDhcpLeaseFile(String sanitizedVmName, String networkCidr) {
|
||||||
|
String ip = null;
|
||||||
|
List<String[]> commands = new ArrayList<>();
|
||||||
commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"});
|
commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"});
|
||||||
commands.add(new String[]{grep_path, ".*\\*.leases"});
|
commands.add(new String[]{grep_path, ".*\\*.leases"});
|
||||||
String leasesList = Script.executePipedCommands(commands, 0).second();
|
Pair<Integer,String> response = executePipedCommands(commands, 0);
|
||||||
if(leasesList != null) {
|
|
||||||
|
if(response != null && response.second() != null) {
|
||||||
|
String leasesList = response.second();
|
||||||
String[] leasesFiles = leasesList.split("\n");
|
String[] leasesFiles = leasesList.split("\n");
|
||||||
for(String leaseFile : leasesFiles){
|
for(String leaseFile : leasesFiles){
|
||||||
//Read from each dhclient lease file inside guest Vm using virt-cat libguestfs utility
|
|
||||||
commands = new ArrayList<>();
|
commands = new ArrayList<>();
|
||||||
commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile});
|
commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile});
|
||||||
commands.add(new String[]{tail_path, "-16"});
|
commands.add(new String[]{tail_path, "-16"});
|
||||||
commands.add(new String[]{grep_path, "fixed-address"});
|
commands.add(new String[]{grep_path, "fixed-address"});
|
||||||
commands.add(new String[]{awk_path, "{print $2}"});
|
commands.add(new String[]{awk_path, "{print $2}"});
|
||||||
commands.add(new String[]{sed_path, "-e", "s/;//"});
|
commands.add(new String[]{sed_path, "-e", "s/;//"});
|
||||||
String ipAddr = Script.executePipedCommands(commands, 0).second();
|
String ipAddr = executePipedCommands(commands, 0).second();
|
||||||
// Check if the IP belongs to the network
|
|
||||||
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) {
|
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) {
|
||||||
ip = ipAddr;
|
ip = ipAddr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For windows, read from guest Vm registry using virt-win-reg libguestfs ulitiy. Registry Path: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces\<service>\DhcpIPAddress
|
logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName);
|
||||||
commands = new ArrayList<>();
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ipFromWindowsRegistry(String sanitizedVmName, String networkCidr) {
|
||||||
|
String ip = null;
|
||||||
|
List<String[]> commands = new ArrayList<>();
|
||||||
commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"});
|
commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"});
|
||||||
commands.add(new String[]{grep_path, "DhcpIPAddress"});
|
commands.add(new String[]{grep_path, "DhcpIPAddress"});
|
||||||
commands.add(new String[]{awk_path, "-F", ":", "{print $2}"});
|
commands.add(new String[]{awk_path, "-F", ":", "{print $2}"});
|
||||||
commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"});
|
commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"});
|
||||||
String ipList = Script.executePipedCommands(commands, 0).second();
|
Pair<Integer,String> pair = executePipedCommands(commands, 0);
|
||||||
if(ipList != null) {
|
if(pair != null && pair.second() != null) {
|
||||||
logger.debug("GetVmIp: "+ vmName + "Ips: "+ipList);
|
String ipList = pair.second();
|
||||||
|
ipList = ipList.replaceAll("\"", "");
|
||||||
|
logger.debug("GetVmIp: "+ sanitizedVmName + "Ips: "+ipList);
|
||||||
String[] ips = ipList.split("\n");
|
String[] ips = ipList.split("\n");
|
||||||
for (String ipAddr : ips){
|
for (String ipAddr : ips){
|
||||||
// Check if the IP belongs to the network
|
|
||||||
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){
|
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){
|
||||||
ip = ipAddr;
|
ip = ipAddr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("ipFromWindowsRegistry: Command execution failed for VM: " + sanitizedVmName);
|
||||||
}
|
}
|
||||||
|
return ip;
|
||||||
}
|
}
|
||||||
if(ip != null){
|
|
||||||
result = true;
|
static Pair<Integer, String> executePipedCommands(List<String[]> commands, long timeout) {
|
||||||
logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip);
|
return Script.executePipedCommands(commands, timeout);
|
||||||
}
|
|
||||||
return new Answer(command, result, ip);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
|
|||||||
int lastIndex = volumePath.lastIndexOf("/");
|
int lastIndex = volumePath.lastIndexOf("/");
|
||||||
newVolumeId = volumePath.substring(lastIndex + 1);
|
newVolumeId = volumePath.substring(lastIndex + 1);
|
||||||
restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid,
|
restoreVolume(backupPath, backupRepoType, backupRepoAddress, volumePath, diskType, restoreVolumeUuid,
|
||||||
new Pair<>(vmName, command.getVmState()));
|
new Pair<>(vmName, command.getVmState()), mountOptions);
|
||||||
} else if (Boolean.TRUE.equals(vmExists)) {
|
} else if (Boolean.TRUE.equals(vmExists)) {
|
||||||
restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions);
|
restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions);
|
||||||
} else {
|
} else {
|
||||||
@ -80,7 +80,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
|
|||||||
private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupPath,
|
private void restoreVolumesOfExistingVM(List<String> volumePaths, String backupPath,
|
||||||
String backupRepoType, String backupRepoAddress, String mountOptions) {
|
String backupRepoType, String backupRepoAddress, String mountOptions) {
|
||||||
String diskType = "root";
|
String diskType = "root";
|
||||||
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType);
|
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions);
|
||||||
try {
|
try {
|
||||||
for (int idx = 0; idx < volumePaths.size(); idx++) {
|
for (int idx = 0; idx < volumePaths.size(); idx++) {
|
||||||
String volumePath = volumePaths.get(idx);
|
String volumePath = volumePaths.get(idx);
|
||||||
@ -101,7 +101,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
|
|||||||
|
|
||||||
private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmName, String backupPath,
|
private void restoreVolumesOfDestroyedVMs(List<String> volumePaths, String vmName, String backupPath,
|
||||||
String backupRepoType, String backupRepoAddress, String mountOptions) {
|
String backupRepoType, String backupRepoAddress, String mountOptions) {
|
||||||
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType);
|
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions);
|
||||||
String diskType = "root";
|
String diskType = "root";
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < volumePaths.size(); i++) {
|
for (int i = 0; i < volumePaths.size(); i++) {
|
||||||
@ -121,8 +121,8 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void restoreVolume(String backupPath, String backupRepoType, String backupRepoAddress, String volumePath,
|
private void restoreVolume(String backupPath, String backupRepoType, String backupRepoAddress, String volumePath,
|
||||||
String diskType, String volumeUUID, Pair<String, VirtualMachine.State> vmNameAndState) {
|
String diskType, String volumeUUID, Pair<String, VirtualMachine.State> vmNameAndState, String mountOptions) {
|
||||||
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType);
|
String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions);
|
||||||
Pair<String, String> bkpPathAndVolUuid;
|
Pair<String, String> bkpPathAndVolUuid;
|
||||||
try {
|
try {
|
||||||
bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, volumeUUID);
|
bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, volumeUUID);
|
||||||
@ -145,12 +145,22 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String mountBackupDirectory(String backupRepoAddress, String backupRepoType) {
|
private String mountBackupDirectory(String backupRepoAddress, String backupRepoType, String mountOptions) {
|
||||||
String randomChars = RandomStringUtils.random(5, true, false);
|
String randomChars = RandomStringUtils.random(5, true, false);
|
||||||
String mountDirectory = String.format("%s.%s",BACKUP_TEMP_FILE_PREFIX , randomChars);
|
String mountDirectory = String.format("%s.%s",BACKUP_TEMP_FILE_PREFIX , randomChars);
|
||||||
try {
|
try {
|
||||||
mountDirectory = Files.createTempDirectory(mountDirectory).toString();
|
mountDirectory = Files.createTempDirectory(mountDirectory).toString();
|
||||||
|
String mountOpts = null;
|
||||||
|
if (Objects.nonNull(mountOptions)) {
|
||||||
|
mountOpts = mountOptions;
|
||||||
|
if ("cifs".equals(backupRepoType)) {
|
||||||
|
mountOpts += ",nobrl";
|
||||||
|
}
|
||||||
|
}
|
||||||
String mount = String.format(MOUNT_COMMAND, backupRepoType, backupRepoAddress, mountDirectory);
|
String mount = String.format(MOUNT_COMMAND, backupRepoType, backupRepoAddress, mountDirectory);
|
||||||
|
if (Objects.nonNull(mountOpts)) {
|
||||||
|
mount += " -o " + mountOpts;
|
||||||
|
}
|
||||||
Script.runSimpleBashScript(mount);
|
Script.runSimpleBashScript(mount);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new CloudRuntimeException(String.format("Failed to mount %s to %s", backupRepoType, backupRepoAddress), e);
|
throw new CloudRuntimeException(String.format("Failed to mount %s to %s", backupRepoType, backupRepoAddress), e);
|
||||||
|
|||||||
@ -0,0 +1,320 @@
|
|||||||
|
//
|
||||||
|
// 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.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.GetVmIpAddressCommand;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.utils.script.Script;
|
||||||
|
|
||||||
|
public class LibvirtGetVmIpAddressCommandWrapperTest {
|
||||||
|
|
||||||
|
private static String VIRSH_DOMIF_OUTPUT = " Name MAC address Protocol Address\n" + //
|
||||||
|
"-------------------------------------------------------------------------------\n" + //
|
||||||
|
" lo 00:00:00:00:00:70 ipv4 127.0.0.1/8\n" + //
|
||||||
|
" eth0 02:0c:02:f9:00:80 ipv4 192.168.0.10/24\n" + //
|
||||||
|
" net1 b2:41:19:69:a4:90 N/A N/A\n" + //
|
||||||
|
" net2 52:a2:36:cf:d1:50 ipv4 10.244.6.93/32\n" + //
|
||||||
|
" net3 a6:1d:d3:52:d3:40 N/A N/A\n" + //
|
||||||
|
" net4 2e:9b:60:dc:49:30 N/A N/A\n" + //
|
||||||
|
" lxc5b7327203b6f 92:b2:77:0b:a9:20 N/A N/A\n";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithValidVmName() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = mockStatic(Script.class);
|
||||||
|
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertTrue(answer.getResult());
|
||||||
|
assertEquals("192.168.0.10", answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithInvalidVmName() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = mockStatic(Script.class);
|
||||||
|
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("invalidVmName!");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithWindowsVm() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10"));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertTrue(answer.getResult());
|
||||||
|
assertEquals("192.168.0.10", answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithNoIpFound() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, ""));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithValidVmNameAndNoIpFound() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, ""));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithValidVmNameAndIpFromDhcpLeaseFile() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10"));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertTrue(answer.getResult());
|
||||||
|
assertEquals("192.168.0.10", answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteWithValidVmNameAndIpFromWindowsRegistry() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "\"192.168.0.10\""));
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertTrue(answer.getResult());
|
||||||
|
assertEquals("192.168.0.10", answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIpFromDomIfCommandExecutionFailure() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIpFromDhcpLeaseFileCommandExecutionFailure() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIpFromWindowsRegistryCommandExecutionFailure() {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||||
|
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||||
|
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||||
|
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||||
|
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||||
|
|
||||||
|
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||||
|
|
||||||
|
assertFalse(answer.getResult());
|
||||||
|
assertNull(answer.getDetails());
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInit() {
|
||||||
|
MockedStatic<Script> scriptMock = null;
|
||||||
|
try {
|
||||||
|
scriptMock = mockStatic(Script.class);
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-ls")).thenReturn("/usr/bin/virt-ls");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-cat")).thenReturn("/usr/bin/virt-cat");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-win-reg")).thenReturn("/usr/bin/virt-win-reg");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("tail")).thenReturn("/usr/bin/tail");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("grep")).thenReturn("/usr/bin/grep");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("awk")).thenReturn("/usr/bin/awk");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("sed")).thenReturn("/usr/bin/sed");
|
||||||
|
scriptMock.when(() -> Script.getExecutableAbsolutePath("virsh")).thenReturn("/usr/bin/virsh");
|
||||||
|
|
||||||
|
LibvirtGetVmIpAddressCommandWrapper.init();
|
||||||
|
|
||||||
|
assertEquals("/usr/bin/virt-ls", LibvirtGetVmIpAddressCommandWrapper.virt_ls_path);
|
||||||
|
assertEquals("/usr/bin/virt-cat", LibvirtGetVmIpAddressCommandWrapper.virt_cat_path);
|
||||||
|
assertEquals("/usr/bin/virt-win-reg", LibvirtGetVmIpAddressCommandWrapper.virt_win_reg_path);
|
||||||
|
assertEquals("/usr/bin/tail", LibvirtGetVmIpAddressCommandWrapper.tail_path);
|
||||||
|
assertEquals("/usr/bin/grep", LibvirtGetVmIpAddressCommandWrapper.grep_path);
|
||||||
|
assertEquals("/usr/bin/awk", LibvirtGetVmIpAddressCommandWrapper.awk_path);
|
||||||
|
assertEquals("/usr/bin/sed", LibvirtGetVmIpAddressCommandWrapper.sed_path);
|
||||||
|
assertEquals("/usr/bin/virsh", LibvirtGetVmIpAddressCommandWrapper.virsh_path);
|
||||||
|
} finally {
|
||||||
|
if (scriptMock != null)
|
||||||
|
scriptMock.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
@ -1468,6 +1469,10 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<KubernetesClusterVmMapVO> vmMapList = kubernetesClusterVmMapDao.listByClusterId(kubernetesClusterId);
|
List<KubernetesClusterVmMapVO> vmMapList = kubernetesClusterVmMapDao.listByClusterId(kubernetesClusterId);
|
||||||
|
List<VMInstanceVO> vms = vmMapList.stream().map(vmMap -> vmInstanceDao.findById(vmMap.getVmId())).collect(Collectors.toList());
|
||||||
|
if (checkIfVmsAssociatedWithBackupOffering(vms)) {
|
||||||
|
throw new CloudRuntimeException("Unable to delete Kubernetes cluster, as node(s) are associated to a backup offering");
|
||||||
|
}
|
||||||
for (KubernetesClusterVmMapVO vmMap : vmMapList) {
|
for (KubernetesClusterVmMapVO vmMap : vmMapList) {
|
||||||
try {
|
try {
|
||||||
userVmService.destroyVm(vmMap.getVmId(), expunge);
|
userVmService.destroyVm(vmMap.getVmId(), expunge);
|
||||||
@ -1490,6 +1495,15 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean checkIfVmsAssociatedWithBackupOffering(List<VMInstanceVO> vms) {
|
||||||
|
for(VMInstanceVO vm : vms) {
|
||||||
|
if (Objects.nonNull(vm.getBackupOfferingId())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListResponse<KubernetesClusterResponse> listKubernetesClusters(ListKubernetesClustersCmd cmd) {
|
public ListResponse<KubernetesClusterResponse> listKubernetesClusters(ListKubernetesClustersCmd cmd) {
|
||||||
if (!KubernetesServiceEnabled.value()) {
|
if (!KubernetesServiceEnabled.value()) {
|
||||||
|
|||||||
@ -245,6 +245,10 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod
|
|||||||
init();
|
init();
|
||||||
validateClusterSate();
|
validateClusterSate();
|
||||||
this.clusterVMs = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
|
this.clusterVMs = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
|
||||||
|
List<VMInstanceVO> vms = this.clusterVMs.stream().map(vmMap -> vmInstanceDao.findById(vmMap.getVmId())).collect(Collectors.toList());
|
||||||
|
if (KubernetesClusterManagerImpl.checkIfVmsAssociatedWithBackupOffering(vms)) {
|
||||||
|
throw new CloudRuntimeException("Unable to delete Kubernetes cluster, as node(s) are associated to a backup offering");
|
||||||
|
}
|
||||||
boolean cleanupNetwork = true;
|
boolean cleanupNetwork = true;
|
||||||
final KubernetesClusterDetailsVO clusterDetails = kubernetesClusterDetailsDao.findDetail(kubernetesCluster.getId(), "networkCleanup");
|
final KubernetesClusterDetailsVO clusterDetails = kubernetesClusterDetailsDao.findDetail(kubernetesCluster.getId(), "networkCleanup");
|
||||||
if (clusterDetails != null) {
|
if (clusterDetails != null) {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
|
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
|
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
|
||||||
@ -110,7 +111,6 @@ import com.cloud.storage.VolumeDetailVO;
|
|||||||
import com.cloud.storage.VolumeVO;
|
import com.cloud.storage.VolumeVO;
|
||||||
import com.cloud.storage.dao.SnapshotDao;
|
import com.cloud.storage.dao.SnapshotDao;
|
||||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
|
||||||
import com.cloud.storage.dao.StoragePoolHostDao;
|
import com.cloud.storage.dao.StoragePoolHostDao;
|
||||||
import com.cloud.storage.dao.VMTemplateDetailsDao;
|
import com.cloud.storage.dao.VMTemplateDetailsDao;
|
||||||
import com.cloud.storage.dao.VolumeDao;
|
import com.cloud.storage.dao.VolumeDao;
|
||||||
@ -640,57 +640,16 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
|
SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
|
||||||
SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE);
|
SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE);
|
||||||
if (snapshotDetail != null) {
|
if (snapshotDetail != null) {
|
||||||
err = String.format("Could not create volume from snapshot due to: %s", resp.getError());
|
err = String.format("Could not create volume from snapshot due to: %s. The snapshot was created with the delayDelete option.", resp.getError());
|
||||||
} else if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
|
} else if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
|
||||||
resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn);
|
SpApiResponse emptyVolumeCreateResp = StorPoolUtil.volumeCreate(volumeName, null, size, null, null, "volume", null, conn);
|
||||||
if (resp.getError() == null) {
|
if (emptyVolumeCreateResp.getError() == null) {
|
||||||
VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO();
|
answer = createVolumeFromSnapshot(srcData, dstData, size, emptyVolumeCreateResp);
|
||||||
dstTO.setSize(size);
|
|
||||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
|
||||||
cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
|
|
||||||
|
|
||||||
EndPoint ep = selector.select(srcData, dstData);
|
|
||||||
if (ep == null) {
|
|
||||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
|
||||||
} else {
|
} else {
|
||||||
answer = ep.sendMessage(cmd);
|
answer = new Answer(cmd, false, String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, emptyVolumeCreateResp.getError()));
|
||||||
}
|
|
||||||
|
|
||||||
if (answer != null && answer.getResult()) {
|
|
||||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
|
||||||
if (resp2.getError() != null) {
|
|
||||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", srcData.getUuid(), resp2.getError());
|
|
||||||
} else {
|
|
||||||
String name = StorPoolUtil.getNameFromResponse(resp, false);
|
|
||||||
SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(sinfo.getId(), sinfo.getUuid());
|
|
||||||
if (snapshotDetails != null) {
|
|
||||||
StorPoolHelper.updateSnapshotDetailsValue(snapshotDetails.getId(), StorPoolUtil.devPath(name), "snapshot");
|
|
||||||
}else {
|
|
||||||
StorPoolHelper.addSnapshotDetails(sinfo.getId(), sinfo.getUuid(), StorPoolUtil.devPath(name), snapshotDetailsDao);
|
|
||||||
}
|
|
||||||
resp = StorPoolUtil.volumeCreate(volumeName, StorPoolUtil.getNameFromResponse(resp, true), size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn);
|
|
||||||
if (resp.getError() == null) {
|
|
||||||
updateStoragePool(dstData.getDataStore().getId(), size);
|
|
||||||
|
|
||||||
VolumeObjectTO to = (VolumeObjectTO) dstData.getTO();
|
|
||||||
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
|
||||||
to.setSize(size);
|
|
||||||
// successfully downloaded snapshot to primary storage
|
|
||||||
answer = new CopyCmdAnswer(to);
|
|
||||||
StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", name, to.getUuid(), snapshotName, sinfo.getUuid());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
|
answer = new Answer(cmd, false, String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", snapshotName));
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", snapshotName);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
||||||
@ -791,22 +750,17 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
||||||
} else {
|
} else {
|
||||||
String volumeNameToSnapshot = StorPoolUtil.getNameFromResponse(resp, true);
|
String volumeNameToSnapshot = StorPoolUtil.getNameFromResponse(resp, true);
|
||||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(volumeNameToSnapshot, conn);
|
|
||||||
if (resp2.getError() != null) {
|
|
||||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", name, resp2.getError());
|
|
||||||
} else {
|
|
||||||
StorPoolUtil.spLog("Storpool snapshot [%s] for a template exists. Creating template on Storpool with name [%s]", tinfo.getUuid(), name);
|
|
||||||
TemplateObjectTO dstTO = (TemplateObjectTO) dstData.getTO();
|
TemplateObjectTO dstTO = (TemplateObjectTO) dstData.getTO();
|
||||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
|
||||||
dstTO.setSize(size);
|
answer = createVolumeSnapshot(cmd, size, conn, volumeNameToSnapshot, dstTO);
|
||||||
answer = new CopyCmdAnswer(dstTO);
|
StorPoolUtil.volumeDelete(volumeNameToSnapshot, conn);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resp = StorPoolUtil.volumeCreate(name, null, size, null, "no", "template", null, conn);
|
resp = StorPoolUtil.volumeCreate(name, null, size, null, "no", "template", null, conn);
|
||||||
if (resp.getError() != null) {
|
if (resp.getError() != null) {
|
||||||
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
||||||
} else {
|
} else {
|
||||||
|
String volName = StorPoolUtil.getNameFromResponse(resp, true);
|
||||||
TemplateObjectTO dstTO = (TemplateObjectTO)dstData.getTO();
|
TemplateObjectTO dstTO = (TemplateObjectTO)dstData.getTO();
|
||||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||||
dstTO.setSize(size);
|
dstTO.setSize(size);
|
||||||
@ -822,19 +776,12 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
|
|
||||||
if (answer != null && answer.getResult()) {
|
if (answer != null && answer.getResult()) {
|
||||||
// successfully downloaded template to primary storage
|
// successfully downloaded template to primary storage
|
||||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
answer = createVolumeSnapshot(cmd, size, conn, volName, dstTO);
|
||||||
if (resp2.getError() != null) {
|
|
||||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", name, resp2.getError());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
|
err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
StorPoolUtil.volumeDelete(volName, conn);
|
||||||
if (err != null) {
|
|
||||||
resp = StorPoolUtil.volumeDelete(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
|
||||||
if (resp.getError() != null) {
|
|
||||||
logger.warn(String.format("Could not clean-up Storpool volume %s. Error: %s", name, resp.getError()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) {
|
} else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) {
|
||||||
@ -1027,6 +974,42 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
callback.complete(res);
|
callback.complete(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Answer createVolumeSnapshot(StorageSubSystemCommand cmd, Long size, SpConnectionDesc conn,
|
||||||
|
String volName, TemplateObjectTO dstTO) {
|
||||||
|
Answer answer;
|
||||||
|
SpApiResponse resp = StorPoolUtil.volumeSnapshot(volName, dstTO.getUuid(), null, "template", null, conn);
|
||||||
|
if (resp.getError() != null) {
|
||||||
|
answer = new Answer(cmd, false, String.format("Could not snapshot volume. Error: %s", resp.getError()));
|
||||||
|
} else {
|
||||||
|
dstTO.setPath(StorPoolUtil.devPath(
|
||||||
|
StorPoolUtil.getSnapshotNameFromResponse(resp, false, StorPoolUtil.GLOBAL_ID)));
|
||||||
|
dstTO.setSize(size);
|
||||||
|
answer = new CopyCmdAnswer(dstTO);
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Answer createVolumeFromSnapshot(DataObject srcData, DataObject dstData, final Long size,
|
||||||
|
SpApiResponse emptyVolumeCreateResp) {
|
||||||
|
Answer answer;
|
||||||
|
String name = StorPoolUtil.getNameFromResponse(emptyVolumeCreateResp, false);
|
||||||
|
VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO();
|
||||||
|
dstTO.setSize(size);
|
||||||
|
dstTO.setPath(StorPoolUtil.devPath(name));
|
||||||
|
StorageSubSystemCommand cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
|
||||||
|
|
||||||
|
EndPoint ep = selector.select(srcData, dstData);
|
||||||
|
if (ep == null) {
|
||||||
|
answer = new Answer(cmd, false, "\"No remote endpoint to send command, check if host or ssvm is down?\"");
|
||||||
|
} else {
|
||||||
|
answer = ep.sendMessage(cmd);
|
||||||
|
}
|
||||||
|
if (answer == null || !answer.getResult()) {
|
||||||
|
answer = new Answer(cmd, false, answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.");
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateVolumePoolType(VolumeInfo vinfo) {
|
private void updateVolumePoolType(VolumeInfo vinfo) {
|
||||||
VolumeVO volumeVO = volumeDao.findById(vinfo.getId());
|
VolumeVO volumeVO = volumeDao.findById(vinfo.getId());
|
||||||
volumeVO.setPoolType(StoragePoolType.StorPool);
|
volumeVO.setPoolType(StoragePoolType.StorPool);
|
||||||
|
|||||||
@ -643,6 +643,12 @@ public class StorPoolUtil {
|
|||||||
return POST("MultiCluster/VolumeRevert/" + name, json, conn);
|
return POST("MultiCluster/VolumeRevert/" + name, json, conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use volumeSnapshot instead
|
||||||
|
* @param volumeName
|
||||||
|
* @param conn
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static SpApiResponse volumeFreeze(final String volumeName, SpConnectionDesc conn) {
|
public static SpApiResponse volumeFreeze(final String volumeName, SpConnectionDesc conn) {
|
||||||
return POST("MultiCluster/VolumeFreeze/" + volumeName, null, conn);
|
return POST("MultiCluster/VolumeFreeze/" + volumeName, null, conn);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,24 +219,23 @@ public class StorPoolDataMotionStrategy implements DataMotionStrategy {
|
|||||||
} else {
|
} else {
|
||||||
answer = (CopyCmdAnswer) ep2.sendMessage(backupSnapshot);
|
answer = (CopyCmdAnswer) ep2.sendMessage(backupSnapshot);
|
||||||
if (answer != null && answer.getResult()) {
|
if (answer != null && answer.getResult()) {
|
||||||
SpApiResponse resSnapshot = StorPoolUtil.volumeFreeze(volumeName, conn);
|
SpApiResponse resSnapshot = StorPoolUtil.volumeSnapshot(volumeName, template.getUuid(), null, "template", null, conn);
|
||||||
if (resSnapshot.getError() != null) {
|
if (resSnapshot.getError() != null) {
|
||||||
logger.debug("Could not snapshot volume [id: {}, name: {}]", snapshot.getId(), snapshot.getName());
|
logger.debug(String.format("Could not snapshot volume with ID={}", snapshot.getId()));
|
||||||
StorPoolUtil.spLog("Volume freeze failed with error=%s", resSnapshot.getError().getDescr());
|
StorPoolUtil.spLog("VolumeSnapshot failed with error=%s", resSnapshot.getError().getDescr());
|
||||||
err = resSnapshot.getError().getDescr();
|
err = resSnapshot.getError().getDescr();
|
||||||
StorPoolUtil.volumeDelete(volumeName, conn);
|
|
||||||
} else {
|
} else {
|
||||||
|
String templPath = StorPoolUtil.devPath(
|
||||||
|
StorPoolUtil.getSnapshotNameFromResponse(resSnapshot, false, StorPoolUtil.GLOBAL_ID));
|
||||||
StorPoolHelper.updateVmStoreTemplate(template.getId(), template.getDataStore().getRole(),
|
StorPoolHelper.updateVmStoreTemplate(template.getId(), template.getDataStore().getRole(),
|
||||||
StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(res, false)), _templStoreDao);
|
templPath, _templStoreDao);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
err = "Could not copy template to secondary " + answer.getResult();
|
|
||||||
StorPoolUtil.volumeDelete(StorPoolUtil.getNameFromResponse(res, true), conn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (CloudRuntimeException e) {
|
} catch (CloudRuntimeException e) {
|
||||||
err = e.getMessage();
|
err = e.getMessage();
|
||||||
}
|
}
|
||||||
|
StorPoolUtil.volumeDelete(volumeName, conn);
|
||||||
}
|
}
|
||||||
_vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID,
|
_vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID,
|
||||||
String.valueOf(vInfo.getDataStore().getId()), false));
|
String.valueOf(vInfo.getDataStore().getId()), false));
|
||||||
|
|||||||
@ -31,6 +31,58 @@ NAS_ADDRESS=""
|
|||||||
MOUNT_OPTS=""
|
MOUNT_OPTS=""
|
||||||
BACKUP_DIR=""
|
BACKUP_DIR=""
|
||||||
DISK_PATHS=""
|
DISK_PATHS=""
|
||||||
|
logFile="/var/log/cloudstack/agent/agent.log"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
[[ "$verb" -eq 1 ]] && builtin echo "$@"
|
||||||
|
if [[ "$1" == "-ne" || "$1" == "-e" || "$1" == "-n" ]]; then
|
||||||
|
builtin echo -e "$(date '+%Y-%m-%d %H-%M-%S>')" "${@: 2}" >> "$logFile"
|
||||||
|
else
|
||||||
|
builtin echo "$(date '+%Y-%m-%d %H-%M-%S>')" "$@" >> "$logFile"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
vercomp() {
|
||||||
|
local IFS=.
|
||||||
|
local i ver1=($1) ver2=($3)
|
||||||
|
|
||||||
|
# Compare each segment of the version numbers
|
||||||
|
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||||
|
if [[ -z ${ver2[i]} ]]; then
|
||||||
|
ver2[i]=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((10#${ver1[i]} > 10#${ver2[i]})); then
|
||||||
|
return 0 # Version 1 is greater
|
||||||
|
elif ((10#${ver1[i]} < 10#${ver2[i]})); then
|
||||||
|
return 2 # Version 2 is greater
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0 # Versions are equal
|
||||||
|
}
|
||||||
|
|
||||||
|
sanity_checks() {
|
||||||
|
hvVersion=$(virsh version | grep hypervisor | awk '{print $(NF)}')
|
||||||
|
libvVersion=$(virsh version | grep libvirt | awk '{print $(NF)}' | tail -n 1)
|
||||||
|
apiVersion=$(virsh version | grep API | awk '{print $(NF)}')
|
||||||
|
|
||||||
|
# Compare qemu version (hvVersion >= 4.2.0)
|
||||||
|
vercomp "$hvVersion" ">=" "4.2.0"
|
||||||
|
hvStatus=$?
|
||||||
|
|
||||||
|
# Compare libvirt version (libvVersion >= 7.2.0)
|
||||||
|
vercomp "$libvVersion" ">=" "7.2.0"
|
||||||
|
libvStatus=$?
|
||||||
|
|
||||||
|
if [[ $hvStatus -eq 0 && $libvStatus -eq 0 ]]; then
|
||||||
|
log -ne "Success... [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]"
|
||||||
|
else
|
||||||
|
echo "Failure... Your QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log -ne "Environment Sanity Checks successfully passed"
|
||||||
|
}
|
||||||
|
|
||||||
### Operation methods ###
|
### Operation methods ###
|
||||||
|
|
||||||
@ -79,7 +131,7 @@ backup_stopped_vm() {
|
|||||||
name="root"
|
name="root"
|
||||||
for disk in $DISK_PATHS; do
|
for disk in $DISK_PATHS; do
|
||||||
volUuid="${disk##*/}"
|
volUuid="${disk##*/}"
|
||||||
qemu-img convert -O qcow2 $disk $dest/$name.$volUuid.qcow2
|
qemu-img convert -O qcow2 $disk $dest/$name.$volUuid.qcow2 | tee -a "$logFile"
|
||||||
name="datadisk"
|
name="datadisk"
|
||||||
done
|
done
|
||||||
sync
|
sync
|
||||||
@ -99,7 +151,16 @@ delete_backup() {
|
|||||||
mount_operation() {
|
mount_operation() {
|
||||||
mount_point=$(mktemp -d -t csbackup.XXXXX)
|
mount_point=$(mktemp -d -t csbackup.XXXXX)
|
||||||
dest="$mount_point/${BACKUP_DIR}"
|
dest="$mount_point/${BACKUP_DIR}"
|
||||||
mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS})
|
if [ ${NAS_TYPE} == "cifs" ]; then
|
||||||
|
MOUNT_OPTS="${MOUNT_OPTS},nobrl"
|
||||||
|
fi
|
||||||
|
mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS}) | tee -a "$logFile"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log -ne "Successfully mounted ${NAS_TYPE} store"
|
||||||
|
else
|
||||||
|
echo "Failed to mount ${NAS_TYPE} store"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function usage {
|
function usage {
|
||||||
@ -157,6 +218,9 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Perform Initial sanity checks
|
||||||
|
sanity_checks
|
||||||
|
|
||||||
if [ "$OP" = "backup" ]; then
|
if [ "$OP" = "backup" ]; then
|
||||||
STATE=$(virsh -c qemu:///system list | grep $VM | awk '{print $3}')
|
STATE=$(virsh -c qemu:///system list | grep $VM | awk '{print $3}')
|
||||||
if [ "$STATE" = "running" ]; then
|
if [ "$STATE" = "running" ]; then
|
||||||
|
|||||||
@ -615,107 +615,81 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi
|
|||||||
|
|
||||||
String msgSubject = null;
|
String msgSubject = null;
|
||||||
String msgContent = null;
|
String msgContent = null;
|
||||||
String totalStr;
|
String percentual = formatPercent(usedCapacity / totalCapacity);
|
||||||
String usedStr;
|
String totalInMB = formatBytesToMegabytes(totalCapacity);
|
||||||
String pctStr = formatPercent(usedCapacity / totalCapacity);
|
String usedInMB = formatBytesToMegabytes(usedCapacity);
|
||||||
|
String totalInString = String.valueOf(totalCapacity);
|
||||||
|
String usedInString = String.valueOf(usedCapacity);
|
||||||
AlertType alertType = null;
|
AlertType alertType = null;
|
||||||
Long podId = pod == null ? null : pod.getId();
|
Long podId = pod == null ? null : pod.getId();
|
||||||
Long clusterId = cluster == null ? null : cluster.getId();
|
Long clusterId = cluster == null ? null : cluster.getId();
|
||||||
|
String clusterName = cluster == null ? null : cluster.getName();
|
||||||
|
String podName = pod == null ? null : pod.getName();
|
||||||
|
String dataCenterName = dc.getName();
|
||||||
|
|
||||||
switch (capacityType) {
|
switch (capacityType) {
|
||||||
|
|
||||||
//Cluster Level
|
|
||||||
case Capacity.CAPACITY_TYPE_MEMORY:
|
case Capacity.CAPACITY_TYPE_MEMORY:
|
||||||
msgSubject = "System Alert: Low Available Memory in cluster " + cluster.getName() + " pod " + pod.getName() + " of availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Low Available Memory in cluster [%s] pod [%s] of availability zone [%s].", clusterName, podName, dataCenterName);
|
||||||
totalStr = formatBytesToMegabytes(totalCapacity);
|
msgContent = String.format("System memory is low, total: %s MB, used: %s MB (%s%%).", totalInMB, usedInMB, percentual);
|
||||||
usedStr = formatBytesToMegabytes(usedCapacity);
|
|
||||||
msgContent = "System memory is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_MEMORY;
|
alertType = AlertManager.AlertType.ALERT_TYPE_MEMORY;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_CPU:
|
case Capacity.CAPACITY_TYPE_CPU:
|
||||||
msgSubject = "System Alert: Low Unallocated CPU in cluster " + cluster.getName() + " pod " + pod.getName() + " of availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Low Unallocated CPU in cluster [%s] pod [%s] of availability zone [%s].", clusterName, podName, dataCenterName);
|
||||||
totalStr = DfWhole.format(totalCapacity);
|
msgContent = String.format("Unallocated CPU is low, total: %s Mhz, used: %s Mhz (%s%%).", DfWhole.format(totalCapacity), DfWhole.format(usedCapacity), percentual);
|
||||||
usedStr = DfWhole.format(usedCapacity);
|
|
||||||
msgContent = "Unallocated CPU is low, total: " + totalStr + " Mhz, used: " + usedStr + " Mhz (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_CPU;
|
alertType = AlertManager.AlertType.ALERT_TYPE_CPU;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_STORAGE:
|
case Capacity.CAPACITY_TYPE_STORAGE:
|
||||||
msgSubject = "System Alert: Low Available Storage in cluster " + cluster.getName() + " pod " + pod.getName() + " of availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Low Available Storage in cluster [%s] pod [%s] of availability zone [%s].", clusterName, podName, dataCenterName);
|
||||||
totalStr = formatBytesToMegabytes(totalCapacity);
|
msgContent = String.format("Available storage space is low, total: %s MB, used: %s MB (%s%%).", totalInMB, usedInMB, percentual);
|
||||||
usedStr = formatBytesToMegabytes(usedCapacity);
|
|
||||||
msgContent = "Available storage space is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_STORAGE;
|
alertType = AlertManager.AlertType.ALERT_TYPE_STORAGE;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED:
|
case Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED:
|
||||||
msgSubject = "System Alert: Remaining unallocated Storage is low in cluster " + cluster.getName() + " pod " + pod.getName() + " of availability zone " +
|
msgSubject = String.format("System Alert: Remaining unallocated Storage is low in cluster [%s] pod [%s] of availability zone [%s].", clusterName, podName,
|
||||||
dc.getName();
|
dataCenterName);
|
||||||
totalStr = formatBytesToMegabytes(totalCapacity);
|
msgContent = String.format("Unallocated storage space is low, total: %s MB, allocated: %s MB (%s%%)", totalInMB, usedInMB, percentual);
|
||||||
usedStr = formatBytesToMegabytes(usedCapacity);
|
|
||||||
msgContent = "Unallocated storage space is low, total: " + totalStr + " MB, allocated: " + usedStr + " MB (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_STORAGE_ALLOCATED;
|
alertType = AlertManager.AlertType.ALERT_TYPE_STORAGE_ALLOCATED;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_LOCAL_STORAGE:
|
case Capacity.CAPACITY_TYPE_LOCAL_STORAGE:
|
||||||
msgSubject = "System Alert: Remaining unallocated Local Storage is low in cluster " + cluster.getName() + " pod " + pod.getName() + " of availability zone " +
|
msgSubject = String.format("System Alert: Remaining unallocated Local Storage is low in cluster [%s] pod [%s] of availability zone [%s].", clusterName, podName,
|
||||||
dc.getName();
|
dataCenterName);
|
||||||
totalStr = formatBytesToMegabytes(totalCapacity);
|
msgContent = String.format("Unallocated storage space is low, total: %s MB, allocated: %s MB (%s%%)", totalInMB, usedInMB, percentual);
|
||||||
usedStr = formatBytesToMegabytes(usedCapacity);
|
|
||||||
msgContent = "Unallocated storage space is low, total: " + totalStr + " MB, allocated: " + usedStr + " MB (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_LOCAL_STORAGE;
|
alertType = AlertManager.AlertType.ALERT_TYPE_LOCAL_STORAGE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//Pod Level
|
|
||||||
case Capacity.CAPACITY_TYPE_PRIVATE_IP:
|
case Capacity.CAPACITY_TYPE_PRIVATE_IP:
|
||||||
msgSubject = "System Alert: Number of unallocated private IPs is low in pod " + pod.getName() + " of availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Number of unallocated private IPs is low in pod %s of availability zone [%s].", podName, dataCenterName);
|
||||||
totalStr = Double.toString(totalCapacity);
|
msgContent = String.format("Number of unallocated private IPs is low, total: %s, allocated: %s (%s%%)", totalInString, usedInString, percentual);
|
||||||
usedStr = Double.toString(usedCapacity);
|
|
||||||
msgContent = "Number of unallocated private IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_PRIVATE_IP;
|
alertType = AlertManager.AlertType.ALERT_TYPE_PRIVATE_IP;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//Zone Level
|
|
||||||
case Capacity.CAPACITY_TYPE_SECONDARY_STORAGE:
|
case Capacity.CAPACITY_TYPE_SECONDARY_STORAGE:
|
||||||
msgSubject = "System Alert: Low Available Secondary Storage in availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Low Available Secondary Storage in availability zone [%s].", dataCenterName);
|
||||||
totalStr = formatBytesToMegabytes(totalCapacity);
|
msgContent = String.format("Available secondary storage space is low, total: %s MB, used: %s MB (%s%%).", totalInMB, usedInMB, percentual);
|
||||||
usedStr = formatBytesToMegabytes(usedCapacity);
|
|
||||||
msgContent = "Available secondary storage space is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_SECONDARY_STORAGE;
|
alertType = AlertManager.AlertType.ALERT_TYPE_SECONDARY_STORAGE;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_PUBLIC_IP:
|
case Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_PUBLIC_IP:
|
||||||
msgSubject = "System Alert: Number of unallocated virtual network public IPs is low in availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Number of unallocated virtual network public IPs is low in availability zone [%s].", dataCenterName);
|
||||||
totalStr = Double.toString(totalCapacity);
|
msgContent = String.format("Number of unallocated public IPs is low, total: %s, allocated: %s (%s%%).", totalInString, usedInString, percentual);
|
||||||
usedStr = Double.toString(usedCapacity);
|
|
||||||
msgContent = "Number of unallocated public IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_VIRTUAL_NETWORK_PUBLIC_IP;
|
alertType = AlertManager.AlertType.ALERT_TYPE_VIRTUAL_NETWORK_PUBLIC_IP;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP:
|
case Capacity.CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP:
|
||||||
msgSubject = "System Alert: Number of unallocated shared network IPs is low in availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Number of unallocated shared network IPs is low in availability zone [%s].", dataCenterName);
|
||||||
totalStr = Double.toString(totalCapacity);
|
msgContent = String.format("Number of unallocated shared network IPs is low, total: %s, allocated: %s (%s%%).", totalInString, usedInString, percentual);
|
||||||
usedStr = Double.toString(usedCapacity);
|
|
||||||
msgContent = "Number of unallocated shared network IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_DIRECT_ATTACHED_PUBLIC_IP;
|
alertType = AlertManager.AlertType.ALERT_TYPE_DIRECT_ATTACHED_PUBLIC_IP;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_VLAN:
|
case Capacity.CAPACITY_TYPE_VLAN:
|
||||||
msgSubject = "System Alert: Number of unallocated VLANs is low in availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Number of unallocated VLANs is low in availability zone [%s].", dataCenterName);
|
||||||
totalStr = Double.toString(totalCapacity);
|
msgContent = String.format("Number of unallocated VLANs is low, total: %s, allocated: %s (%s%%).", totalInString, usedInString, percentual);
|
||||||
usedStr = Double.toString(usedCapacity);
|
|
||||||
msgContent = "Number of unallocated VLANs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_VLAN;
|
alertType = AlertManager.AlertType.ALERT_TYPE_VLAN;
|
||||||
break;
|
break;
|
||||||
case Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET:
|
case Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET:
|
||||||
msgSubject = "System Alert: Number of unallocated virtual network guest IPv6 subnets is low in availability zone " + dc.getName();
|
msgSubject = String.format("System Alert: Number of unallocated virtual network guest IPv6 subnets is low in availability zone [%s].", dc.getName());
|
||||||
totalStr = Double.toString(totalCapacity);
|
msgContent = String.format("Number of unallocated virtual network guest IPv6 subnets is low, total: [%s], allocated: [%s] (%s%%).", totalInString, usedInString, percentual);
|
||||||
usedStr = Double.toString(usedCapacity);
|
|
||||||
msgContent = "Number of unallocated virtual network guest IPv6 subnets is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)";
|
|
||||||
alertType = AlertManager.AlertType.ALERT_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET;
|
alertType = AlertManager.AlertType.ALERT_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("Sending alert with subject [{}] and content [{}].", msgSubject, msgContent);
|
||||||
logger.debug(msgSubject);
|
sendAlert(alertType, dc.getId(), podId, clusterId, msgSubject, msgContent);
|
||||||
logger.debug(msgContent);
|
|
||||||
}
|
|
||||||
sendAlert(alertType, dc, pod, cluster, msgSubject, msgContent);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Exception in CapacityChecker", ex);
|
logger.error("Exception in CapacityChecker", ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2137,7 +2137,7 @@ public class ApiDBUtils {
|
|||||||
for (DiskOfferingJoinVO offering : offerings) {
|
for (DiskOfferingJoinVO offering : offerings) {
|
||||||
DiskOfferingResponse response = s_diskOfferingJoinDao.newDiskOfferingResponse(offering);
|
DiskOfferingResponse response = s_diskOfferingJoinDao.newDiskOfferingResponse(offering);
|
||||||
if (vmId != null) {
|
if (vmId != null) {
|
||||||
response.setSuitableForVm(suitability.get(offering.getId()));
|
response.setSuitableForVm(suitability.getOrDefault(offering.getId(), true));
|
||||||
}
|
}
|
||||||
list.add(response);
|
list.add(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5440,7 +5440,6 @@ public class ApiResponseHelper implements ResponseGenerator {
|
|||||||
response.setAddress(backupRepository.getAddress());
|
response.setAddress(backupRepository.getAddress());
|
||||||
response.setProviderName(backupRepository.getProvider());
|
response.setProviderName(backupRepository.getProvider());
|
||||||
response.setType(backupRepository.getType());
|
response.setType(backupRepository.getType());
|
||||||
response.setMountOptions(backupRepository.getMountOptions());
|
|
||||||
response.setCapacityBytes(backupRepository.getCapacityBytes());
|
response.setCapacityBytes(backupRepository.getCapacityBytes());
|
||||||
response.setObjectName("backuprepository");
|
response.setObjectName("backuprepository");
|
||||||
DataCenter zone = ApiDBUtils.findZoneById(backupRepository.getZoneId());
|
DataCenter zone = ApiDBUtils.findZoneById(backupRepository.getZoneId());
|
||||||
|
|||||||
@ -1840,9 +1840,9 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
|
|||||||
_hostDetailsDao.update(hostDetail.getId(), hostDetail);
|
_hostDetailsDao.update(hostDetail.getId(), hostDetail);
|
||||||
} else if (!isUpdateFromHostHealthCheck && hostDetail != null &&
|
} else if (!isUpdateFromHostHealthCheck && hostDetail != null &&
|
||||||
Boolean.parseBoolean(hostDetail.getValue()) && resourceEvent == ResourceState.Event.Disable) {
|
Boolean.parseBoolean(hostDetail.getValue()) && resourceEvent == ResourceState.Event.Disable) {
|
||||||
logger.info(String.format("The setting %s is enabled but the host %s is manually set into %s state," +
|
logger.info("The setting {} is enabled but {} is manually set into {} state," +
|
||||||
"ignoring future auto enabling of the host based on health check results",
|
"ignoring future auto enabling of the host based on health check results",
|
||||||
AgentManager.EnableKVMAutoEnableDisable.key(), host.getName(), resourceEvent));
|
AgentManager.EnableKVMAutoEnableDisable.key(), host, resourceEvent);
|
||||||
hostDetail.setValue(Boolean.FALSE.toString());
|
hostDetail.setValue(Boolean.FALSE.toString());
|
||||||
_hostDetailsDao.update(hostDetail.getId(), hostDetail);
|
_hostDetailsDao.update(hostDetail.getId(), hostDetail);
|
||||||
} else if (hostDetail == null) {
|
} else if (hostDetail == null) {
|
||||||
|
|||||||
@ -23,21 +23,12 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.amazonaws.util.CollectionUtils;
|
|
||||||
import com.cloud.alert.AlertManager;
|
|
||||||
import com.cloud.configuration.Resource;
|
|
||||||
import com.cloud.exception.ResourceAllocationException;
|
|
||||||
import com.cloud.storage.Snapshot;
|
|
||||||
import com.cloud.storage.VolumeApiService;
|
|
||||||
import com.cloud.user.DomainManager;
|
|
||||||
import com.cloud.user.ResourceLimitService;
|
|
||||||
import com.cloud.utils.fsm.NoTransitionException;
|
|
||||||
import com.cloud.vm.VirtualMachineManager;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
@ -68,7 +59,6 @@ import org.apache.cloudstack.backup.dao.BackupDao;
|
|||||||
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
|
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
|
||||||
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
|
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
|
||||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
|
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
|
||||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||||
@ -83,8 +73,11 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToSt
|
|||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.amazonaws.util.CollectionUtils;
|
||||||
|
import com.cloud.alert.AlertManager;
|
||||||
import com.cloud.api.ApiDispatcher;
|
import com.cloud.api.ApiDispatcher;
|
||||||
import com.cloud.api.ApiGsonHelper;
|
import com.cloud.api.ApiGsonHelper;
|
||||||
|
import com.cloud.configuration.Resource;
|
||||||
import com.cloud.dc.DataCenter;
|
import com.cloud.dc.DataCenter;
|
||||||
import com.cloud.dc.dao.DataCenterDao;
|
import com.cloud.dc.dao.DataCenterDao;
|
||||||
import com.cloud.event.ActionEvent;
|
import com.cloud.event.ActionEvent;
|
||||||
@ -94,6 +87,7 @@ import com.cloud.event.EventVO;
|
|||||||
import com.cloud.event.UsageEventUtils;
|
import com.cloud.event.UsageEventUtils;
|
||||||
import com.cloud.exception.InvalidParameterValueException;
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
import com.cloud.exception.PermissionDeniedException;
|
import com.cloud.exception.PermissionDeniedException;
|
||||||
|
import com.cloud.exception.ResourceAllocationException;
|
||||||
import com.cloud.host.HostVO;
|
import com.cloud.host.HostVO;
|
||||||
import com.cloud.host.dao.HostDao;
|
import com.cloud.host.dao.HostDao;
|
||||||
import com.cloud.hypervisor.Hypervisor;
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
@ -101,13 +95,17 @@ import com.cloud.hypervisor.HypervisorGuru;
|
|||||||
import com.cloud.hypervisor.HypervisorGuruManager;
|
import com.cloud.hypervisor.HypervisorGuruManager;
|
||||||
import com.cloud.projects.Project;
|
import com.cloud.projects.Project;
|
||||||
import com.cloud.storage.ScopeType;
|
import com.cloud.storage.ScopeType;
|
||||||
|
import com.cloud.storage.Snapshot;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
|
import com.cloud.storage.VolumeApiService;
|
||||||
import com.cloud.storage.VolumeVO;
|
import com.cloud.storage.VolumeVO;
|
||||||
import com.cloud.storage.dao.DiskOfferingDao;
|
import com.cloud.storage.dao.DiskOfferingDao;
|
||||||
import com.cloud.storage.dao.VolumeDao;
|
import com.cloud.storage.dao.VolumeDao;
|
||||||
import com.cloud.user.Account;
|
import com.cloud.user.Account;
|
||||||
import com.cloud.user.AccountManager;
|
import com.cloud.user.AccountManager;
|
||||||
import com.cloud.user.AccountService;
|
import com.cloud.user.AccountService;
|
||||||
|
import com.cloud.user.DomainManager;
|
||||||
|
import com.cloud.user.ResourceLimitService;
|
||||||
import com.cloud.user.User;
|
import com.cloud.user.User;
|
||||||
import com.cloud.utils.DateUtil;
|
import com.cloud.utils.DateUtil;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
@ -126,8 +124,10 @@ import com.cloud.utils.db.TransactionCallbackNoReturn;
|
|||||||
import com.cloud.utils.db.TransactionLegacy;
|
import com.cloud.utils.db.TransactionLegacy;
|
||||||
import com.cloud.utils.db.TransactionStatus;
|
import com.cloud.utils.db.TransactionStatus;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.cloud.utils.fsm.NoTransitionException;
|
||||||
import com.cloud.vm.VMInstanceVO;
|
import com.cloud.vm.VMInstanceVO;
|
||||||
import com.cloud.vm.VirtualMachine;
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import com.cloud.vm.VirtualMachineManager;
|
||||||
import com.cloud.vm.dao.UserVmDao;
|
import com.cloud.vm.dao.UserVmDao;
|
||||||
import com.cloud.vm.dao.VMInstanceDao;
|
import com.cloud.vm.dao.VMInstanceDao;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@ -173,8 +173,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
@Inject
|
@Inject
|
||||||
private VolumeApiService volumeApiService;
|
private VolumeApiService volumeApiService;
|
||||||
@Inject
|
@Inject
|
||||||
private VolumeOrchestrationService volumeOrchestrationService;
|
|
||||||
@Inject
|
|
||||||
private ResourceLimitService resourceLimitMgr;
|
private ResourceLimitService resourceLimitMgr;
|
||||||
@Inject
|
@Inject
|
||||||
private AlertManager alertManager;
|
private AlertManager alertManager;
|
||||||
@ -514,17 +512,34 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule")
|
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule")
|
||||||
public boolean deleteBackupSchedule(Long vmId) {
|
public boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd) {
|
||||||
|
Long vmId = cmd.getVmId();
|
||||||
|
Long id = cmd.getId();
|
||||||
|
if (Objects.isNull(vmId) && Objects.isNull(id)) {
|
||||||
|
throw new InvalidParameterValueException("Either instance ID or ID of backup schedule needs to be specified");
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(vmId)) {
|
||||||
final VMInstanceVO vm = findVmById(vmId);
|
final VMInstanceVO vm = findVmById(vmId);
|
||||||
validateForZone(vm.getDataCenterId());
|
validateForZone(vm.getDataCenterId());
|
||||||
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
||||||
|
return deleteAllVMBackupSchedules(vm.getId());
|
||||||
final BackupSchedule schedule = backupScheduleDao.findByVM(vmId);
|
} else {
|
||||||
|
final BackupSchedule schedule = backupScheduleDao.findById(id);
|
||||||
if (schedule == null) {
|
if (schedule == null) {
|
||||||
throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything.");
|
throw new CloudRuntimeException("Could not find the requested backup schedule.");
|
||||||
}
|
}
|
||||||
return backupScheduleDao.remove(schedule.getId());
|
return backupScheduleDao.remove(schedule.getId());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteAllVMBackupSchedules(long vmId) {
|
||||||
|
List<BackupScheduleVO> vmBackupSchedules = backupScheduleDao.listByVM(vmId);
|
||||||
|
boolean success = true;
|
||||||
|
for (BackupScheduleVO vmBackupSchedule : vmBackupSchedules) {
|
||||||
|
success = success && backupScheduleDao.remove(vmBackupSchedule.getId());
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
private void postCreateScheduledBackup(Backup.Type backupType, Long vmId) {
|
private void postCreateScheduledBackup(Backup.Type backupType, Long vmId) {
|
||||||
DateUtil.IntervalType intervalType = DateUtil.IntervalType.valueOf(backupType.name());
|
DateUtil.IntervalType intervalType = DateUtil.IntervalType.valueOf(backupType.name());
|
||||||
@ -740,6 +755,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||||||
!vm.getState().equals(VirtualMachine.State.Destroyed)) {
|
!vm.getState().equals(VirtualMachine.State.Destroyed)) {
|
||||||
throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup");
|
throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup");
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is done to handle historic backups if any with Veeam / Networker plugins
|
// This is done to handle historic backups if any with Veeam / Networker plugins
|
||||||
List<Backup.VolumeInfo> backupVolumes = CollectionUtils.isNullOrEmpty(backup.getBackedUpVolumes()) ?
|
List<Backup.VolumeInfo> backupVolumes = CollectionUtils.isNullOrEmpty(backup.getBackedUpVolumes()) ?
|
||||||
vm.getBackupVolumeList() : backup.getBackedUpVolumes();
|
vm.getBackupVolumeList() : backup.getBackedUpVolumes();
|
||||||
|
|||||||
@ -403,6 +403,8 @@ for example:
|
|||||||
except IOError as e:
|
except IOError as e:
|
||||||
msg = "Failed to save management server secret key file %s due to %s, also please check the default umask"%(self.encryptionKeyFile, e.strerror)
|
msg = "Failed to save management server secret key file %s due to %s, also please check the default umask"%(self.encryptionKeyFile, e.strerror)
|
||||||
self.errorAndExit(msg)
|
self.errorAndExit(msg)
|
||||||
|
os.chmod(self.encryptionKeyFile, 0o640)
|
||||||
|
shutil.chown(self.encryptionKeyFile, user=None, group="cloud")
|
||||||
|
|
||||||
def formatEncryptResult(value):
|
def formatEncryptResult(value):
|
||||||
return 'ENC(%s)'%value
|
return 'ENC(%s)'%value
|
||||||
|
|||||||
@ -18,14 +18,15 @@
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
# All tests inherit from cloudstackTestCase
|
# All tests inherit from cloudstackTestCase
|
||||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||||
|
|
||||||
# Import Integration Libraries
|
# Import Integration Libraries
|
||||||
# base - contains all resources as entities and defines create, delete, list operations on them
|
# base - contains all resources as entities and defines create, delete, list operations on them
|
||||||
from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot, StoragePool, Template, User, \
|
from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot, StoragePool, Template, User
|
||||||
VirtualMachine, Volume
|
from marvin.lib.base import VirtualMachine, Volume, VmSnapshot
|
||||||
|
|
||||||
# common - commonly used methods for all tests are listed here
|
# common - commonly used methods for all tests are listed here
|
||||||
from marvin.lib.common import get_domain, get_template, get_zone, list_clusters, list_hosts, list_virtual_machines, \
|
from marvin.lib.common import get_domain, get_template, get_zone, list_clusters, list_hosts, list_virtual_machines, \
|
||||||
@ -97,8 +98,7 @@ class TestData:
|
|||||||
# hypervisor type to test
|
# hypervisor type to test
|
||||||
hypervisor_type = kvm
|
hypervisor_type = kvm
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, linstor_controller_url):
|
||||||
linstor_controller_url = "http://10.43.224.8"
|
|
||||||
self.testdata = {
|
self.testdata = {
|
||||||
TestData.kvm: {
|
TestData.kvm: {
|
||||||
TestData.username: "admin",
|
TestData.username: "admin",
|
||||||
@ -197,7 +197,7 @@ class TestData:
|
|||||||
"resourceGroup": "acs-test-same"
|
"resourceGroup": "acs-test-same"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# Linstor storage pool on different ScaleIO storage instance
|
# Linstor storage pool on different Linstor storage instance
|
||||||
TestData.primaryStorageDistinctInstance: {
|
TestData.primaryStorageDistinctInstance: {
|
||||||
"name": "Linstor-%d" % random.randint(0, 100),
|
"name": "Linstor-%d" % random.randint(0, 100),
|
||||||
TestData.scope: "ZONE",
|
TestData.scope: "ZONE",
|
||||||
@ -225,6 +225,44 @@ class TestData:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ServiceReady:
|
||||||
|
@classmethod
|
||||||
|
def ready(cls, hostname: str, port: int) -> bool:
|
||||||
|
try:
|
||||||
|
s = socket.create_connection((hostname, port), timeout=1)
|
||||||
|
s.close()
|
||||||
|
return True
|
||||||
|
except (ConnectionRefusedError, socket.timeout, OSError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wait(
|
||||||
|
cls,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
wait_interval = 5,
|
||||||
|
timeout = 90,
|
||||||
|
service_name = 'ssh') -> bool:
|
||||||
|
"""
|
||||||
|
Wait until the controller can be reached.
|
||||||
|
:param hostname:
|
||||||
|
:param port: port of the application
|
||||||
|
:param wait_interval:
|
||||||
|
:param timeout: time to wait until exit with False
|
||||||
|
:param service_name: name of the service to wait
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
starttime = int(round(time.time() * 1000))
|
||||||
|
while not cls.ready(hostname, port):
|
||||||
|
if starttime + timeout * 1000 < int(round(time.time() * 1000)):
|
||||||
|
raise RuntimeError("{s} {h} cannot be reached.".format(s=service_name, h=hostname))
|
||||||
|
time.sleep(wait_interval)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wait_ssh_ready(cls, hostname, wait_interval = 1, timeout = 90):
|
||||||
|
return cls.wait(hostname, 22, wait_interval, timeout, "ssh")
|
||||||
|
|
||||||
|
|
||||||
class TestLinstorVolumes(cloudstackTestCase):
|
class TestLinstorVolumes(cloudstackTestCase):
|
||||||
_volume_vm_id_and_vm_id_do_not_match_err_msg = "The volume's VM ID and the VM's ID do not match."
|
_volume_vm_id_and_vm_id_do_not_match_err_msg = "The volume's VM ID and the VM's ID do not match."
|
||||||
@ -239,7 +277,11 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
cls.apiClient = testclient.getApiClient()
|
cls.apiClient = testclient.getApiClient()
|
||||||
cls.configData = testclient.getParsedTestDataConfig()
|
cls.configData = testclient.getParsedTestDataConfig()
|
||||||
cls.dbConnection = testclient.getDbConnection()
|
cls.dbConnection = testclient.getDbConnection()
|
||||||
cls.testdata = TestData().testdata
|
|
||||||
|
# first host has the linstor controller
|
||||||
|
first_host = list_hosts(cls.apiClient)[0]
|
||||||
|
|
||||||
|
cls.testdata = TestData(first_host.ipaddress).testdata
|
||||||
|
|
||||||
# Get Resources from Cloud Infrastructure
|
# Get Resources from Cloud Infrastructure
|
||||||
cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId])
|
cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId])
|
||||||
@ -326,7 +368,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
serviceofferingid=cls.compute_offering.id,
|
serviceofferingid=cls.compute_offering.id,
|
||||||
templateid=cls.template.id,
|
templateid=cls.template.id,
|
||||||
domainid=cls.domain.id,
|
domainid=cls.domain.id,
|
||||||
startvm=False
|
startvm=False,
|
||||||
|
mode='basic',
|
||||||
)
|
)
|
||||||
|
|
||||||
TestLinstorVolumes._start_vm(cls.virtual_machine)
|
TestLinstorVolumes._start_vm(cls.virtual_machine)
|
||||||
@ -394,7 +437,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
serviceofferingid=self.compute_offering.id,
|
serviceofferingid=self.compute_offering.id,
|
||||||
templateid=self.template.id,
|
templateid=self.template.id,
|
||||||
domainid=self.domain.id,
|
domainid=self.domain.id,
|
||||||
startvm=False
|
startvm=False,
|
||||||
|
mode='basic',
|
||||||
)
|
)
|
||||||
|
|
||||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||||
@ -887,8 +931,31 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
"Check volume was deleted"
|
"Check volume was deleted"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@attr(tags=['basic'], required_hardware=False)
|
||||||
|
def test_09_create_snapshot(self):
|
||||||
|
"""Create snapshot of root disk"""
|
||||||
|
self.virtual_machine.stop(self.apiClient)
|
||||||
|
|
||||||
|
volume = list_volumes(
|
||||||
|
self.apiClient,
|
||||||
|
virtualmachineid = self.virtual_machine.id,
|
||||||
|
type = "ROOT",
|
||||||
|
listall = True,
|
||||||
|
)
|
||||||
|
snapshot = Snapshot.create(
|
||||||
|
self.apiClient,
|
||||||
|
volume_id = volume[0].id,
|
||||||
|
account=self.account.name,
|
||||||
|
domainid=self.domain.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(snapshot, "Could not create snapshot")
|
||||||
|
|
||||||
|
snapshot.delete(self.apiClient)
|
||||||
|
|
||||||
|
|
||||||
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
||||||
def test_09_migrate_volume_to_same_instance_pool(self):
|
def test_10_migrate_volume_to_same_instance_pool(self):
|
||||||
"""Migrate volume to the same instance pool"""
|
"""Migrate volume to the same instance pool"""
|
||||||
|
|
||||||
if not self.testdata[TestData.migrationTests]:
|
if not self.testdata[TestData.migrationTests]:
|
||||||
@ -906,7 +973,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
serviceofferingid=self.compute_offering.id,
|
serviceofferingid=self.compute_offering.id,
|
||||||
templateid=self.template.id,
|
templateid=self.template.id,
|
||||||
domainid=self.domain.id,
|
domainid=self.domain.id,
|
||||||
startvm=False
|
startvm=False,
|
||||||
|
mode='basic',
|
||||||
)
|
)
|
||||||
|
|
||||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||||
@ -1020,7 +1088,7 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
test_virtual_machine.delete(self.apiClient, True)
|
test_virtual_machine.delete(self.apiClient, True)
|
||||||
|
|
||||||
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
||||||
def test_10_migrate_volume_to_distinct_instance_pool(self):
|
def test_11_migrate_volume_to_distinct_instance_pool(self):
|
||||||
"""Migrate volume to distinct instance pool"""
|
"""Migrate volume to distinct instance pool"""
|
||||||
|
|
||||||
if not self.testdata[TestData.migrationTests]:
|
if not self.testdata[TestData.migrationTests]:
|
||||||
@ -1038,7 +1106,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
serviceofferingid=self.compute_offering.id,
|
serviceofferingid=self.compute_offering.id,
|
||||||
templateid=self.template.id,
|
templateid=self.template.id,
|
||||||
domainid=self.domain.id,
|
domainid=self.domain.id,
|
||||||
startvm=False
|
startvm=False,
|
||||||
|
mode='basic',
|
||||||
)
|
)
|
||||||
|
|
||||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||||
@ -1151,6 +1220,132 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
|
|
||||||
test_virtual_machine.delete(self.apiClient, True)
|
test_virtual_machine.delete(self.apiClient, True)
|
||||||
|
|
||||||
|
@attr(tags=["basic"], required_hardware=False)
|
||||||
|
def test_12_create_vm_snapshots(self):
|
||||||
|
"""Test to create VM snapshots
|
||||||
|
"""
|
||||||
|
vm = TestLinstorVolumes._start_vm(self.virtual_machine)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Login to VM and write data to file system
|
||||||
|
self.debug("virt: {}".format(vm))
|
||||||
|
ssh_client = self.virtual_machine.get_ssh_client(vm.ipaddress, retries=5)
|
||||||
|
ssh_client.execute("echo 'hello world' > testfile")
|
||||||
|
ssh_client.execute("sync")
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail("SSH failed for Virtual machine {}: {}".format(self.virtual_machine.ssh_ip, exc))
|
||||||
|
|
||||||
|
time.sleep(10)
|
||||||
|
memory_snapshot = False
|
||||||
|
vm_snapshot = VmSnapshot.create(
|
||||||
|
self.apiClient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
memory_snapshot,
|
||||||
|
"VMSnapshot1",
|
||||||
|
"test snapshot"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
vm_snapshot.state,
|
||||||
|
"Ready",
|
||||||
|
"Check the snapshot of vm is ready!"
|
||||||
|
)
|
||||||
|
|
||||||
|
@attr(tags=["basic"], required_hardware=False)
|
||||||
|
def test_13_revert_vm_snapshots(self):
|
||||||
|
"""Test to revert VM snapshots
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||||
|
result = ssh_client.execute("rm -rf testfile")
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail("SSH failed for Virtual machine %s: %s".format(self.virtual_machine.ipaddress, exc))
|
||||||
|
|
||||||
|
if result is not None and "No such file or directory" in str(result):
|
||||||
|
self.fail("testfile not deleted")
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
list_snapshot_response = VmSnapshot.list(
|
||||||
|
self.apiClient,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
listall=True)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
isinstance(list_snapshot_response, list),
|
||||||
|
True,
|
||||||
|
"Check list response returns a valid list"
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
list_snapshot_response,
|
||||||
|
None,
|
||||||
|
"Check if snapshot exists in ListSnapshot"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list_snapshot_response[0].state,
|
||||||
|
"Ready",
|
||||||
|
"Check the snapshot of vm is ready!"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.virtual_machine.stop(self.apiClient, forced=True)
|
||||||
|
|
||||||
|
VmSnapshot.revertToSnapshot(
|
||||||
|
self.apiClient,
|
||||||
|
list_snapshot_response[0].id
|
||||||
|
)
|
||||||
|
|
||||||
|
TestLinstorVolumes._start_vm(self.virtual_machine)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||||
|
|
||||||
|
result = ssh_client.execute("cat testfile")
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail("SSH failed for Virtual machine {}: {}".format(self.virtual_machine.ipaddress, exc))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
"hello world",
|
||||||
|
result[0],
|
||||||
|
"Check the content is the same as originally written"
|
||||||
|
)
|
||||||
|
|
||||||
|
@attr(tags=["basic"], required_hardware=False)
|
||||||
|
def test_14_delete_vm_snapshots(self):
|
||||||
|
"""Test to delete vm snapshots
|
||||||
|
"""
|
||||||
|
|
||||||
|
list_snapshot_response = VmSnapshot.list(
|
||||||
|
self.apiClient,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
listall=True)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
isinstance(list_snapshot_response, list),
|
||||||
|
True,
|
||||||
|
"Check list response returns a valid list"
|
||||||
|
)
|
||||||
|
self.assertNotEqual(
|
||||||
|
list_snapshot_response,
|
||||||
|
None,
|
||||||
|
"Check if snapshot exists in ListSnapshot"
|
||||||
|
)
|
||||||
|
VmSnapshot.deleteVMSnapshot(
|
||||||
|
self.apiClient,
|
||||||
|
list_snapshot_response[0].id)
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
list_snapshot_response = VmSnapshot.list(
|
||||||
|
self.apiClient,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
listall=False)
|
||||||
|
self.debug('list_snapshot_response -------------------- {}'.format(list_snapshot_response))
|
||||||
|
|
||||||
|
self.assertIsNone(list_snapshot_response, "snapshot is already deleted")
|
||||||
|
|
||||||
def _create_vm_using_template_and_destroy_vm(self, template):
|
def _create_vm_using_template_and_destroy_vm(self, template):
|
||||||
vm_name = "VM-%d" % random.randint(0, 100)
|
vm_name = "VM-%d" % random.randint(0, 100)
|
||||||
|
|
||||||
@ -1177,42 +1372,31 @@ class TestLinstorVolumes(cloudstackTestCase):
|
|||||||
|
|
||||||
virtual_machine.delete(self.apiClient, True)
|
virtual_machine.delete(self.apiClient, True)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_bytes_from_gb(number_in_gb):
|
|
||||||
return number_in_gb * 1024 * 1024 * 1024
|
|
||||||
|
|
||||||
def _get_volume(self, volume_id):
|
def _get_volume(self, volume_id):
|
||||||
list_vols_response = list_volumes(self.apiClient, id=volume_id)
|
list_vols_response = list_volumes(self.apiClient, id=volume_id)
|
||||||
return list_vols_response[0]
|
return list_vols_response[0]
|
||||||
|
|
||||||
def _get_vm(self, vm_id):
|
@classmethod
|
||||||
list_vms_response = list_virtual_machines(self.apiClient, id=vm_id)
|
def _get_vm(cls, vm_id):
|
||||||
|
list_vms_response = list_virtual_machines(cls.apiClient, id=vm_id)
|
||||||
return list_vms_response[0]
|
return list_vms_response[0]
|
||||||
|
|
||||||
def _get_template_cache_name(self):
|
|
||||||
if TestData.hypervisor_type == TestData.kvm:
|
|
||||||
return TestData.templateCacheNameKvm
|
|
||||||
|
|
||||||
self.assert_(False, "Invalid hypervisor type")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _start_vm(cls, vm):
|
def _start_vm(cls, vm):
|
||||||
vm_for_check = list_virtual_machines(
|
vm_for_check = cls._get_vm(vm.id)
|
||||||
cls.apiClient,
|
|
||||||
id=vm.id
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
if vm_for_check.state == VirtualMachine.STOPPED:
|
if vm_for_check.state == VirtualMachine.STOPPED:
|
||||||
vm.start(cls.apiClient)
|
vm.start(cls.apiClient)
|
||||||
|
|
||||||
# For KVM, just give it 90 seconds to boot up.
|
vm_for_check = cls._get_vm(vm.id)
|
||||||
if TestData.hypervisor_type == TestData.kvm:
|
ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)
|
||||||
time.sleep(90)
|
return vm_for_check
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _reboot_vm(cls, vm):
|
def _reboot_vm(cls, vm):
|
||||||
|
vm_for_check = cls._get_vm(vm.id)
|
||||||
vm.reboot(cls.apiClient)
|
vm.reboot(cls.apiClient)
|
||||||
|
|
||||||
# For KVM, just give it 90 seconds to boot up.
|
time.sleep(5)
|
||||||
if TestData.hypervisor_type == TestData.kvm:
|
|
||||||
time.sleep(90)
|
ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)
|
||||||
|
|||||||
@ -259,8 +259,8 @@ class TestPurgeExpungedVms(cloudstackTestCase):
|
|||||||
active_server_ips = []
|
active_server_ips = []
|
||||||
active_server_ips.append(self.mgtSvrDetails["mgtSvrIp"])
|
active_server_ips.append(self.mgtSvrDetails["mgtSvrIp"])
|
||||||
for idx, server in enumerate(servers):
|
for idx, server in enumerate(servers):
|
||||||
if server.state == 'Up' and server.serviceip != self.mgtSvrDetails["mgtSvrIp"]:
|
if server.state == 'Up' and server.ipaddress != self.mgtSvrDetails["mgtSvrIp"]:
|
||||||
active_server_ips.append(server.serviceip)
|
active_server_ips.append(server.ipaddress)
|
||||||
return active_server_ips
|
return active_server_ips
|
||||||
|
|
||||||
def restartAllManagementServers(self):
|
def restartAllManagementServers(self):
|
||||||
|
|||||||
2
ui/public/index.html
vendored
2
ui/public/index.html
vendored
@ -55,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
fetch('./config.json')
|
fetch('./config.json?ts=' + Date.now())
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
document.getElementById("favicon").setAttribute("href", data.loginFavicon);
|
document.getElementById("favicon").setAttribute("href", data.loginFavicon);
|
||||||
|
|||||||
@ -2638,6 +2638,11 @@
|
|||||||
"label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as Instance Templates and Snapshots.",
|
"label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as Instance Templates and Snapshots.",
|
||||||
"label.buckets": "Buckets",
|
"label.buckets": "Buckets",
|
||||||
"label.objectstorageid": "Object Storage Pool",
|
"label.objectstorageid": "Object Storage Pool",
|
||||||
|
"label.oobm.address": "Out-of-band management address",
|
||||||
|
"label.oobm.driver": "Out-of-band management driver",
|
||||||
|
"label.oobm.port": "Out-of-band management port",
|
||||||
|
"label.oobm.powerstate": "Out-of-band management power state",
|
||||||
|
"label.oobm.username": "Out-of-band management username",
|
||||||
"label.bucket.update": "Update Bucket",
|
"label.bucket.update": "Update Bucket",
|
||||||
"label.bucket.delete": "Delete Bucket",
|
"label.bucket.delete": "Delete Bucket",
|
||||||
"label.quotagib": "Quota in GiB",
|
"label.quotagib": "Quota in GiB",
|
||||||
@ -3349,6 +3354,7 @@
|
|||||||
"message.no.description": "No description entered.",
|
"message.no.description": "No description entered.",
|
||||||
"message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.",
|
"message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.",
|
||||||
"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers</a>",
|
"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers</a>",
|
||||||
|
"message.oobm.configured": "Successfully configured out-of-band management for host",
|
||||||
"message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.",
|
"message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.",
|
||||||
"message.password.reset.failed": "Failed to reset password.",
|
"message.password.reset.failed": "Failed to reset password.",
|
||||||
"message.password.reset.success": "Password has been reset successfully. Please login using your new credentials.",
|
"message.password.reset.success": "Password has been reset successfully. Please login using your new credentials.",
|
||||||
@ -3506,6 +3512,7 @@
|
|||||||
"message.success.change.bgp.peers": "Successfully changed BGP peers",
|
"message.success.change.bgp.peers": "Successfully changed BGP peers",
|
||||||
"message.success.change.offering": "Successfully changed offering",
|
"message.success.change.offering": "Successfully changed offering",
|
||||||
"message.success.change.password": "Successfully changed password for User",
|
"message.success.change.password": "Successfully changed password for User",
|
||||||
|
"message.success.change.host.password": "Successfully changed password for host \"{name}\"",
|
||||||
"message.success.clear.webhook.deliveries": "Successfully cleared webhook deliveries",
|
"message.success.clear.webhook.deliveries": "Successfully cleared webhook deliveries",
|
||||||
"message.success.change.scope": "Successfully changed scope for storage pool",
|
"message.success.change.scope": "Successfully changed scope for storage pool",
|
||||||
"message.success.config.backup.schedule": "Successfully configured Instance backup schedule",
|
"message.success.config.backup.schedule": "Successfully configured Instance backup schedule",
|
||||||
|
|||||||
@ -2390,6 +2390,7 @@
|
|||||||
"message.success.change.affinity.group": "Grupos de afinidade alterados com sucesso",
|
"message.success.change.affinity.group": "Grupos de afinidade alterados com sucesso",
|
||||||
"message.success.change.offering": "Oferta alterada com sucesso",
|
"message.success.change.offering": "Oferta alterada com sucesso",
|
||||||
"message.success.change.password": "Senha alterada com sucesso",
|
"message.success.change.password": "Senha alterada com sucesso",
|
||||||
|
"message.success.change.host.password": "Senha do host \"{name}\" foi alterada com sucesso",
|
||||||
"message.success.config.backup.schedule": "Agendamento de backup de VM configurado com sucesso",
|
"message.success.config.backup.schedule": "Agendamento de backup de VM configurado com sucesso",
|
||||||
"message.success.config.sticky.policy": "Sticky policy configurada com sucesso",
|
"message.success.config.sticky.policy": "Sticky policy configurada com sucesso",
|
||||||
"message.success.copy.clipboard": "Copiado com sucesso para a \u00e1rea de transfer\u00eancia",
|
"message.success.copy.clipboard": "Copiado com sucesso para a \u00e1rea de transfer\u00eancia",
|
||||||
|
|||||||
@ -151,7 +151,7 @@ export default {
|
|||||||
],
|
],
|
||||||
mapping: {
|
mapping: {
|
||||||
type: {
|
type: {
|
||||||
options: ['nfs']
|
options: ['nfs', 'cifs']
|
||||||
},
|
},
|
||||||
provider: {
|
provider: {
|
||||||
value: (record) => { return 'nas' }
|
value: (record) => { return 'nas' }
|
||||||
|
|||||||
@ -78,6 +78,14 @@ export default {
|
|||||||
popup: true,
|
popup: true,
|
||||||
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostUpdate')))
|
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostUpdate')))
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
api: 'updateHostPassword',
|
||||||
|
icon: 'key-outlined',
|
||||||
|
label: 'label.action.change.password',
|
||||||
|
dataView: true,
|
||||||
|
popup: true,
|
||||||
|
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/ChangeHostPassword.vue')))
|
||||||
|
},
|
||||||
{
|
{
|
||||||
api: 'provisionCertificate',
|
api: 'provisionCertificate',
|
||||||
icon: 'safety-certificate-outlined',
|
icon: 'safety-certificate-outlined',
|
||||||
@ -147,16 +155,8 @@ export default {
|
|||||||
message: 'label.outofbandmanagement.configure',
|
message: 'label.outofbandmanagement.configure',
|
||||||
docHelp: 'adminguide/hosts.html#out-of-band-management',
|
docHelp: 'adminguide/hosts.html#out-of-band-management',
|
||||||
dataView: true,
|
dataView: true,
|
||||||
post: true,
|
popup: true,
|
||||||
args: ['hostid', 'address', 'port', 'username', 'password', 'driver'],
|
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/ConfigureHostOOBM')))
|
||||||
mapping: {
|
|
||||||
hostid: {
|
|
||||||
value: (record) => { return record.id }
|
|
||||||
},
|
|
||||||
driver: {
|
|
||||||
options: ['ipmitool', 'nestedcloudstack', 'redfish']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'enableOutOfBandManagementForHost',
|
api: 'enableOutOfBandManagementForHost',
|
||||||
|
|||||||
@ -258,25 +258,8 @@ export default {
|
|||||||
show: (record) => {
|
show: (record) => {
|
||||||
return record.state === 'Ready' && (record.vmstate === 'Stopped' || !record.virtualmachineid)
|
return record.state === 'Ready' && (record.vmstate === 'Stopped' || !record.virtualmachineid)
|
||||||
},
|
},
|
||||||
args: (record, store) => {
|
popup: true,
|
||||||
var fields = ['volumeid', 'name', 'displaytext', 'ostypeid', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled']
|
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateTemplate.vue')))
|
||||||
if (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) {
|
|
||||||
fields.push('domainid')
|
|
||||||
fields.push('account')
|
|
||||||
}
|
|
||||||
if (['Admin'].includes(store.userInfo.roletype) || store.features.userpublictemplateenabled) {
|
|
||||||
fields.push('ispublic')
|
|
||||||
}
|
|
||||||
if (['Admin'].includes(store.userInfo.roletype)) {
|
|
||||||
fields.push('isfeatured')
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
},
|
|
||||||
mapping: {
|
|
||||||
volumeid: {
|
|
||||||
value: (record) => { return record.id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'recoverVolume',
|
api: 'recoverVolume',
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export function loadLanguageAsync (lang) {
|
|||||||
return Promise.resolve(setLanguage(lang))
|
return Promise.resolve(setLanguage(lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(`locales/${lang}.json`)
|
return fetch(`locales/${lang}.json?ts=${Date.now()}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => Promise.resolve(setLanguage(lang, json)))
|
.then(json => Promise.resolve(setLanguage(lang, json)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ vueApp.use(genericUtilPlugin)
|
|||||||
vueApp.use(extensions)
|
vueApp.use(extensions)
|
||||||
vueApp.use(directives)
|
vueApp.use(directives)
|
||||||
|
|
||||||
fetch('config.json').then(response => response.json()).then(config => {
|
fetch('config.json?ts=' + Date.now()).then(response => response.json()).then(config => {
|
||||||
vueProps.$config = config
|
vueProps.$config = config
|
||||||
let basUrl = config.apiBase
|
let basUrl = config.apiBase
|
||||||
if (config.multipleServer) {
|
if (config.multipleServer) {
|
||||||
|
|||||||
150
ui/src/views/infra/ChangeHostPassword.vue
Normal file
150
ui/src/views/infra/ChangeHostPassword.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="form-layout" v-ctrl-enter="handleSubmit">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<a-form
|
||||||
|
:ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
layout="vertical"
|
||||||
|
@finish="handleSubmit">
|
||||||
|
<a-form-item name="username" ref="username">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.username')" :tooltip="apiParams.username.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.username"
|
||||||
|
:placeholder="$t('label.username')"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="password" ref="password">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.new.password')" :tooltip="apiParams.password.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.password"
|
||||||
|
:placeholder="$t('label.new.password')"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="confirmpassword" ref="confirmpassword">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.confirmpassword')" :tooltip="apiParams.password.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.confirmpassword"
|
||||||
|
:placeholder="$t('label.confirmpassword.description')"/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<div :span="24" class="action-button">
|
||||||
|
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||||
|
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, reactive, toRaw } from 'vue'
|
||||||
|
import { api } from '@/api'
|
||||||
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ChangeHostPassword',
|
||||||
|
components: {
|
||||||
|
TooltipLabel
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
resource: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate () {
|
||||||
|
this.apiParams = this.$getApiParams('updateHostPassword')
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.initForm()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initForm () {
|
||||||
|
this.formRef = ref()
|
||||||
|
this.form = reactive({})
|
||||||
|
this.rules = reactive({
|
||||||
|
username: [{ required: true, message: this.$t('message.error.host.username') }],
|
||||||
|
password: [{ required: true, message: this.$t('message.error.new.password') }],
|
||||||
|
confirmpassword: [
|
||||||
|
{ required: true, message: this.$t('message.error.confirm.password') },
|
||||||
|
{ validator: this.validateTwoPassword }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async validateTwoPassword (rule, value) {
|
||||||
|
const messageConfirm = this.$t('message.validate.equalto')
|
||||||
|
const passwordVal = this.form.password
|
||||||
|
if (passwordVal !== value) {
|
||||||
|
return Promise.reject(messageConfirm)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (this.loading) return
|
||||||
|
this.formRef.value.validate().then(() => {
|
||||||
|
const values = toRaw(this.form)
|
||||||
|
this.loading = true
|
||||||
|
const params = {
|
||||||
|
username: values.username,
|
||||||
|
hostId: this.resource.id,
|
||||||
|
password: values.password
|
||||||
|
}
|
||||||
|
api('updateHostPassword', {}, 'POST', params).then(json => {
|
||||||
|
this.$notification.success({
|
||||||
|
message: this.$t('label.action.change.password'),
|
||||||
|
description: `${this.$t('message.success.change.host.password', { name: this.resource.name })}`
|
||||||
|
})
|
||||||
|
this.$emit('refresh-data')
|
||||||
|
this.closeAction()
|
||||||
|
}).catch(error => {
|
||||||
|
this.$notifyError(error)
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
this.formRef.value.scrollToField(error.errorFields[0].name)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeAction () {
|
||||||
|
this.$emit('close-action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.form-layout {
|
||||||
|
width: 80vw;
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
width: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
172
ui/src/views/infra/ConfigureHostOOBM.vue
Normal file
172
ui/src/views/infra/ConfigureHostOOBM.vue
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="form-layout">
|
||||||
|
<a-form
|
||||||
|
:ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
@finish="handleSubmit"
|
||||||
|
v-ctrl-enter="handleSubmit"
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<a-alert type="warning">
|
||||||
|
<template #message>
|
||||||
|
<span v-html="$t('label.outofbandmanagement.configure')" />
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<a-form-item name="address" ref="address">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.address')" :tooltip="apiParams.address.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.address"
|
||||||
|
v-focus="true" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="port" ref="port">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.port')" :tooltip="apiParams.port.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.port" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="username" ref="username">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.username')" :tooltip="apiParams.username.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.username" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="password" ref="password">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.password')" :tooltip="apiParams.password.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="form.password"
|
||||||
|
:placeholder="apiParams.password.description"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="driver" ref="driver">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.driver')" :tooltip="apiParams.driver.description"/>
|
||||||
|
</template>
|
||||||
|
<a-select
|
||||||
|
v-model:value="form.driver"
|
||||||
|
style="width: 100%;"
|
||||||
|
optionFilterProp="value"
|
||||||
|
:filterOption="(input, option) => {
|
||||||
|
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}" >
|
||||||
|
<a-select-option key="" label="">{{ }}</a-select-option>
|
||||||
|
<a-select-option value="ipmitool">ipmitool</a-select-option>
|
||||||
|
<a-select-option value="nestedcloudstack">nestedcloudstack</a-select-option>
|
||||||
|
<a-select-option value="redfish">redfish</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
<div :span="24" class="action-button">
|
||||||
|
<a-button @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||||
|
<a-button type="primary" @click="handleSubmit" ref="submit">{{ $t('label.ok') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import { ref, reactive, toRaw } from 'vue'
|
||||||
|
import { api } from '@/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ConfigureHostOOBM',
|
||||||
|
components: {
|
||||||
|
TooltipLabel
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
resource: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate () {
|
||||||
|
this.apiParams = this.$getApiParams('configureOutOfBandManagement')
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.initForm()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initForm () {
|
||||||
|
this.formRef = ref()
|
||||||
|
this.form = reactive({
|
||||||
|
address: this.resource.outofbandmanagement.address || '',
|
||||||
|
port: this.resource.outofbandmanagement.port || '',
|
||||||
|
username: this.resource.outofbandmanagement.username || '',
|
||||||
|
password: '',
|
||||||
|
driver: this.resource.outofbandmanagement.driver || ''
|
||||||
|
})
|
||||||
|
this.rules = reactive({
|
||||||
|
address: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
port: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
username: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
password: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
driver: [{ required: true, message: this.$t('message.error.required.input') }]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSubmit (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.formRef.value.validate().then(() => {
|
||||||
|
const values = toRaw(this.form)
|
||||||
|
const params = {
|
||||||
|
hostid: this.resource.id,
|
||||||
|
address: values.address,
|
||||||
|
port: values.port,
|
||||||
|
username: values.username,
|
||||||
|
password: values.password,
|
||||||
|
driver: values.driver
|
||||||
|
}
|
||||||
|
|
||||||
|
api('configureOutOfBandManagement', {}, 'POST', params).then(_ => {
|
||||||
|
this.$message.success(this.$t('message.oobm.configured'))
|
||||||
|
this.$emit('refresh-data')
|
||||||
|
this.onCloseAction()
|
||||||
|
}).catch(error => {
|
||||||
|
this.$notifyError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onCloseAction () {
|
||||||
|
this.$emit('close-action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-layout {
|
||||||
|
width: 30vw;
|
||||||
|
|
||||||
|
@media (min-width: 500px) {
|
||||||
|
width: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -28,15 +28,15 @@
|
|||||||
>
|
>
|
||||||
<a-alert type="warning">
|
<a-alert type="warning">
|
||||||
<template #message>
|
<template #message>
|
||||||
<span v-html="$t('message.confirm.enable.host')" />
|
<span v-html="resourcestate === 'Disabled' ? $t('message.confirm.enable.host') : $t('message.confirm.disable.host') " />
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<div v-show="enableKVMAutoEnableDisableSetting" class="reason">
|
<div v-show="kvmAutoEnableDisableSetting" class="reason">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
class="form__item"
|
class="form__item"
|
||||||
name="reason"
|
name="reason"
|
||||||
ref="reason"
|
ref="reason"
|
||||||
:label="'The setting \'enable.kvm.host.auto.enable.disable\' is enabled, ' +
|
:label="'The Auto Enable/Disable KVM Hosts functionality is enabled, ' +
|
||||||
' can specify a reason for ' + (resourcestate === 'Enabled' ? 'disabling' : 'enabling') + ' this host'">
|
' can specify a reason for ' + (resourcestate === 'Enabled' ? 'disabling' : 'enabling') + ' this host'">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="form.reason"
|
v-model:value="form.reason"
|
||||||
@ -69,7 +69,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
resourcestate: '',
|
resourcestate: '',
|
||||||
allocationstate: '',
|
allocationstate: '',
|
||||||
enableKVMAutoEnableDisableSetting: false
|
kvmAutoEnableDisableSetting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@ -91,8 +91,8 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
api('listConfigurations', { name: 'enable.kvm.host.auto.enable.disable', clusterid: this.resource.clusterid }).then(json => {
|
api('listConfigurations', { name: 'enable.kvm.host.auto.enable.disable', clusterid: this.resource.clusterid }).then(json => {
|
||||||
if (json.listconfigurationsresponse.configuration[0]) {
|
if (json.listconfigurationsresponse.configuration?.[0]) {
|
||||||
this.enableKVMAutoEnableDisableSetting = json.listconfigurationsresponse.configuration[0].value
|
this.kvmAutoEnableDisableSetting = json?.listconfigurationsresponse?.configuration?.[0]?.value || false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -86,14 +86,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
<a-list-item v-if="host.outofbandmanagement">
|
<span v-if="host?.outofbandmanagement?.enabled">
|
||||||
|
<a-list-item>
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ $t('label.powerstate') }}</strong>
|
<strong>{{ $t('label.oobm.username') }}</strong>
|
||||||
|
<div>
|
||||||
|
{{ host.outofbandmanagement.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<div>
|
||||||
|
<strong>{{ $t('label.oobm.powerstate') }}</strong>
|
||||||
<div>
|
<div>
|
||||||
{{ host.outofbandmanagement.powerstate }}
|
{{ host.outofbandmanagement.powerstate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<div>
|
||||||
|
<strong>{{ $t('label.oobm.driver') }}</strong>
|
||||||
|
<div>
|
||||||
|
{{ host.outofbandmanagement.driver }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<div>
|
||||||
|
<strong>{{ $t('label.oobm.address') }}</strong>
|
||||||
|
<div>
|
||||||
|
{{ host.outofbandmanagement.address }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<div>
|
||||||
|
<strong>{{ $t('label.oobm.port') }}</strong>
|
||||||
|
<div>
|
||||||
|
{{ host.outofbandmanagement.port }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
</span>
|
||||||
<a-list-item v-if="host.hostha">
|
<a-list-item v-if="host.hostha">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ $t('label.haenable') }}</strong>
|
<strong>{{ $t('label.haenable') }}</strong>
|
||||||
|
|||||||
@ -491,7 +491,6 @@ export default {
|
|||||||
const formRaw = toRaw(this.form)
|
const formRaw = toRaw(this.form)
|
||||||
const values = this.handleRemoveFields(formRaw)
|
const values = this.handleRemoveFields(formRaw)
|
||||||
var params = {
|
var params = {
|
||||||
isMirrored: false,
|
|
||||||
name: values.name,
|
name: values.name,
|
||||||
displaytext: values.displaytext,
|
displaytext: values.displaytext,
|
||||||
storageType: values.storagetype,
|
storageType: values.storagetype,
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
v-model:value="form.displaytext"
|
v-model:value="form.displaytext"
|
||||||
:placeholder="apiParams.displaytext.description" />
|
:placeholder="apiParams.displaytext.description" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item ref="zoneid" name="zoneid">
|
<a-form-item v-if="resource.intervaltype" ref="zoneid" name="zoneid">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||||
</template>
|
</template>
|
||||||
@ -130,40 +130,39 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-row :gutter="12">
|
<a-row :gutter="12">
|
||||||
<a-col :md="24" :lg="24">
|
<a-col :md="24" :lg="12">
|
||||||
<a-form-item ref="groupenabled" name="groupenabled">
|
<a-form-item ref="isdynamicallyscalable" name="isdynamicallyscalable">
|
||||||
<a-checkbox-group
|
<template #label>
|
||||||
v-model:value="form.groupenabled"
|
<tooltip-label :title="$t('label.isdynamicallyscalable')" :tooltip="apiParams.isdynamicallyscalable.description"/>
|
||||||
style="width: 100%;"
|
</template>
|
||||||
>
|
<a-switch v-model:checked="form.isdynamicallyscalable" />
|
||||||
<a-row>
|
</a-form-item>
|
||||||
<a-col :span="12">
|
<a-form-item ref="requireshvm" name="requireshvm">
|
||||||
<a-checkbox value="passwordenabled">
|
<template #label>
|
||||||
{{ $t('label.passwordenabled') }}
|
<tooltip-label :title="$t('label.requireshvm')" :tooltip="apiParams.requireshvm.description"/>
|
||||||
</a-checkbox>
|
</template>
|
||||||
</a-col>
|
<a-switch v-model:checked="form.requireshvm" />
|
||||||
<a-col :span="12">
|
</a-form-item>
|
||||||
<a-checkbox value="isdynamicallyscalable">
|
<a-form-item ref="passwordenabled" name="passwordenabled">
|
||||||
{{ $t('label.isdynamicallyscalable') }}
|
<template #label>
|
||||||
</a-checkbox>
|
<tooltip-label :title="$t('label.passwordenabled')" :tooltip="apiParams.passwordenabled.description"/>
|
||||||
</a-col>
|
</template>
|
||||||
<a-col :span="12">
|
<a-switch v-model:checked="form.passwordenabled" />
|
||||||
<a-checkbox value="requireshvm">
|
</a-form-item>
|
||||||
{{ $t('label.requireshvm') }}
|
<a-form-item
|
||||||
</a-checkbox>
|
ref="ispublic"
|
||||||
</a-col>
|
name="ispublic"
|
||||||
<a-col :span="12" v-if="isAdminRole">
|
v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.userpublictemplateenabled" >
|
||||||
<a-checkbox value="isfeatured">
|
<template #label>
|
||||||
{{ $t('label.isfeatured') }}
|
<tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/>
|
||||||
</a-checkbox>
|
</template>
|
||||||
</a-col>
|
<a-switch v-model:checked="form.ispublic" />
|
||||||
<a-col :span="12" v-if="isAdminRole || $store.getters.features.userpublictemplateenabled">
|
</a-form-item>
|
||||||
<a-checkbox value="ispublic">
|
<a-form-item ref="isfeatured" name="isfeatured" v-if="$store.getters.userInfo.roletype === 'Admin'">
|
||||||
{{ $t('label.ispublic') }}
|
<template #label>
|
||||||
</a-checkbox>
|
<tooltip-label :title="$t('label.isfeatured')" :tooltip="apiParams.isfeatured.description"/>
|
||||||
</a-col>
|
</template>
|
||||||
</a-row>
|
<a-switch v-model:checked="form.isfeatured" />
|
||||||
</a-checkbox-group>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@ -234,7 +233,9 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchData () {
|
fetchData () {
|
||||||
this.fetchOsTypes()
|
this.fetchOsTypes()
|
||||||
|
if (this.resource.intervaltype) {
|
||||||
this.fetchSnapshotZones()
|
this.fetchSnapshotZones()
|
||||||
|
}
|
||||||
if ('listDomains' in this.$store.getters.apis) {
|
if ('listDomains' in this.$store.getters.apis) {
|
||||||
this.fetchDomains()
|
this.fetchDomains()
|
||||||
}
|
}
|
||||||
@ -300,22 +301,25 @@ export default {
|
|||||||
this.handleDomainChange(null)
|
this.handleDomainChange(null)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDomainChange (domain) {
|
async handleDomainChange (domain) {
|
||||||
this.domainid = domain
|
this.domainid = domain
|
||||||
this.form.account = null
|
this.form.account = null
|
||||||
this.account = null
|
this.account = null
|
||||||
if ('listAccounts' in this.$store.getters.apis) {
|
if ('listAccounts' in this.$store.getters.apis) {
|
||||||
this.fetchAccounts()
|
await this.fetchAccounts()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchAccounts () {
|
fetchAccounts () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
api('listAccounts', {
|
api('listAccounts', {
|
||||||
domainid: this.domainid
|
domainid: this.domainid
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
this.accounts = response.listaccountsresponse.account || []
|
this.accounts = response?.listaccountsresponse?.account || []
|
||||||
|
resolve(this.accounts)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$notifyError(error)
|
this.$notifyError(error)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
handleAccountChange (acc) {
|
handleAccountChange (acc) {
|
||||||
if (acc) {
|
if (acc) {
|
||||||
@ -329,17 +333,22 @@ export default {
|
|||||||
this.formRef.value.validate().then(() => {
|
this.formRef.value.validate().then(() => {
|
||||||
const formRaw = toRaw(this.form)
|
const formRaw = toRaw(this.form)
|
||||||
const values = this.handleRemoveFields(formRaw)
|
const values = this.handleRemoveFields(formRaw)
|
||||||
values.snapshotid = this.resource.id
|
const params = {}
|
||||||
if (values.groupenabled) {
|
if (this.resource.intervaltype) {
|
||||||
const input = values.groupenabled
|
params.snapshotid = this.resource.id
|
||||||
for (const index in input) {
|
} else {
|
||||||
const name = input[index]
|
params.volumeid = this.resource.id
|
||||||
values[name] = true
|
|
||||||
}
|
}
|
||||||
delete values.groupenabled
|
|
||||||
|
for (const key in values) {
|
||||||
|
const input = values[key]
|
||||||
|
if (input === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
params[key] = input
|
||||||
}
|
}
|
||||||
this.loading = true
|
this.loading = true
|
||||||
api('createTemplate', values).then(response => {
|
api('createTemplate', params).then(response => {
|
||||||
this.$pollJob({
|
this.$pollJob({
|
||||||
jobId: response.createtemplateresponse.jobid,
|
jobId: response.createtemplateresponse.jobid,
|
||||||
title: this.$t('message.success.create.template'),
|
title: this.$t('message.success.create.template'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user