Skip to content

Commit 35ed72c

Browse files
committed
Initial api and service layer changes
1 parent 54bc150 commit 35ed72c

File tree

10 files changed

+376
-15
lines changed

10 files changed

+376
-15
lines changed

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ public class EventTypes {
606606
public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE";
607607
public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE";
608608
public static final String EVENT_VM_BACKUP_RESTORE = "BACKUP.RESTORE";
609+
public static final String EVENT_VM_BACKUP_RESTORE_TO_NEW_VM = "BACKUP.RESTORE.TO.NEW.VM";
609610
public static final String EVENT_VM_BACKUP_DELETE = "BACKUP.DELETE";
610611
public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "BACKUP.RESTORE.VOLUME.TO.VM";
611612
public static final String EVENT_VM_BACKUP_SCHEDULE_CONFIGURE = "BACKUP.SCHEDULE.CONFIGURE";

api/src/main/java/com/cloud/vm/UserVmService.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
2525
import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd;
2626
import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd;
27+
import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd;
2728
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
2829
import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
2930
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
@@ -412,8 +413,7 @@ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffe
412413
void deletePrivateTemplateRecord(Long templateId);
413414

414415
HypervisorType getHypervisorTypeOfUserVM(long vmid);
415-
416-
UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException,
416+
UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException,
417417
StorageUnavailableException, ResourceAllocationException;
418418

419419
/**
@@ -513,4 +513,8 @@ UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemp
513513
* @return true if the VM is successfully unmanaged, false if not.
514514
*/
515515
boolean unmanageUserVM(Long vmId);
516+
517+
UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException;
518+
519+
UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException;
516520
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.user.vm;
18+
19+
import javax.inject.Inject;
20+
21+
import org.apache.cloudstack.api.APICommand;
22+
import org.apache.cloudstack.api.ApiConstants;
23+
import org.apache.cloudstack.api.ApiErrorCode;
24+
import org.apache.cloudstack.api.Parameter;
25+
import org.apache.cloudstack.api.ResponseObject;
26+
import org.apache.cloudstack.api.ServerApiException;
27+
import org.apache.cloudstack.api.command.user.UserCmd;
28+
import org.apache.cloudstack.api.response.BackupResponse;
29+
import org.apache.cloudstack.api.response.UserVmResponse;
30+
import org.apache.cloudstack.backup.BackupManager;
31+
32+
import com.cloud.uservm.UserVm;
33+
import com.cloud.vm.VirtualMachine;
34+
35+
@APICommand(name = "createVMFromBackup",
36+
description = "Creates and automatically starts a VM from a backup.",
37+
responseObject = UserVmResponse.class,
38+
responseView = ResponseObject.ResponseView.Restricted,
39+
entityType = {VirtualMachine.class},
40+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true,
41+
since = "4.21.0")
42+
public class CreateVMFromBackupCmd extends DeployVMCmd implements UserCmd {
43+
44+
@Inject
45+
BackupManager backupManager;
46+
47+
@Parameter(name = ApiConstants.BACKUP_ID,
48+
type = CommandType.UUID,
49+
entityType = BackupResponse.class,
50+
required = true,
51+
description = "backup ID to create the VM from")
52+
private Long backupId;
53+
54+
public Long getBackupId() {
55+
return backupId;
56+
}
57+
58+
@Override
59+
public void create() {
60+
UserVm vm;
61+
try {
62+
vm = _userVmService.allocateVMFromBackup(this);
63+
if (vm != null) {
64+
setEntityId(vm.getId());
65+
setEntityUuid(vm.getUuid());
66+
}
67+
} catch (Exception e) {
68+
}
69+
}
70+
71+
@Override
72+
public void execute () {
73+
UserVm vm = null;
74+
try {
75+
vm = _userVmService.restoreVMFromBackup(this);
76+
} catch (Exception e) {
77+
}
78+
if (vm != null) {
79+
UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", vm).get(0);
80+
response.setResponseName(getCommandName());
81+
setResponseObject(response);
82+
} else {
83+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to deploy vm uuid:"+getEntityUuid());
84+
}
85+
}
86+
}

api/src/main/java/org/apache/cloudstack/backup/BackupManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.cloudstack.framework.config.ConfigKey;
2828
import org.apache.cloudstack.framework.config.Configurable;
2929

30+
import com.cloud.exception.ResourceUnavailableException;
3031
import com.cloud.utils.Pair;
3132
import com.cloud.utils.component.Manager;
3233
import com.cloud.utils.component.PluggableService;
@@ -133,6 +134,11 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
133134
*/
134135
boolean restoreBackup(final Long backupId);
135136

137+
/**
138+
* Restore a backup to a new Instance
139+
*/
140+
boolean restoreBackupToVM(Long backupId, Long vmId) throws ResourceUnavailableException;
141+
136142
/**
137143
* Restore a backed up volume and attach it to a VM
138144
*/
@@ -146,5 +152,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
146152
*/
147153
boolean deleteBackup(final Long backupId, final Boolean forced);
148154

155+
void validateBackupForZone(Long zoneId);
156+
149157
BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupOfferingCmd);
150158
}

api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public interface BackupProvider {
8585
*/
8686
boolean deleteBackup(Backup backup, boolean forced);
8787

88+
boolean restoreBackupToVM(VirtualMachine vm, Backup backup);
89+
8890
/**
8991
* Restore VM from backup
9092
*/

plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,9 @@ public boolean deleteBackup(Backup backup, boolean forced) {
138138
@Override
139139
public void syncBackups(VirtualMachine vm, Backup.Metric metric) {
140140
}
141+
142+
@Override
143+
public boolean restoreBackupToVM(VirtualMachine vm, Backup backup) {
144+
return true;
145+
}
141146
}

plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,4 +647,9 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
647647

648648
@Override
649649
public boolean willDeleteBackupsOnOfferingRemoval() { return false; }
650+
651+
@Override
652+
public boolean restoreBackupToVM(VirtualMachine vm, Backup backup) {
653+
return true;
654+
}
650655
}

plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
397397
});
398398
}
399399

400+
@Override
401+
public boolean restoreBackupToVM(VirtualMachine vm, Backup backup) {
402+
return true;
403+
}
404+
400405
@Override
401406
public String getConfigComponentName() {
402407
return BackupService.class.getSimpleName();

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.apache.cloudstack.api.command.admin.vm.ExpungeVMCmd;
7575
import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd;
7676
import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd;
77+
import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd;
7778
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
7879
import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
7980
import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
@@ -96,6 +97,7 @@
9697
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
9798
import org.apache.cloudstack.backup.Backup;
9899
import org.apache.cloudstack.backup.BackupManager;
100+
import org.apache.cloudstack.backup.BackupVO;
99101
import org.apache.cloudstack.backup.dao.BackupDao;
100102
import org.apache.cloudstack.context.CallContext;
101103
import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity;
@@ -8725,6 +8727,172 @@ public boolean unmanageUserVM(Long vmId) {
87258727
return true;
87268728
}
87278729

8730+
@Override
8731+
public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException {
8732+
//Verify that all objects exist before passing them to the service
8733+
Account owner = _accountService.getActiveAccountById(cmd.getEntityOwnerId());
8734+
Long zoneId = cmd.getZoneId();
8735+
DataCenter zone = _entityMgr.findById(DataCenter.class, zoneId);
8736+
if (zone == null) {
8737+
throw new InvalidParameterValueException("Unable to find zone by id=" + zoneId);
8738+
}
8739+
8740+
BackupVO backup = backupDao.findById(cmd.getBackupId());
8741+
if (backup == null) {
8742+
throw new InvalidParameterValueException("Backup " + cmd.getBackupId() + " does not exist");
8743+
}
8744+
if (backup.getZoneId() != cmd.getZoneId()) {
8745+
throw new InvalidParameterValueException("Instance should be created in the same zone as the backup");
8746+
}
8747+
backupManager.validateBackupForZone(backup.getZoneId());
8748+
8749+
verifyDetails(cmd.getDetails());
8750+
8751+
Long serviceOfferingId = cmd.getServiceOfferingId();
8752+
Long overrideDiskOfferingId = cmd.getOverrideDiskOfferingId();
8753+
8754+
ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId);
8755+
if (serviceOffering == null) {
8756+
throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId);
8757+
}
8758+
8759+
if (ServiceOffering.State.Inactive.equals(serviceOffering.getState())) {
8760+
throw new InvalidParameterValueException(String.format("Service offering is inactive: [%s].", serviceOffering.getUuid()));
8761+
}
8762+
8763+
if (serviceOffering.getDiskOfferingStrictness() && overrideDiskOfferingId != null) {
8764+
throw new InvalidParameterValueException(String.format("Cannot override disk offering id %d since provided service offering is strictly mapped to its disk offering", overrideDiskOfferingId));
8765+
}
8766+
8767+
if (!serviceOffering.isDynamic()) {
8768+
for(String detail: cmd.getDetails().keySet()) {
8769+
if(detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) {
8770+
throw new InvalidParameterValueException("cpuNumber or cpuSpeed or memory should not be specified for static service offering");
8771+
}
8772+
}
8773+
}
8774+
8775+
Long templateId = cmd.getTemplateId();
8776+
VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, templateId);
8777+
// todo : check for dummy template
8778+
if (template == null) {
8779+
throw new InvalidParameterValueException("Unable to use template " + templateId);
8780+
}
8781+
8782+
if (template.isDeployAsIs()) {
8783+
throw new InvalidParameterValueException("Deploy as is template not supported");
8784+
}
8785+
8786+
Long diskOfferingId = cmd.getDiskOfferingId();
8787+
DiskOffering diskOffering = null;
8788+
if (diskOfferingId != null) {
8789+
diskOffering = _entityMgr.findById(DiskOffering.class, diskOfferingId);
8790+
if (diskOffering == null) {
8791+
throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingId);
8792+
}
8793+
if (diskOffering.isComputeOnly()) {
8794+
throw new InvalidParameterValueException(String.format("The disk offering id %d provided is directly mapped to a service offering, please provide an individual disk offering", diskOfferingId));
8795+
}
8796+
}
8797+
8798+
DiskOffering diskOfferingMappedInServiceOffering = _entityMgr.findById(DiskOffering.class, serviceOffering.getDiskOfferingId());
8799+
if (diskOfferingMappedInServiceOffering.isUseLocalStorage()) {
8800+
throw new InvalidParameterValueException("Local storage disk offering not supported for instance created from backup");
8801+
}
8802+
if (diskOffering != null && diskOffering.isUseLocalStorage()) {
8803+
throw new InvalidParameterValueException("Local storage disk offering not supported for instance created from backup");
8804+
}
8805+
8806+
List<Long> networkIds = cmd.getNetworkIds();
8807+
LinkedHashMap<Integer, Long> userVmNetworkMap = getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap());
8808+
if (MapUtils.isNotEmpty(userVmNetworkMap)) {
8809+
networkIds = new ArrayList<>(userVmNetworkMap.values());
8810+
}
8811+
8812+
if (cmd.getUserData() != null) {
8813+
throw new InvalidParameterValueException("User data not supported for instance created from backup");
8814+
}
8815+
8816+
String name = cmd.getName();
8817+
String displayName = cmd.getDisplayName();
8818+
Long size = cmd.getSize();
8819+
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
8820+
List<String> sshKeyPairs = new ArrayList<String>();
8821+
String ipAddress = cmd.getIpAddress();
8822+
String ip6Address = cmd.getIp6Address();
8823+
String macAddress = cmd.getMacAddress();
8824+
IpAddresses addrs = new IpAddresses(ipAddress, ip6Address, macAddress);
8825+
Map<String, String> userVmOVFProperties = new HashMap<>();
8826+
8827+
UserVm vm = null;
8828+
if (zone.getNetworkType() == NetworkType.Basic) {
8829+
if (networkIds != null) {
8830+
throw new InvalidParameterValueException("Can't specify network Ids in Basic zone");
8831+
} else {
8832+
vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId,
8833+
size , null , cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, cmd.getIpToNetworkMap(), addrs, null , null , cmd.getAffinityGroupIdList(),
8834+
cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(),
8835+
dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, overrideDiskOfferingId);
8836+
}
8837+
} else {
8838+
if (_networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
8839+
cmd.getSecurityGroupIdList())) {
8840+
vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name,
8841+
displayName, diskOfferingId, size, null, cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, cmd.getIpToNetworkMap(), addrs, null, null,
8842+
cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(),
8843+
dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, overrideDiskOfferingId, null);
8844+
8845+
} else {
8846+
if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) {
8847+
throw new InvalidParameterValueException("Can't create vm with security groups; security group feature is not enabled per zone");
8848+
}
8849+
vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, null,
8850+
cmd.getHypervisor(), cmd.getHttpMethod(), null, null, null, sshKeyPairs, cmd.getIpToNetworkMap(), addrs, null, null, cmd.getAffinityGroupIdList(), cmd.getDetails(),
8851+
cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, false, null, overrideDiskOfferingId);
8852+
}
8853+
}
8854+
8855+
return vm;
8856+
}
8857+
8858+
@Override
8859+
public UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
8860+
long vmId = cmd.getEntityId();
8861+
Map<Long, DiskOffering> diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
8862+
Map<VirtualMachineProfile.Param, Object> additonalParams = new HashMap<>();
8863+
UserVm vm;
8864+
try {
8865+
vm = startVirtualMachine(vmId, null, null, null, diskOfferingMap, additonalParams, null);
8866+
} catch (ResourceUnavailableException e) {
8867+
throw new CloudRuntimeException("Unable to start the instance " + e);
8868+
}
8869+
boolean status = false;
8870+
try {
8871+
VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid());
8872+
status = vmEntity.stop(Long.toString(CallContext.current().getCallingUserId()));
8873+
if (!status) {
8874+
// todo : error handling
8875+
}
8876+
} catch (ResourceUnavailableException e) {
8877+
throw new CloudRuntimeException("Unable to contact the agent to stop the instance before restore " + e);
8878+
// todo : error handling
8879+
} catch (CloudException e) {
8880+
throw new CloudRuntimeException("Unable to contact the agent to stop the instance before restore " + e);
8881+
// todo : error handling
8882+
}
8883+
8884+
backupManager.restoreBackupToVM(cmd.getBackupId(), vmId);
8885+
8886+
if (cmd.getStartVm()) {
8887+
try {
8888+
vm = startVirtualMachine(vmId, null, null, null, diskOfferingMap, additonalParams, null);
8889+
} catch (ResourceUnavailableException e) {
8890+
throw new CloudRuntimeException("Unable to start the instance " + e);
8891+
}
8892+
}
8893+
return vm;
8894+
}
8895+
87288896
/*
87298897
Generate usage events related to unmanaging a VM
87308898
*/

0 commit comments

Comments
 (0)