Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 api/src/main/java/com/cloud/storage/Snapshot.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public interface Snapshot extends ControlledEntity, Identity, InternalIdentity, StateObject<Snapshot.State> {
public enum Type {
MANUAL, RECURRING, TEMPLATE, HOURLY, DAILY, WEEKLY, MONTHLY, INTERNAL;
// New types should be defined after INTERNAL, and change the max value
private int max = 8;

public void setMax(int max) {
Expand Down
5 changes: 2 additions & 3 deletions 5 api/src/main/java/com/cloud/storage/VolumeApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.util.Map;

import com.cloud.exception.StorageUnavailableException;
import org.apache.cloudstack.api.command.user.vm.CloneVMCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
Expand Down Expand Up @@ -94,7 +93,7 @@ public interface VolumeApiService {

Volume detachVolumeViaDestroyVM(long vmId, long volumeId);

Volume cloneDataVolume(CloneVMCmd cmd, long snapshotId, Volume volume) throws StorageUnavailableException;
Volume cloneDataVolume(long vmId, long snapshotId, Volume volume) throws StorageUnavailableException;

Volume detachVolumeFromVM(DetachVolumeCmd cmd);

Expand All @@ -105,7 +104,7 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc

Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo);

Volume attachVolumeToVm(CloneVMCmd cmd, Long volumeId, Long deviceId);
Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId);

/**
* Extracts the volume to a particular location.
Expand Down
1 change: 1 addition & 0 deletions 1 api/src/main/java/com/cloud/vm/UserVmService.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public interface UserVmService {

void checkCloneCondition(CloneVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ResourceAllocationException;

void prepareCloneVirtualMachine(CloneVMCmd cmd) throws ResourceAllocationException, InsufficientCapacityException, ResourceUnavailableException;
/**
* Resets the password of a virtual machine.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@
import org.apache.cloudstack.api.ApiCommandJobType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCreateCustomIdCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
//import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;

import java.util.Optional;

@APICommand(name = "cloneVirtualMachine", responseObject = UserVmResponse.class, description = "clone a virtual VM in full clone mode",
responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, entityType = {VirtualMachine.class})
public class CloneVMCmd extends BaseAsyncCreateCustomIdCmd implements UserCmd {
@APICommand(name = "cloneVirtualMachine", responseObject = UserVmResponse.class, description = "clone a virtual VM",
responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, entityType = {VirtualMachine.class}, since="4.16.0")
public class CloneVMCmd extends BaseAsyncCreateCmd implements UserCmd {
public static final Logger s_logger = Logger.getLogger(CloneVMCmd.class.getName());
private static final String s_name = "clonevirtualmachineresponse";

Expand Down Expand Up @@ -102,19 +101,7 @@ public void setTemporaryTemlateId(long tempId) {
public void create() throws ResourceAllocationException {
try {
_userVmService.checkCloneCondition(this);
VirtualMachineTemplate template = _templateService.createPrivateTemplateRecord(this, _accountService.getAccount(getEntityOwnerId()), _volumeService);
if (template == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "failed to create a template to db");
}
s_logger.info("The template id recorded is: " + template.getId());
setTemporaryTemlateId(template.getId());
_templateService.createPrivateTemplate(this);
UserVm vmRecord = _userVmService.recordVirtualMachineToDB(this);
if (vmRecord == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "unable to record a new VM to db!");
}
setEntityId(vmRecord.getId());
setEntityUuid(vmRecord.getUuid());
_userVmService.prepareCloneVirtualMachine(this);
}
catch (ResourceUnavailableException | InsufficientCapacityException e) {
s_logger.warn("Exception: ", e);
Expand All @@ -126,11 +113,6 @@ public void create() throws ResourceAllocationException {
throw new ServerApiException(e.getErrorCode(), e.getDescription());
} catch (CloudRuntimeException e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
} finally {
if (getTemporarySnapShotId() != null) {
_snapshotService.deleteSnapshot(getTemporarySnapShotId());
s_logger.warn("clearing the temporary snapshot: " + getTemporarySnapShotId());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
import java.util.concurrent.ExecutionException;

import javax.inject.Inject;

import org.apache.cloudstack.api.command.user.vm.CloneVMCmd;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
Expand Down Expand Up @@ -910,8 +908,7 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) {
}

@Override
public Volume cloneDataVolume(CloneVMCmd cmd, long snapshotId, Volume volume) throws StorageUnavailableException {
long vmId = cmd.getEntityId();
public Volume cloneDataVolume(long vmId, long snapshotId, Volume volume) throws StorageUnavailableException {
return createVolumeFromSnapshot((VolumeVO) volume, snapshotId, vmId);
}

Expand Down Expand Up @@ -1706,10 +1703,6 @@ private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long device
}

@Override
public Volume attachVolumeToVm(CloneVMCmd cmd, Long volumeId, Long deviceId) {
return attachVolumeToVM(cmd.getEntityId(), volumeId, deviceId);
}

public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
Account caller = CallContext.current().getCallingAccount();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,12 @@ public VirtualMachineTemplate createPrivateTemplate(CloneVMCmd cmd) throws Cloud
s_logger.info("successfully created the template with Id: " + templateId);
finalTmpProduct = _tmpltDao.findById(templateId);
TemplateDataStoreVO srcTmpltStore = _tmplStoreDao.findByStoreTemplate(store.getId(), templateId);
try {
srcTmpltStore.getSize();
} catch (NullPointerException e) {
srcTmpltStore.setSize(0L);
_tmplStoreDao.update(srcTmpltStore.getId(), srcTmpltStore);
}
UsageEventVO usageEvent =
new UsageEventVO(EventTypes.EVENT_TEMPLATE_CREATE, finalTmpProduct.getAccountId(), zoneId, finalTmpProduct.getId(), finalTmpProduct.getName(), null,
finalTmpProduct.getSourceTemplateId(), srcTmpltStore.getPhysicalSize(), finalTmpProduct.getSize());
Expand Down
40 changes: 34 additions & 6 deletions 40 server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private BackupDao backupDao;
@Inject
private BackupManager backupManager;
@Inject
private SnapshotApiService _snapshotService;

private ScheduledExecutorService _executor = null;
private ScheduledExecutorService _vmIpFetchExecutor = null;
Expand Down Expand Up @@ -4630,11 +4632,36 @@ private VolumeVO saveDataDiskVolumeFromSnapShot(final Account owner, final Boole
});
}

@Override
public void prepareCloneVirtualMachine(CloneVMCmd cmd) throws ResourceAllocationException, ResourceUnavailableException, InsufficientCapacityException {
try {
VirtualMachineTemplate template = _tmplService.createPrivateTemplateRecord(cmd, _accountService.getAccount(cmd.getEntityOwnerId()), _volumeService);
if (template == null) {
throw new CloudRuntimeException("failed to create a template to db");
}
s_logger.info("The template id recorded is: " + template.getId());
cmd.setTemporaryTemlateId(template.getId());
_tmplService.createPrivateTemplate(cmd);
UserVm vmRecord = recordVirtualMachineToDB(cmd);
if (vmRecord == null) {
throw new CloudRuntimeException("Unable to record the VM to DB!");
}
cmd.setEntityUuid(vmRecord.getUuid());
cmd.setEntityId(vmRecord.getId());
} finally {
if (cmd.getTemporarySnapShotId() != null) {
_snapshotService.deleteSnapshot(cmd.getTemporarySnapShotId());
s_logger.warn("clearing the temporary snapshot: " + cmd.getTemporarySnapShotId());
}
}
}

@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_CLONE, eventDescription = "clone vm", async = true)
public Optional<UserVm> cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService volumeService, SnapshotApiService snapshotService) throws ResourceUnavailableException, ConcurrentOperationException, CloudRuntimeException, InsufficientCapacityException, ResourceAllocationException {
long vmId = cmd.getEntityId();
UserVmVO curVm = _vmDao.findById(vmId);
Account curVmAccount = _accountDao.findById(curVm.getAccountId());
// create and attach data disk
long targetClonedVmId = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
Expand Down Expand Up @@ -4669,14 +4696,15 @@ public Optional<UserVm> cloneVirtualMachine(CloneVMCmd cmd, VolumeApiService vol
DataCenterVO dataCenter = _dcDao.findById(zoneId);
String volumeName = snapshotEntity.getName() + "-DataDisk-Volume";
VolumeVO parentVolume = _volsDao.findByIdIncludingRemoved(snapshotEntity.getVolumeId());
newDatadisk = saveDataDiskVolumeFromSnapShot(caller, true, zoneId,
newDatadisk = saveDataDiskVolumeFromSnapShot(curVmAccount, true, zoneId,
diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, volumeName, _uuidMgr.generateUuid(Volume.class, null), new HashMap<>());
VolumeVO volumeEntity = (VolumeVO) volumeService.cloneDataVolume(cmd, snapshotEntity.getId(), newDatadisk);
VolumeVO volumeEntity = (VolumeVO) volumeService.cloneDataVolume(cmd.getEntityId(), snapshotEntity.getId(), newDatadisk);
createdVolumes.add(volumeEntity);
}

for (VolumeVO createdVol : createdVolumes) {
volumeService.attachVolumeToVm(cmd, createdVol.getId(), createdVol.getDeviceId());
// volumeService.attachVolumeToVm(cmd, createdVol.getId(), createdVol.getDeviceId());
volumeService.attachVolumeToVM(cmd.getEntityId(), createdVol.getId(), createdVol.getDeviceId());
}
} catch (CloudRuntimeException e){
s_logger.warn("data disk process failed during clone, clearing the temporary resources...");
Expand Down Expand Up @@ -5755,18 +5783,18 @@ public UserVm recordVirtualMachineToDB(CloneVMCmd cmd) throws ConcurrentOperatio
if (dataCenter.getNetworkType() == NetworkType.Basic) {
vmResult = createBasicSecurityGroupVirtualMachine(dataCenter, serviceOffering, template, securityGroupIdList, curAccount, hostName, displayName, diskOfferingId,
size, group, hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard, affinityGroupIdList,
curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), cmd.getCustomId(), new HashMap<>(),
curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), null, new HashMap<>(),
null, new HashMap<>(), dynamicScalingEnabled);
} else {
if (dataCenter.isSecurityGroupEnabled()) {
vmResult = createAdvancedSecurityGroupVirtualMachine(dataCenter, serviceOffering, template, networkIds, securityGroupIdList, curAccount, hostName,
displayName, diskOfferingId, size, group, hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard,
affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), cmd.getCustomId(), new HashMap<>(),
affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(), null, new HashMap<>(),
null, new HashMap<>(), dynamicScalingEnabled);
} else {
vmResult = createAdvancedVirtualMachine(dataCenter, serviceOffering, template, networkIds, curAccount, hostName, displayName, diskOfferingId, size, group,
hypervisorType, cmd.getHttpMethod(), userData, sshKeyPair, ipToNetoworkMap, addr, isDisplayVM, keyboard, affinityGroupIdList, curVm.getDetails() == null ? new HashMap<>() : curVm.getDetails(),
cmd.getCustomId(), new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled);
null, new HashMap<>(), null, new HashMap<>(), dynamicScalingEnabled);
}
}
} catch (CloudRuntimeException e) {
Expand Down
127 changes: 126 additions & 1 deletion 127 test/integration/smoke/test_vm_life_cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,6 @@ def test_11_destroy_vm_and_volumes(self):

self.assertEqual(Volume.list(self.apiclient, id=vol1.id), None, "List response contains records when it should not")


class TestSecuredVmMigration(cloudstackTestCase):

@classmethod
Expand Down Expand Up @@ -1943,3 +1942,129 @@ def test_01_vapps_vm_cycle(self):
cmd = destroyVirtualMachine.destroyVirtualMachineCmd()
cmd.id = vm.id
self.apiclient.destroyVirtualMachine(cmd)

class TestCloneVM(cloudstackTestCase):

@classmethod
def setUpClass(cls):
testClient = super(TestCloneVM, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.hypervisor = testClient.getHypervisorInfo()

# Get Zone, Domain and templates
domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
cls.services['mode'] = cls.zone.networktype

# if local storage is enabled, alter the offerings to use localstorage
# this step is needed for devcloud
if cls.zone.localstorageenabled == True:
cls.services["service_offerings"]["tiny"]["storagetype"] = 'local'
cls.services["service_offerings"]["small"]["storagetype"] = 'local'
cls.services["service_offerings"]["medium"]["storagetype"] = 'local'

template = get_suitable_test_template(
cls.apiclient,
cls.zone.id,
cls.services["ostype"],
cls.hypervisor
)
if template == FAILED:
assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"]

# Set Zones and disk offerings
cls.services["small"]["zoneid"] = cls.zone.id
cls.services["small"]["template"] = template.id

cls.services["iso1"]["zoneid"] = cls.zone.id

# Create VMs, NAT Rules etc
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
domainid=domain.id
)

cls.small_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["small"]
)

cls.medium_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["medium"]
)
# create small and large virtual machines
cls.small_virtual_machine = VirtualMachine.create(
cls.apiclient,
cls.services["small"],
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.small_offering.id,
mode=cls.services["mode"]
)
cls.medium_virtual_machine = VirtualMachine.create(
cls.apiclient,
cls.services["small"],
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.medium_offering.id,
mode=cls.services["mode"]
)
cls.virtual_machine = VirtualMachine.create(
cls.apiclient,
cls.services["small"],
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.small_offering.id,
mode=cls.services["mode"]
)
cls._cleanup = [
cls.small_offering,
cls.medium_offering,
cls.account
]

@classmethod
def tearDownClass(cls):
super(TestCloneVM, cls).tearDownClass()

def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()
self.cleanup = []

def tearDown(self):
try:
# Clean up, terminate the created ISOs
cleanup_resources(self.apiclient, self.cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return

@attr(tags = ["clone","devcloud", "advanced", "smoke", "basic", "sg"], required_hardware="false")
def test_clone_vm_and_volumes(self):
small_disk_offering = DiskOffering.list(self.apiclient, name='Small')[0];
small_virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["small"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.small_offering.id,)
vol1 = Volume.create(
self.apiclient,
self.services,
account=self.account.name,
diskofferingid=small_disk_offering.id,
domainid=self.account.domainid,
zoneid=self.zone.id
)
small_virtual_machine.attach_volume(self.apiclient, vol1)
self.debug("Clone VM - ID: %s" % small_virtual_machine.id)
try:
clone_response = small_virtual_machine.clone(self.apiclient, small_virtual_machine)
except Exception as e:
self.debug("Clone --" + str(e))
raise e
self.assertTrue(VirtualMachine.list(self.apiclient, id=clone_response.id) is not None, "vm id should be populated")
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.