Merge branch '4.19'

This commit is contained in:
Vishesh 2024-06-13 11:57:55 +05:30
commit 6fe835e118
No known key found for this signature in database
GPG Key ID: 4E395186CBFA790B
44 changed files with 843 additions and 240 deletions

View File

@ -132,6 +132,8 @@ public interface VpcService {
*/
boolean startVpc(long vpcId, boolean destroyOnFailure) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
void startVpc(CreateVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
/**
* Shuts down the VPC which includes shutting down all VPC provider and rules cleanup on the backend
*

View File

@ -266,6 +266,7 @@ public class ApiConstants {
public static final String IS_CLEANUP_REQUIRED = "iscleanuprequired";
public static final String IS_DYNAMIC = "isdynamic";
public static final String IS_EDGE = "isedge";
public static final String IS_ENCRYPTED = "isencrypted";
public static final String IS_EXTRACTABLE = "isextractable";
public static final String IS_FEATURED = "isfeatured";
public static final String IS_IMPLICIT = "isimplicit";

View File

@ -31,6 +31,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.PodResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
@ -80,6 +81,12 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
RoleType.Admin})
private String storageId;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
description = "list volumes by disk offering of a service offering. If both service offering and " +
"disk offering are passed, service offering is ignored", since = "4.19.1")
private Long serviceOfferingId;
@Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "list volumes by disk offering", since = "4.4")
private Long diskOfferingId;
@ -94,6 +101,9 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.")
private String state;
@Parameter(name = ApiConstants.IS_ENCRYPTED, type = CommandType.BOOLEAN, description = "list only volumes that are encrypted", since = "4.19.1",
authorized = { RoleType.Admin })
private Boolean encrypted;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -118,6 +128,10 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
return podId;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
public Long getDiskOfferingId() {
return diskOfferingId;
}
@ -151,6 +165,10 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
return state;
}
public Boolean isEncrypted() {
return encrypted;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -208,11 +208,7 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd {
public void execute() {
Vpc vpc = null;
try {
if (isStart()) {
_vpcService.startVpc(getEntityId(), true);
} else {
logger.debug("Not starting VPC as " + ApiConstants.START + "=false was passed to the API");
}
_vpcService.startVpc(this);
vpc = _entityMgr.findById(Vpc.class, getEntityId());
} catch (ResourceUnavailableException ex) {
logger.warn("Exception: ", ex);

View File

@ -290,13 +290,17 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
private String externalUuid;
@SerializedName(ApiConstants.VOLUME_CHECK_RESULT)
@Param(description = "details for the volume check result, they may vary for different hypervisors, since = 4.19.1")
@Param(description = "details for the volume check result, they may vary for different hypervisors", since = "4.19.1")
private Map<String, String> volumeCheckResult;
@SerializedName(ApiConstants.VOLUME_REPAIR_RESULT)
@Param(description = "details for the volume repair result, they may vary for different hypervisors, since = 4.19.1")
@Param(description = "details for the volume repair result, they may vary for different hypervisors", since = "4.19.1")
private Map<String, String> volumeRepairResult;
@SerializedName(ApiConstants.ENCRYPT_FORMAT)
@Param(description = "the format of the disk encryption if applicable", since = "4.19.1")
private String encryptionFormat;
public String getPath() {
return path;
}
@ -842,4 +846,8 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
public void setVolumeRepairResult(Map<String, String> volumeRepairResult) {
this.volumeRepairResult = volumeRepairResult;
}
public void setEncryptionFormat(String encryptionFormat) {
this.encryptionFormat = encryptionFormat;
}
}

View File

@ -167,17 +167,16 @@ public class CreateVPCCmdTest extends TestCase {
@Test
public void testExecute() throws ResourceUnavailableException, InsufficientCapacityException {
ReflectionTestUtils.setField(cmd, "start", true);
Vpc vpc = Mockito.mock(Vpc.class);
VpcResponse response = Mockito.mock(VpcResponse.class);
ReflectionTestUtils.setField(cmd, "id", 1L);
responseGenerator = Mockito.mock(ResponseGenerator.class);
Mockito.when(_vpcService.startVpc(1L, true)).thenReturn(true);
Mockito.doNothing().when(_vpcService).startVpc(cmd);
Mockito.when(_entityMgr.findById(Mockito.eq(Vpc.class), Mockito.any(Long.class))).thenReturn(vpc);
cmd._responseGenerator = responseGenerator;
Mockito.when(responseGenerator.createVpcResponse(ResponseObject.ResponseView.Restricted, vpc)).thenReturn(response);
cmd.execute();
Mockito.verify(_vpcService, Mockito.times(1)).startVpc(Mockito.anyLong(), Mockito.anyBoolean());
Mockito.verify(_vpcService, Mockito.times(1)).startVpc(cmd);
}
}

View File

@ -0,0 +1,85 @@
//
// 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.agent.api;
import com.cloud.agent.api.LogLevel.Log4jLevel;
import com.cloud.storage.Storage.StoragePoolType;
@LogLevel(Log4jLevel.Trace)
public class GetVolumeStatAnswer extends Answer {
String poolUuid;
StoragePoolType poolType;
String volumePath;
long size = 0;
long virtualSize = 0;
public GetVolumeStatAnswer(GetVolumeStatCommand cmd, long size, long virtualSize) {
super(cmd, true, "");
this.poolUuid = cmd.getPoolUuid();
this.poolType = cmd.getPoolType();
this.volumePath = cmd.getVolumePath();
this.size = size;
this.virtualSize = virtualSize;
}
public GetVolumeStatAnswer(GetVolumeStatCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
protected GetVolumeStatAnswer() {
//no-args constructor for json serialization-deserialization
}
public String getPoolUuid() {
return poolUuid;
}
public void setPoolUuid(String poolUuid) {
this.poolUuid = poolUuid;
}
public StoragePoolType getPoolType() {
return poolType;
}
public void setPoolType(StoragePoolType poolType) {
this.poolType = poolType;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public long getVirtualSize() {
return virtualSize;
}
public void setVirtualSize(long virtualSize) {
this.virtualSize = virtualSize;
}
public String getString() {
return "GetVolumeStatAnswer [poolUuid=" + poolUuid + ", poolType=" + poolType + ", volumePath=" + volumePath + ", size=" + size + ", virtualSize=" + virtualSize + "]";
}
}

View File

@ -0,0 +1,72 @@
//
// 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.agent.api;
import com.cloud.agent.api.LogLevel.Log4jLevel;
import com.cloud.storage.Storage.StoragePoolType;
@LogLevel(Log4jLevel.Trace)
public class GetVolumeStatCommand extends Command {
String volumePath;
StoragePoolType poolType;
String poolUuid;
protected GetVolumeStatCommand() {
}
public GetVolumeStatCommand(String volumePath, StoragePoolType poolType, String poolUuid) {
this.volumePath = volumePath;
this.poolType = poolType;
this.poolUuid = poolUuid;
}
public String getVolumePath() {
return volumePath;
}
public void setVolumePath(String volumePath) {
this.volumePath = volumePath;
}
public StoragePoolType getPoolType() {
return poolType;
}
public void setPoolType(StoragePoolType poolType) {
this.poolType = poolType;
}
public String getPoolUuid() {
return poolUuid;
}
public void setPoolUuid(String storeUuid) {
this.poolUuid = storeUuid;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getString() {
return "GetVolumeStatCommand [volumePath=" + volumePath + ", poolType=" + poolType + ", poolUuid=" + poolUuid + "]";
}
}

View File

@ -39,6 +39,7 @@ public class VolumeObjectTO extends DownloadableObjectTO implements DataTO {
private DataStoreTO dataStore;
private String name;
private Long size;
private Long usableSize;
private String path;
private Long volumeId;
private String vmName;
@ -161,6 +162,10 @@ public class VolumeObjectTO extends DownloadableObjectTO implements DataTO {
return size;
}
public Long getUsableSize() {
return usableSize;
}
@Override
public DataObjectType getObjectType() {
return DataObjectType.VOLUME;
@ -178,6 +183,10 @@ public class VolumeObjectTO extends DownloadableObjectTO implements DataTO {
this.size = size;
}
public void setUsableSize(Long usableSize) {
this.usableSize = usableSize;
}
public void setPath(String path) {
this.path = path;
}

View File

@ -112,7 +112,8 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
/**
* Gets the Total Primary Storage space allocated for an account
*
* @param list of ids of virtual router VMs under this account
* @param accountId
* @param virtualRouters list of ids of virtual router VMs under this account
* @return total Primary Storage space (in bytes) used
*/
long primaryStorageUsedForAccount(long accountId, List<Long> virtualRouters);

View File

@ -81,7 +81,6 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
@Inject
ResourceTagDao _tagsDao;
protected static final String SELECT_VM_SQL = "SELECT DISTINCT instance_id from volumes v where v.host_id = ? and v.mirror_state = ?";
// need to account for zone-wide primary storage where storage_pool has
// null-value pod and cluster, where hypervisor information is stored in
// storage_pool

View File

@ -130,7 +130,7 @@ public class ImageStoreDaoImpl extends GenericDaoBase<ImageStoreVO, Long> implem
}
if (scope.getScopeId() != null) {
SearchCriteria<ImageStoreVO> scc = createSearchCriteria();
scc.addOr("scope", SearchCriteria.Op.EQ, ScopeType.REGION);
scc.addOr("scope", SearchCriteria.Op.EQ, ScopeType.ZONE);
scc.addOr("dcId", SearchCriteria.Op.EQ, scope.getScopeId());
sc.addAnd("scope", SearchCriteria.Op.SC, scc);
}

View File

@ -39,6 +39,7 @@ SELECT
`volumes`.`path` AS `path`,
`volumes`.`chain_info` AS `chain_info`,
`volumes`.`external_uuid` AS `external_uuid`,
`volumes`.`encrypt_format` AS `encrypt_format`,
`account`.`id` AS `account_id`,
`account`.`uuid` AS `account_uuid`,
`account`.`account_name` AS `account_name`,

View File

@ -47,9 +47,7 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetR
@Override
public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingResource libvirtComputingResource) {
String result = null;
String hypervisorURI = "qemu+tcp://" + command.getRemoteIp() +
"/system";
String hypervisorURI = "qemu+tcp://" + command.getRemoteIp() + "/system";
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
try {
Connect conn = LibvirtConnection.getConnection(hypervisorURI);
@ -58,26 +56,28 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetR
final Domain domain = libvirtComputingResource.getDomain(conn, name);
final DomainInfo.DomainState ps = domain.getInfo().state;
final VirtualMachine.PowerState state = libvirtComputingResource.convertToPowerState(ps);
logger.debug("VM " + domain.getName() + ": powerstate = " + ps + "; vm state=" + state.toString());
logger.debug("VM " + domain.getName() + " - powerstate: " + ps + ", state: " + state.toString());
if (state == VirtualMachine.PowerState.PowerOff) {
try {
UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn);
unmanagedInstances.put(instance.getName(), instance);
} catch (Exception e) {
logger.error("Error while fetching instance details", e);
logger.error("Couldn't fetch VM " + domain.getName() + " details, due to: " + e.getMessage(), e);
}
}
domain.free();
}
logger.debug("Found Vms: "+ unmanagedInstances.size());
return new GetRemoteVmsAnswer(command, "", unmanagedInstances);
logger.debug("Found " + unmanagedInstances.size() + " stopped VMs on host " + command.getRemoteIp());
return new GetRemoteVmsAnswer(command, "", unmanagedInstances);
} catch (final LibvirtException e) {
logger.error("Error while listing stopped Vms on remote host: "+ e.getMessage());
return new Answer(command, false, result);
logger.error("Failed to list stopped VMs on remote host " + command.getRemoteIp() + ", due to: " + e.getMessage(), e);
if (e.getMessage().toLowerCase().contains("connection refused")) {
return new Answer(command, false, "Unable to connect to remote host " + command.getRemoteIp() + ", please check the libvirtd tcp connectivity and retry");
}
return new Answer(command, false, "Unable to list stopped VMs on remote host " + command.getRemoteIp() + ", due to: " + e.getMessage());
}
}
@ -103,8 +103,8 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetR
return instance;
} catch (Exception e) {
logger.debug("Unable to retrieve unmanaged instance info. ", e);
throw new CloudRuntimeException("Unable to retrieve unmanaged instance info. " + e.getMessage());
logger.debug("Unable to retrieve unmanaged instance info, due to: " + e.getMessage(), e);
throw new CloudRuntimeException("Unable to retrieve unmanaged instance info, due to: " + e.getMessage());
}
}
@ -116,7 +116,6 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetR
return UnmanagedInstanceTO.PowerState.PowerOff;
default:
return UnmanagedInstanceTO.PowerState.PowerUnknown;
}
}
@ -163,7 +162,6 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetR
disk.setLabel(diskDef.getDiskLabel());
disk.setController(diskDef.getBusType().toString());
Pair<String, String> sourceHostPath = getSourceHostPath(libvirtComputingResource, diskDef.getSourcePath());
if (sourceHostPath != null) {
disk.setDatastoreHost(sourceHostPath.first());

View File

@ -0,0 +1,66 @@
//
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//with the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
//
package com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.GetVolumeStatAnswer;
import com.cloud.agent.api.GetVolumeStatCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.utils.exception.CloudRuntimeException;
@ResourceWrapper(handles = GetVolumeStatCommand.class)
public final class LibvirtGetVolumeStatCommandWrapper extends CommandWrapper<GetVolumeStatCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtGetVolumeStatCommandWrapper.class);
@Override
public Answer execute(final GetVolumeStatCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
try {
String volumePath = cmd.getVolumePath();
StoragePoolType poolType = cmd.getPoolType();
String poolUuid = cmd.getPoolUuid();
KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(poolType, poolUuid);
if (primaryPool == null) {
String msg = "Can't get volume stats as pool details unavailable for volume: " + volumePath + " on the storage pool: " + poolUuid;
return new GetVolumeStatAnswer(cmd, false, msg);
}
KVMPhysicalDisk disk = primaryPool.getPhysicalDisk(volumePath);
if (disk == null) {
String msg = "Can't get volume stats as disk details unavailable for volume: " + volumePath + " on the storage pool: " + poolUuid;
return new GetVolumeStatAnswer(cmd, false, msg);
}
return new GetVolumeStatAnswer(cmd, disk.getSize(), disk.getVirtualSize());
} catch (CloudRuntimeException e) {
s_logger.error("Can't get volume stats, due to: " + e.getMessage(), e);
return new GetVolumeStatAnswer(cmd, false, "Can't get volume stats, due to: " + e.getMessage());
}
}
}

View File

@ -67,6 +67,7 @@ import com.cloud.agent.api.Answer;
import com.cloud.agent.api.MigrateAnswer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.agent.api.to.DpdkTO;
import com.cloud.agent.api.to.VirtualMachineTO;
@ -90,6 +91,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
private static final String GRAPHICS_ELEM_END = "/graphics>";
private static final String GRAPHICS_ELEM_START = "<graphics";
private static final String CONTENTS_WILDCARD = "(?s).*";
private static final String CDROM_LABEL = "hdc";
protected String createMigrationURI(final String destinationIp, final LibvirtComputingResource libvirtComputingResource) {
if (StringUtils.isEmpty(destinationIp)) {
@ -164,6 +166,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
String vncPassword = org.apache.commons.lang3.StringUtils.truncate(to.getVncPassword(), 8);
xmlDesc = replaceIpForVNCInDescFileAndNormalizePassword(xmlDesc, target, vncPassword, vmName);
// Replace Config Drive ISO path
String oldIsoVolumePath = getOldVolumePath(disks, vmName);
String newIsoVolumePath = getNewVolumePathIfDatastoreHasChanged(libvirtComputingResource, conn, to);
if (newIsoVolumePath != null && !newIsoVolumePath.equals(oldIsoVolumePath)) {
@ -173,6 +176,14 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
logger.debug(String.format("Replaced disk mount point [%s] with [%s] in VM [%s] XML configuration. New XML configuration is [%s].", oldIsoVolumePath, newIsoVolumePath, vmName, xmlDesc));
}
}
// Replace CDROM ISO path
String oldCdromIsoPath = getOldVolumePathForCdrom(disks, vmName);
String newCdromIsoPath = getNewVolumePathForCdrom(libvirtComputingResource, conn, to);
if (newCdromIsoPath != null && !newCdromIsoPath.equals(oldCdromIsoPath)) {
xmlDesc = replaceCdromIsoPath(xmlDesc, vmName, oldCdromIsoPath, newCdromIsoPath);
}
// delete the metadata of vm snapshots before migration
vmsnapshots = libvirtComputingResource.cleanVMSnapshotMetadata(dm);
@ -701,6 +712,81 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
return newIsoVolumePath;
}
private String getOldVolumePathForCdrom(List<DiskDef> disks, String vmName) {
String oldIsoVolumePath = null;
for (DiskDef disk : disks) {
if (DiskDef.DeviceType.CDROM.equals(disk.getDeviceType())
&& CDROM_LABEL.equals(disk.getDiskLabel())
&& disk.getDiskPath() != null) {
oldIsoVolumePath = disk.getDiskPath();
break;
}
}
return oldIsoVolumePath;
}
private String getNewVolumePathForCdrom(LibvirtComputingResource libvirtComputingResource, Connect conn, VirtualMachineTO to) throws LibvirtException, URISyntaxException {
DiskTO newDisk = null;
for (DiskTO disk : to.getDisks()) {
DataTO data = disk.getData();
if (disk.getDiskSeq() == 3 && data != null && data.getPath() != null) {
newDisk = disk;
break;
}
}
String newIsoVolumePath = null;
if (newDisk != null) {
newIsoVolumePath = libvirtComputingResource.getVolumePath(conn, newDisk);
}
return newIsoVolumePath;
}
protected String replaceCdromIsoPath(String xmlDesc, String vmName, String oldIsoVolumePath, String newIsoVolumePath) throws IOException, ParserConfigurationException, TransformerException, SAXException {
InputStream in = IOUtils.toInputStream(xmlDesc);
DocumentBuilderFactory docFactory = ParserUtils.getSaferDocumentBuilderFactory();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(in);
// Get the root element
Node domainNode = doc.getFirstChild();
NodeList domainChildNodes = domainNode.getChildNodes();
for (int i = 0; i < domainChildNodes.getLength(); i++) {
Node domainChildNode = domainChildNodes.item(i);
if ("devices".equals(domainChildNode.getNodeName())) {
NodeList devicesChildNodes = domainChildNode.getChildNodes();
for (int x = 0; x < devicesChildNodes.getLength(); x++) {
Node deviceChildNode = devicesChildNodes.item(x);
if ("disk".equals(deviceChildNode.getNodeName())) {
Node diskNode = deviceChildNode;
NodeList diskChildNodes = diskNode.getChildNodes();
for (int z = 0; z < diskChildNodes.getLength(); z++) {
Node diskChildNode = diskChildNodes.item(z);
if ("source".equals(diskChildNode.getNodeName())) {
NamedNodeMap sourceNodeAttributes = diskChildNode.getAttributes();
Node sourceNodeAttribute = sourceNodeAttributes.getNamedItem("file");
if (oldIsoVolumePath != null && sourceNodeAttribute != null
&& oldIsoVolumePath.equals(sourceNodeAttribute.getNodeValue())) {
diskNode.removeChild(diskChildNode);
Element newChildSourceNode = doc.createElement("source");
newChildSourceNode.setAttribute("file", newIsoVolumePath);
diskNode.appendChild(newChildSourceNode);
logger.debug(String.format("Replaced ISO path [%s] with [%s] in VM [%s] XML configuration.", oldIsoVolumePath, newIsoVolumePath, vmName));
return getXml(doc);
}
}
}
}
}
}
}
return getXml(doc);
}
private String getPathFromSourceText(Set<String> paths, String sourceText) {
if (paths != null && StringUtils.isNotBlank(sourceText)) {
for (String path : paths) {

View File

@ -140,7 +140,6 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
logger.info(String.format("Block copy has started for the volume %s : %s ", destDiskLabel, srcPath));
return checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, srcSecretUUID);
} catch (Exception e) {
String msg = "Migrate volume failed due to " + e.toString();
logger.warn(msg, e);
@ -166,17 +165,27 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
protected MigrateVolumeAnswer checkBlockJobStatus(MigrateVolumeCommand command, Domain dm, String diskLabel, String srcPath, String destPath, LibvirtComputingResource libvirtComputingResource, Connect conn, String srcSecretUUID) throws LibvirtException {
int timeBetweenTries = 1000; // Try more frequently (every sec) and return early if disk is found
int waitTimeInSec = command.getWait();
double blockCopyProgress = 0;
while (waitTimeInSec > 0) {
DomainBlockJobInfo blockJobInfo = dm.getBlockJobInfo(diskLabel, 0);
if (blockJobInfo != null) {
logger.debug(String.format("Volume %s : %s block copy progress: %s%% current value:%s end value:%s", diskLabel, srcPath, (blockJobInfo.end == 0)? 0 : 100*(blockJobInfo.cur / (double) blockJobInfo.end), blockJobInfo.cur, blockJobInfo.end));
blockCopyProgress = (blockJobInfo.end == 0)? blockCopyProgress : 100 * (blockJobInfo.cur / (double) blockJobInfo.end);
logger.debug(String.format("Volume %s : %s, block copy progress: %s%%, current value: %s end value: %s, job info - type: %s, bandwidth: %s",
diskLabel, srcPath, blockCopyProgress, blockJobInfo.cur, blockJobInfo.end, blockJobInfo.type, blockJobInfo.bandwidth));
if (blockJobInfo.cur == blockJobInfo.end) {
logger.info(String.format("Block copy completed for the volume %s : %s", diskLabel, srcPath));
dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.PIVOT);
if (StringUtils.isNotEmpty(srcSecretUUID)) {
libvirtComputingResource.removeLibvirtVolumeSecret(conn, srcSecretUUID);
if (blockJobInfo.end > 0) {
logger.info(String.format("Block copy completed for the volume %s : %s", diskLabel, srcPath));
dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.PIVOT);
if (StringUtils.isNotEmpty(srcSecretUUID)) {
libvirtComputingResource.removeLibvirtVolumeSecret(conn, srcSecretUUID);
}
break;
} else {
// cur = 0, end = 0 - at this point, disk does not have an active block job (so, no need to abort job)
String msg = String.format("No active block copy job for the volume %s : %s - job stopped at %s progress", diskLabel, srcPath, blockCopyProgress);
logger.warn(msg);
return new MigrateVolumeAnswer(command, false, msg, null);
}
break;
}
} else {
logger.info("Failed to get the block copy status, trying to abort the job");

View File

@ -38,6 +38,9 @@ public interface KVMStoragePool {
public static final long HeartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
public static final long HeartBeatCheckerTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT);
public default KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
return createPhysicalDisk(volumeUuid, format, provisioningType, size, passphrase);
}
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);

View File

@ -1633,7 +1633,7 @@ public class KVMStorageProcessor implements StorageProcessor {
}
} else {
vol = primaryPool.createPhysicalDisk(volume.getUuid(), format,
volume.getProvisioningType(), disksize, volume.getPassphrase());
volume.getProvisioningType(), disksize, volume.getUsableSize(), volume.getPassphrase());
}
final VolumeObjectTO newVol = new VolumeObjectTO();

View File

@ -154,6 +154,11 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
return MapStorageUuidToStoragePool.remove(uuid) != null;
}
@Override
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
return createPhysicalDisk(name, pool, format, provisioningType, size, null, passphrase);
}
/**
* ScaleIO doesn't need to communicate with the hypervisor normally to create a volume. This is used only to prepare a ScaleIO data disk for encryption.
* Thin encrypted volumes are provisioned in QCOW2 format, which insulates the guest from zeroes/unallocated blocks in the block device that would
@ -163,11 +168,12 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
* @param format disk format
* @param provisioningType provisioning type
* @param size disk size
* @param usableSize usage disk size
* @param passphrase passphrase
* @return the disk object
*/
@Override
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
if (passphrase == null || passphrase.length == 0) {
return null;
}
@ -185,7 +191,12 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
QemuImg qemuImg = new QemuImg(0, true, false);
Map<String, String> options = new HashMap<>();
List<QemuObject> qemuObjects = new ArrayList<>();
long formattedSize = getUsableBytesFromRawBytes(disk.getSize());
long formattedSize;
if (usableSize != null && usableSize > 0) {
formattedSize = usableSize;
} else {
formattedSize = getUsableBytesFromRawBytes(disk.getSize());
}
options.put("preallocation", QemuImg.PreallocationType.Metadata.toString());
qemuObjects.add(QemuObject.prepareSecretForQemuImg(disk.getFormat(), disk.getQemuEncryptFormat(), keyFile.toString(), "sec0", options));

View File

@ -72,6 +72,11 @@ public class ScaleIOStoragePool implements KVMStoragePool {
}
}
@Override
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, QemuImg.PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
return this.storageAdaptor.createPhysicalDisk(volumeUuid, this, format, provisioningType, size, usableSize, passphrase);
}
@Override
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, QemuImg.PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
return this.storageAdaptor.createPhysicalDisk(volumeUuid, this, format, provisioningType, size, passphrase);

View File

@ -41,6 +41,11 @@ public interface StorageAdaptor {
public boolean deleteStoragePool(String uuid);
public default KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
return createPhysicalDisk(name, pool, format, provisioningType, size, passphrase);
}
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
@ -52,8 +57,17 @@ public interface StorageAdaptor {
public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect);
// given local path to file/device (per Libvirt XML), 1) check that device is
// handled by your adaptor, return false if not. 2) clean up device, return true
/**
* Given local path to file/device (per Libvirt XML),
* 1) Make sure to check that device is handled by your adaptor, return false if not.
* 2) clean up device, return true
* 3) if clean up fails, then return false
*
* If the method wrongly returns true, then there are chances that disconnect will not reach the right storage adapter
*
* @param localPath path for the file/device from the disk definition per Libvirt XML.
* @return true if the operation is successful; false if the operation fails or the adapter fails to handle the path.
*/
public boolean disconnectPhysicalDiskByPath(String localPath);
public boolean deletePhysicalDisk(String uuid, KVMStoragePool pool, Storage.ImageFormat format);

View File

@ -132,6 +132,7 @@ public class LibvirtMigrateCommandWrapperTest {
" </disk>\n" +
" <disk type='file' device='cdrom'>\n" +
" <driver name='qemu' type='raw' cache='none'/>\n" +
" <source file='/mnt/ec8dfdd8-f341-3b0a-988f-cfbc93e46fc4/251-2-2b2071a4-21c7-340e-a861-1bd30fb5cbed.iso'/>\n" +
" <backingStore/>\n" +
" <target dev='hdc' bus='ide'/>\n" +
" <readonly/>\n" +
@ -249,6 +250,7 @@ public class LibvirtMigrateCommandWrapperTest {
" </disk>\n" +
" <disk type='file' device='cdrom'>\n" +
" <driver name='qemu' type='raw' cache='none'/>\n" +
" <source file='/mnt/ec8dfdd8-f341-3b0a-988f-cfbc93e46fc4/251-2-2b2071a4-21c7-340e-a861-1bd30fb5cbed.iso'/>\n" +
" <backingStore/>\n" +
" <target dev='hdc' bus='ide'/>\n" +
" <readonly/>\n" +
@ -1007,4 +1009,14 @@ public class LibvirtMigrateCommandWrapperTest {
assertTrue(result.containsAll(Arrays.asList("vda", "vdb")));
}
@Test
public void replaceCdromIsoPathTest() throws ParserConfigurationException, IOException, TransformerException,
SAXException {
String oldIsoVolumePath = "/mnt/ec8dfdd8-f341-3b0a-988f-cfbc93e46fc4/251-2-2b2071a4-21c7-340e-a861-1bd30fb5cbed.iso";
String newIsoVolumePath = "/mnt/50bf9d15-1b0f-3cc1-9e8a-55df3e17e0c4/251-2-2b2071a4-21c7-340e-a861-1bd30fb5cbed.iso";
String finalXml = libvirtMigrateCmdWrapper.replaceCdromIsoPath(fullfile, null, oldIsoVolumePath, newIsoVolumePath);
Assert.assertTrue(finalXml.contains(newIsoVolumePath));
}
}

View File

@ -79,6 +79,20 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne
public static final String MINIMUN_AUTOSCALER_SUPPORTED_VERSION = "1.15.0";
protected void updateTemplateDetailsInKubernetesSupportedVersionResponse(
final KubernetesSupportedVersion kubernetesSupportedVersion, KubernetesSupportedVersionResponse response) {
TemplateJoinVO template = templateJoinDao.findById(kubernetesSupportedVersion.getIsoId());
if (template == null) {
return;
}
response.setIsoId(template.getUuid());
response.setIsoName(template.getName());
if (template.getState() != null) {
response.setIsoState(template.getState().toString());
}
response.setDirectDownload(template.isDirectDownload());
}
private KubernetesSupportedVersionResponse createKubernetesSupportedVersionResponse(final KubernetesSupportedVersion kubernetesSupportedVersion) {
KubernetesSupportedVersionResponse response = new KubernetesSupportedVersionResponse();
response.setObjectName("kubernetessupportedversion");
@ -98,15 +112,7 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne
response.setSupportsHA(compareSemanticVersions(kubernetesSupportedVersion.getSemanticVersion(),
KubernetesClusterService.MIN_KUBERNETES_VERSION_HA_SUPPORT)>=0);
response.setSupportsAutoscaling(versionSupportsAutoscaling(kubernetesSupportedVersion));
TemplateJoinVO template = templateJoinDao.findById(kubernetesSupportedVersion.getIsoId());
if (template != null) {
response.setIsoId(template.getUuid());
response.setIsoName(template.getName());
if (template.getState() != null) {
response.setIsoState(template.getState().toString());
}
response.setDirectDownload(template.isDirectDownload());
}
updateTemplateDetailsInKubernetesSupportedVersionResponse(kubernetesSupportedVersion, response);
response.setCreated(kubernetesSupportedVersion.getCreated());
return response;
}

View File

@ -0,0 +1,73 @@
// 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.kubernetes.version;
import java.util.UUID;
import org.apache.cloudstack.api.response.KubernetesSupportedVersionResponse;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.api.query.dao.TemplateJoinDao;
import com.cloud.api.query.vo.TemplateJoinVO;
@RunWith(MockitoJUnitRunner.class)
public class KubernetesVersionManagerImplTest {
@Mock
TemplateJoinDao templateJoinDao;
@InjectMocks
KubernetesVersionManagerImpl kubernetesVersionManager = new KubernetesVersionManagerImpl();
@Test
public void testUpdateTemplateDetailsInKubernetesSupportedVersionResponseNullTemplate() {
KubernetesSupportedVersion kubernetesSupportedVersion = Mockito.mock(KubernetesSupportedVersion.class);
Mockito.when(kubernetesSupportedVersion.getIsoId()).thenReturn(1L);
KubernetesSupportedVersionResponse response = new KubernetesSupportedVersionResponse();
kubernetesVersionManager.updateTemplateDetailsInKubernetesSupportedVersionResponse(kubernetesSupportedVersion,
response);
Assert.assertNull(ReflectionTestUtils.getField(response, "isoId"));
}
@Test
public void testUpdateTemplateDetailsInKubernetesSupportedVersionResponseValidTemplate() {
KubernetesSupportedVersion kubernetesSupportedVersion = Mockito.mock(KubernetesSupportedVersion.class);
Mockito.when(kubernetesSupportedVersion.getIsoId()).thenReturn(1L);
KubernetesSupportedVersionResponse response = new KubernetesSupportedVersionResponse();
TemplateJoinVO templateJoinVO = Mockito.mock(TemplateJoinVO.class);
String uuid = UUID.randomUUID().toString();
Mockito.when(templateJoinVO.getUuid()).thenReturn(uuid);
Mockito.when(templateJoinDao.findById(1L)).thenReturn(templateJoinVO);
kubernetesVersionManager.updateTemplateDetailsInKubernetesSupportedVersionResponse(kubernetesSupportedVersion,
response);
Assert.assertEquals(uuid, ReflectionTestUtils.getField(response, "isoId"));
Assert.assertNull(ReflectionTestUtils.getField(response, "isoState"));
ObjectInDataStoreStateMachine.State state = ObjectInDataStoreStateMachine.State.Ready;
Mockito.when(templateJoinVO.getState()).thenReturn(state);
kubernetesVersionManager.updateTemplateDetailsInKubernetesSupportedVersionResponse(kubernetesSupportedVersion,
response);
Assert.assertEquals(state.toString(), ReflectionTestUtils.getField(response, "isoState"));
}
}

View File

@ -66,6 +66,8 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.GetVolumeStatAnswer;
import com.cloud.agent.api.GetVolumeStatCommand;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
@ -491,10 +493,10 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId) {
return createVolume(volumeInfo, storagePoolId, false);
return createVolume(volumeInfo, storagePoolId, false, null);
}
public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId, boolean migrationInvolved) {
public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId, boolean migrationInvolved, Long usageSize) {
logger.debug("Creating PowerFlex volume");
StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
@ -544,6 +546,9 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
VolumeObjectTO prepVolume = (VolumeObjectTO) createdObject.getTO();
prepVolume.setPath(volumePath);
prepVolume.setUuid(volumePath);
if (usageSize != null) {
prepVolume.setUsableSize(usageSize);
}
CreateObjectCommand cmd = new CreateObjectCommand(prepVolume);
EndPoint ep = selector.select(volumeInfo, true);
if (ep == null) {
@ -846,7 +851,8 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
// Volume migration across different PowerFlex/ScaleIO clusters
final long srcVolumeId = srcData.getId();
DataStore srcStore = srcData.getDataStore();
Map<String, String> srcDetails = getVolumeDetails((VolumeInfo) srcData, srcStore);
VolumeInfo srcVolumeInfo = (VolumeInfo) srcData;
Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo, srcStore);
DataStore destStore = destData.getDataStore();
final long destPoolId = destStore.getId();
@ -858,8 +864,17 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(host);
Answer answer = null;
Long srcVolumeUsableSize = null;
try {
CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId(), true);
GetVolumeStatCommand statCmd = new GetVolumeStatCommand(srcVolumeInfo.getPath(), srcVolumeInfo.getStoragePoolType(), srcStore.getUuid());
GetVolumeStatAnswer statAnswer = (GetVolumeStatAnswer) ep.sendMessage(statCmd);
if (!statAnswer.getResult() ) {
logger.warn(String.format("Unable to get volume %s stats", srcVolumeInfo.getId()));
} else if (statAnswer.getVirtualSize() > 0) {
srcVolumeUsableSize = statAnswer.getVirtualSize();
}
CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId(), true, srcVolumeUsableSize);
destVolumePath = createAnswer.getData().getPath();
destVolTO.setPath(destVolumePath);

View File

@ -20,7 +20,10 @@
package org.apache.cloudstack.storage.datastore.driver;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.GetVolumeStatAnswer;
import com.cloud.agent.api.GetVolumeStatCommand;
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.configuration.Config;
@ -205,16 +208,23 @@ public class ScaleIOPrimaryDataStoreDriverTest {
RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
remoteHostEndPointMock.when(() -> RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
long volumeVirtualSize = 68673077248L;
DataTO dataTO = Mockito.mock(DataTO.class);
CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true);
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true, volumeVirtualSize);
when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
doReturn(true).when(scaleIOPrimaryDataStoreDriver)
.grantAccess(any(), any(), any());
when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
GetVolumeStatAnswer getVolumeStatAnswer = Mockito.mock(GetVolumeStatAnswer.class);
when(ep.sendMessage(any(GetVolumeStatCommand.class))).thenReturn(getVolumeStatAnswer);
when(getVolumeStatAnswer.getResult()).thenReturn(true);
when(getVolumeStatAnswer.getVirtualSize()).thenReturn(volumeVirtualSize);
MigrateVolumeAnswer migrateVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
when(ep.sendMessage(any(MigrateVolumeCommand.class))).thenReturn(migrateVolumeAnswer);
when(migrateVolumeAnswer.getResult()).thenReturn(true);
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
@ -251,16 +261,21 @@ public class ScaleIOPrimaryDataStoreDriverTest {
DataTO dataTO = Mockito.mock(DataTO.class);
CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true);
when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
doReturn(true).when(scaleIOPrimaryDataStoreDriver)
Mockito.lenient().doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true, null);
Mockito.lenient().when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
Mockito.lenient().doReturn(true).when(scaleIOPrimaryDataStoreDriver)
.grantAccess(any(), any(), any());
when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
Mockito.lenient().when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
GetVolumeStatAnswer getVolumeStatAnswer = Mockito.mock(GetVolumeStatAnswer.class);
Mockito.lenient().when(ep.sendMessage(any(GetVolumeStatCommand.class))).thenReturn(getVolumeStatAnswer);
Mockito.lenient().when(getVolumeStatAnswer.getResult()).thenReturn(false);
MigrateVolumeAnswer migrateVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
when(migrateVolumeAnswer.getResult()).thenReturn(false);
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
Mockito.lenient().when(migrateVolumeAnswer.getResult()).thenReturn(false);
Mockito.lenient().doNothing().when(scaleIOPrimaryDataStoreDriver)
.revertBlockCopyVolumeOperations(any(), any(), any(), any());
Answer answer = scaleIOPrimaryDataStoreDriver.liveMigrateVolume(srcData, destData);

View File

@ -2397,7 +2397,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
private Pair<List<Long>, Integer> searchForVolumeIdsAndCount(ListVolumesCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
List<Long> permittedAccounts = new ArrayList<Long>();
List<Long> permittedAccounts = new ArrayList<>();
Long id = cmd.getId();
Long vmInstanceId = cmd.getVirtualMachineId();
@ -2407,7 +2407,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
Map<String, String> tags = cmd.getTags();
String storageId = cmd.getStorageId();
Long clusterId = cmd.getClusterId();
Long diskOffId = cmd.getDiskOfferingId();
Long serviceOfferingId = cmd.getServiceOfferingId();
Long diskOfferingId = cmd.getDiskOfferingId();
Boolean display = cmd.getDisplay();
String state = cmd.getState();
boolean shouldListSystemVms = shouldListSystemVms(cmd, caller.getId());
@ -2417,7 +2418,14 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
if (diskOfferingId == null && serviceOfferingId != null) {
ServiceOfferingVO serviceOffering = _srvOfferingDao.findById(serviceOfferingId);
if (serviceOffering != null) {
diskOfferingId = serviceOffering.getDiskOfferingId();
}
}
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
@ -2437,6 +2445,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
volumeSearchBuilder.and("uuid", volumeSearchBuilder.entity().getUuid(), SearchCriteria.Op.NNULL);
volumeSearchBuilder.and("instanceId", volumeSearchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ);
volumeSearchBuilder.and("dataCenterId", volumeSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ);
if (cmd.isEncrypted() != null) {
if (cmd.isEncrypted()) {
volumeSearchBuilder.and("encryptFormat", volumeSearchBuilder.entity().getEncryptFormat(), SearchCriteria.Op.NNULL);
} else {
volumeSearchBuilder.and("encryptFormat", volumeSearchBuilder.entity().getEncryptFormat(), SearchCriteria.Op.NULL);
}
}
if (keyword != null) {
volumeSearchBuilder.and().op("keywordName", volumeSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
@ -2540,8 +2555,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
}
}
if (diskOffId != null) {
sc.setParameters("diskOfferingId", diskOffId);
if (diskOfferingId != null) {
sc.setParameters("diskOfferingId", diskOfferingId);
}
if (id != null) {
@ -4668,8 +4683,12 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
// other criteria
if (keyword != null) {
sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
} else if (name != null) {
SearchCriteria<TemplateJoinVO> scc = _templateJoinDao.createSearchCriteria();
scc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
scc.addOr("displayText", SearchCriteria.Op.LIKE, "%" + keyword + "%");
sc.addAnd("name", SearchCriteria.Op.SC, scc);
}
if (name != null) {
sc.addAnd("name", SearchCriteria.Op.EQ, name);
}

View File

@ -178,6 +178,7 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
if (view == ResponseView.Full) {
volResponse.setPath(volume.getPath());
volResponse.setEncryptionFormat(volume.getEncryptionFormat());
}
// populate owner.
@ -276,6 +277,7 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
volResponse.setObjectName("volume");
volResponse.setExternalUuid(volume.getExternalUuid());
volResponse.setEncryptionFormat(volume.getEncryptionFormat());
return volResponse;
}

View File

@ -277,6 +277,9 @@ public class VolumeJoinVO extends BaseViewWithTagInformationVO implements Contro
@Column(name = "external_uuid")
private String externalUuid = null;
@Column(name = "encrypt_format")
private String encryptionFormat = null;
public VolumeJoinVO() {
}
@ -612,9 +615,12 @@ public class VolumeJoinVO extends BaseViewWithTagInformationVO implements Contro
this.externalUuid = externalUuid;
}
public String getEncryptionFormat() {
return encryptionFormat;
}
@Override
public Class<?> getEntityType() {
return Volume.class;
}
}

View File

@ -3280,10 +3280,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
DiskOfferingVO diskOffering = null;
if (diskOfferingId == null) {
diskOffering = createDiskOfferingInternal(userId, isSystem, vmType,
name, cpu, ramSize, speed, displayText, typedProvisioningType, localStorageRequired,
offerHA, limitResourceUse, volatileVm, tags, domainIds, zoneIds, hostTag,
networkRate, deploymentPlanner, details, rootDiskSizeInGiB, isCustomizedIops, minIops, maxIops,
diskOffering = createDiskOfferingInternal(
name, displayText, typedProvisioningType, localStorageRequired,
tags, details, rootDiskSizeInGiB, isCustomizedIops, minIops, maxIops,
bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength,
bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength,
iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength,
@ -3291,6 +3290,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
hypervisorSnapshotReserve, cacheMode, storagePolicyID, encryptRoot);
} else {
diskOffering = _diskOfferingDao.findById(diskOfferingId);
String diskStoragePolicyId = diskOfferingDetailsDao.getDetail(diskOfferingId, ApiConstants.STORAGE_POLICY);
if (storagePolicyID != null && diskStoragePolicyId != null) {
throw new InvalidParameterValueException("Storage policy cannot be defined on both compute and disk offering");
}
}
if (diskOffering != null) {
serviceOffering.setDiskOfferingId(diskOffering.getId());
@ -3330,10 +3333,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
private DiskOfferingVO createDiskOfferingInternal(final long userId, final boolean isSystem, final VirtualMachine.Type vmType,
final String name, final Integer cpu, final Integer ramSize, final Integer speed, final String displayText, final ProvisioningType typedProvisioningType, final boolean localStorageRequired,
final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final List<Long> domainIds, List<Long> zoneIds, final String hostTag,
final Integer networkRate, final String deploymentPlanner, final Map<String, String> details, Long rootDiskSizeInGiB, final Boolean isCustomizedIops, Long minIops, Long maxIops,
private DiskOfferingVO createDiskOfferingInternal(final String name, final String displayText, final ProvisioningType typedProvisioningType, final boolean localStorageRequired,
String tags, final Map<String, String> details, Long rootDiskSizeInGiB, final Boolean isCustomizedIops, Long minIops, Long maxIops,
Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength,
Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength,
@ -3394,8 +3395,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
diskOffering.setHypervisorSnapshotReserve(hypervisorSnapshotReserve);
if ((diskOffering = _diskOfferingDao.persist(diskOffering)) != null) {
if (details != null && !details.isEmpty()) {
List<DiskOfferingDetailVO> diskDetailsVO = new ArrayList<DiskOfferingDetailVO>();
if ((details != null && !details.isEmpty()) || (storagePolicyID != null)) {
List<DiskOfferingDetailVO> diskDetailsVO = new ArrayList<>();
// Support disk offering details for below parameters
if (details.containsKey(Volume.BANDWIDTH_LIMIT_IN_MBPS)) {
diskDetailsVO.add(new DiskOfferingDetailVO(diskOffering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, details.get(Volume.BANDWIDTH_LIMIT_IN_MBPS), false));
@ -3403,6 +3404,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
if (details.containsKey(Volume.IOPS_LIMIT)) {
diskDetailsVO.add(new DiskOfferingDetailVO(diskOffering.getId(), Volume.IOPS_LIMIT, details.get(Volume.IOPS_LIMIT), false));
}
if (storagePolicyID != null) {
diskDetailsVO.add(new DiskOfferingDetailVO(diskOffering.getId(), ApiConstants.STORAGE_POLICY, String.valueOf(storagePolicyID), false));
}
if (!diskDetailsVO.isEmpty()) {
diskOfferingDetailsDao.saveDetails(diskDetailsVO);
}

View File

@ -1780,6 +1780,17 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
return result;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", async = true)
public void startVpc(final CreateVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
if (!cmd.isStart()) {
logger.debug("Not starting VPC as " + ApiConstants.START + "=false was passed to the API");
return;
}
startVpc(cmd.getEntityId(), true);
}
protected boolean startVpc(final Vpc vpc, final DeployDestination dest, final ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException,
InsufficientCapacityException {
// deploy provider

View File

@ -1921,6 +1921,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_MAINTENANCE_PREPARE_PRIMARY_STORAGE,
eventDescription = "preparing storage pool for maintenance", async = true)
public PrimaryDataStoreInfo preparePrimaryStorageForMaintenance(Long primaryStorageId) throws ResourceUnavailableException, InsufficientCapacityException {
StoragePoolVO primaryStorage = null;
primaryStorage = _storagePoolDao.findById(primaryStorageId);
@ -1989,6 +1991,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_MAINTENANCE_CANCEL_PRIMARY_STORAGE,
eventDescription = "canceling maintenance for primary storage pool", async = true)
public PrimaryDataStoreInfo cancelPrimaryStorageForMaintenance(CancelPrimaryStorageMaintenanceCmd cmd) throws ResourceUnavailableException {
Long primaryStorageId = cmd.getId();
StoragePoolVO primaryStorage = null;
@ -2893,18 +2897,9 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
List<Pair<Volume, Answer>> answers = new ArrayList<Pair<Volume, Answer>>();
for (Pair<Volume, DiskProfile> volumeDiskProfilePair : volumes) {
String storagePolicyId = null;
Volume volume = volumeDiskProfilePair.first();
DiskProfile diskProfile = volumeDiskProfilePair.second();
if (volume.getVolumeType() == Type.ROOT) {
Long vmId = volume.getInstanceId();
if (vmId != null) {
VMInstanceVO vm = _vmInstanceDao.findByIdIncludingRemoved(vmId);
storagePolicyId = _serviceOfferingDetailsDao.getDetail(vm.getServiceOfferingId(), ApiConstants.STORAGE_POLICY);
}
} else {
storagePolicyId = _diskOfferingDetailsDao.getDetail(diskProfile.getDiskOfferingId(), ApiConstants.STORAGE_POLICY);
}
String storagePolicyId = _diskOfferingDetailsDao.getDetail(diskProfile.getDiskOfferingId(), ApiConstants.STORAGE_POLICY);
Answer answer = getCheckDatastorePolicyComplianceAnswer(storagePolicyId, pool);
if (answer != null) {
answers.add(new Pair<>(volume, answer));

View File

@ -157,6 +157,7 @@ import com.cloud.offering.DiskOffering;
import com.cloud.org.Grouping;
import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceState;
import com.cloud.serializer.GsonHelper;
import com.cloud.server.ManagementService;
@ -256,6 +257,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject
private ConfigurationManager _configMgr;
@Inject
private ResourceManager _resourceMgr;
@Inject
private VolumeDao _volsDao;
@Inject
private VolumeDetailsDao _volsDetailsDao;
@ -566,7 +569,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
_resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage);
}
sanitizeFormat(format);
checkFormatWithSupportedHypervisorsInZone(format, zoneId);
// Check that the disk offering specified is valid
if (diskOfferingId != null) {
@ -582,6 +585,15 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return false;
}
private void checkFormatWithSupportedHypervisorsInZone(String format, Long zoneId) {
ImageFormat imageformat = ImageFormat.valueOf(format);
final List<HypervisorType> supportedHypervisorTypesInZone = _resourceMgr.getSupportedHypervisorTypes(zoneId, false, null);
final HypervisorType hypervisorTypeFromFormat = ApiDBUtils.getHypervisorTypeFromFormat(zoneId, imageformat);
if (!(supportedHypervisorTypesInZone.contains(hypervisorTypeFromFormat))) {
throw new InvalidParameterValueException(String.format("The %s hypervisor supported for %s file format, is not found on the zone", hypervisorTypeFromFormat.toString(), format));
}
}
public String getRandomVolumeName() {
return UUID.randomUUID().toString();
}

View File

@ -8096,7 +8096,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
if (needRestart) {
try {
if (vm.getDetail(VmDetailConstants.PASSWORD) != null) {
if (Objects.nonNull(password)) {
params = new HashMap<>();
params.put(VirtualMachineProfile.Param.VmPassword, password);
}

View File

@ -2079,10 +2079,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
throw new InvalidParameterValueException("Username need to be provided.");
}
HashMap<String, UnmanagedInstanceTO> instancesMap = getRemoteVms(zoneId, remoteUrl, cmd.getUsername(), cmd.getPassword());
HashMap<String, UnmanagedInstanceTO> instancesMap = getRemoteVmsOnKVMHost(zoneId, remoteUrl, cmd.getUsername(), cmd.getPassword());
unmanagedInstanceTO = instancesMap.get(cmd.getName());
if (unmanagedInstanceTO == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Vm with name: %s not found on remote host", instanceName));
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM with name: %s not found on remote host %s", instanceName, remoteUrl));
}
}
@ -2493,7 +2493,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
List<UnmanagedInstanceResponse> responses = new ArrayList<>();
HashMap<String, UnmanagedInstanceTO> vmMap = getRemoteVms(zoneId, cmd.getHost(), cmd.getUsername(), cmd.getPassword());
HashMap<String, UnmanagedInstanceTO> vmMap = getRemoteVmsOnKVMHost(zoneId, cmd.getHost(), cmd.getUsername(), cmd.getPassword());
for (String key : vmMap.keySet()) {
UnmanagedInstanceTO instance = vmMap.get(key);
if (StringUtils.isNotEmpty(keyword) &&
@ -2508,17 +2508,17 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
return listResponses;
}
private HashMap<String, UnmanagedInstanceTO> getRemoteVms(long zoneId, String remoteUrl, String username, String password) {
private HashMap<String, UnmanagedInstanceTO> getRemoteVmsOnKVMHost(long zoneId, String remoteHostUrl, String username, String password) {
//ToDo: add option to list one Vm by name
List<HostVO> hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, zoneId);
if(hosts.size() < 1) {
throw new CloudRuntimeException("No hosts available for Vm Import");
throw new CloudRuntimeException("No hosts available for VM import");
}
HostVO host = hosts.get(0);
GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(remoteUrl, username, password);
GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(remoteHostUrl, username, password);
Answer answer = agentManager.easySend(host.getId(), getRemoteVmsCommand);
if (!(answer instanceof GetRemoteVmsAnswer)) {
throw new CloudRuntimeException("Error while listing remote Vms");
throw new CloudRuntimeException("Failed to list VMs, due to: " + answer.getDetails());
}
GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer;
return getRemoteVmsAnswer.getUnmanagedInstances();

View File

@ -296,7 +296,8 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.warn(responseContent.toString(), cause);
logger.warn(String.format("%s. Exception occurred: %s", responseContent.toString(), cause.getMessage()));
logger.debug("Exception caught by HTTP upload handler, caused due to: ", cause);
responseContent.append("\r\nException occurred: ").append(cause.getMessage());
writeResponse(ctx.channel(), HttpResponseStatus.INTERNAL_SERVER_ERROR);
ctx.channel().close();

View File

@ -16,138 +16,25 @@
// under the License.
<template>
<a-dropdown>
<a-dropdown v-if="accessibleCreateActions && accessibleCreateActions.length > 0">
<template #overlay>
<a-menu>
<a-menu-item style="width: 100%; padding: 12px" v-if="'deployVirtualMachine' in $store.getters.apis">
<router-link :to="{ path: '/action/deployVirtualMachine'}">
<a-menu-item style="width: 100%; padding: 12px" v-for="menuItem in accessibleCreateActions" :key="menuItem.api">
<router-link :to="menuItem.route">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<cloud-server-outlined/>
<render-icon v-if="(typeof menuItem.icon === 'string')" :icon="menuItem.icon" />
<font-awesome-icon v-else :icon="menuItem.icon" />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.instance') }}
{{ menuItem.title }}
</h3>
<small>{{ $t('label.create.instance') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'createKubernetesCluster' in $store.getters.apis">
<router-link :to="{ path: '/kubernetes', query: { action: 'createKubernetesCluster' } }">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<font-awesome-icon :icon="['fa-solid', 'fa-dharmachakra']" />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.kubernetes') }}
</h3>
<small>{{ $t('label.kubernetes.cluster.create') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'createVolume' in $store.getters.apis">
<router-link :to="{ path: '/volume', query: { action: 'createVolume' } }">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<hdd-outlined />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.volume') }}
</h3>
<small>{{ $t('label.action.create.volume') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'createNetwork' in $store.getters.apis">
<router-link :to="{ path: '/guestnetwork', query: { action: 'createNetwork' } }">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<apartment-outlined />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.network') }}
</h3>
<small>{{ $t('label.add.network') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'createVPC' in $store.getters.apis">
<router-link :to="{ path: '/vpc', query: { action: 'createVPC' } }">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<deployment-unit-outlined />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.vpc') }}
</h3>
<small>{{ $t('label.add.vpc') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'registerTemplate' in $store.getters.apis">
<router-link :to="{ path: '/template', query: { action: 'registerTemplate' } }">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<picture-outlined />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.templatename') }}
</h3>
<small>{{ $t('label.action.register.template') }}</small>
</a-col>
</a-row>
</router-link>
</a-menu-item>
<a-menu-item style="width: 100%; padding: 12px" v-if="'deployVnfAppliance' in $store.getters.apis">
<router-link :to="{ path: '/action/deployVnfAppliance'}">
<a-row>
<a-col style="margin-right: 12px">
<a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
<template #icon>
<font-awesome-icon :icon="['fa-solid', 'fa-dharmachakra']" />
</template>
</a-avatar>
</a-col>
<a-col>
<h3 style="margin-bottom: 0px;">
{{ $t('label.vnf.appliance') }}
</h3>
<small>{{ $t('label.vnf.appliance.add') }}</small>
<small>{{ menuItem.subtitle }}</small>
</a-col>
</a-row>
</router-link>
@ -165,7 +52,59 @@
export default {
name: 'CreateMenu',
components: {
beforeCreate () {
const menuItems = [
{
api: 'deployVirtualMachine',
title: this.$t('label.instance'),
subtitle: this.$t('label.create.instance'),
icon: 'cloud-server-outlined',
route: { path: '/action/deployVirtualMachine' }
},
{
api: 'createKubernetesCluster',
title: this.$t('label.kubernetes'),
subtitle: this.$t('label.kubernetes.cluster.create'),
icon: ['fa-solid', 'fa-dharmachakra'],
route: { path: '/kubernetes', query: { action: 'createKubernetesCluster' } }
},
{
api: 'createVolume',
title: this.$t('label.volume'),
subtitle: this.$t('label.action.create.volume'),
icon: 'hdd-outlined',
route: { path: '/volume', query: { action: 'createVolume' } }
},
{
api: 'createNetwork',
title: this.$t('label.network'),
subtitle: this.$t('label.add.network'),
icon: 'apartment-outlined',
route: { path: '/guestnetwork', query: { action: 'createNetwork' } }
},
{
api: 'createVPC',
title: this.$t('label.vpc'),
subtitle: this.$t('label.add.vpc'),
icon: 'deployment-unit-outlined',
route: { path: '/vpc', query: { action: 'createVPC' } }
},
{
api: 'registerTemplate',
title: this.$t('label.templatename'),
subtitle: this.$t('label.action.register.template'),
icon: 'picture-outlined',
route: { path: '/template', query: { action: 'registerTemplate' } }
},
{
api: 'deployVnfAppliance',
title: this.$t('label.vnf.appliance'),
subtitle: this.$t('label.vnf.appliance.add'),
icon: 'gateway-outlined',
route: { path: '/action/deployVnfAppliance' }
}
]
this.accessibleCreateActions = menuItems.filter(m => m.api in this.$store.getters.apis)
}
}
</script>

View File

@ -286,7 +286,7 @@ export default {
}
if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level',
'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider',
'type', 'scope', 'managementserverid'].includes(item)
'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid'].includes(item)
) {
type = 'list'
} else if (item === 'tags') {
@ -406,6 +406,8 @@ export default {
let clusterIndex = -1
let groupIndex = -1
let managementServerIdIndex = -1
let serviceOfferingIndex = -1
let diskOfferingIndex = -1
if (arrayField.includes('type')) {
if (this.$route.path === '/alert') {
@ -479,6 +481,18 @@ export default {
promises.push(await this.fetchManagementServers(searchKeyword))
}
if (arrayField.includes('serviceofferingid')) {
serviceOfferingIndex = this.fields.findIndex(item => item.name === 'serviceofferingid')
this.fields[serviceOfferingIndex].loading = true
promises.push(await this.fetchServiceOfferings(searchKeyword))
}
if (arrayField.includes('diskofferingid')) {
diskOfferingIndex = this.fields.findIndex(item => item.name === 'diskofferingid')
this.fields[diskOfferingIndex].loading = true
promises.push(await this.fetchDiskOfferings(searchKeyword))
}
Promise.all(promises).then(response => {
if (typeIndex > -1) {
const types = response.filter(item => item.type === 'type')
@ -540,12 +554,27 @@ export default {
this.fields[groupIndex].opts = this.sortArray(groups[0].data)
}
}
if (managementServerIdIndex > -1) {
const managementServers = response.filter(item => item.type === 'managementserverid')
if (managementServers && managementServers.length > 0) {
this.fields[managementServerIdIndex].opts = this.sortArray(managementServers[0].data)
}
}
if (serviceOfferingIndex > -1) {
const serviceOfferings = response.filter(item => item.type === 'serviceofferingid')
if (serviceOfferings && serviceOfferings.length > 0) {
this.fields[serviceOfferingIndex].opts = this.sortArray(serviceOfferings[0].data)
}
}
if (diskOfferingIndex > -1) {
const diskOfferings = response.filter(item => item.type === 'diskofferingid')
if (diskOfferings && diskOfferings.length > 0) {
this.fields[diskOfferingIndex].opts = this.sortArray(diskOfferings[0].data)
}
}
}).finally(() => {
if (typeIndex > -1) {
this.fields[typeIndex].loading = false
@ -574,6 +603,12 @@ export default {
if (managementServerIdIndex > -1) {
this.fields[managementServerIdIndex].loading = false
}
if (serviceOfferingIndex > -1) {
this.fields[serviceOfferingIndex].loading = false
}
if (diskOfferingIndex > -1) {
this.fields[diskOfferingIndex].loading = false
}
this.fillFormFieldValues()
})
},
@ -723,6 +758,32 @@ export default {
})
})
},
fetchServiceOfferings (searchKeyword) {
return new Promise((resolve, reject) => {
api('listServiceOfferings', { listAll: true, keyword: searchKeyword }).then(json => {
const serviceOfferings = json.listserviceofferingsresponse.serviceoffering
resolve({
type: 'serviceofferingid',
data: serviceOfferings
})
}).catch(error => {
reject(error.response.headers['x-description'])
})
})
},
fetchDiskOfferings (searchKeyword) {
return new Promise((resolve, reject) => {
api('listDiskOfferings', { listAll: true, keyword: searchKeyword }).then(json => {
const diskOfferings = json.listdiskofferingsresponse.diskoffering
resolve({
type: 'diskofferingid',
data: diskOfferings
})
}).catch(error => {
reject(error.response.headers['x-description'])
})
})
},
fetchAlertTypes () {
if (this.alertTypes.length > 0) {
return new Promise((resolve, reject) => {

View File

@ -91,7 +91,7 @@ export default {
}
],
searchFilters: () => {
var filters = ['name', 'zoneid', 'domainid', 'account', 'state', 'tags']
var filters = ['name', 'zoneid', 'domainid', 'account', 'state', 'tags', 'serviceofferingid', 'diskofferingid']
if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
filters.push('storageid')
}

View File

@ -112,7 +112,11 @@ router.beforeEach((to, from, next) => {
} else {
next({ path: redirect })
}
const project = vueProps.$localStorage.get(CURRENT_PROJECT)
var project = vueProps.$localStorage.get(CURRENT_PROJECT)
if (project == null) {
project = {}
store.commit('SET_PROJECT', project)
}
store.dispatch('ToggleTheme', project.id === undefined ? 'light' : 'dark')
})
})

View File

@ -332,8 +332,9 @@ export default {
this.zoneLoading = true
params.showicon = true
api('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
var listZones = json.listzonesresponse.zone
if (listZones) {
listZones = listZones.filter(x => x.allocationstate === 'Enabled')
this.zones = this.zones.concat(listZones)
}
}).finally(() => {

View File

@ -184,7 +184,7 @@
</div>
<a-progress
status="active"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal) : 0"
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
stroke-color="#52c41a"
size="small"
@ -214,7 +214,7 @@
</div>
<a-progress
status="active"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal) : 0"
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
stroke-color="#52c41a"
size="small"
@ -244,7 +244,7 @@
</div>
<a-progress
status="active"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) : 0"
:percent="statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal) : 0"
:format="p => statsMap[ctype]?.capacitytotal > 0 ? parseFloat(100.0 * statsMap[ctype]?.capacityused / statsMap[ctype]?.capacitytotal).toFixed(2) + '%' : '0%'"
stroke-color="#52c41a"
size="small"

View File

@ -31,10 +31,27 @@
<template #label>
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.addAccountToProject.account.description"/>
</template>
<a-input
v-model:value="form.account"
:placeholder="apiParams.addAccountToProject.account.description"
v-focus="true" />
<a-select
show-search
v-model:value="form.account"
:placeholder="apiParams.addAccountToProject.account.description"
v-focus="true"
:filterOption="false"
@search="fetchAccounts"
>
<template v-if="load.accounts" #notFoundContent>
<a-spin size="small" />
</template>
<template v-if="!load.accounts">
<a-select-option v-for="account in accounts" :key="account.name" :value="account.name">
<span v-if="account.icon">
<resource-icon :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
</span>
<block-outlined v-else style="margin-right: 5px" />
{{ account.name }}
</a-select-option>
</template>
</a-select>
</a-form-item>
<a-form-item name="email" ref="email">
<template #label>
@ -104,10 +121,27 @@
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.addUserToProject.username.description"/>
</template>
<a-input
v-model:value="form.username"
:placeholder="apiParams.addUserToProject.username.description"
v-focus="true" />
<a-select
show-search
v-model:value="form.username"
:placeholder="apiParams.addUserToProject.username.description"
v-focus="true"
:filterOption="false"
@search="fetchUsers"
>
<template v-if="load.users" #notFoundContent>
<a-spin size="small" />
</template>
<template v-if="!load.users">
<a-select-option v-for="user in users" :key="user.username" :value="user.username">
<span v-if="user.icon">
<resource-icon :image="user.icon.base64image" size="1x" style="margin-right: 5px"/>
</span>
<block-outlined v-else style="margin-right: 5px" />
{{ user.firstname + ' ' + user.lastname + " (" + user.username + ")" }}
</a-select-option>
</template>
</a-select>
</a-form-item>
<a-form-item name="email" ref="email">
<template #label>
@ -163,11 +197,13 @@
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
export default {
name: 'AddAccountOrUserToProject',
components: {
ResourceIcon,
TooltipLabel
},
props: {
@ -218,9 +254,13 @@ export default {
this.fetchProjectRoles()
}
},
fetchUsers () {
fetchUsers (keyword) {
this.load.users = true
api('listUsers', { listall: true }).then(response => {
const params = { listall: true, showicon: true }
if (keyword) {
params.keyword = keyword
}
api('listUsers', params).then(response => {
this.users = response.listusersresponse.user ? response.listusersresponse.user : []
}).catch(error => {
this.$notifyError(error)
@ -228,11 +268,13 @@ export default {
this.load.users = false
})
},
fetchAccounts () {
fetchAccounts (keyword) {
this.load.accounts = true
api('listAccounts', {
domainid: this.resource.domainid
}).then(response => {
const params = { domainid: this.resource.domainid, showicon: true }
if (keyword) {
params.keyword = keyword
}
api('listAccounts', params).then(response => {
this.accounts = response.listaccountsresponse.account || []
}).catch(error => {
this.$notifyError(error)