This commit is contained in:
Abhishek Kumar 2026-05-13 07:17:56 +00:00 committed by GitHub
commit 9a511ea444
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
315 changed files with 33039 additions and 317 deletions

View File

@ -78,6 +78,14 @@ zone=default
# Generated with "uuidgen".
local.storage.uuid=
# Enable TLS for image server transfers. The keys are read from:
# cert file = /etc/cloudstack/agent/cloud.crt
# key file = /etc/cloudstack/agent/cloud.key
image.server.tls.enabled=true
# The Address for the network interface that the image server listens on. If not specified, it will listen on the Management network.
#image.server.listen.address=
# Location for KVM virtual router scripts.
# The path defined in this property is relative to the directory "/usr/share/cloudstack-common/".
domr.scripts.dir=scripts/network/domr/kvm

View File

@ -123,6 +123,20 @@ public class AgentProperties{
*/
public static final Property<String> LOCAL_STORAGE_PATH = new Property<>("local.storage.path", "/var/lib/libvirt/images/");
/**
* Enables TLS on the KVM image server transfer endpoint.<br>
* Data type: Boolean.<br>
* Default value: <code>true</code>
*/
public static final Property<Boolean> IMAGE_SERVER_TLS_ENABLED = new Property<>("image.server.tls.enabled", true);
/**
* The IP address that the KVM image server listens on.<br>
* Data type: String.<br>
* Default value: <code>null</code>
*/
public static final Property<String> IMAGE_SERVER_LISTEN_ADDRESS = new Property<>("image.server.listen.address", null, String.class);
/**
* Directory where Qemu sockets are placed.<br>
* These sockets are for the Qemu Guest Agent and SSVM provisioning.<br>

View File

@ -22,6 +22,7 @@ import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;
import com.cloud.dc.DataCenter;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.offering.DiskOffering;
import com.cloud.user.Account;
@ -70,6 +71,10 @@ public interface VolumeApiService {
*/
Volume allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException;
Volume allocVolume(long ownerId, Long zoneId, Long diskOfferingId, Long vmId, Long snapshotId, String name,
Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId)
throws ResourceAllocationException;
/**
* Creates the volume based on the given criteria
*
@ -80,6 +85,8 @@ public interface VolumeApiService {
*/
Volume createVolume(CreateVolumeCmd cmd);
Volume createVolume(long volumeId, Long vmId, Long snapshotId, Long storageId, Boolean display);
/**
* Resizes the volume based on the given criteria
*
@ -203,4 +210,6 @@ public interface VolumeApiService {
Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException;
Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo);
Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone);
}

View File

@ -88,10 +88,16 @@ public interface AccountService {
Account getActiveAccountById(long accountId);
Account getActiveAccountByUuid(String accountUuid);
Account getAccount(long accountId);
Account getAccountByUuid(String accountUuid);
User getActiveUser(long userId);
User getOneActiveUserForAccount(Account account);
User getUserIncludingRemoved(long userId);
boolean isRootAdmin(Long accountId);

View File

@ -130,4 +130,10 @@ public interface VmDetailConstants {
String EXTERNAL_DETAIL_PREFIX = "External:";
String CLOUDSTACK_VM_DETAILS = "cloudstack.vm.details";
String CLOUDSTACK_VLAN = "cloudstack.vlan";
// KVM Checkpoints related
String ACTIVE_CHECKPOINT_ID = "active.checkpoint.id";
String ACTIVE_CHECKPOINT_CREATE_TIME = "active.checkpoint.create.time";
String LAST_CHECKPOINT_ID = "last.checkpoint.id";
String LAST_CHECKPOINT_CREATE_TIME = "last.checkpoint.create.time";
}

View File

@ -77,6 +77,7 @@ public class ApiConstants {
public static final String BOOTABLE = "bootable";
public static final String BIND_DN = "binddn";
public static final String BIND_PASSWORD = "bindpass";
public static final String BLANK_INSTANCE = "blankinstance";
public static final String BUS_ADDRESS = "busaddress";
public static final String BYTES_READ_RATE = "bytesreadrate";
public static final String BYTES_READ_RATE_MAX = "bytesreadratemax";
@ -217,6 +218,7 @@ public class ApiConstants {
public static final String DOMAIN_PATH = "domainpath";
public static final String DOMAIN_ID = "domainid";
public static final String DOMAIN__ID = "domainId";
public static final String DUMMY = "dummy";
public static final String DURATION = "duration";
public static final String ELIGIBLE = "eligible";
public static final String EMAIL = "email";
@ -260,6 +262,7 @@ public class ApiConstants {
public static final String FOR_VIRTUAL_NETWORK = "forvirtualnetwork";
public static final String FOR_SYSTEM_VMS = "forsystemvms";
public static final String FOR_PROVIDER = "forprovider";
public static final String FROM_CHECKPOINT_ID = "fromcheckpointid";
public static final String FULL_PATH = "fullpath";
public static final String GATEWAY = "gateway";
public static final String IP6_GATEWAY = "ip6gateway";
@ -332,6 +335,7 @@ public class ApiConstants {
public static final String IS_2FA_VERIFIED = "is2faverified";
public static final String IS_2FA_MANDATED = "is2famandated";
public static final String IS_ACTIVE = "isactive";
public static final String IS_ASYNC = "isasync";
public static final String IP_AVAILABLE = "ipavailable";
public static final String IP_LIMIT = "iplimit";
@ -608,6 +612,7 @@ public class ApiConstants {
public static final String TENANT_NAME = "tenantname";
public static final String TOTAL = "total";
public static final String TOTAL_SUBNETS = "totalsubnets";
public static final String TO_CHECKPOINT_ID = "tocheckpointid";
public static final String TOTAL_QUOTA = "totalquota";
public static final String TYPE = "type";
public static final String TRUST_STORE = "truststore";

View File

@ -21,8 +21,11 @@ import java.util.Map;
import javax.servlet.http.HttpSession;
import org.apache.cloudstack.context.CallContext;
import com.cloud.domain.Domain;
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.user.Account;
import com.cloud.user.UserAccount;
public interface ApiServerService {
@ -52,4 +55,20 @@ public interface ApiServerService {
String getDomainId(Map<String, Object[]> params);
boolean isPostRequestsAndTimestampsEnforced();
AsyncCmdResult processAsyncCmd(BaseAsyncCmd cmdObj, Map<String, String> params, CallContext ctx, Long callerUserId, Account caller) throws Exception;
class AsyncCmdResult {
public final Long objectId;
public final String objectUuid;
public final BaseAsyncCmd asyncCmd;
public final long jobId;
public AsyncCmdResult(Long objectId, String objectUuid, BaseAsyncCmd asyncCmd, long jobId) {
this.objectId = objectId;
this.objectUuid = objectUuid;
this.asyncCmd = asyncCmd;
this.jobId = jobId;
}
}
}

View File

@ -0,0 +1,100 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
import com.cloud.utils.EnumUtils;
@APICommand(name = "createImageTransfer",
description = "Create image transfer for a disk in backup. This API is intended for testing only and is disabled by default.",
responseObject = ImageTransferResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class CreateImageTransferCmd extends BaseCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Parameter(name = ApiConstants.BACKUP_ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
description = "ID of the backup")
private Long backupId;
@Parameter(name = ApiConstants.VOLUME_ID,
type = CommandType.UUID,
entityType = VolumeResponse.class,
required = true,
description = "ID of the disk/volume")
private Long volumeId;
@Parameter(name = ApiConstants.DIRECTION,
type = CommandType.STRING,
required = true,
description = "Direction of the transfer: upload, download")
private String direction;
@Parameter(name = ApiConstants.FORMAT,
type = CommandType.STRING,
description = "Format for the image transfer: raw/cow. 'raw' will create an NBD backend. 'cow' will use the File backend." +
"For download, only the 'raw' format is supported. Default: raw")
private String format;
public Long getBackupId() {
return backupId;
}
public Long getVolumeId() {
return volumeId;
}
public ImageTransfer.Direction getDirection() {
return ImageTransfer.Direction.valueOf(direction);
}
public ImageTransfer.Format getFormat() {
return EnumUtils.getEnum(ImageTransfer.Format.class, format);
}
@Override
public void execute() {
ImageTransferResponse response = kvmBackupExportService.createImageTransfer(this);
response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "deleteVirtualMachineCheckpoint",
description = "Delete a VM checkpoint. This API is intended for testing only and is disabled by default.",
responseObject = SuccessResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class DeleteVmCheckpointCmd extends BaseCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
@Parameter(name = "checkpointid",
type = CommandType.STRING,
required = true,
description = "Checkpoint ID")
private String checkpointId;
public Long getVmId() {
return vmId;
}
public String getCheckpointId() {
return checkpointId;
}
public void setVmId(Long vmId) {
this.vmId = vmId;
}
public void setCheckpointId(String checkpointId) {
this.checkpointId = checkpointId;
}
@Override
public void execute() {
boolean result = kvmBackupExportService.deleteVmCheckpoint(this);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,103 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
import com.cloud.event.EventTypes;
@APICommand(name = "finalizeBackup",
description = "Finalize a VM backup session. This API is intended for testing only and is disabled by default.",
responseObject = BackupResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class FinalizeBackupCmd extends BaseAsyncCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Inject
private BackupManager backupManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
required = true,
description = "ID of the backup")
private Long backupId;
public Long getVmId() {
return vmId;
}
public Long getBackupId() {
return backupId;
}
@Override
public void execute() {
Backup backup = kvmBackupExportService.finalizeBackup(this);
if (backup == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup");
}
BackupResponse response = backupManager.createBackupResponse(backup, null);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public String getEventType() {
return EventTypes.EVENT_VM_BACKUP_CREATE;
}
@Override
public String getEventDescription() {
return "Finalizing backup " + backupId;
}
}

View File

@ -0,0 +1,69 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "finalizeImageTransfer",
description = "Finalize an image transfer. This API is intended for testing only and is disabled by default.",
responseObject = SuccessResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class FinalizeImageTransferCmd extends BaseCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = ImageTransferResponse.class,
required = true,
description = "ID of the image transfer")
private Long imageTransferId;
public Long getImageTransferId() {
return imageTransferId;
}
@Override
public void execute() {
boolean result = kvmBackupExportService.finalizeImageTransfer(this);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,81 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "listImageTransfers",
description = "List image transfers for a backup. This API is intended for testing only and is disabled by default.",
responseObject = ImageTransferResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class ListImageTransfersCmd extends BaseListCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = ImageTransferResponse.class,
description = "ID of the Image Transfer")
private Long id;
@Parameter(name = ApiConstants.BACKUP_ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
description = "ID of the backup")
private Long backupId;
public Long getId() {
return id;
}
public Long getBackupId() {
return backupId;
}
@Override
public void execute() {
List<ImageTransferResponse> responses = kvmBackupExportService.listImageTransfers(this);
ListResponse<ImageTransferResponse> response = new ListResponse<>();
response.setResponses(responses);
response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase());
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,69 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.CheckpointResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.KVMBackupExportService;
@APICommand(name = "listVirtualMachineCheckpoints",
description = "List checkpoints for a VM. This API is intended for testing only and is disabled by default.",
responseObject = CheckpointResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class ListVmCheckpointsCmd extends BaseListCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
public Long getVmId() {
return vmId;
}
@Override
public void execute() {
List<CheckpointResponse> responses = kvmBackupExportService.listVmCheckpoints(this);
ListResponse<CheckpointResponse> response = new ListResponse<>();
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return 0;
}
}

View File

@ -0,0 +1,120 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.KVMBackupExportService;
import org.apache.cloudstack.context.CallContext;
import com.cloud.event.EventTypes;
@APICommand(name = "startBackup",
description = "Start a VM backup session using pull mode backup-begin on the KVM host. This API is intended for testing only and is disabled by default.",
responseObject = BackupResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class StartBackupCmd extends BaseAsyncCreateCmd implements AdminCmd {
@Inject
private KVMBackupExportService kvmBackupExportService;
@Inject
private BackupManager backupManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
@Parameter(name = ApiConstants.NAME,
type = CommandType.STRING,
description = "the name of the backup")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION,
type = CommandType.STRING,
description = "the description for the backup")
private String description;
public Long getVmId() {
return vmId;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
@Override
public void execute() {
try {
Backup backup = kvmBackupExportService.startBackup(this);
BackupResponse response = backupManager.createBackupResponse(backup, null);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public void create() {
Backup backup = kvmBackupExportService.createBackup(this);
if (backup != null) {
setEntityId(backup.getId());
setEntityUuid(backup.getUuid());
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup");
}
}
@Override
public String getEventType() {
return EventTypes.EVENT_VM_BACKUP_CREATE;
}
@Override
public String getEventDescription() {
return "Starting backup for Instance " + vmId;
}
}

View File

@ -19,23 +19,23 @@ package org.apache.cloudstack.api.command.admin.config;
import java.util.ArrayList;
import java.util.List;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.ConfigurationResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ManagementServerResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.commons.lang3.StringUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.Pair;
@ -94,6 +94,13 @@ public class ListCfgsByCmd extends BaseListCmd {
description = "The ID of the Image Store to update the parameter value for corresponding image store")
private Long imageStoreId;
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID,
type = CommandType.UUID,
entityType = ManagementServerResponse.class,
description = "the ID of the Management Server to update the parameter value for corresponding management server",
since = "4.23.0")
private Long managementServerId;
@Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "Lists configuration by group name (primarily used for UI)", since = "4.18.0")
private String groupName;
@ -139,6 +146,10 @@ public class ListCfgsByCmd extends BaseListCmd {
return imageStoreId;
}
public Long getManagementServerId() {
return managementServerId;
}
public String getGroupName() {
return groupName;
}
@ -200,6 +211,9 @@ public class ListCfgsByCmd extends BaseListCmd {
if (getImageStoreId() != null){
cfgResponse.setScope("imagestore");
}
if (getManagementServerId() != null){
cfgResponse.setScope("managementserver");
}
}
@Override

View File

@ -23,16 +23,16 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.ConfigurationResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.ManagementServerResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.framework.config.ConfigKey;
import com.cloud.user.Account;
import com.cloud.utils.Pair;
@ -84,6 +84,13 @@ public class ResetCfgCmd extends BaseCmd {
description = "The ID of the Image Store to reset the parameter value for corresponding image store")
private Long imageStoreId;
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID,
type = CommandType.UUID,
entityType = ManagementServerResponse.class,
description = "the ID of the Management Server to update the parameter value for corresponding management server",
since = "4.23.0")
private Long managementServerId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -116,6 +123,10 @@ public class ResetCfgCmd extends BaseCmd {
return imageStoreId;
}
public Long getManagementServerId() {
return managementServerId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -149,6 +160,9 @@ public class ResetCfgCmd extends BaseCmd {
if (getImageStoreId() != null) {
response.setScope(ConfigKey.Scope.ImageStore.name());
}
if (getManagementServerId() != null) {
response.setScope(ConfigKey.Scope.ManagementServer.name());
}
response.setValue(cfg.second());
this.setResponseObject(response);
} else {

View File

@ -16,9 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.admin.config;
import com.cloud.utils.crypt.DBEncryptionUtil;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
@ -29,13 +27,17 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.ConfigurationResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.ManagementServerResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.commons.lang3.StringUtils;
import com.cloud.user.Account;
import com.cloud.utils.crypt.DBEncryptionUtil;
@APICommand(name = "updateConfiguration", description = "Updates a configuration.", responseObject = ConfigurationResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -88,6 +90,13 @@ public class UpdateCfgCmd extends BaseCmd {
validations = ApiArgValidator.PositiveNumber)
private Long imageStoreId;
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID,
type = CommandType.UUID,
entityType = ManagementServerResponse.class,
description = "the ID of the Management Server to update the parameter value for corresponding management server",
since = "4.23.0")
private Long managementServerId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -112,7 +121,7 @@ public class UpdateCfgCmd extends BaseCmd {
return clusterId;
}
public Long getStoragepoolId() {
public Long getStoragePoolId() {
return storagePoolId;
}
@ -128,6 +137,10 @@ public class UpdateCfgCmd extends BaseCmd {
return imageStoreId;
}
public Long getManagementServerId() {
return managementServerId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -182,7 +195,7 @@ public class UpdateCfgCmd extends BaseCmd {
if (getClusterId() != null) {
response.setScope("cluster");
}
if (getStoragepoolId() != null) {
if (getStoragePoolId() != null) {
response.setScope("storagepool");
}
if (getAccountId() != null) {
@ -191,6 +204,9 @@ public class UpdateCfgCmd extends BaseCmd {
if (getDomainId() != null) {
response.setScope("domain");
}
if (getManagementServerId() != null) {
response.setScope(ConfigKey.Scope.ManagementServer.name().toLowerCase());
}
return response;
}
}

View File

@ -85,6 +85,9 @@ public class AssignVMCmd extends BaseCmd {
"In case no security groups are provided the Instance is part of the default security group.")
private List<Long> securityGroupIdList;
// Internal flag to allow assignment without adding a network
private boolean skipNetwork = false;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -113,6 +116,34 @@ public class AssignVMCmd extends BaseCmd {
return securityGroupIdList;
}
public boolean isSkipNetwork() {
return skipNetwork;
}
/////////////////////////////////////////////////////
/////////////////// Setters /////////////////////////
/////////////////////////////////////////////////////
public void setVirtualMachineId(Long virtualMachineId) {
this.virtualMachineId = virtualMachineId;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public void setSkipNetwork(boolean skipNetwork) {
this.skipNetwork = skipNetwork;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -41,6 +41,19 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd {
@Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "Destination Cluster ID to deploy the Instance to - parameter available for root admin only", since = "4.13")
private Long clusterId;
@Parameter(name = ApiConstants.BLANK_INSTANCE,
type = CommandType.BOOLEAN,
description = "Whether to create a blank instance without storage and network",
since = "4.23.0")
private Boolean blankInstance;
// Internal flag to allow deploying instance with a given type
private String instanceType;
/////////////////////////////////////////////////////
////////////////// Getters //////////////////////////
/////////////////////////////////////////////////////
public Long getPodId() {
return podId;
}
@ -48,4 +61,33 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd {
public Long getClusterId() {
return clusterId;
}
@Override
public boolean isBlankInstance() {
return Boolean.TRUE.equals(blankInstance);
}
@Override
public String getInstanceType() {
if (!isBlankInstance()) {
return null;
}
return instanceType;
}
/////////////////////////////////////////////////////
////////////////// Setters //////////////////////////
/////////////////////////////////////////////////////
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public void setBlankInstance(boolean blankInstance) {
this.blankInstance = blankInstance;
}
public void setInstanceType(String instanceType) {
this.instanceType = instanceType;
}
}

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.api.command.admin.vm;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
@ -27,4 +29,20 @@ import com.cloud.vm.VirtualMachine;
@APICommand(name = "destroyVirtualMachine", description = "Destroys an Instance. Once destroyed, only the administrator can recover it.", responseObject = UserVmResponse.class, responseView = ResponseView.Full, entityType = {VirtualMachine.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd {}
public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd {
@Parameter( name = ApiConstants.FORCED,
type = CommandType.BOOLEAN,
description = "Force destroy the Instance",
since = "4.23.0")
Boolean forced;
@Override
public boolean isForced() {
return Boolean.TRUE.equals(forced);
}
public void setForced(Boolean forced) {
this.forced = forced;
}
}

View File

@ -193,6 +193,18 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC
return gpuEnabled;
}
public void setZoneId(Long zoneId) {
this.zoneId = zoneId;
}
public void setCpuNumber(Integer cpuNumber) {
this.cpuNumber = cpuNumber;
}
public void setMemory(Integer memory) {
this.memory = memory;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -100,6 +100,26 @@ public class AddNicToVMCmd extends BaseAsyncCmd implements UserCmd {
return NetUtils.standardizeMacAddress(macaddr);
}
public void setVmId(Long vmId) {
this.vmId = vmId;
}
public void setNetworkId(Long netId) {
this.netId = netId;
}
public void setIpaddr(String ipaddr) {
this.ipaddr = ipaddr;
}
public void setMacAddress(String macaddr) {
this.macaddr = macaddr;
}
public void setDhcpOptions(Map dhcpOptions) {
this.dhcpOptions = dhcpOptions;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -61,10 +61,10 @@ import com.cloud.network.Network;
import com.cloud.network.Network.IpAddresses;
import com.cloud.offering.DiskOffering;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.utils.net.Dhcp;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.VmDiskInfo;
import com.cloud.utils.net.Dhcp;
public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityGroupAction, UserCmd {
@ -75,13 +75,13 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "availability zone for the virtual machine")
private Long zoneId;
protected Long zoneId;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine", validations = {ApiArgValidator.RFCComplianceDomainName})
private String name;
protected String name;
@Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "an optional user generated name for the virtual machine")
private String displayName;
protected String displayName;
@Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, description="The password of the virtual machine. If null, a random password will be generated for the VM.",
since="4.19.0.0")
@ -89,21 +89,21 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
//Owner information
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the virtual machine. Must be used with domainId.")
private String accountName;
protected String accountName;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "an optional domainId for the virtual machine. If the account parameter is used, domainId must also be used. If account is NOT provided then virtual machine will be assigned to the caller account and domain.")
private Long domainId;
protected Long domainId;
//Network information
//@ACL(accessType = AccessType.UseEntry)
@Parameter(name = ApiConstants.NETWORK_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = NetworkResponse.class, description = "list of network ids used by virtual machine. Can't be specified with ipToNetworkList parameter")
private List<Long> networkIds;
protected List<Long> networkIds;
@Parameter(name = ApiConstants.BOOT_TYPE, type = CommandType.STRING, required = false, description = "Guest VM Boot option either custom[UEFI] or default boot [BIOS]. Not applicable with VMware if the template is marked as deploy-as-is, as we honour what is defined in the template.", since = "4.14.0.0")
private String bootType;
protected String bootType;
@Parameter(name = ApiConstants.BOOT_MODE, type = CommandType.STRING, required = false, description = "Boot Mode [Legacy] or [Secure] Applicable when Boot Type Selected is UEFI, otherwise Legacy only for BIOS. Not applicable with VMware if the template is marked as deploy-as-is, as we honour what is defined in the template.", since = "4.14.0.0")
private String bootMode;
protected String bootMode;
@Parameter(name = ApiConstants.BOOT_INTO_SETUP, type = CommandType.BOOLEAN, required = false, description = "Boot into hardware setup or not (ignored if startVm = false, only valid for vmware)", since = "4.15.0.0")
private Boolean bootIntoSetup;
@ -138,7 +138,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
@Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "the hypervisor on which to deploy the virtual machine. "
+ "The parameter is required and respected only when hypervisor info is not set on the ISO/Template passed to the call")
private String hypervisor;
protected String hypervisor;
@Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING,
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
@ -147,10 +147,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
"You also need to change vm.userdata.max.length value",
length = 1048576)
private String userData;
protected String userData;
@Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "the ID of the Userdata", since = "4.18")
private Long userdataId;
protected Long userdataId;
@Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP, description = "used to specify the parameters values for the variables in userdata.", since = "4.18")
private Map userdataDetails;
@ -160,7 +160,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
private String sshKeyPairName;
@Parameter(name = ApiConstants.SSH_KEYPAIRS, type = CommandType.LIST, collectionType = CommandType.STRING, since="4.17", description = "names of the ssh key pairs used to login to the virtual machine")
private List<String> sshKeyPairNames;
protected List<String> sshKeyPairNames;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "destination Host ID to deploy the VM to - parameter available for root admin only")
private Long hostId;
@ -168,7 +168,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
@ACL
@Parameter(name = ApiConstants.SECURITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups id that going to be applied to the virtual machine. "
+ "Should be passed only when vm is created from a zone with Basic Network support." + " Mutually exclusive with securitygroupnames parameter")
private List<Long> securityGroupIdList;
protected List<Long> securityGroupIdList;
@ACL
@Parameter(name = ApiConstants.SECURITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups names that going to be applied to the virtual machine."
@ -189,10 +189,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
private String macAddress;
@Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, description = "an optional keyboard device type for the virtual machine. valid value can be one of de,de-ch,es,es-latam,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us")
private String keyboard;
protected String keyboard;
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Deploy vm for the project")
private Long projectId;
protected Long projectId;
@Parameter(name = ApiConstants.START_VM, type = CommandType.BOOLEAN, description = "true if start vm after creating; defaulted to true if not specified")
private Boolean startVm;
@ -200,7 +200,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
@ACL
@Parameter(name = ApiConstants.AFFINITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups id that are going to be applied to the virtual machine."
+ " Mutually exclusive with affinitygroupnames parameter")
private List<Long> affinityGroupIdList;
protected List<Long> affinityGroupIdList;
@ACL
@Parameter(name = ApiConstants.AFFINITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups names that are going to be applied to the virtual machine."
@ -208,10 +208,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
private List<String> affinityGroupNameList;
@Parameter(name = ApiConstants.DISPLAY_VM, type = CommandType.BOOLEAN, since = "4.2", description = "an optional field, whether to the display the vm to the end user or not.", authorized = {RoleType.Admin})
private Boolean displayVm;
protected Boolean displayVm;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.3", description = "used to specify the custom parameters. 'extraconfig' is not allowed to be passed in details")
private Map details;
protected Map details;
@Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin })
private String deploymentPlanner;
@ -225,7 +225,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
private Map dataDiskTemplateToDiskOfferingList;
@Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "an optional URL encoded string that can be passed to the virtual machine upon successful deployment", length = 5120)
private String extraConfig;
protected String extraConfig;
@Parameter(name = ApiConstants.COPY_IMAGE_TAGS, type = CommandType.BOOLEAN, since = "4.13", description = "if true the image tags (if any) will be copied to the VM, default value is false")
private Boolean copyImageTags;
@ -798,6 +798,11 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
}
return null;
}
public String getInstanceType() {
return null;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -16,10 +16,11 @@
// under the License.
package org.apache.cloudstack.api.command.user.vm;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@ -40,6 +41,7 @@ import com.cloud.exception.InsufficientServerCapacityException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.uservm.UserVm;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
@APICommand(name = "deployVirtualMachine", description = "Creates and automatically starts an Instance based on a service offering, disk offering, and Template.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
@ -88,6 +90,111 @@ public class DeployVMCmd extends BaseDeployVMCmd {
return volumeId != null || snapshotId != null;
}
public boolean isBlankInstance() {
return false;
}
/////////////////////////////////////////////////////
////////////////// Setters //////////////////////////
/////////////////////////////////////////////////////
public void setZoneId(Long zoneId) {
this.zoneId = zoneId;
}
public void setName(String name) {
this.name = name;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public void setNetworkIds(List<Long> networkIds) {
this.networkIds = networkIds;
}
public void setBootType(String bootType) {
this.bootType = bootType;
}
public void setBootMode(String bootMode) {
this.bootMode = bootMode;
}
public void setHypervisor(String hypervisor) {
this.hypervisor = hypervisor;
}
public void setUserData(String userData) {
this.userData = userData;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public void setDisplayVm(Boolean displayVm) {
this.displayVm = displayVm;
}
public void setUserDataId(Long userDataId) {
this.userdataId = userDataId;
}
public void setAffinityGroupIds(List<Long> ids) {
this.affinityGroupIdList = ids;
}
public void setDetails(Map details) {
this.details = details;
}
public void setExtraConfig(String extraConfig) {
this.extraConfig = extraConfig;
}
public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) {
this.dynamicScalingEnabled = dynamicScalingEnabled;
}
public void setServiceOfferingId(Long serviceOfferingId) {
this.serviceOfferingId = serviceOfferingId;
}
public void setTemplateId(Long templateId) {
this.templateId = templateId;
}
public void setVolumeId(Long volumeId) {
this.volumeId = volumeId;
}
public void setSnapshotId(Long snapshotId) {
this.snapshotId = snapshotId;
}
public void setSshKeyPairNames(List<String> sshKeyPairNames) {
this.sshKeyPairNames = sshKeyPairNames;
}
public void setSecurityGroupList(List<Long> securityGroupIdList) {
this.securityGroupIdList = securityGroupIdList;
}
@Override
public void execute() {
UserVm result;
@ -132,7 +239,7 @@ public class DeployVMCmd extends BaseDeployVMCmd {
@Override
public void create() throws ResourceAllocationException {
if (Stream.of(templateId, snapshotId, volumeId).filter(Objects::nonNull).count() != 1) {
if (!isBlankInstance() && Stream.of(templateId, snapshotId, volumeId).filter(Objects::nonNull).count() != 1) {
throw new CloudRuntimeException("Please provide only one of the following parameters - template ID, volume ID or snapshot ID");
}

View File

@ -90,6 +90,10 @@ public class DestroyVMCmd extends BaseAsyncCmd implements UserCmd {
return volumeIds;
}
public boolean isForced() {
return false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -70,6 +70,21 @@ public class AssignVolumeCmd extends BaseCmd implements UserCmd {
return projectid;
}
/////////////////////////////////////////////////////
/////////////////// Setter///////////////////////////
/////////////////////////////////////////////////////
public void setVolumeId(Long volumeId) {
this.volumeId = volumeId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public void setProjectId(Long projectid) {
this.projectid = projectid;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -114,7 +114,8 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
type = CommandType.UUID,
entityType = StoragePoolResponse.class,
description = "Storage pool ID to create the volume in. Cannot be used with the snapshotid parameter.",
authorized = {RoleType.Admin})
authorized = {RoleType.Admin},
since = "4.22.1")
private Long storageId;
/////////////////////////////////////////////////////
@ -150,6 +151,10 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
}
public Long getSnapshotId() {
if (storageId != null && snapshotId != null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Snapshot ID cannot be specified with the Storage ID.");
}
return snapshotId;
}
@ -163,7 +168,8 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
public Long getStorageId() {
if (snapshotId != null && storageId != null) {
throw new IllegalArgumentException("StorageId parameter cannot be specified with the SnapshotId parameter.");
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Storage ID cannot be specified with the Snapshot ID.");
}
return storageId;
}

View File

@ -77,6 +77,10 @@ public class DetachVolumeCmd extends BaseAsyncCmd implements UserCmd {
return virtualMachineId;
}
public void setId(Long id) {
this.id = id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -127,6 +127,18 @@ public class BackupResponse extends BaseResponse {
@Param(description = "Indicates whether the VM from which the backup was taken is expunged or not", since = "4.22.0")
private Boolean isVmExpunged;
@SerializedName(ApiConstants.FROM_CHECKPOINT_ID)
@Param(description = "Previous active checkpoint ID for incremental backups", since = "4.23.0")
private String fromCheckpointId;
@SerializedName(ApiConstants.TO_CHECKPOINT_ID)
@Param(description = "Next checkpoint ID for incremental backups", since = "4.23.0")
private String toCheckpointId;
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "Host ID where the backup is running", since = "4.23.0")
private String hostId;
public String getId() {
return id;
}
@ -314,4 +326,28 @@ public class BackupResponse extends BaseResponse {
public void setVmExpunged(Boolean isVmExpunged) {
this.isVmExpunged = isVmExpunged;
}
public void setFromCheckpointId(String fromCheckpointId) {
this.fromCheckpointId = fromCheckpointId;
}
public String getFromCheckpointId() {
return this.fromCheckpointId;
}
public void setToCheckpointId(String toCheckpointId) {
this.toCheckpointId = toCheckpointId;
}
public String getToCheckpointId() {
return this.toCheckpointId;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public String getHostId() {
return this.hostId;
}
}

View File

@ -0,0 +1,53 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.response;
import java.util.Date;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class CheckpointResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "the checkpoint ID")
private String id;
@SerializedName(ApiConstants.CREATED)
@Param(description = "the checkpoint creation time")
private Date created;
@SerializedName(ApiConstants.IS_ACTIVE)
@Param(description = "whether this is the active checkpoint")
private Boolean isActive;
public void setId(String id) {
this.id = id;
}
public void setCreated(Date created) {
this.created = created;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@ -0,0 +1,104 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.response;
import java.util.Date;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.backup.ImageTransfer;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = ImageTransfer.class)
public class ImageTransferResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "the ID of the image transfer")
private String id;
@SerializedName("backupid")
@Param(description = "the backup ID")
private String backupId;
@SerializedName("vmid")
@Param(description = "the VM ID")
private String vmId;
@SerializedName(ApiConstants.VOLUME_ID)
@Param(description = "the disk/volume ID")
private String diskId;
@SerializedName("devicename")
@Param(description = "the device name (vda, vdb, etc)")
private String deviceName;
@SerializedName("transferurl")
@Param(description = "the transfer URL")
private String transferUrl;
@SerializedName("phase")
@Param(description = "the transfer phase")
private String phase;
@SerializedName("direction")
@Param(description = "the image transfer direction: upload / download")
private String direction;
@SerializedName(ApiConstants.CREATED)
@Param(description = "the date created")
private Date created;
public void setId(String id) {
this.id = id;
}
public void setBackupId(String backupId) {
this.backupId = backupId;
}
public void setVmId(String vmId) {
this.vmId = vmId;
}
public void setDiskId(String diskId) {
this.diskId = diskId;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
public void setPhase(String phase) {
this.phase = phase;
}
public void setDirection(String direction) {
this.direction = direction;
}
public void setCreated(Date created) {
this.created = created;
}
}

View File

@ -30,8 +30,16 @@ import com.cloud.storage.Volume;
public interface Backup extends ControlledEntity, InternalIdentity, Identity {
String getFromCheckpointId();
String getToCheckpointId();
Long getCheckpointCreateTime();
Long getHostId();
enum Status {
Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged
Allocated, Queued, BackingUp, ReadyForImageTransfer, FinalizingImageTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged
}
class Metric {

View File

@ -0,0 +1,61 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.InternalIdentity;
public interface ImageTransfer extends ControlledEntity, InternalIdentity {
long getDataCenterId();
public enum Direction {
upload, download
}
public enum Format {
raw,
cow
}
public enum Backend {
nbd,
file
}
public enum Phase {
initializing, transferring, finished, failed
}
String getUuid();
Long getBackupId();
long getVolumeId();
long getHostId();
String getTransferUrl();
Phase getPhase();
Direction getDirection();
Backend getBackend();
String getSignedTicketId();
}

View File

@ -0,0 +1,101 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.List;
import org.apache.cloudstack.api.command.admin.backup.CreateImageTransferCmd;
import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd;
import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd;
import org.apache.cloudstack.api.command.admin.backup.FinalizeImageTransferCmd;
import org.apache.cloudstack.api.command.admin.backup.ListImageTransfersCmd;
import org.apache.cloudstack.api.command.admin.backup.ListVmCheckpointsCmd;
import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd;
import org.apache.cloudstack.api.response.CheckpointResponse;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.utils.component.PluggableService;
/**
* Service for Creating Backups and ImageTransfer sessions which will be consumed by an external orchestrator.
*/
public interface KVMBackupExportService extends Configurable, PluggableService {
ConfigKey<Integer> ImageTransferIdleTimeoutSeconds = new ConfigKey<>("Advanced", Integer.class,
"image.transfer.idle.timeout.seconds",
"600",
"Seconds since last completed HTTP request to an image transfer before the image server unregisters it (idle timeout).",
true, ConfigKey.Scope.Zone);
ConfigKey<Boolean> ExposeKVMBackupExportServiceApis = new ConfigKey<>("Advanced", Boolean.class,
"expose.kvm.backup.export.service.apis",
"false",
"Enable to expose APIs for testing the KVM Backup Export Service.",
false, ConfigKey.Scope.Global);
/**
* Creates a backup session for a VM
*/
Backup createBackup(StartBackupCmd cmd);
/**
* Start a backup session for a VM
* Creates a new checkpoint and starts NBD server for pull-mode backup
*/
Backup startBackup(StartBackupCmd cmd);
/**
* Finalize a backup session
* Stops NBD server, updates checkpoint tracking, deletes old checkpoints
*/
Backup finalizeBackup(FinalizeBackupCmd cmd);
/**
* Create an image transfer object for a disk
* Registers NBD endpoint with ImageIO (stubbed for POC)
*/
ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd);
ImageTransfer createImageTransfer(long volumeId, Long backupId, ImageTransfer.Direction direction, ImageTransfer.Format format);
boolean cancelImageTransfer(long imageTransferId);
/**
* Finalize an image transfer
* Marks transfer as complete (NBD is closed globally in finalize backup)
*/
boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd);
boolean finalizeImageTransfer(long imageTransferId);
/**
* List image transfers for a backup
*/
List<ImageTransferResponse> listImageTransfers(ListImageTransfersCmd cmd);
/**
* List checkpoints for a VM
*/
List<CheckpointResponse> listVmCheckpoints(ListVmCheckpointsCmd cmd);
/**
* Delete a VM checkpoint (no-op for normal flow, kept for API parity)
*/
boolean deleteVmCheckpoint(DeleteVmCheckpointCmd cmd);
}

View File

@ -23,6 +23,10 @@ import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSDis
import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ManagementServerException;
@ -31,11 +35,6 @@ import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd;
import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.ListResponse;
public interface SharedFSService {
List<SharedFSProvider> getSharedFSProviders();
@ -69,4 +68,10 @@ public interface SharedFSService {
SharedFS recoverSharedFS(Long sharedFSId);
void deleteSharedFS(Long sharedFSId);
SharedFS getSharedFSByUuid(String uuid);
SharedFS getSharedFSForVmId(long vmId);
SharedFS updateSharedFSPostRestore(long sharedFsId, long volumeId);
}

View File

@ -0,0 +1,55 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.vm;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class AssignVMCmdTest {
@Test
public void test_setSkipNetwork_default() {
AssignVMCmd assignVMCmd = new AssignVMCmd();
Object value = ReflectionTestUtils.getField(assignVMCmd, "skipNetwork");
Assert.assertTrue(value instanceof Boolean);
Assert.assertFalse((Boolean) value);
}
@Test
public void test_setSkipNetwork_set() {
AssignVMCmd assignVMCmd = new AssignVMCmd();
assignVMCmd.setSkipNetwork(true);
Object value = ReflectionTestUtils.getField(assignVMCmd, "skipNetwork");
Assert.assertTrue(value instanceof Boolean);
Assert.assertTrue((Boolean) value);
}
@Test
public void test_isSkipNetwork_default() {
AssignVMCmd assignVMCmd = new AssignVMCmd();
Assert.assertFalse(assignVMCmd.isSkipNetwork());
}
@Test
public void test_isSkipNetwork_set() {
AssignVMCmd assignVMCmd = new AssignVMCmd();
ReflectionTestUtils.setField(assignVMCmd, "skipNetwork", true);
Assert.assertTrue(assignVMCmd.isSkipNetwork());
}
}

View File

@ -0,0 +1,75 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.vm;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class DeployVMCmdByAdminTest {
@InjectMocks
private DeployVMCmdByAdmin cmd;
@Test
public void testIsBlankInstance_default() {
assertFalse(cmd.isBlankInstance());
}
@Test
public void testIsBlankInstance_true() {
ReflectionTestUtils.setField(cmd, "blankInstance", true);
assertTrue(cmd.isBlankInstance());
}
@Test
public void testIsBlankInstance_false() {
ReflectionTestUtils.setField(cmd, "blankInstance", false);
assertFalse(cmd.isBlankInstance());
}
@Test
public void testSetBlankInstance_default() {
Object obj = ReflectionTestUtils.getField(cmd, "blankInstance");
assertNull(obj);
}
@Test
public void testSetBlankInstance_true() {
cmd.setBlankInstance(true);
Object obj = ReflectionTestUtils.getField(cmd, "blankInstance");
assertNotNull(obj);
assertTrue((boolean)obj);
}
@Test
public void testSetBlankInstance_false() {
cmd.setBlankInstance(false);
Object obj = ReflectionTestUtils.getField(cmd, "blankInstance");
assertNotNull(obj);
assertFalse((boolean)obj);
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.vm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@ -41,6 +42,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.NetworkService;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VmDetailConstants;
@ -480,4 +482,146 @@ public class DeployVMCmdTest {
});
assertTrue(thrownException.getMessage().contains("Unable to translate and find entity with datadisktemplateid"));
}
@Test
public void testSetServiceOfferingId() {
cmd.setServiceOfferingId(101L);
assertEquals(Long.valueOf(101L), cmd.getServiceOfferingId());
}
@Test
public void testSetTemplateId() {
cmd.setTemplateId(102L);
assertEquals(Long.valueOf(102L), cmd.getTemplateId());
}
@Test
public void testSetVolumeId() {
cmd.setVolumeId(103L);
assertEquals(Long.valueOf(103L), cmd.getVolumeId());
}
@Test
public void testSetSnapshotId() {
cmd.setSnapshotId(104L);
assertEquals(Long.valueOf(104L), cmd.getSnapshotId());
}
@Test
public void testSetZoneId() {
cmd.setZoneId(105L);
assertEquals(Long.valueOf(105L), cmd.getZoneId());
}
@Test
public void testSetName() {
cmd.setName("vm-name");
assertEquals("vm-name", cmd.getName());
}
@Test
public void testSetDisplayName() {
cmd.setDisplayName("vm-display-name");
assertEquals("vm-display-name", cmd.getDisplayName());
}
@Test
public void testSetAccountName() {
cmd.setAccountName("account-name");
assertEquals("account-name", cmd.getAccountName());
}
@Test
public void testSetDomainId() {
cmd.setDomainId(106L);
assertEquals(Long.valueOf(106L), cmd.getDomainId());
}
@Test
public void testSetNetworkIds() {
List<Long> networkIds = Arrays.asList(11L, 12L);
cmd.setNetworkIds(networkIds);
assertEquals(networkIds, cmd.getNetworkIds());
}
@Test
public void testSetBootType() {
cmd.setBootType("UEFI");
assertEquals(BootType.UEFI, cmd.getBootType());
}
@Test
public void testSetBootMode() {
cmd.setBootType("UEFI");
cmd.setBootMode("SECURE");
assertEquals(BootMode.SECURE, cmd.getBootMode());
}
@Test
public void testSetHypervisor() {
cmd.setHypervisor("KVM");
assertEquals(HypervisorType.KVM, cmd.getHypervisor());
}
@Test
public void testSetUserData() {
cmd.setUserData("dXNlci1kYXRh");
assertEquals("dXNlci1kYXRh", cmd.getUserData());
}
@Test
public void testSetKeyboard() {
cmd.setKeyboard("us");
assertEquals("us", cmd.getKeyboard());
}
@Test
public void testSetProjectId() {
cmd.setProjectId(107L);
assertEquals(Long.valueOf(107L), ReflectionTestUtils.getField(cmd, "projectId"));
}
@Test
public void testSetDisplayVm() {
cmd.setDisplayVm(Boolean.FALSE);
assertEquals(Boolean.FALSE, cmd.isDisplayVm());
}
@Test
public void testSetUserDataId() {
cmd.setUserDataId(108L);
assertEquals(Long.valueOf(108L), cmd.getUserdataId());
}
@Test
public void testSetAffinityGroupIds() {
List<Long> affinityGroupIds = Arrays.asList(21L, 22L);
cmd.setAffinityGroupIds(affinityGroupIds);
assertEquals(affinityGroupIds, cmd.getAffinityGroupIdList());
}
@Test
public void testSetDetails() {
Map<String, String> details = new HashMap<>();
details.put("key", "value");
cmd.setDetails(details);
assertEquals(details, ReflectionTestUtils.getField(cmd, "details"));
}
@Test
public void testSetExtraConfig() {
cmd.setExtraConfig("cpu-mode=host-passthrough");
assertEquals("cpu-mode=host-passthrough", cmd.getExtraConfig());
}
@Test
public void testSetDynamicScalingEnabled() {
cmd.setDynamicScalingEnabled(Boolean.FALSE);
assertFalse(cmd.isDynamicScalingEnabled());
}
@Test
public void testIsBlankInstance() {
assertFalse(cmd.isBlankInstance());
}
}

View File

@ -612,6 +612,11 @@
<artifactId>cloud-plugin-backup-nas</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-integrations-veeam-control-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-integrations-kubernetes-service</artifactId>

View File

@ -0,0 +1,56 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class CreateImageTransferAnswer extends Answer {
private String imageTransferId;
private String transferUrl;
public CreateImageTransferAnswer() {
}
public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details,
String imageTransferId, String transferUrl) {
super(cmd, success, details);
this.imageTransferId = imageTransferId;
this.transferUrl = transferUrl;
}
public String getImageTransferId() {
return imageTransferId;
}
public void setImageTransferId(String imageTransferId) {
this.imageTransferId = imageTransferId;
}
public String getTransferUrl() {
return transferUrl;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
}

View File

@ -0,0 +1,94 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class CreateImageTransferCommand extends Command {
private String transferId;
private String exportName;
private String socket;
private String direction;
private String checkpointId;
private String file;
private ImageTransfer.Backend backend;
private int idleTimeoutSeconds;
public CreateImageTransferCommand() {
}
private CreateImageTransferCommand(String transferId, String direction, String socket, int idleTimeoutSeconds) {
this.transferId = transferId;
this.direction = direction;
this.socket = socket;
this.idleTimeoutSeconds = idleTimeoutSeconds;
}
public CreateImageTransferCommand(String transferId, String direction, String exportName, String socket, String checkpointId, int idleTimeoutSeconds) {
this(transferId, direction, socket, idleTimeoutSeconds);
this.backend = ImageTransfer.Backend.nbd;
this.exportName = exportName;
this.checkpointId = checkpointId;
}
public CreateImageTransferCommand(String transferId, String direction, String socket, String file, int idleTimeoutSeconds) {
this(transferId, direction, socket, idleTimeoutSeconds);
if (direction == ImageTransfer.Direction.download.toString()) {
throw new IllegalArgumentException("File backend is only supported for upload");
}
this.backend = ImageTransfer.Backend.file;
this.file = file;
}
public String getExportName() {
return exportName;
}
public String getSocket() {
return socket;
}
public String getFile() {
return file;
}
public ImageTransfer.Backend getBackend() {
return backend;
}
public String getTransferId() {
return transferId;
}
@Override
public boolean executeInSequence() {
return true;
}
public String getDirection() {
return direction;
}
public String getCheckpointId() {
return checkpointId;
}
public int getIdleTimeoutSeconds() {
return idleTimeoutSeconds;
}
}

View File

@ -0,0 +1,60 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Map;
import com.cloud.agent.api.Command;
public class DeleteVmCheckpointCommand extends Command {
private String vmName;
private String checkpointId;
private Map<String, String> diskPathUuidMap;
private boolean stoppedVM;
public DeleteVmCheckpointCommand() {
}
public DeleteVmCheckpointCommand(String vmName, String checkpointId, Map<String, String> diskPathUuidMap, boolean stoppedVM) {
this.vmName = vmName;
this.checkpointId = checkpointId;
this.diskPathUuidMap = diskPathUuidMap;
this.stoppedVM = stoppedVM;
}
public String getVmName() {
return vmName;
}
public String getCheckpointId() {
return checkpointId;
}
public Map<String, String> getDiskPathUuidMap() {
return diskPathUuidMap;
}
public boolean isStoppedVM() {
return stoppedVM;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,40 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class FinalizeImageTransferCommand extends Command {
private String transferId;
public FinalizeImageTransferCommand() {
}
public FinalizeImageTransferCommand(String transferId) {
this.transferId = transferId;
}
public String getTransferId() {
return transferId;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,44 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class StartBackupAnswer extends Answer {
private Long checkpointCreateTime;
public StartBackupAnswer() {
}
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details, Long checkpointCreateTime) {
super(cmd, success, details);
this.checkpointCreateTime = checkpointCreateTime;
}
public Long getCheckpointCreateTime() {
return checkpointCreateTime;
}
public void setCheckpointCreateTime(Long checkpointCreateTime) {
this.checkpointCreateTime = checkpointCreateTime;
}
}

View File

@ -0,0 +1,83 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Map;
import com.cloud.agent.api.Command;
public class StartBackupCommand extends Command {
private String vmName;
private String toCheckpointId;
private String fromCheckpointId;
private Long fromCheckpointCreateTime;
private String socket;
private Map<String, String> diskPathUuidMap;
private boolean stoppedVM;
public StartBackupCommand() {
}
public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId, Long fromCheckpointCreateTime,
String socket, Map<String, String> diskPathUuidMap, boolean stoppedVM) {
this.vmName = vmName;
this.toCheckpointId = toCheckpointId;
this.fromCheckpointId = fromCheckpointId;
this.fromCheckpointCreateTime = fromCheckpointCreateTime;
this.socket = socket;
this.diskPathUuidMap = diskPathUuidMap;
this.stoppedVM = stoppedVM;
}
public String getVmName() {
return vmName;
}
public String getToCheckpointId() {
return toCheckpointId;
}
public String getFromCheckpointId() {
return fromCheckpointId;
}
public Long getFromCheckpointCreateTime() {
return fromCheckpointCreateTime;
}
public String getSocket() {
return socket;
}
public Map<String, String> getDiskPathUuidMap() {
return diskPathUuidMap;
}
public boolean isIncremental() {
return fromCheckpointId != null && !fromCheckpointId.isEmpty();
}
public boolean isStoppedVM() {
return stoppedVM;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,56 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class StartNBDServerAnswer extends Answer {
private String imageTransferId;
private String transferUrl;
public StartNBDServerAnswer() {
}
public StartNBDServerAnswer(StartNBDServerCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public StartNBDServerAnswer(StartNBDServerCommand cmd, boolean success, String details,
String imageTransferId, String transferUrl) {
super(cmd, success, details);
this.imageTransferId = imageTransferId;
this.transferUrl = transferUrl;
}
public String getImageTransferId() {
return imageTransferId;
}
public void setImageTransferId(String imageTransferId) {
this.imageTransferId = imageTransferId;
}
public String getTransferUrl() {
return transferUrl;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
}

View File

@ -0,0 +1,70 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class StartNBDServerCommand extends Command {
private String transferId;
private String exportName;
private String volumePath;
private String socket;
private String direction;
private String fromCheckpointId;
public StartNBDServerCommand() {
}
protected StartNBDServerCommand(String transferId, String exportName, String volumePath, String socket, String direction, String fromCheckpointId) {
this.transferId = transferId;
this.socket = socket;
this.exportName = exportName;
this.volumePath = volumePath;
this.direction = direction;
this.fromCheckpointId = fromCheckpointId;
}
public String getExportName() {
return exportName;
}
public String getSocket() {
return socket;
}
public String getTransferId() {
return transferId;
}
@Override
public boolean executeInSequence() {
return true;
}
public String getVolumePath() {
return volumePath;
}
public String getDirection() {
return direction;
}
public String getFromCheckpointId() {
return fromCheckpointId;
}
}

View File

@ -0,0 +1,30 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class StopBackupAnswer extends Answer {
public StopBackupAnswer() {
}
public StopBackupAnswer(StopBackupCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
}

View File

@ -0,0 +1,52 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class StopBackupCommand extends Command {
private String vmName;
private Long vmId;
private Long backupId;
public StopBackupCommand() {
}
public StopBackupCommand(String vmName, Long vmId, Long backupId) {
this.vmName = vmName;
this.vmId = vmId;
this.backupId = backupId;
}
public String getVmName() {
return vmName;
}
public Long getVmId() {
return vmId;
}
public Long getBackupId() {
return backupId;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,46 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class StopNBDServerCommand extends Command {
private String transferId;
private String direction;
public StopNBDServerCommand() {
}
public StopNBDServerCommand(String transferId, String direction) {
this.transferId = transferId;
this.direction = direction;
}
public String getTransferId() {
return transferId;
}
public String getDirection() {
return direction;
}
@Override
public boolean executeInSequence() {
return true;
}
}

2
debian/control vendored
View File

@ -24,7 +24,7 @@ Description: CloudStack server library
Package: cloudstack-agent
Architecture: all
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat
Recommends: init-system-helpers
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent

View File

@ -59,6 +59,8 @@ import com.cloud.utils.fsm.NoTransitionException;
*/
public interface VirtualMachineManager extends Manager {
String KVM_BLANK_VM_TEMPLATE_NAME = "kvm-blank-vm-template";
ConfigKey<Boolean> ExecuteInSequence = new ConfigKey<>("Advanced", Boolean.class, "execute.in.sequence.hypervisor.commands", "false",
"If set to true, start, stop, reboot, copy and migrate commands will be serialized on the agent side. If set to false the commands are executed in parallel. Default value is false.", false);
@ -312,4 +314,8 @@ public interface VirtualMachineManager extends Manager {
ServiceOffering serviceOffering, Account systemAccount, DeploymentPlan plan)
throws InsufficientServerCapacityException;
boolean isBlankInstanceDefaultTemplate(VirtualMachineTemplate template);
boolean isBlankInstance(VirtualMachineTemplate template);
}

View File

@ -302,8 +302,8 @@ import com.cloud.vm.VirtualMachine.PowerState;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.snapshot.VMSnapshotManager;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
@ -575,7 +575,13 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
logger.debug("Allocating disks for {}", persistedVm);
allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot);
if (isBlankInstance(template)) {
logger.debug("Template is a dummy template for hypervisor {}, skipping volume allocation", hyperType);
return;
} else {
allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot);
}
// Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk.
CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume);
@ -6696,4 +6702,18 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
vmProfile), DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile));
}
}
@Override
public boolean isBlankInstanceDefaultTemplate(VirtualMachineTemplate template) {
return KVM_BLANK_VM_TEMPLATE_NAME.equals(template.getUniqueName());
}
@Override
public boolean isBlankInstance(VirtualMachineTemplate template) {
if (isBlankInstanceDefaultTemplate(template)) {
return true;
}
return Boolean.TRUE.equals(
MapUtils.getBoolean(CallContext.current().getContextParameters(), ApiConstants.BLANK_INSTANCE));
}
}

View File

@ -19,6 +19,7 @@
package org.apache.cloudstack.engine.orchestration;
import com.cloud.storage.Snapshot;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.template.VirtualMachineTemplate;
import java.net.URL;
@ -292,9 +293,11 @@ public class CloudOrchestrator implements OrchestrationService {
ServiceOfferingVO computeOffering = _serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId());
VMTemplateVO iso = _templateDao.findByIdIncludingRemoved(Long.valueOf(isoId));
DiskOfferingInfo rootDiskOfferingInfo = new DiskOfferingInfo();
if (diskOfferingId == null) {
if (diskOfferingId == null && !_itMgr.isBlankInstance(iso)) {
throw new InvalidParameterValueException("Installing from ISO requires a disk offering to be specified for the root disk.");
}
DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId);
@ -345,7 +348,7 @@ public class CloudOrchestrator implements OrchestrationService {
HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor);
_itMgr.allocate(vm.getInstanceName(), _templateDao.findByIdIncludingRemoved(new Long(isoId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, dataDiskDeviceIds,
_itMgr.allocate(vm.getInstanceName(), iso, computeOffering, rootDiskOfferingInfo, dataDiskOfferings, dataDiskDeviceIds,
networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null, volume, snapshot);
return vmEntity;

View File

@ -88,6 +88,7 @@
<entry key="VirtualMachineManagerImpl" value-ref="clusteredVirtualMachineManagerImpl" />
<entry key="VolumeApiServiceImpl" value-ref="volumeApiServiceImpl" />
<entry key="VMSnapshotManagerImpl" value-ref="vMSnapshotManagerImpl" />
<entry key="KVMBackupExportServiceImpl" value-ref="kvmBackupExportService" />
</map>
</property>
</bean>

View File

@ -23,6 +23,7 @@ import com.cloud.cpu.CPU;
import com.cloud.dc.ClusterVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
public interface ClusterDao extends GenericDao<ClusterVO, Long> {
@ -61,4 +62,6 @@ public interface ClusterDao extends GenericDao<ClusterVO, Long> {
List<String> listDistinctStorageAccessGroups(String name, String keyword);
List<Long> listEnabledClusterIdsByZoneHypervisorArch(Long zoneId, HypervisorType hypervisorType, CPU.CPUArch arch);
List<ClusterVO> listByHypervisorType(HypervisorType hypervisorType, Filter filter);
}

View File

@ -38,6 +38,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.org.Grouping;
import com.cloud.org.Managed;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.JoinBuilder;
@ -413,4 +414,11 @@ public class ClusterDaoImpl extends GenericDaoBase<ClusterVO, Long> implements C
}
return customSearch(sc, null);
}
@Override
public List<ClusterVO> listByHypervisorType(HypervisorType hypervisorType, Filter filter) {
SearchCriteria<ClusterVO> sc = ZoneHyTypeSearch.create();
sc.setParameters("hypervisorType", hypervisorType.toString());
return listBy(sc, filter);
}
}

View File

@ -24,6 +24,7 @@ import com.cloud.network.Network;
import com.cloud.network.Network.GuestType;
import com.cloud.network.Network.State;
import com.cloud.network.Networks.TrafficType;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.fsm.StateDao;
@ -96,8 +97,13 @@ public interface NetworkDao extends GenericDao<NetworkVO, Long>, StateDao<State,
boolean update(Long networkId, NetworkVO network, Map<String, String> serviceProviderMap);
List<NetworkVO> listByZoneAndTrafficType(long zoneId, TrafficType trafficType, Filter filter);
List<NetworkVO> listByZoneAndTrafficType(long zoneId, TrafficType trafficType);
List<NetworkVO> listByTrafficTypeAndOwners(final TrafficType trafficType, List<Long> accountIds,
List<Long> domainIds, Filter filter);
void setCheckForGc(long networkId);
int getNetworkCountByNetworkOffId(long networkOfferingId);

View File

@ -29,9 +29,9 @@ import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.TableGenerator;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.network.Network;
@ -63,6 +63,7 @@ import com.cloud.utils.db.SearchCriteria.Func;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.SequenceFetcher;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
@Component
@ -632,12 +633,41 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
}
@Override
public List<NetworkVO> listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType) {
public List<NetworkVO> listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType, Filter filter) {
final SearchCriteria<NetworkVO> sc = AllFieldsSearch.create();
sc.setParameters("datacenter", zoneId);
sc.setParameters("trafficType", trafficType);
return listBy(sc, null);
return listBy(sc, filter);
}
@Override
public List<NetworkVO> listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType) {
return listByZoneAndTrafficType(zoneId, trafficType, null);
}
@Override
public List<NetworkVO> listByTrafficTypeAndOwners(final TrafficType trafficType, List<Long> accountIds,
List<Long> domainIds, Filter filter) {
SearchBuilder<NetworkVO> sb = createSearchBuilder();
sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ);
boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds);
boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds);
if (accountIdsNotEmpty || domainIdsNotEmpty) {
sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN);
sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.done();
final SearchCriteria<NetworkVO> sc = sb.create();
sc.setParameters("trafficType", trafficType);
if (accountIdsNotEmpty) {
sc.setParameters("account", accountIds.toArray());
}
if (domainIdsNotEmpty) {
sc.setParameters("domain", domainIds.toArray());
}
return listBy(sc, filter);
}
@Override

View File

@ -106,4 +106,6 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
CPU.CPUArch arch, String urlPathSuffix);
VMTemplateVO findByAccountAndName(Long accountId, String templateName);
}

View File

@ -945,4 +945,12 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
}
return rows > 0;
}
@Override
public VMTemplateVO findByAccountAndName(Long accountId, String templateName) {
SearchCriteria<VMTemplateVO> sc = NameAccountIdSearch.create();
sc.setParameters("name", templateName);
sc.setParameters("accountId", accountId);
return findOneBy(sc);
}
}

View File

@ -46,6 +46,9 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
List<VolumeVO> findByInstanceAndType(long id, Volume.Type vType);
List<VolumeVO> findByInstanceAndNotStates(long id, Volume.State...states);
List<VolumeVO> findIncludingRemovedByInstanceAndType(long id, Volume.Type vType);
List<VolumeVO> findNonDestroyedVolumesByInstanceIdAndPoolId(long instanceId, long poolId);

View File

@ -208,6 +208,17 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
return listBy(sc);
}
@Override
public List<VolumeVO> findByInstanceAndNotStates(long id, Volume.State...states) {
SearchBuilder<VolumeVO> sb = createSearchBuilder();
sb.and("instanceId", sb.entity().getInstanceId(), Op.EQ);
sb.and("state", sb.entity().getState(), Op.NIN);
SearchCriteria<VolumeVO> sc = sb.create();
sc.setParameters("instanceId", id);
sc.setParameters("state", (Object[]) states);
return listBy(sc);
}
@Override
public List<VolumeVO> findIncludingRemovedByInstanceAndType(long id, Type vType) {
SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();

View File

@ -20,11 +20,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.tags.ResourceTagVO;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.response.ResourceTagResponse;
public interface ResourceTagDao extends GenericDao<ResourceTagVO, Long> {
@ -60,4 +62,13 @@ public interface ResourceTagDao extends GenericDao<ResourceTagVO, Long> {
void removeByResourceIdAndKey(long resourceId, ResourceObjectType resourceType, String key);
List<? extends ResourceTag> listByResourceUuid(String resourceUuid);
List<String> listByResourceTypeKeyPrefixAndOwners(ResourceObjectType resourceType, String key,
List<Long> accountIds, List<Long> domainIds,
Filter filter);
ResourceTagVO findByResourceTypeKeyPrefixAndValue(ResourceObjectType resourceType, String key, String value);
List<ResourceTagVO> listByResourceTypeIdAndKeyPrefix(ResourceObjectType resourceType, long resourceId, String key);
}

View File

@ -16,19 +16,22 @@
// under the License.
package com.cloud.tags.dao;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.tags.ResourceTagVO;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
@ -120,4 +123,62 @@ public class ResourceTagsDaoImpl extends GenericDaoBase<ResourceTagVO, Long> imp
sc.setParameters("resourceUuid", resourceUuid);
return listBy(sc);
}
@Override
public List<String> listByResourceTypeKeyPrefixAndOwners(ResourceObjectType resourceType, String key,
List<Long> accountIds, List<Long> domainIds,
Filter filter) {
GenericSearchBuilder<ResourceTagVO, String> sb = createSearchBuilder(String.class);
sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getValue());
sb.and("resourceType", sb.entity().getResourceType(), Op.EQ);
sb.and("key", sb.entity().getKey(), Op.LIKE);
boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds);
boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds);
if (accountIdsNotEmpty || domainIdsNotEmpty) {
sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN);
sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.done();
final SearchCriteria<String> sc = sb.create();
sc.setParameters("resourceType", resourceType);
sc.setParameters("key", key + "%");
if (accountIdsNotEmpty) {
sc.setParameters("account", accountIds.toArray());
}
if (domainIdsNotEmpty) {
sc.setParameters("domain", domainIds.toArray());
}
return customSearch(sc, filter);
}
@Override
public ResourceTagVO findByResourceTypeKeyPrefixAndValue(ResourceObjectType resourceType, String key,
String value) {
SearchBuilder<ResourceTagVO> sb = createSearchBuilder();
sb.and("resourceType", sb.entity().getResourceType(), Op.EQ);
sb.and("key", sb.entity().getKey(), Op.LIKE);
sb.and("value", sb.entity().getValue(), Op.EQ);
sb.done();
final SearchCriteria<ResourceTagVO> sc = sb.create();
sc.setParameters("resourceType", resourceType);
sc.setParameters("key", key + "%");
sc.setParameters("value", value);
return findOneBy(sc);
}
@Override
public List<ResourceTagVO> listByResourceTypeIdAndKeyPrefix(ResourceObjectType resourceType, long resourceId,
String key) {
SearchBuilder<ResourceTagVO> sb = createSearchBuilder();
sb.and("resourceType", sb.entity().getResourceType(), Op.EQ);
sb.and("resourceId", sb.entity().getResourceId(), Op.EQ);
sb.and("key", sb.entity().getKey(), Op.LIKE);
sb.done();
final SearchCriteria<ResourceTagVO> sc = sb.create();
sc.setParameters("resourceType", resourceType);
sc.setParameters("resourceId", resourceId);
sc.setParameters("key", key + "%");
return listBy(sc);
}
}

View File

@ -197,4 +197,6 @@ public interface VMInstanceDao extends GenericDao<VMInstanceVO, Long>, StateDao<
List<VMInstanceVO> listDeleteProtectedVmsByAccountId(long accountId);
List<VMInstanceVO> listDeleteProtectedVmsByDomainIds(Set<Long> domainIds);
List<Long> listIdsByHostIdForVolumeStats(long hostIds);
}

View File

@ -1336,4 +1336,20 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L);
return listBy(sc, filter);
}
@Override
public List<Long> listIdsByHostIdForVolumeStats(long hostId) {
GenericSearchBuilder<VMInstanceVO, Long> sb = createSearchBuilder(Long.class);
sb.selectFields(sb.entity().getId());
sb.and().op("host", sb.entity().getHostId(), SearchCriteria.Op.EQ);
sb.or().op("hostNull", sb.entity().getHostId(), Op.NULL);
sb.and("lastHost", sb.entity().getLastHostId(), SearchCriteria.Op.EQ);
sb.cp();
sb.cp();
sb.done();
SearchCriteria<Long> sc = sb.create();
sc.setParameters("host", hostId);
sc.setParameters("lastHost", hostId);
return customSearch(sc, null);
}
}

View File

@ -103,6 +103,18 @@ public class BackupVO implements Backup {
@Column(name = "backup_schedule_id")
private Long backupScheduleId;
@Column(name = "from_checkpoint_id")
private String fromCheckpointId;
@Column(name = "to_checkpoint_id")
private String toCheckpointId;
@Column(name = "checkpoint_create_time")
private Long checkpointCreateTime;
@Column(name = "host_id")
private Long hostId;
@Transient
Map<String, String> details;
@ -288,4 +300,40 @@ public class BackupVO implements Backup {
public void setBackupScheduleId(Long backupScheduleId) {
this.backupScheduleId = backupScheduleId;
}
@Override
public String getFromCheckpointId() {
return fromCheckpointId;
}
public void setFromCheckpointId(String fromCheckpointId) {
this.fromCheckpointId = fromCheckpointId;
}
@Override
public String getToCheckpointId() {
return toCheckpointId;
}
public void setToCheckpointId(String toCheckpointId) {
this.toCheckpointId = toCheckpointId;
}
@Override
public Long getCheckpointCreateTime() {
return checkpointCreateTime;
}
public void setCheckpointCreateTime(Long checkpointCreateTime) {
this.checkpointCreateTime = checkpointCreateTime;
}
@Override
public Long getHostId() {
return hostId;
}
public void setHostId(Long hostId) {
this.hostId = hostId;
}
}

View File

@ -0,0 +1,242 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "image_transfer")
public class ImageTransferVO implements ImageTransfer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "backup_id")
private Long backupId;
@Column(name = "volume_id")
private long volumeId;
@Column(name = "host_id")
private long hostId;
@Column(name = "socket")
private String socket;
@Column(name = "file")
private String file;
@Column(name = "transfer_url")
private String transferUrl;
@Enumerated(value = EnumType.STRING)
@Column(name = "phase")
private Phase phase;
@Enumerated(value = EnumType.STRING)
@Column(name = "direction")
private Direction direction;
@Enumerated(value = EnumType.STRING)
@Column(name = "backend")
private Backend backend;
@Column(name = "signed_ticket_id")
private String signedTicketId;
@Column(name = "account_id")
Long accountId;
@Column(name = "domain_id")
Long domainId;
@Column(name = "data_center_id")
Long dataCenterId;
@Column(name = "created")
@Temporal(value = TemporalType.TIMESTAMP)
private Date created;
@Column(name = "updated")
@Temporal(value = TemporalType.TIMESTAMP)
private Date updated;
@Column(name = "removed")
@Temporal(value = TemporalType.TIMESTAMP)
private Date removed;
public ImageTransferVO() {
}
private ImageTransferVO(String uuid, long volumeId, long hostId, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) {
this.uuid = uuid;
this.volumeId = volumeId;
this.hostId = hostId;
this.phase = phase;
this.direction = direction;
this.accountId = accountId;
this.domainId = domainId;
this.dataCenterId = dataCenterId;
this.created = new Date();
}
public ImageTransferVO(String uuid, Long backupId, long volumeId, long hostId, String socket, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) {
this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId);
this.backupId = backupId;
this.socket = socket;
this.backend = Backend.nbd;
}
public ImageTransferVO(String uuid, long volumeId, long hostId, String file, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) {
this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId);
this.file = file;
this.backend = Backend.file;
}
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public Long getBackupId() {
return backupId;
}
public void setBackupId(long backupId) {
this.backupId = backupId;
}
@Override
public long getVolumeId() {
return volumeId;
}
public void setVolumeId(long volumeId) {
this.volumeId = volumeId;
}
@Override
public long getHostId() {
return hostId;
}
public void setHostId(long hostId) {
this.hostId = hostId;
}
public void setSocket(String socket) {
this.socket = socket;
}
@Override
public String getTransferUrl() {
return transferUrl;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
@Override
public Phase getPhase() {
return phase;
}
public void setPhase(Phase phase) {
this.phase = phase;
this.updated = new Date();
}
@Override
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
@Override
public Backend getBackend() {
return backend;
}
@Override
public String getSignedTicketId() {
return signedTicketId;
}
public void setSignedTicketId(String signedTicketId) {
this.signedTicketId = signedTicketId;
}
@Override
public Class<?> getEntityType() {
return ImageTransfer.class;
}
@Override
public String getName() {
return null;
}
@Override
public long getDomainId() {
return domainId;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public long getDataCenterId() {
return dataCenterId;
}
public Date getCreated() {
return created;
}
public Date getUpdated() {
return updated;
}
}

View File

@ -0,0 +1,35 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup.dao;
import java.util.List;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.backup.ImageTransferVO;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
public interface ImageTransferDao extends GenericDao<ImageTransferVO, Long> {
List<ImageTransferVO> listByBackupId(Long backupId);
ImageTransferVO findByUuid(String uuid);
ImageTransferVO findByVolume(Long volumeId);
ImageTransferVO findUnfinishedByVolume(Long volumeId);
List<ImageTransferVO> listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction);
List<ImageTransferVO> listByOwners(List<Long> accountIds, List<Long> domainIds, Filter filter);
}

View File

@ -0,0 +1,129 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup.dao;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.backup.ImageTransferVO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@Component
public class ImageTransferDaoImpl extends GenericDaoBase<ImageTransferVO, Long> implements ImageTransferDao {
private SearchBuilder<ImageTransferVO> backupIdSearch;
private SearchBuilder<ImageTransferVO> uuidSearch;
private SearchBuilder<ImageTransferVO> volumeSearch;
private SearchBuilder<ImageTransferVO> volumeUnfinishedSearch;
private SearchBuilder<ImageTransferVO> phaseDirectionSearch;
public ImageTransferDaoImpl() {
}
@PostConstruct
protected void init() {
backupIdSearch = createSearchBuilder();
backupIdSearch.and("backupId", backupIdSearch.entity().getBackupId(), SearchCriteria.Op.EQ);
backupIdSearch.done();
uuidSearch = createSearchBuilder();
uuidSearch.and("uuid", uuidSearch.entity().getUuid(), SearchCriteria.Op.EQ);
uuidSearch.done();
volumeSearch = createSearchBuilder();
volumeSearch.and("volumeId", volumeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
volumeSearch.done();
volumeUnfinishedSearch = createSearchBuilder();
volumeUnfinishedSearch.and("volumeId", volumeUnfinishedSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
volumeUnfinishedSearch.and("phase", volumeUnfinishedSearch.entity().getPhase(), SearchCriteria.Op.NEQ);
volumeUnfinishedSearch.done();
phaseDirectionSearch = createSearchBuilder();
phaseDirectionSearch.and("phase", phaseDirectionSearch.entity().getPhase(), SearchCriteria.Op.EQ);
phaseDirectionSearch.and("direction", phaseDirectionSearch.entity().getDirection(), SearchCriteria.Op.EQ);
phaseDirectionSearch.done();
}
@Override
public List<ImageTransferVO> listByBackupId(Long backupId) {
SearchCriteria<ImageTransferVO> sc = backupIdSearch.create();
sc.setParameters("backupId", backupId);
return listBy(sc);
}
@Override
public ImageTransferVO findByUuid(String uuid) {
SearchCriteria<ImageTransferVO> sc = uuidSearch.create();
sc.setParameters("uuid", uuid);
return findOneBy(sc);
}
@Override
public ImageTransferVO findByVolume(Long volumeId) {
SearchCriteria<ImageTransferVO> sc = volumeSearch.create();
sc.setParameters("volumeId", volumeId);
return findOneBy(sc);
}
@Override
public ImageTransferVO findUnfinishedByVolume(Long volumeId) {
SearchCriteria<ImageTransferVO> sc = volumeUnfinishedSearch.create();
sc.setParameters("volumeId", volumeId);
sc.setParameters("phase", ImageTransferVO.Phase.finished.toString());
return findOneBy(sc);
}
@Override
public List<ImageTransferVO> listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction) {
SearchCriteria<ImageTransferVO> sc = phaseDirectionSearch.create();
sc.setParameters("phase", phase);
sc.setParameters("direction", direction);
return listBy(sc);
}
@Override
public List<ImageTransferVO> listByOwners(List<Long> accountIds, List<Long> domainIds, Filter filter) {
SearchBuilder<ImageTransferVO> sb = createSearchBuilder();
boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds);
boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds);
if (accountIdsNotEmpty || domainIdsNotEmpty) {
sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN);
sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.done();
final SearchCriteria<ImageTransferVO> sc = sb.create();
if (accountIdsNotEmpty) {
sc.setParameters("account", accountIds.toArray());
}
if (domainIdsNotEmpty) {
sc.setParameters("domain", domainIds.toArray());
}
return listBy(sc, filter);
}
}

View File

@ -29,4 +29,6 @@ public interface SharedFSDao extends GenericDao<SharedFSVO, Long>, StateDao<Shar
List<SharedFSVO> listSharedFSToBeDestroyed(Date date);
SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId);
SharedFSVO findByVm(long vmId);
}

View File

@ -114,4 +114,14 @@ public class SharedFSDaoImpl extends GenericDaoBase<SharedFSVO, Long> implements
sc.setParameters("domainId", domainId);
return findOneBy(sc);
}
@Override
public SharedFSVO findByVm(long vmId) {
SearchBuilder<SharedFSVO> sb = createSearchBuilder();
sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<SharedFSVO> sc = sb.create();
sc.setParameters("vmId", vmId);
return findOneBy(sc);
}
}

View File

@ -116,6 +116,7 @@
<bean id="loadBalancerVMMapDaoImpl" class="com.cloud.network.dao.LoadBalancerVMMapDaoImpl" />
<bean id="loadBalancerCertMapDaoImpl" class="com.cloud.network.dao.LoadBalancerCertMapDaoImpl" />
<bean id="managementServerHostDaoImpl" class="com.cloud.cluster.dao.ManagementServerHostDaoImpl" />
<bean id="managementServerHostDetailsDaoImpl" class="com.cloud.cluster.dao.ManagementServerHostDetailsDaoImpl" />
<bean id="managementServerHostPeerDaoImpl" class="com.cloud.cluster.dao.ManagementServerHostPeerDaoImpl" />
<bean id="managementServerHostPeerJoinDaoImpl" class="com.cloud.cluster.dao.ManagementServerHostPeerJoinDaoImpl" />
<bean id="managementServerStatusDaoImpl" class="com.cloud.cluster.dao.ManagementServerStatusDaoImpl" />
@ -271,6 +272,7 @@
<bean id="backupDaoImpl" class="org.apache.cloudstack.backup.dao.BackupDaoImpl" />
<bean id="backupDetailsDaoImpl" class="org.apache.cloudstack.backup.dao.BackupDetailsDaoImpl" />
<bean id="backupRepositoryDaoImpl" class="org.apache.cloudstack.backup.dao.BackupRepositoryDaoImpl" />
<bean id="imageTransferDaoImpl" class="org.apache.cloudstack.backup.dao.ImageTransferDaoImpl" />
<bean id="directDownloadCertificateDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateDaoImpl" />
<bean id="directDownloadCertificateHostMapDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMapDaoImpl" />
<bean id="routerHealthCheckResultsDaoImpl" class="com.cloud.network.dao.RouterHealthCheckResultDaoImpl" />

View File

@ -19,7 +19,6 @@
-- Schema upgrade from 4.21.0.0 to 4.22.0.0
--;
-- health check status as enum
CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('router_health_check', 'check_result', 'check_result', 'varchar(16) NOT NULL COMMENT "check executions result: SUCCESS, FAILURE, WARNING, UNKNOWN"');

View File

@ -131,3 +131,50 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
-- Add management_server_details table to allow ManagementServer scope configs
CREATE TABLE IF NOT EXISTS `management_server_details` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`management_server_id` bigint unsigned NOT NULL COMMENT 'management server the detail is related to',
`name` varchar(255) NOT NULL COMMENT 'name of the detail',
`value` varchar(255) NOT NULL,
`display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user',
PRIMARY KEY (`id`),
CONSTRAINT `fk_management_server_details__management_server_id` FOREIGN KEY `fk_management_server_details__management_server_id`(`management_server_id`) REFERENCES `mshost`(`id`) ON DELETE CASCADE,
KEY `i_management_server_details__name__value` (`name`(128),`value`(128))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Add checkpoint tracking fields to backups table for incremental backup support
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'from_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Previous active checkpoint id for incremental backups"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for the next incremental backup"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Checkpoint creation timestamp from libvirt"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'host_id', 'BIGINT UNSIGNED DEFAULT NULL COMMENT "Host where backup is running"');
-- Create image_transfer table for per-disk image transfers
CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
`uuid` varchar(40) NOT NULL COMMENT 'uuid',
`account_id` bigint unsigned NOT NULL COMMENT 'Account ID',
`domain_id` bigint unsigned NOT NULL COMMENT 'Domain ID',
`data_center_id` bigint unsigned NOT NULL COMMENT 'Data Center ID',
`backup_id` bigint unsigned COMMENT 'Backup ID',
`volume_id` bigint unsigned NOT NULL COMMENT 'Volume ID',
`host_id` bigint unsigned NOT NULL COMMENT 'Host ID',
`transfer_url` varchar(255) COMMENT 'ImageIO transfer URL',
`file` varchar(255) COMMENT 'File for the file backend',
`phase` varchar(20) NOT NULL COMMENT 'Transfer phase: initializing, transferring, finished, failed',
`socket` varchar(255) COMMENT 'Unix socket for nbd backend',
`direction` varchar(20) NOT NULL COMMENT 'Direction: upload, download',
`backend` varchar(20) NOT NULL COMMENT 'Backend: nbd, file',
`progress` int COMMENT 'Transfer progress percentage (0-100)',
`signed_ticket_id` varchar(255) COMMENT 'Signed ticket ID from ImageIO',
`created` datetime NOT NULL COMMENT 'date created',
`updated` datetime COMMENT 'date updated if not null',
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
CONSTRAINT `fk_image_transfer__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_image_transfer__volume_id` FOREIGN KEY (`volume_id`) REFERENCES `volumes`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
INDEX `i_image_transfer__backup_id`(`backup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -56,6 +56,7 @@ SELECT
`vm_instance`.`display_vm` AS `display_vm`,
`vm_instance`.`delete_protection` AS `delete_protection`,
`guest_os`.`uuid` AS `guest_os_uuid`,
`guest_os`.`display_name` AS `guest_os_display_name`,
`vm_instance`.`pod_id` AS `pod_id`,
`host_pod_ref`.`uuid` AS `pod_uuid`,
`vm_instance`.`private_ip_address` AS `private_ip_address`,

View File

@ -41,6 +41,7 @@ import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
@ -113,6 +114,69 @@ public class VolumeDaoImplTest {
verify(preparedStatementMock, times(1)).executeQuery();
}
@Test
public void findByInstanceAndNotState_queriesWithInstanceIdAndExcludedStates() {
SearchBuilder<VolumeVO> sb = Mockito.mock(SearchBuilder.class);
SearchCriteria<VolumeVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc);
Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb);
VolumeVO mockedVO = Mockito.mock(VolumeVO.class);
Mockito.when(sb.entity()).thenReturn(mockedVO);
volumeDao.findByInstanceAndNotStates(42L, Volume.State.Ready);
Mockito.verify(sc).setParameters("instanceId", 42L);
Mockito.verify(sc).setParameters("state", (Object[]) new Volume.State[]{Volume.State.Ready});
}
@Test
public void findByInstanceAndNotStates_withMultipleExcludedStates_passesAllStatesToCriteria() {
SearchBuilder<VolumeVO> sb = Mockito.mock(SearchBuilder.class);
SearchCriteria<VolumeVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc);
Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb);
VolumeVO mockedVO = Mockito.mock(VolumeVO.class);
Mockito.when(sb.entity()).thenReturn(mockedVO);
volumeDao.findByInstanceAndNotStates(7L, Volume.State.Destroy, Volume.State.Expunged);
Mockito.verify(sc).setParameters("instanceId", 7L);
Mockito.verify(sc).setParameters("state",
(Object[]) new Volume.State[]{Volume.State.Destroy, Volume.State.Expunged});
}
@Test
public void findByInstanceAndNotStates_returnsResultFromDao() {
SearchBuilder<VolumeVO> sb = Mockito.mock(SearchBuilder.class);
SearchCriteria<VolumeVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
VolumeVO vol = Mockito.mock(VolumeVO.class);
Mockito.doReturn(List.of(vol)).when(volumeDao).listBy(sc);
Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb);
Mockito.when(sb.entity()).thenReturn(Mockito.mock(VolumeVO.class));
List<VolumeVO> result = volumeDao.findByInstanceAndNotStates(1L, Volume.State.Ready);
Assert.assertEquals(1, result.size());
Assert.assertSame(vol, result.get(0));
}
@Test
public void findByInstanceAndNotStates_noMatchingVolumes_returnsEmptyList() {
SearchBuilder<VolumeVO> sb = Mockito.mock(SearchBuilder.class);
SearchCriteria<VolumeVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(sb.create()).thenReturn(sc);
Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc);
Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb);
Mockito.when(sb.entity()).thenReturn(Mockito.mock(VolumeVO.class));
List<VolumeVO> result = volumeDao.findByInstanceAndNotStates(99L, Volume.State.Ready);
Assert.assertTrue(result.isEmpty());
}
@Test
public void testSearchRemovedByVmsNoVms() {
Assert.assertTrue(CollectionUtils.isEmpty(volumeDao.searchRemovedByVms(
@ -141,5 +205,4 @@ public class VolumeDaoImplTest {
Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null),
Mockito.eq(false));
}
}

View File

@ -119,10 +119,10 @@ public class UserDataManagerImpl extends ManagerBase implements UserDataManager
byte[] decodedUserData = null;
// If GET, use 4K. If POST, support up to 1M.
if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
} else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
if (BaseCmd.HTTPMethod.POST.equals(httpmethod)) {
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
} else {
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
}
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.

View File

@ -48,6 +48,12 @@
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-engine-schema</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,87 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.cluster;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.ResourceDetail;
@Entity
@Table(name = "management_server_details")
public class ManagementServerHostDetailVO implements ResourceDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
long id;
@Column(name = "management_server_id")
long resourceId;
@Column(name = "name")
String name;
@Column(name = "value")
String value;
@Column(name = "display")
private boolean display = true;
public ManagementServerHostDetailVO(long poolId, String name, String value, boolean display) {
this.resourceId = poolId;
this.name = name;
this.value = value;
this.display = display;
}
public ManagementServerHostDetailVO() {
}
@Override
public long getId() {
return id;
}
@Override
public long getResourceId() {
return resourceId;
}
@Override
public String getName() {
return name;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
@Override
public boolean isDisplay() {
return display;
}
}

View File

@ -0,0 +1,26 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.cluster.dao;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
import com.cloud.cluster.ManagementServerHostDetailVO;
import com.cloud.utils.db.GenericDao;
public interface ManagementServerHostDetailsDao extends GenericDao<ManagementServerHostDetailVO, Long>, ResourceDetailsDao<ManagementServerHostDetailVO> {
}

View File

@ -0,0 +1,46 @@
// 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.cluster.dao;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import com.cloud.cluster.ManagementServerHostDetailVO;
public class ManagementServerHostDetailsDaoImpl extends ResourceDetailsDaoBase<ManagementServerHostDetailVO> implements ManagementServerHostDetailsDao, ScopedConfigStorage {
public ManagementServerHostDetailsDaoImpl() {
}
@Override
public ConfigKey.Scope getScope() {
return ConfigKey.Scope.ManagementServer;
}
@Override
public String getConfigValue(long id, String key) {
ManagementServerHostDetailVO vo = findDetail(id, key);
return vo == null ? null : vo.getValue();
}
@Override
public void addDetail(long resourceId, String key, String value, boolean display) {
super.addDetail(new ManagementServerHostDetailVO(resourceId, key, value, display));
}
}

View File

@ -68,4 +68,6 @@ public interface AsyncJobDao extends GenericDao<AsyncJobVO, Long> {
// Returns the number of pending jobs for the given Management server msids.
// NOTE: This is the msid and NOT the id
long countPendingNonPseudoJobs(Long... msIds);
List<Long> listPendingJobIdsForAccount(long accountId);
}

View File

@ -299,4 +299,14 @@ public class AsyncJobDaoImpl extends GenericDaoBase<AsyncJobVO, Long> implements
List<Long> results = customSearch(sc, null);
return results.get(0);
}
@Override
public List<Long> listPendingJobIdsForAccount(long accountId) {
GenericSearchBuilder<AsyncJobVO, Long> sb = createSearchBuilder(Long.class);
sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ);
sb.selectFields(sb.entity().getId());
SearchCriteria<Long> sc = sb.create();
sc.setParameters("accountId", accountId);
return customSearch(sc, null);
}
}

View File

@ -127,6 +127,8 @@ Requires: rng-tools
Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Requires: (selinux-tools if selinux-tools)
Requires: sysstat
Requires: python3-libnbd
Requires: socat
Provides: cloud-agent
Group: System Environment/Libraries
%description agent

View File

@ -0,0 +1,123 @@
//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
//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;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* Communicates with the cloudstack-image-server control socket via socat.
*
* Protocol: newline-delimited JSON over a Unix domain socket.
* Actions: register, unregister, status.
*/
public class ImageServerControlSocket {
private static final Logger LOGGER = LogManager.getLogger(ImageServerControlSocket.class);
static final String CONTROL_SOCKET_PATH = "/var/run/cloudstack/image-server.sock";
private static final Gson GSON = new GsonBuilder().create();
private ImageServerControlSocket() {
}
/**
* Send a JSON message to the image server control socket and return the
* parsed response, or null on communication failure.
*/
static JsonObject sendMessage(Map<String, Object> message) {
String json = GSON.toJson(message);
Script script = new Script("/bin/bash", LOGGER);
script.add("-c");
script.add(String.format("echo '%s' | socat -t5 - UNIX-CONNECT:%s",
json.replace("'", "'\\''"), CONTROL_SOCKET_PATH));
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
String result = script.execute(parser);
if (result != null) {
LOGGER.error("Control socket communication failed: {}", result);
return null;
}
String output = parser.getLines();
if (output == null || output.trim().isEmpty()) {
LOGGER.error("Empty response from control socket");
return null;
}
try {
return JsonParser.parseString(output.trim()).getAsJsonObject();
} catch (Exception e) {
LOGGER.error("Failed to parse control socket response: {}", output, e);
return null;
}
}
/**
* Register a transfer config with the image server.
* @return true if the server accepted the registration.
*/
public static boolean registerTransfer(String transferId, Map<String, Object> config) {
Map<String, Object> msg = new HashMap<>();
msg.put("action", "register");
msg.put("transfer_id", transferId);
msg.put("config", config);
JsonObject resp = sendMessage(msg);
if (resp == null) {
return false;
}
return "ok".equals(resp.has("status") ? resp.get("status").getAsString() : null);
}
/**
* Unregister a transfer from the image server.
* @return the number of remaining active transfers, or -1 on error.
*/
public static int unregisterTransfer(String transferId) {
Map<String, Object> msg = new HashMap<>();
msg.put("action", "unregister");
msg.put("transfer_id", transferId);
JsonObject resp = sendMessage(msg);
if (resp == null) {
return -1;
}
if (!"ok".equals(resp.has("status") ? resp.get("status").getAsString() : null)) {
return -1;
}
return resp.has("active_transfers") ? resp.get("active_transfers").getAsInt() : -1;
}
/**
* Check whether the image server control socket is responsive.
* @return true if the server responded with status "ok".
*/
public static boolean isReady() {
Map<String, Object> msg = new HashMap<>();
msg.put("action", "status");
JsonObject resp = sendMessage(msg);
if (resp == null) {
return false;
}
return "ok".equals(resp.has("status") ? resp.get("status").getAsString() : null);
}
}

View File

@ -386,6 +386,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public static final String CHECKPOINT_DELETE_COMMAND = "virsh checkpoint-delete --domain %s --checkpointname %s --metadata";
public static final int IMAGE_SERVER_DEFAULT_PORT = 54322;
public static final String IMAGE_SERVER_SYSTEMD_UNIT_NAME = "cloudstack-image-server";
protected int qcow2DeltaMergeTimeout;
private String modifyVlanPath;
@ -399,6 +402,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
private String heartBeatPath;
private String vmActivityCheckPath;
private String nasBackupPath;
private String imageServerPath;
private boolean imageServerTlsEnabled = false;
private String imageServerListenAddress;
private String securityGroupPath;
private String ovsPvlanDhcpHostPath;
private String ovsPvlanVmPath;
@ -813,6 +819,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return nasBackupPath;
}
public String getImageServerPath() {
return imageServerPath;
}
public boolean isImageServerTlsEnabled() {
return imageServerTlsEnabled;
}
public String getImageServerListenAddress() {
return imageServerListenAddress;
}
public String getOvsPvlanDhcpHostPath() {
return ovsPvlanDhcpHostPath;
}
@ -1057,6 +1075,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
cachePath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_CACHE_LOCATION);
imageServerTlsEnabled = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_TLS_ENABLED);
imageServerListenAddress = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_LISTEN_ADDRESS);
params.put("domr.scripts.dir", domrScriptsDir);
virtRouterResource = new VirtualRoutingResource(this);
@ -1120,6 +1141,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
throw new ConfigurationException("Unable to find nasbackup.sh");
}
String imageServerMain = Script.findScript(kvmScriptsDir, "imageserver/__main__.py");
if (imageServerMain == null) {
throw new ConfigurationException("Unable to find imageserver package");
}
imageServerPath = new File(imageServerMain).getParent();
createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh");
if (createTmplPath == null) {
throw new ConfigurationException("Unable to find the createtmplt.sh");
@ -5290,6 +5317,24 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
logger.debug("Removed all checkpoints of volume [{}] on VM [{}].", volumeUuid, vmName);
}
public Map<String, String> getDiskPathLabelMap(String vmName) {
try {
Connect conn = LibvirtConnection.getConnectionByVmName(vmName);
List<DiskDef> disks = getDisks(conn, vmName);
Map<String, String> diskPathLabelMap = new HashMap<>();
for (DiskDef disk : disks) {
if (disk.getDeviceType() != DeviceType.DISK) {
continue;
}
diskPathLabelMap.put(disk.getDiskPath(), disk.getDiskLabel());
}
return diskPathLabelMap;
} catch (LibvirtException e) {
logger.error("Failed to get disk path label map for VM [{}] due to: [{}].", vmName, e.getMessage(), e);
throw new CloudRuntimeException(e);
}
}
public boolean recreateCheckpointsOnVm(List<VolumeObjectTO> volumes, String vmName, Connect conn) {
logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes);
try {

View File

@ -0,0 +1,178 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.backup.CreateImageTransferAnswer;
import org.apache.cloudstack.backup.CreateImageTransferCommand;
import org.apache.cloudstack.backup.ImageTransfer;
import org.apache.cloudstack.storage.resource.IpTablesHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.StringUtils;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = CreateImageTransferCommand.class)
public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper<CreateImageTransferCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
private static final String IMAGE_SERVER_TLS_CERT_FILE = "/etc/cloudstack/agent/cloud.crt";
private static final String IMAGE_SERVER_TLS_KEY_FILE = "/etc/cloudstack/agent/cloud.key";
private void resetService(String unitName) {
Script resetScript = new Script("/bin/bash", logger);
resetScript.add("-c");
resetScript.add(String.format("systemctl reset-failed %s || true", unitName));
resetScript.execute();
}
private static String shellQuote(String value) {
return "'" + value.replace("'", "'\\''") + "'";
}
private boolean startImageServerIfNotRunning(int imageServerPort, String listenAddress, LibvirtComputingResource resource) {
final String imageServerPackageDir = resource.getImageServerPath();
final String imageServerParentDir = new File(imageServerPackageDir).getParent();
final String imageServerModuleName = new File(imageServerPackageDir).getName();
final boolean tlsEnabled = resource.isImageServerTlsEnabled();
String unitName = resource.IMAGE_SERVER_SYSTEMD_UNIT_NAME;
Script checkScript = new Script("/bin/bash", logger);
checkScript.add("-c");
checkScript.add(String.format("systemctl is-active --quiet %s", unitName));
String checkResult = checkScript.execute();
if (checkResult == null && ImageServerControlSocket.isReady()) {
return true;
}
resetService(unitName);
if (checkResult != null) {
StringBuilder systemdRunCmd = new StringBuilder(String.format(
"systemd-run --unit=%s --property=Restart=no --property=WorkingDirectory=%s /usr/bin/python3 -m %s --listen %s --port %d",
unitName, shellQuote(imageServerParentDir), imageServerModuleName, shellQuote(listenAddress), imageServerPort));
if (tlsEnabled) {
systemdRunCmd.append(" --tls-enabled");
systemdRunCmd.append(" --tls-cert-file ").append(IMAGE_SERVER_TLS_CERT_FILE);
systemdRunCmd.append(" --tls-key-file ").append(IMAGE_SERVER_TLS_KEY_FILE);
}
Script startScript = new Script("/bin/bash", logger);
startScript.add("-c");
startScript.add(systemdRunCmd.toString());
String startResult = startScript.execute();
if (startResult != null) {
logger.error(String.format("Failed to start the Image server: %s", startResult));
return false;
}
}
int maxWaitSeconds = 10;
int pollIntervalMs = 1000;
int maxAttempts = (maxWaitSeconds * 1000) / pollIntervalMs;
boolean serverReady = false;
for (int attempt = 0; attempt < maxAttempts; attempt++) {
if (ImageServerControlSocket.isReady()) {
serverReady = true;
logger.info(String.format("Image server control socket is ready (attempt %d)", attempt + 1));
break;
}
try {
Thread.sleep(pollIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
if (!serverReady) {
logger.error(String.format("Image server control socket not ready within %d seconds", maxWaitSeconds));
return false;
}
String rule = String.format("-p tcp -m state --state NEW -m tcp --dport %d -j ACCEPT", imageServerPort);
IpTablesHelper.addConditionally(IpTablesHelper.INPUT_CHAIN, true, rule,
String.format("Error in opening up image server port %d", imageServerPort));
return true;
}
public Answer execute(CreateImageTransferCommand cmd, LibvirtComputingResource resource) {
final String transferId = cmd.getTransferId();
ImageTransfer.Backend backend = cmd.getBackend();
if (StringUtils.isBlank(transferId)) {
return new CreateImageTransferAnswer(cmd, false, "transferId is empty.");
}
final Map<String, Object> payload = new HashMap<>();
payload.put("backend", backend.toString());
payload.put("idle_timeout_seconds", cmd.getIdleTimeoutSeconds());
if (backend == ImageTransfer.Backend.file) {
final String filePath = cmd.getFile();
if (StringUtils.isBlank(filePath)) {
return new CreateImageTransferAnswer(cmd, false, "file path is empty for file backend.");
}
payload.put("file", filePath);
} else {
String socket = cmd.getSocket();
final String exportName = cmd.getExportName();
if (StringUtils.isBlank(socket)) {
return new CreateImageTransferAnswer(cmd, false, "Empty socket.");
}
if (StringUtils.isBlank(exportName)) {
return new CreateImageTransferAnswer(cmd, false, "exportName is empty.");
}
payload.put("socket", "/tmp/imagetransfer/" + socket + ".sock");
payload.put("export", exportName);
String checkpointId = cmd.getCheckpointId();
if (checkpointId != null) {
payload.put("export_bitmap", cmd.getCheckpointId());
}
}
final int imageServerPort = LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT;
String listenAddress = resource.getImageServerListenAddress();
if (StringUtils.isBlank(listenAddress)) {
listenAddress = resource.getPrivateIp();
}
if (!startImageServerIfNotRunning(imageServerPort, listenAddress, resource)) {
return new CreateImageTransferAnswer(cmd, false, "Failed to start image server.");
}
if (!ImageServerControlSocket.registerTransfer(transferId, payload)) {
return new CreateImageTransferAnswer(cmd, false, "Failed to register transfer with image server.");
}
final String transferScheme = resource.isImageServerTlsEnabled() ? "https" : "http";
final String transferUrl = String.format("%s://%s:%d/images/%s", transferScheme, listenAddress, imageServerPort, transferId);
return new CreateImageTransferAnswer(cmd, true, "Image transfer prepared on KVM host.", transferId, transferUrl);
}
}

View File

@ -0,0 +1,79 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.Map;
import org.apache.cloudstack.backup.DeleteVmCheckpointCommand;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = DeleteVmCheckpointCommand.class)
public class LibvirtDeleteVmCheckpointCommandWrapper extends CommandWrapper<DeleteVmCheckpointCommand, Answer, LibvirtComputingResource> {
@Override
public Answer execute(DeleteVmCheckpointCommand cmd, LibvirtComputingResource resource) {
if (cmd.isStoppedVM()) {
return deleteBitmapsOnDisks(cmd);
}
return deleteDomainCheckpoint(cmd);
}
private Answer deleteDomainCheckpoint(DeleteVmCheckpointCommand cmd) {
String vmName = cmd.getVmName();
String checkpointId = cmd.getCheckpointId();
String virshCmd = String.format("virsh checkpoint-delete %s %s", vmName, checkpointId);
Script script = new Script("/bin/bash");
script.add("-c");
script.add(virshCmd);
String result = script.execute();
if (result != null) {
return new Answer(cmd, false, "Failed to delete checkpoint: " + result);
}
return new Answer(cmd, true, "Checkpoint deleted");
}
/**
* Stopped VM: persistent bitmaps on disk images ({@code qemu-img bitmap --remove}), matching {@link LibvirtStartBackupCommandWrapper} bitmap --add.
*/
private Answer deleteBitmapsOnDisks(DeleteVmCheckpointCommand cmd) {
String checkpointId = cmd.getCheckpointId();
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
if (diskPathUuidMap == null || diskPathUuidMap.isEmpty()) {
return new Answer(cmd, false, "No disks provided for bitmap removal");
}
for (Map.Entry<String, String> entry : diskPathUuidMap.entrySet()) {
String diskPath = entry.getKey();
Script script = new Script("qemu-img");
script.add("bitmap");
script.add("--remove");
script.add(diskPath);
script.add(checkpointId);
String result = script.execute();
if (result != null) {
return new Answer(cmd, false,
"Failed to remove bitmap " + checkpointId + " from disk " + diskPath + ": " + result);
}
}
return new Answer(cmd, true, "Checkpoint bitmap removed from disks");
}
}

View File

@ -0,0 +1,101 @@
//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
//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.cloudstack.backup.FinalizeImageTransferCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.StringUtils;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = FinalizeImageTransferCommand.class)
public class LibvirtFinalizeImageTransferCommandWrapper extends CommandWrapper<FinalizeImageTransferCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
private void resetService(String unitName) {
Script resetScript = new Script("/bin/bash", logger);
resetScript.add("-c");
resetScript.add(String.format("systemctl reset-failed %s || true", unitName));
resetScript.execute();
}
private boolean stopImageServer(int imageServerPort, LibvirtComputingResource resource) {
String unitName = resource.IMAGE_SERVER_SYSTEMD_UNIT_NAME;
Script checkScript = new Script("/bin/bash", logger);
checkScript.add("-c");
checkScript.add(String.format("systemctl is-active --quiet %s", unitName));
String checkResult = checkScript.execute();
if (checkResult != null) {
logger.info("Image server not running, resetting failed state");
resetService(unitName);
removeFirewallRule(imageServerPort);
return true;
}
Script stopScript = new Script("/bin/bash", logger);
stopScript.add("-c");
stopScript.add(String.format("systemctl stop %s", unitName));
stopScript.execute();
resetService(unitName);
logger.info("Image server {} stopped", unitName);
removeFirewallRule(imageServerPort);
return true;
}
private void removeFirewallRule(int port) {
String rule = String.format("-p tcp -m state --state NEW -m tcp --dport %d -j ACCEPT", port);
Script removeScript = new Script("/bin/bash", logger);
removeScript.add("-c");
removeScript.add(String.format("iptables -D INPUT %s || true", rule));
String result = removeScript.execute();
if (result != null && !result.isEmpty() && !result.contains("iptables: Bad rule")) {
logger.debug("Firewall rule removal result for port {}: {}", port, result);
} else {
logger.info("Firewall rule removed for port {} (or did not exist)", port);
}
}
public Answer execute(FinalizeImageTransferCommand cmd, LibvirtComputingResource resource) {
final String transferId = cmd.getTransferId();
final int imageServerPort = LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT;
if (StringUtils.isBlank(transferId)) {
return new Answer(cmd, false, "transferId is empty.");
}
int activeTransfers = ImageServerControlSocket.unregisterTransfer(transferId);
if (activeTransfers < 0) {
logger.warn("Could not reach image server to unregister transfer {}; assuming server is down", transferId);
stopImageServer(imageServerPort, resource);
return new Answer(cmd, true, "Image transfer finalized (server unreachable, forced stop).");
}
if (activeTransfers == 0) {
stopImageServer(imageServerPort, resource);
}
return new Answer(cmd, true, "Image transfer finalized.");
}
}

View File

@ -0,0 +1,281 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.io.File;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.backup.StartBackupAnswer;
import org.apache.cloudstack.backup.StartBackupCommand;
import org.apache.cloudstack.utils.qemu.QemuCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.json.JSONArray;
import org.json.JSONObject;
import org.libvirt.Domain;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.StringUtils;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StartBackupCommand.class)
public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackupCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(StartBackupCommand cmd, LibvirtComputingResource resource) {
if (cmd.isStoppedVM()) {
return handleStoppedVmBackup(cmd, cmd.getToCheckpointId());
}
return handleRunningVmBackup(cmd, resource);
}
public Answer handleRunningVmBackup(StartBackupCommand cmd, LibvirtComputingResource resource) {
String vmName = cmd.getVmName();
String toCheckpointId = cmd.getToCheckpointId();
String fromCheckpointId = cmd.getFromCheckpointId();
Long fromCheckpointCreateTime = cmd.getFromCheckpointCreateTime();
String socket = cmd.getSocket();
try {
if (StringUtils.isNotBlank(fromCheckpointId)) {
Answer redefineAnswer = ensureFromCheckpointExists(cmd, fromCheckpointId, fromCheckpointCreateTime);
if (redefineAnswer != null) {
return redefineAnswer;
}
}
File dir = new File("/tmp/imagetransfer");
if (!dir.exists()) {
dir.mkdirs();
}
// Create backup XML
String backupXml = createBackupXml(cmd, fromCheckpointId, socket, resource);
String checkpointXml = createCheckpointXml(toCheckpointId);
// Write XMLs to temp files
File backupXmlFile = File.createTempFile("backup-", ".xml");
File checkpointXmlFile = File.createTempFile("checkpoint-", ".xml");
try (FileWriter writer = new FileWriter(backupXmlFile)) {
writer.write(backupXml);
}
try (FileWriter writer = new FileWriter(checkpointXmlFile)) {
writer.write(checkpointXml);
}
// Execute virsh backup-begin
String backupCmd = String.format("virsh backup-begin %s %s --checkpointxml %s",
vmName, backupXmlFile.getAbsolutePath(), checkpointXmlFile.getAbsolutePath());
Script script = new Script("/bin/bash");
script.add("-c");
script.add(backupCmd);
String result = script.execute();
backupXmlFile.delete();
checkpointXmlFile.delete();
if (result != null) {
return new StartBackupAnswer(cmd, false, "Backup begin failed: " + result);
}
long checkpointCreateTime = getCheckpointCreateTime();
return new StartBackupAnswer(cmd, true, "Backup started successfully", checkpointCreateTime);
} catch (Exception e) {
return new StartBackupAnswer(cmd, false, "Error starting backup: " + e.getMessage());
}
}
private Answer ensureFromCheckpointExists(StartBackupCommand cmd, String fromCheckpointId, Long fromCheckpointCreateTime) {
String vmName = cmd.getVmName();
Script dumpScript = new Script("/bin/bash");
dumpScript.add("-c");
dumpScript.add(String.format("virsh checkpoint-dumpxml --domain %s --checkpointname %s --no-domain",
vmName, fromCheckpointId));
if (dumpScript.execute() == null) {
return null;
}
if (fromCheckpointCreateTime == null) {
return new StartBackupAnswer(cmd, false, "From checkpoint create time is null for checkpoint " + fromCheckpointId);
}
String redefineXml = createCheckpointXmlForRedefine(fromCheckpointId, fromCheckpointCreateTime);
File redefineFile;
try {
redefineFile = File.createTempFile("checkpoint-redefine-", ".xml");
} catch (Exception e) {
return new StartBackupAnswer(cmd, false, "Failed to create temp file for checkpoint redefine: " + e.getMessage());
}
try (FileWriter writer = new FileWriter(redefineFile)) {
writer.write(redefineXml);
} catch (Exception e) {
redefineFile.delete();
return new StartBackupAnswer(cmd, false, "Failed to write checkpoint redefine XML: " + e.getMessage());
}
String createCmd = String.format(LibvirtComputingResource.CHECKPOINT_CREATE_COMMAND, vmName, redefineFile.getAbsolutePath());
Script createScript = new Script("/bin/bash");
createScript.add("-c");
createScript.add(createCmd);
String result = createScript.execute();
redefineFile.delete();
if (result != null) {
return new StartBackupAnswer(cmd, false, "Failed to redefine from-checkpoint " + fromCheckpointId + ": " + result);
}
return null;
}
private String createCheckpointXmlForRedefine(String checkpointName, Long createTime) {
StringBuilder xml = new StringBuilder();
xml.append("<domaincheckpoint>\n");
xml.append(" <name>").append(checkpointName).append("</name>\n");
xml.append(" <creationTime>").append(createTime).append("</creationTime>\n");
xml.append("</domaincheckpoint>");
return xml.toString();
}
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, String socket, LibvirtComputingResource resource) throws LibvirtException {
StringBuilder xml = new StringBuilder();
xml.append("<domainbackup mode=\"pull\">\n");
xml.append(String.format(" <server transport=\"unix\" socket=\"/tmp/imagetransfer/%s.sock\"/>\n", socket));
xml.append(" <disks>\n");
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
Map<String, String> diskPathLabelMap = resource.getDiskPathLabelMap(cmd.getVmName());
Map<String, Boolean> diskPathHasFromCheckpointMap = new HashMap<>();
if (StringUtils.isNotBlank(fromCheckpointId)) {
Domain vm = null;
try {
vm = resource.getDomain(resource.getLibvirtUtilitiesHelper().getConnection(), cmd.getVmName());
if (vm != null) {
diskPathHasFromCheckpointMap = getVmDiskPathHasFromCheckpointMap(vm, fromCheckpointId);
} else {
logger.warn("Failed to get domain for VM [{}] while evaluating export bitmap [{}]. Falling back to full Backup",
cmd.getVmName(), fromCheckpointId);
}
} finally {
if (vm != null) {
vm.free();
}
}
}
for (Map.Entry<String, String> entry : diskPathLabelMap.entrySet()) {
String diskPath = entry.getKey();
if (!diskPathUuidMap.containsKey(diskPath)) {
continue;
}
String diskName = entry.getValue();
String export = diskPathUuidMap.get(diskPath);
String scratchFile = "/var/tmp/scratch-" + export + ".qcow2";
xml.append(" <disk name=\"").append(diskName).append("\" type=\"file\" exportname=\"").append(export);
if (StringUtils.isNotBlank(fromCheckpointId) && Boolean.TRUE.equals(diskPathHasFromCheckpointMap.get(diskPath))) {
xml.append("\" backupmode=\"incremental\"")
.append(" incremental=\"").append(fromCheckpointId)
.append("\" exportbitmap=\"").append(fromCheckpointId);
}
xml.append("\">\n");
xml.append(" <scratch file=\"").append(scratchFile).append("\"/>\n");
xml.append(" </disk>\n");
}
xml.append(" </disks>\n");
xml.append("</domainbackup>");
return xml.toString();
}
private String createCheckpointXml(String checkpointId) {
return "<domaincheckpoint>\n" +
" <name>" + checkpointId + "</name>\n" +
"</domaincheckpoint>";
}
private Answer handleStoppedVmBackup(StartBackupCommand cmd, String toCheckpointId) {
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
for (Map.Entry<String, String> entry : diskPathUuidMap.entrySet()) {
String diskPath = entry.getKey();
Script script = new Script("sudo");
script.add("qemu-img");
script.add("bitmap");
script.add("--add");
script.add(diskPath);
script.add(toCheckpointId);
String result = script.execute();
if (result != null) {
return new StartBackupAnswer(cmd, false,
"Failed to add bitmap " + toCheckpointId + " to disk " + diskPath + ": " + result);
}
}
long checkpointCreateTime = getCheckpointCreateTime();
return new StartBackupAnswer(cmd, true, "Stopped VM backup: checkpoint bitmap added successfully",
checkpointCreateTime);
}
private long getCheckpointCreateTime() {
return System.currentTimeMillis() / 1000;
}
private Map<String, Boolean> getVmDiskPathHasFromCheckpointMap(Domain vm, String fromCheckpointId) throws LibvirtException {
Map<String, Boolean> diskPathHasFromCheckpointMap = new HashMap<>();
String queryBlock = vm.qemuMonitorCommand(QemuCommand.buildQemuCommand("query-block", null), 0);
JSONObject response = new JSONObject(queryBlock);
JSONArray blocks = response.optJSONArray("return");
if (blocks == null) {
logger.warn("Couldn't get bitmap information for the VM [{}]. Falling back to full Backup", vm.getName());
return diskPathHasFromCheckpointMap;
}
for (int i = 0; i < blocks.length(); i++) {
JSONObject block = blocks.getJSONObject(i);
JSONObject inserted = block.optJSONObject("inserted");
if (inserted == null) {
continue;
}
String file = inserted.optString("file");
if (StringUtils.isBlank(file)) {
continue;
}
JSONArray dirtyBitmaps = inserted.optJSONArray("dirty-bitmaps");
boolean hasFromCheckpointBitmap = false;
if (dirtyBitmaps != null) {
for (int j = 0; j < dirtyBitmaps.length(); j++) {
JSONObject dirtyBitmap = dirtyBitmaps.optJSONObject(j);
if (dirtyBitmap == null) {
continue;
}
String bitmapName = dirtyBitmap.optString("name");
if (fromCheckpointId.equals(bitmapName)) {
hasFromCheckpointBitmap = true;
break;
}
}
}
diskPathHasFromCheckpointMap.put(file, hasFromCheckpointBitmap);
}
return diskPathHasFromCheckpointMap;
}
}

View File

@ -0,0 +1,171 @@
//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
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.io.File;
import org.apache.cloudstack.backup.StartNBDServerAnswer;
import org.apache.cloudstack.backup.StartNBDServerCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.json.JSONArray;
import org.json.JSONObject;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.StringUtils;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StartNBDServerCommand.class)
public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper<StartNBDServerCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(StartNBDServerCommand cmd, LibvirtComputingResource resource) {
String volumePath = cmd.getVolumePath();
String socket = cmd.getSocket();
String exportName = cmd.getExportName();
String transferId = cmd.getTransferId();
if (StringUtils.isBlank(volumePath)) {
return new StartNBDServerAnswer(cmd, false, "Volume path is required for the nbd server");
}
if (StringUtils.isBlank(exportName)) {
return new StartNBDServerAnswer(cmd, false, "Export name is required for the nbd server");
}
if (StringUtils.isBlank(socket)) {
return new StartNBDServerAnswer(cmd, false, "Socket is required for the nbd server");
}
String unitName = "qemu-nbd-" + transferId.hashCode();
Script checkScript = new Script("/bin/bash", logger);
checkScript.add("-c");
checkScript.add(String.format("systemctl is-active --quiet %s", unitName));
String checkResult = checkScript.execute();
if (checkResult == null) {
return new StartNBDServerAnswer(cmd, false, "A qemu-nbd service is already running on the port.");
}
File dir = new File("/tmp/imagetransfer");
if (!dir.exists()) {
dir.mkdirs();
}
String socketName = "/tmp/imagetransfer/" + socket + ".sock";
String bitmapArg = "";
if (StringUtils.isNotBlank(cmd.getFromCheckpointId())
&& isBitmapPresentOnDisk(volumePath, cmd.getFromCheckpointId())) {
bitmapArg = "-B " + cmd.getFromCheckpointId();
}
// --persistent: Don't stop the service when the last client disconnects.
// --shared=NUM: Allow up to NUM clients to share the device (default 1), 0 for unlimited. Number of parallel connections is managed by the image server.
String systemdRunCmd = String.format(
"systemd-run --unit=%s --property=Restart=no qemu-nbd --export-name %s --socket %s --persistent --shared=0 %s %s %s",
unitName,
exportName,
socketName,
bitmapArg,
cmd.getDirection().equals("download") ? "--read-only" : "",
volumePath
);
Script startScript = new Script("/bin/bash", logger);
startScript.add("-c");
startScript.add(systemdRunCmd);
String startResult = startScript.execute();
if (startResult != null) {
logger.error(String.format("Failed to start qemu-nbd service: %s", startResult));
return new StartNBDServerAnswer(cmd, false, "Failed to start qemu-nbd service: " + startResult);
}
// Wait with timeout until the service is up
int maxWaitSeconds = 10;
int pollIntervalMs = 1000;
int maxAttempts = (maxWaitSeconds * 1000) / pollIntervalMs;
boolean serviceActive = false;
for (int attempt = 0; attempt < maxAttempts; attempt++) {
Script verifyScript = new Script("/bin/bash", logger);
verifyScript.add("-c");
verifyScript.add(String.format("systemctl is-active --quiet %s", unitName));
String verifyResult = verifyScript.execute();
if (verifyResult == null) {
serviceActive = true;
logger.info(String.format("qemu-nbd service %s is now active (attempt %d)", unitName, attempt + 1));
break;
}
try {
Thread.sleep(pollIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new StartNBDServerAnswer(cmd, false, "Interrupted while waiting for qemu-nbd service to start");
}
}
if (!serviceActive) {
logger.error(String.format("qemu-nbd service %s failed to become active within %d seconds", unitName, maxWaitSeconds));
return new StartNBDServerAnswer(cmd, false,
String.format("qemu-nbd service failed to start within %d seconds", maxWaitSeconds));
}
String transferUrl = String.format("nbd+unix:///%s", cmd.getSocket());
return new StartNBDServerAnswer(cmd, true, "qemu-nbd service started for upload",
transferId, transferUrl);
}
private boolean isBitmapPresentOnDisk(String volumePath, String fromCheckpointId) {
String qemuImgInfo = Script.runBashScriptIgnoreExitValue(
String.format("qemu-img info --output=json %s", volumePath), 0);
if (StringUtils.isBlank(qemuImgInfo)) {
logger.warn("Unable to read qemu-img info output for disk path [{}].", volumePath);
return false;
}
try {
JSONObject info = new JSONObject(qemuImgInfo);
JSONObject formatSpecific = info.optJSONObject("format-specific");
if (formatSpecific == null) {
return false;
}
JSONObject formatData = formatSpecific.optJSONObject("data");
if (formatData == null) {
return false;
}
JSONArray bitmaps = formatData.optJSONArray("bitmaps");
if (bitmaps == null) {
return false;
}
for (int i = 0; i < bitmaps.length(); i++) {
JSONObject bitmap = bitmaps.optJSONObject(i);
if (bitmap == null) {
continue;
}
if (fromCheckpointId.equals(bitmap.optString("name"))) {
return true;
}
}
} catch (Exception e) {
logger.warn("Failed to parse qemu-img info output for disk path [{}].", volumePath, e);
}
return false;
}
}

View File

@ -0,0 +1,69 @@
//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
//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.cloudstack.backup.StopBackupAnswer;
import org.apache.cloudstack.backup.StopBackupCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.libvirt.Connect;
import org.libvirt.Domain;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StopBackupCommand.class)
public class LibvirtStopBackupCommandWrapper extends CommandWrapper<StopBackupCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(StopBackupCommand cmd, LibvirtComputingResource resource) {
String vmName = cmd.getVmName();
try {
Connect conn = LibvirtConnection.getConnection();
Domain dm = conn.domainLookupByName(vmName);
if (dm == null) {
return new StopBackupAnswer(cmd, false, "Domain not found: " + vmName);
}
// Execute virsh domjobabort
String abortCmd = String.format("virsh domjobabort %s", vmName);
Script script = new Script("/bin/bash");
script.add("-c");
script.add(abortCmd);
String result = script.execute();
if (result != null && !result.isEmpty()) {
// Job abort may fail if no job is running, which is acceptable
logger.debug("domjobabort result: " + result);
}
return new StopBackupAnswer(cmd, true, "Backup stopped successfully");
} catch (Exception e) {
return new StopBackupAnswer(cmd, false, "Error stopping backup: " + e.getMessage());
}
}
}

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
//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.cloudstack.backup.StopNBDServerCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StopNBDServerCommand.class)
public class LibvirtStopNBDServerCommandWrapper extends CommandWrapper<StopNBDServerCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
private void resetService(String unitName) {
Script resetScript = new Script("/bin/bash", logger);
resetScript.add("-c");
resetScript.add(String.format("systemctl reset-failed %s || true", unitName));
resetScript.execute();
}
@Override
public Answer execute(StopNBDServerCommand cmd, LibvirtComputingResource resource) {
try {
String unitName = "qemu-nbd-" + cmd.getTransferId().hashCode();
// Check if the service is running
Script checkScript = new Script("/bin/bash", logger);
checkScript.add("-c");
checkScript.add(String.format("systemctl is-active --quiet %s", unitName));
String checkResult = checkScript.execute();
if (checkResult != null) {
// Service is not running, but still reset-failed to clear any stale state
logger.info(String.format("qemu-nbd service %s is not running, resetting failed state", unitName));
resetService(unitName);
return new Answer(cmd, true, "Image transfer finalized");
}
// Stop the systemd service
Script stopScript = new Script("/bin/bash", logger);
stopScript.add("-c");
stopScript.add(String.format("systemctl stop %s", unitName));
stopScript.execute();
resetService(unitName);
return new Answer(cmd, true, "Image transfer finalized");
} catch (Exception e) {
logger.error("Error finalizing image transfer for upload", e);
return new Answer(cmd, false, "Error finalizing image transfer: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,121 @@
// 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.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import org.apache.cloudstack.backup.CreateImageTransferAnswer;
import org.apache.cloudstack.backup.CreateImageTransferCommand;
import org.apache.cloudstack.backup.ImageTransfer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtCreateImageTransferCommandWrapperTest {
private LibvirtCreateImageTransferCommandWrapper wrapper;
private CreateImageTransferCommand command;
private LibvirtComputingResource resource;
@Before
public void setUp() {
wrapper = new LibvirtCreateImageTransferCommandWrapper();
command = Mockito.mock(CreateImageTransferCommand.class);
resource = Mockito.mock(LibvirtComputingResource.class);
}
@Test
public void testExecuteBlankTransferIdReturnsFailure() {
Mockito.when(command.getTransferId()).thenReturn("");
Mockito.when(command.getBackend()).thenReturn(ImageTransfer.Backend.nbd);
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("transferId is empty.", answer.getDetails());
}
@Test
public void testExecuteNbdBackendSuccessReturnsUrl() {
Mockito.when(command.getTransferId()).thenReturn("tr-1");
Mockito.when(command.getBackend()).thenReturn(ImageTransfer.Backend.nbd);
Mockito.when(command.getIdleTimeoutSeconds()).thenReturn(120);
Mockito.when(command.getSocket()).thenReturn("sock-1");
Mockito.when(command.getExportName()).thenReturn("vol-1");
Mockito.when(command.getCheckpointId()).thenReturn("cp-1");
Mockito.when(resource.getImageServerPath()).thenReturn("/opt/cloudstack/image/server.py");
Mockito.when(resource.getImageServerListenAddress()).thenReturn("");
Mockito.when(resource.getPrivateIp()).thenReturn("10.0.0.10");
Mockito.when(resource.isImageServerTlsEnabled()).thenReturn(false);
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn(null));
MockedStatic<ImageServerControlSocket> imageServerControlMock = Mockito.mockStatic(ImageServerControlSocket.class)) {
imageServerControlMock.when(ImageServerControlSocket::isReady).thenReturn(true);
imageServerControlMock.when(() -> ImageServerControlSocket.registerTransfer(eq("tr-1"), anyMap())).thenReturn(true);
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertTrue(answer instanceof CreateImageTransferAnswer);
CreateImageTransferAnswer transferAnswer = (CreateImageTransferAnswer) answer;
Assert.assertEquals("tr-1", transferAnswer.getImageTransferId());
Assert.assertEquals(
"http://10.0.0.10:" + LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT + "/images/tr-1",
transferAnswer.getTransferUrl());
}
}
@Test
public void testExecuteRegisterTransferFailureReturnsFailure() {
Mockito.when(command.getTransferId()).thenReturn("tr-2");
Mockito.when(command.getBackend()).thenReturn(ImageTransfer.Backend.nbd);
Mockito.when(command.getIdleTimeoutSeconds()).thenReturn(120);
Mockito.when(command.getSocket()).thenReturn("sock-2");
Mockito.when(command.getExportName()).thenReturn("vol-2");
Mockito.when(command.getCheckpointId()).thenReturn(null);
Mockito.when(resource.getImageServerPath()).thenReturn("/opt/cloudstack/image/server.py");
Mockito.when(resource.getImageServerListenAddress()).thenReturn("192.168.10.10");
Mockito.when(resource.isImageServerTlsEnabled()).thenReturn(true);
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn(null));
MockedStatic<ImageServerControlSocket> imageServerControlMock = Mockito.mockStatic(ImageServerControlSocket.class)) {
imageServerControlMock.when(ImageServerControlSocket::isReady).thenReturn(true);
imageServerControlMock.when(() -> ImageServerControlSocket.registerTransfer(eq("tr-2"), anyMap())).thenReturn(false);
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("Failed to register transfer with image server.", answer.getDetails());
}
}
}

View File

@ -0,0 +1,88 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.Collections;
import java.util.Map;
import org.apache.cloudstack.backup.DeleteVmCheckpointCommand;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtDeleteVmCheckpointCommandWrapperTest {
private LibvirtDeleteVmCheckpointCommandWrapper wrapper;
private DeleteVmCheckpointCommand command;
@Before
public void setUp() {
wrapper = new LibvirtDeleteVmCheckpointCommandWrapper();
command = Mockito.mock(DeleteVmCheckpointCommand.class);
}
@Test
public void testExecuteStoppedVmWithoutDisksReturnsFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(true);
Mockito.when(command.getCheckpointId()).thenReturn("cp-1");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Collections.emptyMap());
Answer answer = wrapper.execute(command, Mockito.mock(LibvirtComputingResource.class));
Assert.assertFalse(answer.getResult());
Assert.assertEquals("No disks provided for bitmap removal", answer.getDetails());
}
@Test
public void testExecuteStoppedVmBitmapRemovalFailureReturnsFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(true);
Mockito.when(command.getCheckpointId()).thenReturn("cp-2");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn("bitmap remove error"))) {
Answer answer = wrapper.execute(command, Mockito.mock(LibvirtComputingResource.class));
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Failed to remove bitmap cp-2"));
}
}
@Test
public void testExecuteRunningVmDeleteCheckpointSuccess() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getCheckpointId()).thenReturn("cp-3");
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn(null))) {
Answer answer = wrapper.execute(command, Mockito.mock(LibvirtComputingResource.class));
Assert.assertTrue(answer.getResult());
Assert.assertEquals("Checkpoint deleted", answer.getDetails());
}
}
}

View File

@ -0,0 +1,87 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.cloudstack.backup.FinalizeImageTransferCommand;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtFinalizeImageTransferCommandWrapperTest {
private LibvirtFinalizeImageTransferCommandWrapper wrapper;
private FinalizeImageTransferCommand command;
private LibvirtComputingResource resource;
@Before
public void setUp() {
wrapper = new LibvirtFinalizeImageTransferCommandWrapper();
command = Mockito.mock(FinalizeImageTransferCommand.class);
resource = Mockito.mock(LibvirtComputingResource.class);
}
@Test
public void testExecuteBlankTransferIdReturnsFailure() {
Mockito.when(command.getTransferId()).thenReturn("");
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("transferId is empty.", answer.getDetails());
}
@Test
public void testExecuteWithActiveTransfersReturnsSuccessWithoutStoppingServer() {
Mockito.when(command.getTransferId()).thenReturn("tr-1");
try (MockedStatic<ImageServerControlSocket> imageServerControlMock = Mockito.mockStatic(ImageServerControlSocket.class)) {
imageServerControlMock.when(() -> ImageServerControlSocket.unregisterTransfer("tr-1")).thenReturn(2);
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertEquals("Image transfer finalized.", answer.getDetails());
}
}
@Test
public void testExecuteServerUnreachableReturnsSuccessAndForcesStop() {
Mockito.when(command.getTransferId()).thenReturn("tr-2");
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn("inactive"));
MockedStatic<ImageServerControlSocket> imageServerControlMock = Mockito.mockStatic(ImageServerControlSocket.class)) {
imageServerControlMock.when(() -> ImageServerControlSocket.unregisterTransfer("tr-2")).thenReturn(-1);
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertEquals("Image transfer finalized (server unreachable, forced stop).", answer.getDetails());
}
}
}

View File

@ -0,0 +1,182 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.Map;
import org.apache.cloudstack.backup.StartBackupAnswer;
import org.apache.cloudstack.backup.StartBackupCommand;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtStartBackupCommandWrapperTest {
private LibvirtStartBackupCommandWrapper wrapper;
private StartBackupCommand command;
private LibvirtComputingResource resource;
@Before
public void setUp() {
wrapper = new LibvirtStartBackupCommandWrapper();
command = Mockito.mock(StartBackupCommand.class);
resource = Mockito.mock(LibvirtComputingResource.class);
}
@Test
public void testExecuteStoppedVmBitmapAddSuccess() {
Mockito.when(command.isStoppedVM()).thenReturn(true);
Mockito.when(command.getToCheckpointId()).thenReturn("cp-stopped-1");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn(null))) {
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertTrue(answer instanceof StartBackupAnswer);
Assert.assertTrue(answer.getDetails().contains("checkpoint bitmap added successfully"));
}
}
@Test
public void testExecuteStoppedVmBitmapAddFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(true);
Mockito.when(command.getToCheckpointId()).thenReturn("cp-stopped-2");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn("bitmap add error"))) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Failed to add bitmap cp-stopped-2"));
}
}
@Test
public void testExecuteRunningVmMissingFromCheckpointCreateTimeReturnsFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getToCheckpointId()).thenReturn("cp-running-1");
Mockito.when(command.getFromCheckpointId()).thenReturn("cp-running-0");
Mockito.when(command.getFromCheckpointCreateTime()).thenReturn(null);
Mockito.when(command.getSocket()).thenReturn("sock-1");
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn("not found"))) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("From checkpoint create time is null"));
}
}
@Test
public void testExecuteRunningVmBackupBeginSuccess() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getToCheckpointId()).thenReturn("cp-running-2");
Mockito.when(command.getFromCheckpointId()).thenReturn(null);
Mockito.when(command.getSocket()).thenReturn("sock-2");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
Mockito.when(resource.getDiskPathLabelMap("i-2-3-VM")).thenReturn(Map.of("/path/disk1.qcow2", "vda"));
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class,
(mock, context) -> Mockito.when(mock.execute()).thenReturn(null))) {
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Backup started successfully"));
}
}
@Test
public void testExecuteRunningVmCheckpointRedefineFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getToCheckpointId()).thenReturn("cp-running-3");
Mockito.when(command.getFromCheckpointId()).thenReturn("cp-running-missing");
Mockito.when(command.getFromCheckpointCreateTime()).thenReturn(12345L);
Mockito.when(command.getSocket()).thenReturn("sock-3");
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
int constructionIndex = context.getCount();
if (constructionIndex == 1) {
Mockito.when(mock.execute()).thenReturn("checkpoint missing");
} else if (constructionIndex == 2) {
Mockito.when(mock.execute()).thenReturn("checkpoint redefine failed");
} else {
Mockito.when(mock.execute()).thenReturn(null);
}
})) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Failed to redefine from-checkpoint cp-running-missing"));
}
}
@Test
public void testExecuteRunningVmBackupBeginFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getToCheckpointId()).thenReturn("cp-running-4");
Mockito.when(command.getFromCheckpointId()).thenReturn(null);
Mockito.when(command.getSocket()).thenReturn("sock-4");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
Mockito.when(resource.getDiskPathLabelMap("i-2-3-VM")).thenReturn(Map.of("/path/disk1.qcow2", "vda"));
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
if (context.getCount() == 1) {
Mockito.when(mock.execute()).thenReturn("backup begin failed");
} else {
Mockito.when(mock.execute()).thenReturn(null);
}
})) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Backup begin failed: backup begin failed"));
}
}
@Test
public void testExecuteRunningVmCreateBackupXmlExceptionReturnsFailure() {
Mockito.when(command.isStoppedVM()).thenReturn(false);
Mockito.when(command.getVmName()).thenReturn("i-2-3-VM");
Mockito.when(command.getToCheckpointId()).thenReturn("cp-running-5");
Mockito.when(command.getFromCheckpointId()).thenReturn(null);
Mockito.when(command.getSocket()).thenReturn("sock-5");
Mockito.when(command.getDiskPathUuidMap()).thenReturn(Map.of("/path/disk1.qcow2", "vol-1"));
Mockito.when(resource.getDiskPathLabelMap("i-2-3-VM")).thenThrow(new RuntimeException("disk labels unavailable"));
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Error starting backup: disk labels unavailable"));
}
}

View File

@ -0,0 +1,179 @@
// 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.cloudstack.backup.StartNBDServerAnswer;
import org.apache.cloudstack.backup.StartNBDServerCommand;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtStartNBDServerCommandWrapperTest {
private LibvirtStartNBDServerCommandWrapper wrapper;
private StartNBDServerCommand command;
private LibvirtComputingResource resource;
@Before
public void setUp() {
wrapper = new LibvirtStartNBDServerCommandWrapper();
command = Mockito.mock(StartNBDServerCommand.class);
resource = Mockito.mock(LibvirtComputingResource.class);
Mockito.when(command.getVolumePath()).thenReturn("/path/disk.qcow2");
Mockito.when(command.getExportName()).thenReturn("vol-1");
Mockito.when(command.getSocket()).thenReturn("sock-1");
Mockito.when(command.getTransferId()).thenReturn("transfer-1");
Mockito.when(command.getDirection()).thenReturn("upload");
Mockito.when(command.getFromCheckpointId()).thenReturn(null);
}
@Test
public void testExecuteMissingVolumePathReturnsFailure() {
Mockito.when(command.getVolumePath()).thenReturn(null);
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("Volume path is required for the nbd server", answer.getDetails());
}
@Test
public void testExecuteMissingExportNameReturnsFailure() {
Mockito.when(command.getExportName()).thenReturn(" ");
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("Export name is required for the nbd server", answer.getDetails());
}
@Test
public void testExecuteMissingSocketReturnsFailure() {
Mockito.when(command.getSocket()).thenReturn("");
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertEquals("Socket is required for the nbd server", answer.getDetails());
}
@Test
public void testExecuteAlreadyActiveServiceReturnsFailure() {
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) ->
Mockito.when(mock.execute()).thenReturn(null))) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("already running"));
}
}
@Test
public void testExecuteStartScriptFailureReturnsFailure() {
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
if (context.getCount() == 1) {
Mockito.when(mock.execute()).thenReturn("inactive");
} else if (context.getCount() == 2) {
Mockito.when(mock.execute()).thenReturn("start failed");
} else {
Mockito.when(mock.execute()).thenReturn(null);
}
})) {
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Failed to start qemu-nbd service: start failed"));
}
}
@Test
public void testExecuteInterruptedWhileWaitingReturnsFailure() {
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
if (context.getCount() <= 2) {
Mockito.when(mock.execute()).thenReturn(context.getCount() == 1 ? "inactive" : null);
} else {
Mockito.when(mock.execute()).thenReturn("inactive");
}
})) {
Thread.currentThread().interrupt();
Answer answer = wrapper.execute(command, resource);
Assert.assertFalse(answer.getResult());
Assert.assertTrue(answer.getDetails().contains("Interrupted while waiting"));
Assert.assertTrue(Thread.currentThread().isInterrupted());
Thread.interrupted();
}
}
@Test
public void testExecuteSuccessReturnsTransferDetails() {
try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
if (context.getCount() == 1) {
Mockito.when(mock.execute()).thenReturn("inactive");
} else {
Mockito.when(mock.execute()).thenReturn(null);
}
})) {
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Assert.assertTrue(answer instanceof StartNBDServerAnswer);
StartNBDServerAnswer startNBDServerAnswer = (StartNBDServerAnswer) answer;
Assert.assertEquals("transfer-1", startNBDServerAnswer.getImageTransferId());
Assert.assertEquals("nbd+unix:///sock-1", startNBDServerAnswer.getTransferUrl());
}
}
@Test
public void testExecuteAddsBitmapOptionWhenBitmapExists() {
Mockito.when(command.getFromCheckpointId()).thenReturn("cp-1");
try (MockedStatic<Script> scriptStaticMock = Mockito.mockStatic(Script.class);
MockedConstruction<Script> scriptConstruction = Mockito.mockConstruction(Script.class, (mock, context) -> {
if (context.getCount() == 1) {
Mockito.when(mock.execute()).thenReturn("inactive");
} else {
Mockito.when(mock.execute()).thenReturn(null);
}
})) {
scriptStaticMock.when(() -> Script.runBashScriptIgnoreExitValue(
Mockito.anyString(), Mockito.anyInt())).thenReturn(
"{\"format-specific\":{\"data\":{\"bitmaps\":[{\"name\":\"cp-1\"}]}}}");
Answer answer = wrapper.execute(command, resource);
Assert.assertTrue(answer.getResult());
Script startScript = scriptConstruction.constructed().get(1);
boolean bitmapOptionAdded = Mockito.mockingDetails(startScript).getInvocations().stream().anyMatch(invocation ->
invocation.getMethod().getName().equals("add")
&& invocation.getArguments().length > 0
&& invocation.getArguments()[0] instanceof String
&& ((String) invocation.getArguments()[0]).contains("-B cp-1"));
Assert.assertTrue(bitmapOptionAdded);
}
}
}

Some files were not shown because too many files have changed in this diff Show More