introduce backup repository concept for NAS & other backups

Each backup repository is for a specific zone & provider.
Eack NAS bkcp offering is tied to a back repo & each bkup offer assinged
to VM -> defined its backup repository

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2024-08-02 12:58:40 +05:30
parent 18e05894c7
commit 97ce34ce2b
13 changed files with 512 additions and 125 deletions

View File

@ -0,0 +1,34 @@
//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 org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface BackupRepository extends InternalIdentity, Identity {
String getProvider();
long getZoneId();
String getName();
String getType();
String getAddress();
String getMountOptions();
Long getCapacityBytes();
Long getUsedBytes();
Date getCreated();
}

View File

@ -22,19 +22,19 @@ package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.LogLevel;
import java.util.Map;
public class DeleteBackupCommand extends Command {
private String backupPath;
private String backupStoragePath;
private String backupRepoType;
private String backupRepoAddress;
@LogLevel(LogLevel.Log4jLevel.Off)
private Map<String, String> details;
private String mountOptions;
public DeleteBackupCommand(String backupPath, String backupStoragePath, Map<String, String> details) {
public DeleteBackupCommand(String backupPath, String backupRepoType, String backupRepoAddress, String mountOptions) {
super();
this.backupPath = backupPath;
this.backupStoragePath = backupStoragePath;
this.details = details;
this.backupRepoType = backupRepoType;
this.backupRepoAddress = backupRepoAddress;
this.mountOptions = mountOptions;
}
public String getBackupPath() {
@ -45,20 +45,28 @@ public class DeleteBackupCommand extends Command {
this.backupPath = backupPath;
}
public String getBackupStoragePath() {
return backupStoragePath;
public String getBackupRepoType() {
return backupRepoType;
}
public void setBackupStoragePath(String backupStoragePath) {
this.backupStoragePath = backupStoragePath;
public void setBackupRepoType(String backupRepoType) {
this.backupRepoType = backupRepoType;
}
public Map<String, String> getDetails() {
return details;
public String getBackupRepoAddress() {
return backupRepoAddress;
}
public void setDetails(Map<String, String> details) {
this.details = details;
public void setBackupRepoAddress(String backupRepoAddress) {
this.backupRepoAddress = backupRepoAddress;
}
public String getMountOptions() {
return mountOptions;
}
public void setMountOptions(String mountOptions) {
this.mountOptions = mountOptions;
}
@Override

View File

@ -22,21 +22,18 @@ package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.LogLevel;
import java.util.Map;
public class TakeBackupCommand extends Command {
private String vmName;
private String backupPath;
private String backupStoragePath;
private String backupRepoType;
private String backupRepoAddress;
@LogLevel(LogLevel.Log4jLevel.Off)
private Map<String, String> details;
private String mountOptions;
public TakeBackupCommand(String vmName, String backupPath, String backupStoragePath, Map<String, String> details) {
public TakeBackupCommand(String vmName, String backupPath) {
super();
this.vmName = vmName;
this.backupPath = backupPath;
this.backupStoragePath = backupStoragePath;
this.details = details;
}
public String getVmName() {
@ -55,20 +52,28 @@ public class TakeBackupCommand extends Command {
this.backupPath = backupPath;
}
public String getBackupStoragePath() {
return backupStoragePath;
public String getBackupRepoType() {
return backupRepoType;
}
public void setBackupStoragePath(String backupStoragePath) {
this.backupStoragePath = backupStoragePath;
public void setBackupRepoType(String backupRepoType) {
this.backupRepoType = backupRepoType;
}
public Map<String, String> getDetails() {
return details;
public String getBackupRepoAddress() {
return backupRepoAddress;
}
public void setDetails(Map<String, String> details) {
this.details = details;
public void setBackupRepoAddress(String backupRepoAddress) {
this.backupRepoAddress = backupRepoAddress;
}
public String getMountOptions() {
return mountOptions;
}
public void setMountOptions(String mountOptions) {
this.mountOptions = mountOptions;
}
@Override

View File

@ -0,0 +1,152 @@
// 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 java.util.Date;
import java.util.UUID;
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 javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "backup_repository")
public class BackupRepositoryVO implements BackupRepository {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "name")
private String name;
@Column(name = "zone_id", nullable = false)
private long zoneId;
@Column(name = "provider", nullable = false)
private String provider;
@Column(name = "type", nullable = false)
private String type;
@Column(name = "address", nullable = false)
private String address;
@Column(name = "mount_opts")
private String mountOptions;
@Column(name = "used_bytes",nullable = true)
private Long usedBytes;
@Column(name = "capacity_bytes", nullable = true)
private Long capacityBytes;
@Column(name = "created")
@Temporal(value = TemporalType.TIMESTAMP)
private Date created;
@Column(name = "removed")
@Temporal(value = TemporalType.TIMESTAMP)
private Date removed;
public BackupRepositoryVO() {
this.uuid = UUID.randomUUID().toString();
}
public BackupRepositoryVO(final long zoneId, final String provider, final String name, final String type, final String address, final String mountOptions, final Long capacityBytes) {
this();
this.zoneId = zoneId;
this.provider = provider;
this.name = name;
this.type = type;
this.address = address;
this.mountOptions = mountOptions;
this.capacityBytes = capacityBytes;
this.created = new Date();
}
public String getUuid() {
return uuid;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public long getZoneId() {
return zoneId;
}
@Override
public String getProvider() {
return provider;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String getAddress() {
return address;
}
@Override
public String getMountOptions() {
return mountOptions;
}
@Override
public Long getUsedBytes() {
return usedBytes;
}
@Override
public Long getCapacityBytes() {
return capacityBytes;
}
public Date getCreated() {
return created;
}
}

View File

@ -0,0 +1,31 @@
// 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.dao;
import java.util.List;
import org.apache.cloudstack.backup.BackupRepository;
import org.apache.cloudstack.backup.BackupRepositoryVO;
import com.cloud.utils.db.GenericDao;
public interface BackupRepositoryDao extends GenericDao<BackupRepositoryVO, Long> {
List<BackupRepository> listByZoneAndProvider(Long zoneId, String provider);
BackupRepository findByBackupOfferingId(Long backupOfferingId);
}

View File

@ -0,0 +1,67 @@
// 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.dao;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.apache.cloudstack.backup.BackupOfferingVO;
import org.apache.cloudstack.backup.BackupRepository;
import org.apache.cloudstack.backup.BackupRepositoryVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class BackupRepositoryDaoImpl extends GenericDaoBase<BackupRepositoryVO, Long> implements BackupRepositoryDao {
@Inject
BackupOfferingDao backupOfferingDao;
private SearchBuilder<BackupRepositoryVO> backupRepoSearch;
public BackupRepositoryDaoImpl() {
}
@PostConstruct
protected void init() {
backupRepoSearch = createSearchBuilder();
backupRepoSearch.and("zone_id", backupRepoSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
backupRepoSearch.and("provider", backupRepoSearch.entity().getProvider(), SearchCriteria.Op.EQ);
backupRepoSearch.done();
}
@Override
public List<BackupRepository> listByZoneAndProvider(Long zoneId, String provider) {
SearchCriteria<BackupRepositoryVO> sc = backupRepoSearch.create();
sc.setParameters("zone_id", zoneId);
sc.setParameters("provider", provider);
return new ArrayList<>(listBy(sc));
}
@Override
public BackupRepository findByBackupOfferingId(Long backupOfferingId) {
BackupOfferingVO offering = backupOfferingDao.findByIdIncludingRemoved(backupOfferingId);
if (offering == null) {
return null;
}
return findByUuid(offering.getExternalId());
}
}

View File

@ -269,6 +269,7 @@
<bean id="annotationDaoImpl" class="org.apache.cloudstack.annotation.dao.AnnotationDaoImpl" />
<bean id="backupScheduleDaoImpl" class="org.apache.cloudstack.backup.dao.BackupScheduleDaoImpl" />
<bean id="backupDaoImpl" class="org.apache.cloudstack.backup.dao.BackupDaoImpl" />
<bean id="backupRepositoryDaoImpl" class="org.apache.cloudstack.backup.dao.BackupRepositoryDaoImpl" />
<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

@ -134,6 +134,26 @@ CREATE TABLE `cloud`.`webhook_delivery` (
CONSTRAINT `fk_webhook__webhook_id` FOREIGN KEY (`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Backup Repository NAS feature
DROP TABLE IF EXISTS `cloud`.`backup_repository`;
CREATE TABLE `cloud`.`backup_repository` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the backup repository',
`uuid` varchar(255) NOT NULL COMMENT 'uuid of the backup repository',
`name` varchar(255) NOT NULL COMMENT 'name of the backup repository',
`zone_id` bigint unsigned NOT NULL COMMENT 'id of zone',
`provider` varchar(255) NOT NULL COMMENT 'backup provider name',
`type` varchar(255) NOT NULL COMMENT 'backup repo type',
`address` varchar(1024) NOT NULL COMMENT 'url of the backup repository',
`mount_opts` varchar(1024) COMMENT 'mount options for the backup repository',
`used_bytes` bigint unsigned,
`capacity_bytes` bigint unsigned,
`created` datetime,
`removed` datetime,
PRIMARY KEY(`id`),
INDEX `i_backup_repository__uuid`(`uuid`),
INDEX `i_backup_repository__zone_id_provider`(`zone_id`, `provider`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Normalize quota.usage.smtp.useStartTLS, quota.usage.smtp.useAuth, alert.smtp.useAuth and project.smtp.useAuth values
UPDATE
`cloud`.`configuration`

View File

@ -36,6 +36,8 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupRepositoryDao;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.commons.collections.CollectionUtils;
@ -43,6 +45,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import javax.inject.Inject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -50,24 +53,16 @@ import java.util.HashMap;
public class NASBackupProvider extends AdapterBase implements BackupProvider, Configurable {
private static final Logger LOG = LogManager.getLogger(NASBackupProvider.class);
private final ConfigKey<String> NasType = new ConfigKey<>("Advanced", String.class,
"backup.plugin.nas.target.type", "nfs",
"The NAS storage target type. Only supported: nfs and cephfs", true, ConfigKey.Scope.Zone);
private final ConfigKey<String> NfsPool = new ConfigKey<>("Advanced", String.class,
"backup.plugin.nas.nfs.pool", "",
"The NFS NAS storage pool URL (format <domain|ip>:<path>", true, ConfigKey.Scope.Zone);
private final ConfigKey<String> CephFSPool = new ConfigKey<>("Advanced", String.class,
"backup.plugin.nas.cephfs.pool", "",
"The CephFS storage pool URL (format: <comma-separated domain|ip>:<path>)", true, ConfigKey.Scope.Zone);
private final ConfigKey<String> CephFSPoolCredentials = new ConfigKey<>("Advanced", String.class,
"backup.plugin.nas.cephfs.credentials", "",
"The CephFS storage pool URL (format: <name=username,secret=secretkey>)", true, ConfigKey.Scope.Zone);
@Inject
private BackupDao backupDao;
@Inject
private BackupRepositoryDao backupRepositoryDao;
@Inject
private BackupOfferingDao backupOfferingDao;
@Inject
private HostDao hostDao;
@ -86,18 +81,6 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Inject
private AgentManager agentManager;
protected String getNasType(final Long zoneId) {
return NasType.valueIn(zoneId);
}
protected String getBackupStoragePath(final Long zoneId) {
final String type = getNasType(zoneId);
if ("nfs".equalsIgnoreCase(type)) {
return NfsPool.valueIn(zoneId);
}
throw new CloudRuntimeException("NAS backup plugin not configured");
}
protected Host getLastVMHypervisorHost(VirtualMachine vm) {
Long hostId = vm.getLastHostId();
if (hostId == null) {
@ -145,14 +128,18 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
// TODO: add support for backup of stopped VMs
final Host host = getRunningVMHypervisorHost(vm);
final String backupStoragePath = getBackupStoragePath(vm.getDataCenterId());
final String nasType = getNasType(vm.getDataCenterId());
final Map<String, String> backupDetails = Map.of(
"type", nasType
);
final String backupPath = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date());
final BackupRepository backupRepository = backupRepositoryDao.findByBackupOfferingId(vm.getBackupOfferingId());
if (backupRepository == null) {
throw new CloudRuntimeException("No valid backup repository found for the VM, please check the attached backup offering");
}
TakeBackupCommand command = new TakeBackupCommand(vm.getInstanceName(), backupPath, backupStoragePath, backupDetails);
final String backupPath = String.format("%s/%s", vm.getInstanceName(),
new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()));
TakeBackupCommand command = new TakeBackupCommand(vm.getInstanceName(), backupPath);
command.setBackupRepoType(backupRepository.getType());
command.setBackupRepoAddress(backupRepository.getAddress());
command.setMountOptions(backupRepository.getMountOptions());
BackupAnswer answer = null;
try {
@ -166,7 +153,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
if (answer != null) {
BackupVO backup = new BackupVO();
backup.setVmId(vm.getId());
backup.setExternalId(String.format("%s:%s/%s", nasType, vm.getInstanceName(), backupPath));
backup.setExternalId(backupPath);
backup.setType("FULL");
backup.setDate(new Date());
backup.setSize(answer.getSize());
@ -224,19 +211,17 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Override
public boolean deleteBackup(Backup backup, boolean forced) {
final Long zoneId = backup.getZoneId();
final String backupStoragePath = getBackupStoragePath(zoneId);
final String nasType = getNasType(zoneId);
final Map<String, String> backupDetails = Map.of(
"type", nasType
);
final String backupPath = backup.getExternalId().split(":")[1];
final BackupRepository backupRepository = backupRepositoryDao.findByBackupOfferingId(backup.getBackupOfferingId());
if (backupRepository == null) {
throw new CloudRuntimeException("No valid backup repository found for the VM, please check the attached backup offering");
}
// TODO: this can be any host in the cluster or last host
final VirtualMachine vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
final Host host = getRunningVMHypervisorHost(vm);
DeleteBackupCommand command = new DeleteBackupCommand(backupPath, backupStoragePath, backupDetails);
DeleteBackupCommand command = new DeleteBackupCommand(backup.getExternalId(), backupRepository.getType(),
backupRepository.getAddress(), backupRepository.getMountOptions());
BackupAnswer answer = null;
try {
@ -299,8 +284,12 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Override
public List<BackupOffering> listBackupOfferings(Long zoneId) {
BackupOffering policy = new BackupOfferingVO(zoneId, "default", getName(), "Default", "Default Backup Offering", true);
return List.of(policy);
final List<BackupRepository> repositories = backupRepositoryDao.listByZoneAndProvider(zoneId, getName());
final List<BackupOffering> offerings = new ArrayList<>();
for (final BackupRepository repository : repositories) {
offerings.add(new NasBackupOffering(repository.getName(), repository.getUuid()));
}
return offerings;
}
@Override
@ -311,10 +300,6 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[]{
NasType,
NfsPool,
CephFSPool,
CephFSPoolCredentials
};
}
@ -325,7 +310,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Override
public String getDescription() {
return "NAS KVM Backup Plugin";
return "NAS Backup Plugin";
}
@Override

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.backup;
import java.util.Date;
public class NasBackupOffering implements BackupOffering {
private String name;
private String uid;
public NasBackupOffering(String name, String uid) {
this.name = name;
this.uid = uid;
}
@Override
public String getExternalId() {
return uid;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return "NAS Backup Offering (Repository)";
}
@Override
public long getZoneId() {
return -1;
}
@Override
public boolean isUserDrivenBackupAllowed() {
return true;
}
@Override
public String getProvider() {
return "nas";
}
@Override
public Date getCreated() {
return null;
}
@Override
public String getUuid() {
return uid;
}
@Override
public long getId() {
return -1;
}
}

View File

@ -35,14 +35,19 @@ import java.util.List;
public class LibvirtDeleteBackupCommandWrapper extends CommandWrapper<DeleteBackupCommand, Answer, LibvirtComputingResource> {
@Override
public Answer execute(DeleteBackupCommand command, LibvirtComputingResource libvirtComputingResource) {
final String backupStoragePath = command.getBackupStoragePath();
final String backupFolder = command.getBackupPath();
final String backupPath = command.getBackupPath();
final String backupRepoType = command.getBackupRepoType();
final String backupRepoAddress = command.getBackupRepoAddress();
final String mountOptions = command.getMountOptions();
List<String[]> commands = new ArrayList<>();
commands.add(new String[]{
libvirtComputingResource.getNasBackupPath(),
"-d", backupFolder,
"-s", backupStoragePath
"-o", "delete",
"-t", backupRepoType,
"-s", backupRepoAddress,
"-m", mountOptions,
"-p", backupPath
});
Pair<Integer, String> result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout());

View File

@ -29,6 +29,8 @@ import org.apache.cloudstack.backup.BackupAnswer;
import org.apache.cloudstack.backup.TakeBackupCommand;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@ -37,15 +39,20 @@ public class LibvirtTakeBackupCommandWrapper extends CommandWrapper<TakeBackupCo
@Override
public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvirtComputingResource) {
final String vmName = command.getVmName();
final String backupStoragePath = command.getBackupStoragePath();
final String backupFolder = command.getBackupPath();
final String backupPath = command.getBackupPath();
final String backupRepoType = command.getBackupRepoType();
final String backupRepoAddress = command.getBackupRepoAddress();
final String mountOptions = command.getMountOptions();
List<String[]> commands = new ArrayList<>();
commands.add(new String[]{
libvirtComputingResource.getNasBackupPath(),
"-b", vmName,
"-s", backupStoragePath,
"-p", String.format("%s%s%s", vmName, File.separator, backupFolder)
"-b", "backup",
"-v", vmName,
"-t", backupRepoType,
"-s", backupRepoAddress,
"-m", mountOptions,
"-p", backupPath
});
Pair<Integer, String> result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout());

View File

@ -22,16 +22,22 @@ set -e
# TODO: do libvirt/logging etc checks
### Declare variables ###
OP=""
VM=""
NAS_TYPE=""
NAS_ADDRESS=""
MOUNT_OPTS=""
BACKUP_DIR=""
### Operation methods ###
backup_vm() {
vm=$1
path=$2
storage=$3
mount_point=$(mktemp -d -t csbackup.XXXXX)
dest="$mount_point/$path"
dest="$mount_point/${BACKUP_DIR}"
mount -t nfs $storage $mount_point
mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS})
mkdir -p $dest
echo "<domainbackup mode='push'><disks>" > $dest/backup.xml
@ -42,11 +48,11 @@ backup_vm() {
virsh -c qemu:///system backup-begin --domain $vm --backupxml $dest/backup.xml > /dev/null 2>/dev/null
virsh -c qemu:///system dumpxml $vm > $dest/domain-$vm.xml 2>/dev/null
rm -f $dest/backup.xml
until virsh -c qemu:///system domjobinfo $vm --completed 2>/dev/null | grep "Completed" > /dev/null; do
sleep 5
done
rm -f $dest/backup.xml
# Print directory size
sync
@ -56,25 +62,18 @@ backup_vm() {
}
delete_backup() {
path=$1
storage=$2
mount_point=$(mktemp -d -t csbackup.XXXXX)
dest="$mount_point/$path"
dest="$mount_point/${BACKUP_DIR}"
mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS})
mount -t nfs $storage $mount_point
rm -frv $dest
sync
umount $mount_point
rmdir $mount_point
}
OP=""
VM=""
DEST=""
NAS=""
TYPE=""
function usage {
echo ""
echo "Usage: $0 -b <domain> -s <NAS storage mount path> -p <backup dest path>"
@ -84,37 +83,36 @@ function usage {
while [[ $# -gt 0 ]]; do
case $1 in
-b|--backup)
OP="backup"
-o|--operation)
OP="$2"
shift
shift
;;
-v|--vm)
VM="$2"
shift
shift
;;
-d|--delete)
OP="delete"
DEST="$2"
-t|--type)
NAS_TYPE="$2"
shift
shift
;;
-s|--storage)
NAS="$2"
TYPE="nfs"
NAS_ADDRESS="$2"
shift
shift
;;
-m|--mount)
MOUNT_OPTS="$2"
shift
shift
;;
-p|--path)
DEST="$2"
BACKUP_DIR="$2"
shift
shift
;;
-r|--recover)
OP="recover"
shift
;;
-rv|--recover)
OP="recover-volume"
shift
;;
-h|--help)
usage
shift
@ -127,8 +125,7 @@ while [[ $# -gt 0 ]]; do
done
if [ "$OP" = "backup" ]; then
backup_vm $VM $DEST $NAS
backup_vm
elif [ "$OP" = "delete" ]; then
delete_backup $DEST $NAS
delete_backup
fi