mirror of https://github.com/apache/cloudstack.git
Image server with disk upload
This commit is contained in:
parent
23ecb1f5ce
commit
5389fe60aa
|
|
@ -28,6 +28,7 @@ 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.IncrementalBackupService;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
|
|
@ -44,7 +45,6 @@ public class CreateImageTransferCmd extends BaseCmd implements AdminCmd {
|
|||
@Parameter(name = ApiConstants.BACKUP_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = BackupResponse.class,
|
||||
required = true,
|
||||
description = "ID of the backup")
|
||||
private Long backupId;
|
||||
|
||||
|
|
@ -69,8 +69,8 @@ public class CreateImageTransferCmd extends BaseCmd implements AdminCmd {
|
|||
return volumeId;
|
||||
}
|
||||
|
||||
public String getDirection() {
|
||||
return direction;
|
||||
public ImageTransfer.Direction getDirection() {
|
||||
return ImageTransfer.Direction.valueOf(direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ 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
|
||||
}
|
||||
|
|
@ -33,12 +35,8 @@ public interface ImageTransfer extends ControlledEntity, InternalIdentity {
|
|||
|
||||
long getBackupId();
|
||||
|
||||
long getVmId();
|
||||
|
||||
long getDiskId();
|
||||
|
||||
String getDeviceName();
|
||||
|
||||
long getHostId();
|
||||
|
||||
int getNbdPort();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.cloud.agent.api.Answer;
|
|||
public class CreateImageTransferAnswer extends Answer {
|
||||
private String imageTransferId;
|
||||
private String transferUrl;
|
||||
private String phase;
|
||||
|
||||
public CreateImageTransferAnswer() {
|
||||
}
|
||||
|
|
@ -32,11 +31,10 @@ public class CreateImageTransferAnswer extends Answer {
|
|||
}
|
||||
|
||||
public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details,
|
||||
String imageTransferId, String transferUrl, String phase) {
|
||||
String imageTransferId, String transferUrl) {
|
||||
super(cmd, success, details);
|
||||
this.imageTransferId = imageTransferId;
|
||||
this.transferUrl = transferUrl;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public String getImageTransferId() {
|
||||
|
|
@ -55,11 +53,4 @@ public class CreateImageTransferAnswer extends Answer {
|
|||
this.transferUrl = transferUrl;
|
||||
}
|
||||
|
||||
public String getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
public void setPhase(String phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,21 +22,25 @@ import com.cloud.agent.api.Command;
|
|||
public class CreateImageTransferCommand extends Command {
|
||||
private String transferId;
|
||||
private String hostIpAddress;
|
||||
private String deviceName;
|
||||
private String exportName;
|
||||
private String volumePath;
|
||||
private int nbdPort;
|
||||
private String direction;
|
||||
|
||||
public CreateImageTransferCommand() {
|
||||
}
|
||||
|
||||
public CreateImageTransferCommand(Long vmId, String transferId, String hostIpAddress, Long backupId, Long diskId, String deviceName, int nbdPort) {
|
||||
public CreateImageTransferCommand(String transferId, String hostIpAddress, String exportName, String volumePath, int nbdPort, String direction) {
|
||||
this.transferId = transferId;
|
||||
this.hostIpAddress = hostIpAddress;
|
||||
this.deviceName = deviceName;
|
||||
this.exportName = exportName;
|
||||
this.volumePath = volumePath;
|
||||
this.nbdPort = nbdPort;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
public String getExportName() {
|
||||
return exportName;
|
||||
}
|
||||
|
||||
public int getNbdPort() {
|
||||
|
|
@ -55,4 +59,12 @@ public class CreateImageTransferCommand extends Command {
|
|||
public boolean executeInSequence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVolumePath() {
|
||||
return volumePath;
|
||||
}
|
||||
|
||||
public String getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,30 @@ import com.cloud.agent.api.Command;
|
|||
|
||||
public class FinalizeImageTransferCommand extends Command {
|
||||
private String transferId;
|
||||
private String direction;
|
||||
private int nbdPort;
|
||||
|
||||
public FinalizeImageTransferCommand() {
|
||||
}
|
||||
|
||||
public FinalizeImageTransferCommand(String transferId) {
|
||||
public FinalizeImageTransferCommand(String transferId, String direction, int nbdPort) {
|
||||
this.transferId = transferId;
|
||||
this.direction = direction;
|
||||
this.nbdPort = nbdPort;
|
||||
}
|
||||
|
||||
public String getTransferId() {
|
||||
return transferId;
|
||||
}
|
||||
|
||||
public int getNbdPort() {
|
||||
return nbdPort;
|
||||
}
|
||||
|
||||
public String getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -17,13 +17,11 @@
|
|||
|
||||
package org.apache.cloudstack.backup;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
public class StartBackupAnswer extends Answer {
|
||||
private Long checkpointCreateTime;
|
||||
private Map<Long, String> deviceMappings; // volumeId -> device name (vda, vdb, etc.)
|
||||
private Boolean isIncremental;
|
||||
|
||||
public StartBackupAnswer() {
|
||||
}
|
||||
|
|
@ -32,11 +30,9 @@ public class StartBackupAnswer extends Answer {
|
|||
super(cmd, success, details);
|
||||
}
|
||||
|
||||
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details,
|
||||
Long checkpointCreateTime, Map<Long, String> deviceMappings) {
|
||||
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details, Long checkpointCreateTime) {
|
||||
super(cmd, success, details);
|
||||
this.checkpointCreateTime = checkpointCreateTime;
|
||||
this.deviceMappings = deviceMappings;
|
||||
}
|
||||
|
||||
public Long getCheckpointCreateTime() {
|
||||
|
|
@ -47,11 +43,11 @@ public class StartBackupAnswer extends Answer {
|
|||
this.checkpointCreateTime = checkpointCreateTime;
|
||||
}
|
||||
|
||||
public Map<Long, String> getDeviceMappings() {
|
||||
return deviceMappings;
|
||||
public Boolean getIncremental() {
|
||||
return isIncremental;
|
||||
}
|
||||
|
||||
public void setDeviceMappings(Map<Long, String> deviceMappings) {
|
||||
this.deviceMappings = deviceMappings;
|
||||
public void setIncremental(Boolean incremental) {
|
||||
isIncremental = incremental;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,20 +23,18 @@ import com.cloud.agent.api.Command;
|
|||
|
||||
public class StartBackupCommand extends Command {
|
||||
private String vmName;
|
||||
private Long vmId;
|
||||
private String toCheckpointId;
|
||||
private String fromCheckpointId;
|
||||
private int nbdPort;
|
||||
private Map<Long, String> diskVolumePaths; // volumeId -> path mapping
|
||||
private Map<String, String> diskVolumePaths; // volumeId -> path mapping
|
||||
private String hostIpAddress;
|
||||
|
||||
public StartBackupCommand() {
|
||||
}
|
||||
|
||||
public StartBackupCommand(String vmName, Long vmId, String toCheckpointId, String fromCheckpointId,
|
||||
int nbdPort, Map<Long, String> diskVolumePaths, String hostIpAddress) {
|
||||
public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId,
|
||||
int nbdPort, Map<String, String> diskVolumePaths, String hostIpAddress) {
|
||||
this.vmName = vmName;
|
||||
this.vmId = vmId;
|
||||
this.toCheckpointId = toCheckpointId;
|
||||
this.fromCheckpointId = fromCheckpointId;
|
||||
this.nbdPort = nbdPort;
|
||||
|
|
@ -48,10 +46,6 @@ public class StartBackupCommand extends Command {
|
|||
return vmName;
|
||||
}
|
||||
|
||||
public Long getVmId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
public String getToCheckpointId() {
|
||||
return toCheckpointId;
|
||||
}
|
||||
|
|
@ -64,7 +58,7 @@ public class StartBackupCommand extends Command {
|
|||
return nbdPort;
|
||||
}
|
||||
|
||||
public Map<Long, String> getDiskVolumePaths() {
|
||||
public Map<String, String> getDiskVolumePaths() {
|
||||
return diskVolumePaths;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 hostIpAddress;
|
||||
private String exportName;
|
||||
private String volumePath;
|
||||
private int nbdPort;
|
||||
private String direction;
|
||||
|
||||
public StartNBDServerCommand() {
|
||||
}
|
||||
|
||||
public StartNBDServerCommand(String transferId, String hostIpAddress, String exportName, String volumePath, int nbdPort, String direction) {
|
||||
this.transferId = transferId;
|
||||
this.hostIpAddress = hostIpAddress;
|
||||
this.exportName = exportName;
|
||||
this.volumePath = volumePath;
|
||||
this.nbdPort = nbdPort;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public String getExportName() {
|
||||
return exportName;
|
||||
}
|
||||
|
||||
public int getNbdPort() {
|
||||
return nbdPort;
|
||||
}
|
||||
|
||||
public String getHostIpAddress() {
|
||||
return hostIpAddress;
|
||||
}
|
||||
|
||||
public String getTransferId() {
|
||||
return transferId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVolumePath() {
|
||||
return volumePath;
|
||||
}
|
||||
|
||||
public String getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 StopNBDServerCommand extends Command {
|
||||
private String transferId;
|
||||
private String direction;
|
||||
private int nbdPort;
|
||||
|
||||
public StopNBDServerCommand() {
|
||||
}
|
||||
|
||||
public StopNBDServerCommand(String transferId, String direction, int nbdPort) {
|
||||
this.transferId = transferId;
|
||||
this.direction = direction;
|
||||
this.nbdPort = nbdPort;
|
||||
}
|
||||
|
||||
public String getTransferId() {
|
||||
return transferId;
|
||||
}
|
||||
|
||||
public int getNbdPort() {
|
||||
return nbdPort;
|
||||
}
|
||||
|
||||
public String getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,15 +45,9 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
@Column(name = "backup_id")
|
||||
private long backupId;
|
||||
|
||||
@Column(name = "vm_id")
|
||||
private long vmId;
|
||||
|
||||
@Column(name = "disk_id")
|
||||
private long diskId;
|
||||
|
||||
@Column(name = "device_name")
|
||||
private String deviceName;
|
||||
|
||||
@Column(name = "host_id")
|
||||
private long hostId;
|
||||
|
||||
|
|
@ -80,6 +74,9 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
@Column(name = "domain_id")
|
||||
Long domainId;
|
||||
|
||||
@Column(name = "data_center_id")
|
||||
Long dataCenterId;
|
||||
|
||||
@Column(name = "created")
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
|
@ -95,18 +92,17 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
public ImageTransferVO() {
|
||||
}
|
||||
|
||||
public ImageTransferVO(String uuid, long backupId, long vmId, long diskId, String deviceName, long hostId, int nbdPort, Phase phase, Direction direction, Long accountId, Long domainId) {
|
||||
public ImageTransferVO(String uuid, Long backupId, long diskId, long hostId, int nbdPort, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) {
|
||||
this.uuid = uuid;
|
||||
this.backupId = backupId;
|
||||
this.vmId = vmId;
|
||||
this.diskId = diskId;
|
||||
this.deviceName = deviceName;
|
||||
this.hostId = hostId;
|
||||
this.nbdPort = nbdPort;
|
||||
this.phase = phase;
|
||||
this.direction = direction;
|
||||
this.accountId = accountId;
|
||||
this.domainId = domainId;
|
||||
this.dataCenterId = dataCenterId;
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
|
|
@ -129,15 +125,6 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
this.backupId = backupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVmId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
public void setVmId(long vmId) {
|
||||
this.vmId = vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDiskId() {
|
||||
return diskId;
|
||||
|
|
@ -147,15 +134,6 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
this.diskId = diskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHostId() {
|
||||
return hostId;
|
||||
|
|
@ -231,6 +209,11 @@ public class ImageTransferVO implements ImageTransfer {
|
|||
return accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDataCenterId() {
|
||||
return dataCenterId;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ import com.cloud.utils.db.GenericDao;
|
|||
|
||||
public interface ImageTransferDao extends GenericDao<ImageTransferVO, Long> {
|
||||
List<ImageTransferVO> listByBackupId(Long backupId);
|
||||
List<ImageTransferVO> listByVmId(Long vmId);
|
||||
ImageTransferVO findByUuid(String uuid);
|
||||
ImageTransferVO findByNbdPort(int port);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ import com.cloud.utils.db.SearchCriteria;
|
|||
public class ImageTransferDaoImpl extends GenericDaoBase<ImageTransferVO, Long> implements ImageTransferDao {
|
||||
|
||||
private SearchBuilder<ImageTransferVO> backupIdSearch;
|
||||
private SearchBuilder<ImageTransferVO> vmIdSearch;
|
||||
private SearchBuilder<ImageTransferVO> uuidSearch;
|
||||
private SearchBuilder<ImageTransferVO> nbdPortSearch;
|
||||
|
||||
public ImageTransferDaoImpl() {
|
||||
}
|
||||
|
|
@ -44,13 +44,13 @@ public class ImageTransferDaoImpl extends GenericDaoBase<ImageTransferVO, Long>
|
|||
backupIdSearch.and("backupId", backupIdSearch.entity().getBackupId(), SearchCriteria.Op.EQ);
|
||||
backupIdSearch.done();
|
||||
|
||||
vmIdSearch = createSearchBuilder();
|
||||
vmIdSearch.and("vmId", vmIdSearch.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
vmIdSearch.done();
|
||||
|
||||
uuidSearch = createSearchBuilder();
|
||||
uuidSearch.and("uuid", uuidSearch.entity().getUuid(), SearchCriteria.Op.EQ);
|
||||
uuidSearch.done();
|
||||
|
||||
nbdPortSearch = createSearchBuilder();
|
||||
nbdPortSearch.and("nbdPort", nbdPortSearch.entity().getNbdPort(), SearchCriteria.Op.EQ);
|
||||
nbdPortSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -60,17 +60,17 @@ public class ImageTransferDaoImpl extends GenericDaoBase<ImageTransferVO, Long>
|
|||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImageTransferVO> listByVmId(Long vmId) {
|
||||
SearchCriteria<ImageTransferVO> sc = vmIdSearch.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTransferVO findByUuid(String uuid) {
|
||||
SearchCriteria<ImageTransferVO> sc = uuidSearch.create();
|
||||
sc.setParameters("uuid", uuid);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTransferVO findByNbdPort(int port) {
|
||||
SearchCriteria<ImageTransferVO> sc = nbdPortSearch.create();
|
||||
sc.setParameters("nbdPort", port);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,14 +131,13 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'active_checkpoint_cre
|
|||
|
||||
-- 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',
|
||||
`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',
|
||||
`backup_id` bigint unsigned NOT NULL COMMENT 'Backup ID',
|
||||
`vm_id` bigint unsigned NOT NULL COMMENT 'VM ID',
|
||||
`data_center_id` bigint unsigned NOT NULL COMMENT 'Data Center ID',
|
||||
`backup_id` bigint unsigned COMMENT 'Backup ID',
|
||||
`disk_id` bigint unsigned NOT NULL COMMENT 'Disk/Volume ID',
|
||||
`device_name` varchar(10) NOT NULL COMMENT 'Device name (vda, vdb, etc)',
|
||||
`host_id` bigint unsigned NOT NULL COMMENT 'Host ID',
|
||||
`nbd_port` int NOT NULL COMMENT 'NBD port',
|
||||
`transfer_url` varchar(255) COMMENT 'ImageIO transfer URL',
|
||||
|
|
@ -151,9 +150,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
|
|||
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__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_image_transfer__disk_id` FOREIGN KEY (`disk_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`),
|
||||
INDEX `i_image_transfer__vm_id`(`vm_id`)
|
||||
INDEX `i_image_transfer__backup_id`(`backup_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
|
|||
|
||||
import org.apache.cloudstack.backup.CreateImageTransferAnswer;
|
||||
import org.apache.cloudstack.backup.CreateImageTransferCommand;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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;
|
||||
|
|
@ -31,27 +31,31 @@ import com.cloud.resource.ResourceWrapper;
|
|||
public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper<CreateImageTransferCommand, Answer, LibvirtComputingResource> {
|
||||
protected Logger logger = LogManager.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public Answer execute(CreateImageTransferCommand cmd, LibvirtComputingResource resource) {
|
||||
String deviceName = cmd.getDeviceName();
|
||||
private CreateImageTransferAnswer handleUpload(CreateImageTransferCommand cmd) {
|
||||
return new CreateImageTransferAnswer(cmd, false, "Image Upload is not handled by KVM agent");
|
||||
}
|
||||
|
||||
private CreateImageTransferAnswer handleDownload(CreateImageTransferCommand cmd) {
|
||||
String exportName = cmd.getExportName();
|
||||
int nbdPort = cmd.getNbdPort();
|
||||
|
||||
try {
|
||||
// POC: ImageIO interaction is stubbed out
|
||||
// In production, this would:
|
||||
// 1. Register NBD endpoint nbd://127.0.0.1:{nbdPort}/{deviceName} with ImageIO
|
||||
// 2. Create transfer object in ImageIO
|
||||
// 3. Get signed ticket and transfer URL
|
||||
|
||||
String hostIpAddress = cmd.getHostIpAddress();
|
||||
String transferUrl = String.format("nbd://%s:%d/%s", hostIpAddress, nbdPort, deviceName);
|
||||
String phase = "initializing";
|
||||
String transferUrl = String.format("nbd://%s:%d/%s", hostIpAddress, nbdPort, exportName);
|
||||
|
||||
return new CreateImageTransferAnswer(cmd, true, "Image transfer created (stub)",
|
||||
cmd.getTransferId(), transferUrl, phase);
|
||||
return new CreateImageTransferAnswer(cmd, true, "Image transfer created for download",
|
||||
cmd.getTransferId(), transferUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
return new CreateImageTransferAnswer(cmd, false, "Error creating image transfer: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer execute(CreateImageTransferCommand cmd, LibvirtComputingResource resource) {
|
||||
if (cmd.getDirection().equals("download")) {
|
||||
return handleDownload(cmd);
|
||||
} else {
|
||||
return handleUpload(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ 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;
|
||||
|
|
@ -95,11 +94,7 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
// Get checkpoint creation time - using current time for POC
|
||||
long checkpointCreateTime = System.currentTimeMillis();
|
||||
|
||||
// Build device mappings from domblklist
|
||||
Map<Long, String> deviceMappings = getDeviceMappings(vmName, cmd.getDiskVolumePaths(), resource);
|
||||
|
||||
return new StartBackupAnswer(cmd, true, "Backup started successfully",
|
||||
checkpointCreateTime, deviceMappings);
|
||||
return new StartBackupAnswer(cmd, true, "Backup started successfully", checkpointCreateTime);
|
||||
|
||||
} catch (Exception e) {
|
||||
return new StartBackupAnswer(cmd, false, "Error starting backup: " + e.getMessage());
|
||||
|
|
@ -118,13 +113,13 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
xml.append(" <disks>\n");
|
||||
|
||||
// Add disk entries - simplified for POC
|
||||
Map<Long, String> diskPaths = cmd.getDiskVolumePaths();
|
||||
Map<String, String> diskPaths = cmd.getDiskVolumePaths();
|
||||
int diskIndex = 0;
|
||||
for (Map.Entry<Long, String> entry : diskPaths.entrySet()) {
|
||||
for (Map.Entry<String, String> entry : diskPaths.entrySet()) {
|
||||
String deviceName = "vd" + (char)('a' + diskIndex);
|
||||
String scratchFile = "/var/tmp/scratch-" + entry.getKey() + ".qcow2";
|
||||
xml.append(" <disk name=\"").append(deviceName).append("\" type=\"file\" exportname=\"")
|
||||
.append(deviceName).append("\">\n");
|
||||
.append(entry.getKey()).append("\">\n");
|
||||
xml.append(" <scratch file=\"").append(scratchFile).append("\"/>\n");
|
||||
xml.append(" </disk>\n");
|
||||
diskIndex++;
|
||||
|
|
@ -141,19 +136,4 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
" <name>" + checkpointId + "</name>\n" +
|
||||
"</domaincheckpoint>";
|
||||
}
|
||||
|
||||
private Map<Long, String> getDeviceMappings(String vmName, Map<Long, String> diskPaths,
|
||||
LibvirtComputingResource resource) {
|
||||
Map<Long, String> mappings = new HashMap<>();
|
||||
|
||||
// Simplified for POC - map volumeIds to device names in order
|
||||
int diskIndex = 0;
|
||||
for (Long volumeId : diskPaths.keySet()) {
|
||||
String deviceName = "vd" + (char)('a' + diskIndex);
|
||||
mappings.put(volumeId, deviceName);
|
||||
diskIndex++;
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
//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.StartNBDServerAnswer;
|
||||
import org.apache.cloudstack.backup.StartNBDServerCommand;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
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 = StartNBDServerCommand.class)
|
||||
public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper<StartNBDServerCommand, Answer, LibvirtComputingResource> {
|
||||
protected Logger logger = LogManager.getLogger(getClass());
|
||||
|
||||
private StartNBDServerAnswer handleUpload(StartNBDServerCommand cmd) {
|
||||
String volumePath = cmd.getVolumePath();
|
||||
int nbdPort = cmd.getNbdPort();
|
||||
String hostIpAddress = cmd.getHostIpAddress();
|
||||
String exportName = cmd.getExportName();
|
||||
String transferId = cmd.getTransferId();
|
||||
|
||||
if (volumePath == null || volumePath.isEmpty()) {
|
||||
return new StartNBDServerAnswer(cmd, false, "Volume path is required for upload");
|
||||
}
|
||||
if (exportName == null || exportName.isEmpty()) {
|
||||
return new StartNBDServerAnswer(cmd, false, "Export name is required for upload");
|
||||
}
|
||||
if (hostIpAddress == null || hostIpAddress.isEmpty()) {
|
||||
return new StartNBDServerAnswer(cmd, false, "Host IP address is required for upload");
|
||||
}
|
||||
|
||||
String unitName = String.format("qemu-nbd-%d", nbdPort);
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
String systemdRunCmd = String.format(
|
||||
"systemd-run --unit=%s --property=Restart=no qemu-nbd --export-name %s --bind %s --port %d --persistent %s",
|
||||
unitName, exportName, hostIpAddress, nbdPort, 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://%s:%d/%s", hostIpAddress, nbdPort, exportName);
|
||||
return new StartNBDServerAnswer(cmd, true, "qemu-nbd service started for upload",
|
||||
transferId, transferUrl);
|
||||
}
|
||||
|
||||
private StartNBDServerAnswer handleDownload(StartNBDServerCommand cmd) {
|
||||
String exportName = cmd.getExportName();
|
||||
int nbdPort = cmd.getNbdPort();
|
||||
String hostIpAddress = cmd.getHostIpAddress();
|
||||
String transferUrl = String.format("nbd://%s:%d/%s", hostIpAddress, nbdPort, exportName);
|
||||
|
||||
return new StartNBDServerAnswer(cmd, true, "qemu-nbd service started for download",
|
||||
cmd.getTransferId(), transferUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer execute(StartNBDServerCommand cmd, LibvirtComputingResource resource) {
|
||||
if (cmd.getDirection().equals("download")) {
|
||||
return handleDownload(cmd);
|
||||
} else {
|
||||
return handleUpload(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
//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();
|
||||
}
|
||||
|
||||
private Answer handleUpload(StopNBDServerCommand cmd) {
|
||||
try {
|
||||
int nbdPort = cmd.getNbdPort();
|
||||
String unitName = String.format("qemu-nbd-%d", nbdPort);
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
private Answer handleDownload(StopNBDServerCommand cmd) {
|
||||
return new Answer(cmd, true, "Image transfer finalized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer execute(StopNBDServerCommand cmd, LibvirtComputingResource resource) {
|
||||
if (cmd.getDirection().equals("download")) {
|
||||
return handleDownload(cmd);
|
||||
} else {
|
||||
return handleUpload(cmd);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,8 @@ import org.apache.cloudstack.backup.dao.BackupOfferingDao;
|
|||
import org.apache.cloudstack.backup.dao.ImageTransferDao;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -52,8 +54,11 @@ import com.cloud.agent.api.Answer;
|
|||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.storage.ScopeType;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
|
@ -85,6 +90,9 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
@Inject
|
||||
private HostDao hostDao;
|
||||
|
||||
@Inject
|
||||
private PrimaryDataStoreDao primaryDataStoreDao;
|
||||
|
||||
@Inject
|
||||
EndPointSelector _epSelector;
|
||||
|
||||
|
|
@ -110,7 +118,6 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
public BackupResponse startBackup(StartBackupCmd cmd) {
|
||||
Long vmId = cmd.getVmId();
|
||||
|
||||
// Get VM
|
||||
VMInstanceVO vm = vmInstanceDao.findById(vmId);
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException("VM not found: " + vmId);
|
||||
|
|
@ -120,7 +127,6 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
throw new CloudRuntimeException("VM must be running to start backup");
|
||||
}
|
||||
|
||||
// Check if backup already in progress
|
||||
Backup existingBackup = backupDao.findByVmId(vmId);
|
||||
if (existingBackup != null && existingBackup.getStatus() == Backup.Status.BackingUp) {
|
||||
throw new CloudRuntimeException("Backup already in progress for VM: " + vmId);
|
||||
|
|
@ -128,45 +134,39 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
|
||||
boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId());
|
||||
|
||||
// Create backup record
|
||||
BackupVO backup = new BackupVO();
|
||||
backup.setVmId(vmId);
|
||||
backup.setName(vmId + "-" + DateTime.now());
|
||||
backup.setAccountId(vm.getAccountId());
|
||||
backup.setDomainId(vm.getDomainId());
|
||||
// todo: set to Increment if it is incremental backup
|
||||
backup.setType("FULL");
|
||||
backup.setZoneId(vm.getDataCenterId());
|
||||
backup.setStatus(Backup.Status.BackingUp);
|
||||
backup.setBackupOfferingId(vm.getBackupOfferingId());
|
||||
backup.setDate(new Date());
|
||||
|
||||
// Generate checkpoint IDs
|
||||
String toCheckpointId = "ckp-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String fromCheckpointId = vm.getActiveCheckpointId(); // null for first full backup
|
||||
String fromCheckpointId = vm.getActiveCheckpointId();
|
||||
|
||||
backup.setToCheckpointId(toCheckpointId);
|
||||
backup.setFromCheckpointId(fromCheckpointId);
|
||||
|
||||
// Allocate NBD port
|
||||
int nbdPort = allocateNbdPort();
|
||||
backup.setNbdPort(nbdPort);
|
||||
backup.setHostId(vm.getHostId());
|
||||
// Will be changed later if incremental was done
|
||||
backup.setType("FULL");
|
||||
|
||||
// Persist backup record
|
||||
backup = backupDao.persist(backup);
|
||||
|
||||
// Get disk volume paths
|
||||
List<? extends Volume> volumes = volumeDao.findByInstance(vmId);
|
||||
Map<Long, String> diskVolumePaths = new HashMap<>();
|
||||
List<VolumeVO> volumes = volumeDao.findByInstance(vmId);
|
||||
Map<String, String> diskVolumePaths = new HashMap<>();
|
||||
for (Volume vol : volumes) {
|
||||
diskVolumePaths.put(vol.getId(), vol.getPath());
|
||||
diskVolumePaths.put(vol.getUuid(), vol.getPath());
|
||||
}
|
||||
|
||||
Host host = hostDao.findById(vm.getHostId());
|
||||
StartBackupCommand startCmd = new StartBackupCommand(
|
||||
vm.getInstanceName(),
|
||||
vmId,
|
||||
toCheckpointId,
|
||||
fromCheckpointId,
|
||||
nbdPort,
|
||||
|
|
@ -178,7 +178,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
StartBackupAnswer answer;
|
||||
|
||||
if (dummyOffering) {
|
||||
answer = new StartBackupAnswer(startCmd, true, "Dummy answer", System.currentTimeMillis(), diskVolumePaths);
|
||||
answer = new StartBackupAnswer(startCmd, true, "Dummy answer", System.currentTimeMillis());
|
||||
} else {
|
||||
answer = (StartBackupAnswer) agentManager.send(vm.getHostId(), startCmd);
|
||||
}
|
||||
|
|
@ -190,9 +190,12 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
|
||||
// Update backup with checkpoint creation time
|
||||
backup.setCheckpointCreateTime(answer.getCheckpointCreateTime());
|
||||
if (Boolean.TRUE.equals(answer.getIncremental())) {
|
||||
// todo: set it in the backend
|
||||
backup.setType("Incremental");
|
||||
}
|
||||
backupDao.update(backup.getId(), backup);
|
||||
|
||||
// Return response
|
||||
BackupResponse response = new BackupResponse();
|
||||
response.setId(backup.getUuid());
|
||||
response.setVmId(vm.getUuid());
|
||||
|
|
@ -270,48 +273,35 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd) {
|
||||
private ImageTransferVO createDownloadImageTransfer(CreateImageTransferCmd cmd) {
|
||||
Long backupId = cmd.getBackupId();
|
||||
Long volumeId = cmd.getVolumeId();
|
||||
|
||||
BackupVO backup = backupDao.findById(backupId);
|
||||
if (backup == null) {
|
||||
throw new CloudRuntimeException("Backup not found: " + backupId);
|
||||
}
|
||||
boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId());
|
||||
|
||||
Volume volume = volumeDao.findById(volumeId);
|
||||
if (volume == null) {
|
||||
throw new CloudRuntimeException("Volume not found: " + volumeId);
|
||||
}
|
||||
|
||||
VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId());
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException("VM not found: " + backup.getVmId());
|
||||
}
|
||||
boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId());
|
||||
|
||||
// Resolve device name (simplified for POC)
|
||||
List<? extends Volume> volumes = volumeDao.findByInstance(backup.getVmId());
|
||||
String deviceName = resolveDeviceName(volumes, volumeId);
|
||||
|
||||
String transferId = UUID.randomUUID().toString();
|
||||
Host host = hostDao.findById(backup.getHostId());
|
||||
// Create CreateImageTransferCommand
|
||||
CreateImageTransferCommand transferCmd = new CreateImageTransferCommand(
|
||||
backup.getVmId(),
|
||||
transferId,
|
||||
host.getPrivateIpAddress(),
|
||||
backupId,
|
||||
volumeId,
|
||||
deviceName,
|
||||
backup.getNbdPort()
|
||||
transferId,
|
||||
host.getPrivateIpAddress(),
|
||||
volume.getUuid(),
|
||||
null,
|
||||
backup.getNbdPort(),
|
||||
cmd.getDirection().toString()
|
||||
);
|
||||
|
||||
try {
|
||||
CreateImageTransferAnswer answer;
|
||||
if (dummyOffering) {
|
||||
answer = new CreateImageTransferAnswer(transferCmd, true, "Dummy answer", "image-transfer-id", "nbd://127.0.0.1:10809/vda", "initializing");
|
||||
answer = new CreateImageTransferAnswer(transferCmd, true, "Dummy answer", "image-transfer-id", "nbd://127.0.0.1:10809/vda");
|
||||
} else if (DATAPLANE_PROXY_MODE) {
|
||||
EndPoint ssvm = _epSelector.findSsvm(backup.getZoneId());
|
||||
answer = (CreateImageTransferAnswer) ssvm.sendMessage(transferCmd);
|
||||
|
|
@ -323,55 +313,131 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
throw new CloudRuntimeException("Failed to create image transfer: " + answer.getDetails());
|
||||
}
|
||||
|
||||
// Create ImageTransfer record
|
||||
ImageTransferVO imageTransfer = new ImageTransferVO(
|
||||
transferId,
|
||||
backupId,
|
||||
backup.getVmId(),
|
||||
volumeId,
|
||||
deviceName,
|
||||
backup.getHostId(),
|
||||
backup.getNbdPort(),
|
||||
ImageTransferVO.Phase.initializing,
|
||||
ImageTransfer.Direction.valueOf(cmd.getDirection()),
|
||||
backup.getAccountId(),
|
||||
backup.getDomainId()
|
||||
transferId,
|
||||
backupId,
|
||||
volumeId,
|
||||
backup.getHostId(),
|
||||
backup.getNbdPort(),
|
||||
ImageTransferVO.Phase.transferring,
|
||||
ImageTransfer.Direction.download,
|
||||
backup.getAccountId(),
|
||||
backup.getDomainId(),
|
||||
backup.getZoneId()
|
||||
);
|
||||
imageTransfer.setTransferUrl(answer.getTransferUrl());
|
||||
imageTransfer.setSignedTicketId(answer.getImageTransferId());
|
||||
imageTransfer = imageTransferDao.persist(imageTransfer);
|
||||
|
||||
// Return response
|
||||
ImageTransferResponse response = new ImageTransferResponse();
|
||||
response.setId(imageTransfer.getUuid());
|
||||
response.setBackupId(backup.getUuid());
|
||||
response.setVmId(vm.getUuid());
|
||||
response.setDiskId(volume.getUuid());
|
||||
response.setDeviceName(deviceName);
|
||||
response.setTransferUrl(answer.getTransferUrl());
|
||||
response.setPhase(ImageTransferVO.Phase.initializing.toString());
|
||||
response.setDirection(imageTransfer.getDirection().toString());
|
||||
response.setCreated(imageTransfer.getCreated());
|
||||
return response;
|
||||
return imageTransfer;
|
||||
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd) {
|
||||
Long imageTransferId = cmd.getImageTransferId();
|
||||
private HostVO getFirstHostFromStoragePool(StoragePoolVO storagePoolVO) {
|
||||
List<HostVO> hosts = null;
|
||||
if (storagePoolVO.getScope().equals(ScopeType.CLUSTER)) {
|
||||
hosts = hostDao.findByClusterId(storagePoolVO.getClusterId());
|
||||
|
||||
ImageTransferVO imageTransfer = imageTransferDao.findById(imageTransferId);
|
||||
if (imageTransfer == null) {
|
||||
throw new CloudRuntimeException("Image transfer not found: " + imageTransferId);
|
||||
} else if (storagePoolVO.getScope().equals(ScopeType.ZONE)) {
|
||||
hosts = hostDao.findByDataCenterId(storagePoolVO.getDataCenterId());
|
||||
}
|
||||
return hosts.get(0);
|
||||
}
|
||||
|
||||
private ImageTransferVO createUploadImageTransfer(CreateImageTransferCmd cmd) {
|
||||
String transferId = UUID.randomUUID().toString();
|
||||
|
||||
int nbdPort = allocateNbdPort();
|
||||
VolumeVO volume = volumeDao.findById(cmd.getVolumeId());
|
||||
Long poolId = volume.getPoolId();
|
||||
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(poolId);
|
||||
Host host = getFirstHostFromStoragePool(storagePoolVO);
|
||||
|
||||
StartNBDServerAnswer nbdServerAnswer;
|
||||
StartNBDServerCommand nbdServerCmd = new StartNBDServerCommand(
|
||||
transferId,
|
||||
host.getPrivateIpAddress(),
|
||||
volume.getUuid(),
|
||||
volume.getPath(),
|
||||
nbdPort,
|
||||
cmd.getDirection().toString()
|
||||
);
|
||||
|
||||
try {
|
||||
nbdServerAnswer = (StartNBDServerAnswer) agentManager.send(host.getId(), nbdServerCmd);
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
|
||||
}
|
||||
if (!nbdServerAnswer.getResult()) {
|
||||
throw new CloudRuntimeException("Failed to start the NBD server");
|
||||
}
|
||||
|
||||
CreateImageTransferAnswer transferAnswer;
|
||||
CreateImageTransferCommand transferCmd = new CreateImageTransferCommand(
|
||||
transferId,
|
||||
host.getPrivateIpAddress(),
|
||||
volume.getUuid(),
|
||||
volume.getPath(),
|
||||
nbdPort,
|
||||
cmd.getDirection().toString()
|
||||
);
|
||||
|
||||
EndPoint ssvm = _epSelector.findSsvm(volume.getDataCenterId());
|
||||
transferAnswer = (CreateImageTransferAnswer) ssvm.sendMessage(transferCmd);
|
||||
|
||||
if (!transferAnswer.getResult()) {
|
||||
StopNBDServerCommand stopNbdServerCommand = new StopNBDServerCommand(transferId, cmd.getDirection().toString(), nbdPort);
|
||||
throw new CloudRuntimeException("Failed to create image transfer: " + transferAnswer.getDetails());
|
||||
}
|
||||
|
||||
ImageTransferVO imageTransfer = new ImageTransferVO(
|
||||
transferId,
|
||||
null,
|
||||
volume.getId(),
|
||||
host.getId(),
|
||||
nbdPort,
|
||||
ImageTransferVO.Phase.initializing,
|
||||
ImageTransfer.Direction.upload,
|
||||
volume.getAccountId(),
|
||||
volume.getDomainId(),
|
||||
volume.getDataCenterId()
|
||||
);
|
||||
|
||||
imageTransfer.setTransferUrl(transferAnswer.getTransferUrl());
|
||||
imageTransfer.setSignedTicketId(transferAnswer.getImageTransferId());
|
||||
imageTransfer = imageTransferDao.persist(imageTransfer);
|
||||
return imageTransfer;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd) {
|
||||
ImageTransfer imageTransfer;
|
||||
if (cmd.getDirection().equals(ImageTransfer.Direction.upload)) {
|
||||
imageTransfer = createUploadImageTransfer(cmd);
|
||||
} else if (cmd.getDirection().equals(ImageTransfer.Direction.download)) {
|
||||
imageTransfer = createDownloadImageTransfer(cmd);
|
||||
} else {
|
||||
throw new CloudRuntimeException("Invalid direction: " + cmd.getDirection());
|
||||
}
|
||||
|
||||
ImageTransferVO imageTransferVO = imageTransferDao.findById(imageTransfer.getId());
|
||||
ImageTransferResponse response = toImageTransferResponse(imageTransferVO);
|
||||
return response;
|
||||
}
|
||||
|
||||
private void finalizeDownloadImageTransfer(ImageTransferVO imageTransfer) {
|
||||
|
||||
String transferId = imageTransfer.getUuid();
|
||||
int nbdPort = imageTransfer.getNbdPort();
|
||||
String direction = imageTransfer.getDirection().toString();
|
||||
FinalizeImageTransferCommand finalizeCmd = new FinalizeImageTransferCommand(transferId, direction, nbdPort);
|
||||
|
||||
BackupVO backup = backupDao.findById(imageTransfer.getBackupId());
|
||||
boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId());
|
||||
|
||||
FinalizeImageTransferCommand finalizeCmd = new FinalizeImageTransferCommand(imageTransfer.getUuid());
|
||||
try {
|
||||
Answer answer;
|
||||
if (dummyOffering) {
|
||||
|
|
@ -384,17 +450,56 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
}
|
||||
|
||||
if (!answer.getResult()) {
|
||||
throw new CloudRuntimeException("Failed to create image transfer: " + answer.getDetails());
|
||||
throw new CloudRuntimeException("Failed to finalize image transfer: " + answer.getDetails());
|
||||
}
|
||||
|
||||
imageTransfer.setPhase(ImageTransferVO.Phase.finished);
|
||||
imageTransferDao.update(imageTransferId, imageTransfer);
|
||||
imageTransferDao.remove(imageTransferId);
|
||||
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void finalizeUploadImageTransfer(ImageTransferVO imageTransfer) {
|
||||
String transferId = imageTransfer.getUuid();
|
||||
int nbdPort = imageTransfer.getNbdPort();
|
||||
String direction = imageTransfer.getDirection().toString();
|
||||
|
||||
StopNBDServerCommand stopNbdServerCommand = new StopNBDServerCommand(transferId, direction, nbdPort);
|
||||
Answer answer;
|
||||
try {
|
||||
answer = agentManager.send(imageTransfer.getHostId(), stopNbdServerCommand);
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
|
||||
}
|
||||
if (!answer.getResult()) {
|
||||
throw new CloudRuntimeException("Failed to stop the nbd server");
|
||||
}
|
||||
|
||||
FinalizeImageTransferCommand finalizeCmd = new FinalizeImageTransferCommand(transferId, direction, nbdPort);
|
||||
EndPoint ssvm = _epSelector.findSsvm(imageTransfer.getDataCenterId());
|
||||
answer = ssvm.sendMessage(finalizeCmd);
|
||||
|
||||
if (!answer.getResult()) {
|
||||
throw new CloudRuntimeException("Failed to finalize image transfer: " + answer.getDetails());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd) {
|
||||
Long imageTransferId = cmd.getImageTransferId();
|
||||
|
||||
ImageTransferVO imageTransfer = imageTransferDao.findById(imageTransferId);
|
||||
if (imageTransfer == null) {
|
||||
throw new CloudRuntimeException("Image transfer not found: " + imageTransferId);
|
||||
}
|
||||
|
||||
if (imageTransfer.getDirection().equals(ImageTransfer.Direction.download)) {
|
||||
finalizeDownloadImageTransfer(imageTransfer);
|
||||
} else {
|
||||
finalizeUploadImageTransfer(imageTransfer);
|
||||
}
|
||||
imageTransfer.setPhase(ImageTransferVO.Phase.finished);
|
||||
imageTransferDao.update(imageTransfer.getId(), imageTransfer);
|
||||
imageTransferDao.remove(imageTransfer.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -463,43 +568,34 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
|
|||
return cmdList;
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private int allocateNbdPort() {
|
||||
// Simplified port allocation for POC
|
||||
private int getRandomNbdPort() {
|
||||
Random random = new Random();
|
||||
return NBD_PORT_RANGE_START + random.nextInt(NBD_PORT_RANGE_END - NBD_PORT_RANGE_START);
|
||||
}
|
||||
|
||||
private String resolveDeviceName(List<? extends Volume> volumes, Long targetDiskId) {
|
||||
// Simplified device name resolution for POC
|
||||
int index = 0;
|
||||
for (Volume vol : volumes) {
|
||||
if (Long.valueOf(vol.getId()).equals(targetDiskId)) {
|
||||
return "vd" + (char)('a' + index);
|
||||
}
|
||||
index++;
|
||||
private int allocateNbdPort() {
|
||||
int port = getRandomNbdPort();
|
||||
while (imageTransferDao.findByNbdPort(port) != null) {
|
||||
port = getRandomNbdPort();
|
||||
}
|
||||
return "vda"; // fallback
|
||||
return port;
|
||||
}
|
||||
|
||||
private ImageTransferResponse toImageTransferResponse(ImageTransferVO imageTransfer) {
|
||||
private ImageTransferResponse toImageTransferResponse(ImageTransferVO imageTransferVO) {
|
||||
ImageTransferResponse response = new ImageTransferResponse();
|
||||
response.setId(imageTransfer.getUuid());
|
||||
|
||||
BackupVO backup = backupDao.findById(imageTransfer.getBackupId());
|
||||
VMInstanceVO vm = vmInstanceDao.findById(imageTransfer.getVmId());
|
||||
Volume volume = volumeDao.findById(imageTransfer.getDiskId());
|
||||
|
||||
if (backup != null) response.setBackupId(backup.getUuid());
|
||||
if (vm != null) response.setVmId(vm.getUuid());
|
||||
if (volume != null) response.setDiskId(volume.getUuid());
|
||||
|
||||
response.setDeviceName(imageTransfer.getDeviceName());
|
||||
response.setTransferUrl(imageTransfer.getTransferUrl());
|
||||
response.setPhase(imageTransfer.getPhase().toString());
|
||||
response.setCreated(imageTransfer.getCreated());
|
||||
|
||||
response.setId(imageTransferVO.getUuid());
|
||||
Long backupId = imageTransferVO.getBackupId();
|
||||
if (backupId != null) {
|
||||
Backup backup = backupDao.findById(backupId);
|
||||
response.setBackupId(backup.getUuid());
|
||||
}
|
||||
Long volumeId = imageTransferVO.getDiskId();
|
||||
Volume volume = volumeDao.findById(volumeId);
|
||||
response.setDiskId(volume.getUuid());
|
||||
response.setTransferUrl(imageTransferVO.getTransferUrl());
|
||||
response.setPhase(ImageTransferVO.Phase.initializing.toString());
|
||||
response.setDirection(imageTransferVO.getDirection().toString());
|
||||
response.setCreated(imageTransferVO.getCreated());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3716,6 +3716,93 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
|||
return new QuerySnapshotZoneCopyAnswer(cmd, files);
|
||||
}
|
||||
|
||||
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() {
|
||||
String unitName = "cloudstack-image-server";
|
||||
|
||||
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(String.format("Image server not running, resetting failed state", unitName));
|
||||
resetService(unitName);
|
||||
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(String.format("Image server %s stoppped", unitName));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean startImageServerIfNotRunning(int imageServerPort) {
|
||||
final String imageServerScript = "/opt/cloud/bin/image_server.py";
|
||||
String unitName = "cloudstack-image-server";
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
String systemdRunCmd = String.format(
|
||||
"systemd-run --unit=%s --property=Restart=no /usr/bin/python3 %s --listen 0.0.0.0 --port 54323",
|
||||
unitName, imageServerScript, imageServerPort);
|
||||
|
||||
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 the Image serer: %s", startResult));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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("Image server is now active (attempt %d)", unitName, attempt + 1));
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(pollIntervalMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!serviceActive) {
|
||||
logger.error(String.format("Image server failed to start within %d seconds", unitName, maxWaitSeconds));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Answer execute(CreateImageTransferCommand cmd) {
|
||||
if (!_inSystemVM) {
|
||||
return new CreateImageTransferAnswer(cmd, true, "Not running inside SSVM; skipping image transfer setup.");
|
||||
|
|
@ -3724,7 +3811,7 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
|||
final String transferId = cmd.getTransferId();
|
||||
|
||||
final String hostIp = cmd.getHostIpAddress();
|
||||
final String exportName = cmd.getDeviceName();
|
||||
final String exportName = cmd.getExportName();
|
||||
final int nbdPort = cmd.getNbdPort();
|
||||
|
||||
if (StringUtils.isBlank(transferId)) {
|
||||
|
|
@ -3734,15 +3821,13 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
|||
return new CreateImageTransferAnswer(cmd, false, "hostIpAddress is empty.");
|
||||
}
|
||||
if (StringUtils.isBlank(exportName)) {
|
||||
return new CreateImageTransferAnswer(cmd, false, "deviceName is empty.");
|
||||
return new CreateImageTransferAnswer(cmd, false, "exportName is empty.");
|
||||
}
|
||||
if (nbdPort <= 0) {
|
||||
return new CreateImageTransferAnswer(cmd, false, "Invalid nbdPort: " + nbdPort);
|
||||
}
|
||||
|
||||
final String imageServerScript = "/opt/cloud/bin/image_server.py";
|
||||
final int imageServerPort = 54323;
|
||||
final String imageServerLogFile = "/var/log/image_server.log";
|
||||
|
||||
try {
|
||||
// 1) Write /tmp/<transferId> with NBD endpoint details.
|
||||
|
|
@ -3752,40 +3837,22 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
|||
payload.put("export", exportName);
|
||||
|
||||
final String json = new GsonBuilder().create().toJson(payload);
|
||||
final File transferFile = new File("/tmp", transferId);
|
||||
File dir = new File("/tmp/imagetransfer");
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
final File transferFile = new File("/tmp/imagetransfer", transferId);
|
||||
FileUtils.writeStringToFile(transferFile, json, "UTF-8");
|
||||
|
||||
// 2) Start image_server if not already running.
|
||||
final File scriptFile = new File(imageServerScript);
|
||||
if (!scriptFile.exists()) {
|
||||
return new CreateImageTransferAnswer(cmd, false, "Missing image server script: " + imageServerScript);
|
||||
}
|
||||
|
||||
final Script isRunning = new Script("/bin/bash", logger);
|
||||
isRunning.add("-c");
|
||||
isRunning.add(String.format("pgrep -f '%s.*--port %d' >/dev/null 2>&1", imageServerScript, imageServerPort));
|
||||
final String runningResult = isRunning.execute();
|
||||
if (runningResult != null) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"python3", imageServerScript,
|
||||
"--listen", "0.0.0.0",
|
||||
"--port", String.valueOf(imageServerPort)
|
||||
);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(new File(imageServerLogFile)));
|
||||
pb.redirectErrorStream(true);
|
||||
pb.start();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to start Image Server");
|
||||
return new CreateImageTransferAnswer(cmd, false, "Failed to start image server");
|
||||
}
|
||||
}
|
||||
final String transferUrl = String.format("http://%s:%d/images/%s", _publicIp, imageServerPort, transferId);
|
||||
return new CreateImageTransferAnswer(cmd, true, "Image transfer prepared on SSVM.", transferId, transferUrl, "initializing");
|
||||
} catch (Exception e) {
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to prepare image transfer on SSVM", e);
|
||||
return new CreateImageTransferAnswer(cmd, false, "Failed to prepare image transfer on SSVM: " + e.getMessage());
|
||||
}
|
||||
|
||||
startImageServerIfNotRunning(imageServerPort);
|
||||
|
||||
final String transferUrl = String.format("http://%s:%d/images/%s", _publicIp, imageServerPort, transferId);
|
||||
return new CreateImageTransferAnswer(cmd, true, "Image transfer prepared on SSVM.", transferId, transferUrl);
|
||||
}
|
||||
|
||||
protected Answer execute(FinalizeImageTransferCommand cmd) {
|
||||
|
|
@ -3798,26 +3865,17 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
|||
return new Answer(cmd, false, "transferId is empty.");
|
||||
}
|
||||
|
||||
final File transferFile = new File("/tmp", transferId);
|
||||
final File transferFile = new File("/tmp/imagetransfer", transferId);
|
||||
if (transferFile.exists() && !transferFile.delete()) {
|
||||
return new Answer(cmd, false, "Failed to delete transfer config file: " + transferFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
// Stop image_server.py only if /tmp directory is empty.
|
||||
final File tmpDir = new File("/tmp");
|
||||
final File[] tmpEntries = tmpDir.listFiles();
|
||||
if (tmpEntries != null && tmpEntries.length == 0) {
|
||||
final String imageServerScript = "/opt/cloud/bin/image_server.py";
|
||||
final int imageServerPort = 54323;
|
||||
|
||||
// Use bash "|| true" so Script returns success even if process isn't running.
|
||||
final Script stop = new Script("/bin/bash", logger);
|
||||
stop.add("-c");
|
||||
stop.add(String.format("pkill -f '%s.*--port %d' >/dev/null 2>&1 || true", imageServerScript, imageServerPort));
|
||||
final String stopResult = stop.execute();
|
||||
if (stopResult != null) {
|
||||
return new Answer(cmd, false, "Failed to stop image server: " + stopResult);
|
||||
try (Stream<Path> stream = Files.list(Paths.get("/tmp/imagetransfer"))) {
|
||||
if (!stream.findAny().isPresent()) {
|
||||
stopImageServer();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to list /tmp/imagetransfer", e);
|
||||
}
|
||||
|
||||
return new Answer(cmd, true, "Image transfer finalized.");
|
||||
|
|
|
|||
|
|
@ -62,11 +62,11 @@ _IMAGE_LOCKS_GUARD = threading.Lock()
|
|||
|
||||
|
||||
# Dynamic image_id(transferId) -> NBD export mapping:
|
||||
# CloudStack writes a JSON file at /tmp/<transferId> with:
|
||||
# CloudStack writes a JSON file at /tmp/imagetransfer/<transferId> with:
|
||||
# {"host": "...", "port": 10809, "export": "vda"}
|
||||
#
|
||||
# This server reads that file on-demand.
|
||||
_CFG_DIR = "/tmp"
|
||||
_CFG_DIR = "/tmp/imagetransfer"
|
||||
_CFG_CACHE: Dict[str, Tuple[float, Dict[str, Any]]] = {}
|
||||
_CFG_CACHE_GUARD = threading.Lock()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue