Skip to content

Commit c916518

Browse files
committed
add config for using offerings with encryption
Adds a zone-level config to use offerings with encryption while restore --------- Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent da5651a commit c916518

10 files changed

Lines changed: 173 additions & 25 deletions

File tree

api/src/main/java/com/cloud/storage/VolumeApiService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,5 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId,
211211

212212
Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo);
213213

214-
Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone);
214+
Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone, boolean encryptEnabledOnly);
215215
}

api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ public void setMemory(Integer memory) {
205205
this.memory = memory;
206206
}
207207

208+
public void setEncryptRoot(Boolean encryptRoot) {
209+
this.encryptRoot = encryptRoot;
210+
}
211+
208212
/////////////////////////////////////////////////////
209213
/////////////// API Implementation///////////////////
210214
/////////////////////////////////////////////////////

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/VeeamControlService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public interface VeeamControlService extends PluggableService, Configurable {
5151
"false", "Attempt to assign restored Instance to the owner based on OVF and network " +
5252
"details. If the assignment fails or set to false then the Instance will remain owned by the service " +
5353
"account", true);
54+
ConfigKey<Boolean> InstanceEncryptVolumes = new ConfigKey<>("Advanced", Boolean.class,
55+
"integration.veeam.control.instance.encrypt.volumes",
56+
"false", "Whether to always use offerings with encryption enabled when creating volumes " +
57+
"and Instances. If false, offerings will be selected without explicitly considering encryption based on " +
58+
"backup metadata, availability and order.", true, ConfigKey.Scope.Zone);
5459
ConfigKey<String> AllowedClientCidrs = new ConfigKey<>("Advanced", String.class,
5560
"integration.veeam.control.allowed.client.cidrs",
5661
"", "Comma-separated list of CIDR blocks representing clients allowed to access the API. " +

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/VeeamControlServiceImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public ConfigKey<?>[] getConfigKeys() {
149149
Password,
150150
ServiceAccountId,
151151
InstanceRestoreAssignOwner,
152+
InstanceEncryptVolumes,
152153
AllowedClientCidrs,
153154
DeveloperLogs
154155
};

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@
184184
import com.cloud.server.TaggedResourceService;
185185
import com.cloud.service.ServiceOfferingVO;
186186
import com.cloud.service.dao.ServiceOfferingDao;
187+
import com.cloud.storage.DiskOfferingVO;
187188
import com.cloud.storage.GuestOS;
188189
import com.cloud.storage.Storage;
189190
import com.cloud.storage.VMTemplateVO;
190191
import com.cloud.storage.Volume;
191192
import com.cloud.storage.VolumeApiService;
192193
import com.cloud.storage.VolumeVO;
194+
import com.cloud.storage.dao.DiskOfferingDao;
193195
import com.cloud.storage.dao.GuestOSDao;
194196
import com.cloud.storage.dao.VMTemplateDao;
195197
import com.cloud.storage.dao.VolumeDao;
@@ -360,6 +362,9 @@ public class ServerAdapter extends ManagerBase {
360362
@Inject
361363
AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao;
362364

365+
@Inject
366+
DiskOfferingDao diskOfferingDao;
367+
363368
protected static Map<String, Tag> getDummyTags() {
364369
Map<String, Tag> tags = new HashMap<>();
365370
Tag rootTag = ResourceTagVOToTagConverter.getRootTag();
@@ -499,13 +504,13 @@ protected ServiceOfferingVO getServiceOfferingFromRequest(com.cloud.dc.DataCente
499504
try {
500505
accountService.checkAccess(account, offering, zone);
501506
} catch (PermissionDeniedException e) {
502-
logger.warn("Service offering with ID {} linked with the VM request is not accessible for the account {}. Offering: {}, zone: {}",
503-
uuid, account, offering, zone);
507+
logger.warn("{} linked with the VM request is not accessible for the account {} in zone: {}",
508+
offering, account, zone);
504509
return null;
505510
}
506511
if (!offering.isCustomized() && (offering.getCpu() != cpu || offering.getRamSize() != memory)) {
507-
logger.warn("Service offering with ID {} linked with the VM request has different CPU or memory than requested. Offering: {}, requested CPU: {}, requested memory: {}",
508-
uuid, offering, cpu, memory);
512+
logger.warn("{} linked with the VM request has different CPU or memory than requested. Requested CPU: {}, requested memory: {}",
513+
offering, cpu, memory);
509514
return null;
510515
}
511516
if (offering.isCustomized()) {
@@ -518,8 +523,16 @@ protected ServiceOfferingVO getServiceOfferingFromRequest(com.cloud.dc.DataCente
518523
offering.setCpu(cpu);
519524
offering.setRamSize(memory);
520525
} catch (InvalidParameterValueException e) {
521-
logger.warn("Service offering with ID {} linked with the VM request is customized but does not support requested CPU or memory. Offering: {}, requested CPU: {}, requested memory: {}",
522-
uuid, offering, cpu, memory);
526+
logger.warn("{} linked with the VM request is customized but does not support requested CPU or memory. Requested CPU: {}, requested memory: {}",
527+
offering, cpu, memory);
528+
return null;
529+
}
530+
}
531+
if (VeeamControlService.InstanceEncryptVolumes.valueIn(zone.getId())) {
532+
DiskOfferingVO diskOfferingVO = diskOfferingDao.findById(offering.getDiskOfferingId());
533+
if (!diskOfferingVO.getEncrypt()) {
534+
logger.warn("{} is set to true, but {} linked with the VM request does not support encrypted volumes.",
535+
VeeamControlService.InstanceEncryptVolumes.key(), offering);
523536
return null;
524537
}
525538
}
@@ -537,6 +550,11 @@ protected ServiceOffering getServiceOfferingIdForVmCreation(com.cloud.dc.DataCen
537550
cmd.setZoneId(zone.getId());
538551
cmd.setCpuNumber(cpu);
539552
cmd.setMemory(memory);
553+
if (VeeamControlService.InstanceEncryptVolumes.valueIn(zone.getId())) {
554+
logger.debug("{} is set to true, filtering service offerings that support encrypted volumes",
555+
VeeamControlService.InstanceEncryptVolumes.key());
556+
cmd.setEncryptRoot(true);
557+
}
540558
ListResponse<ServiceOfferingResponse> offerings = queryService.searchForServiceOfferings(cmd);
541559
if (offerings.getResponses().isEmpty()) {
542560
return null;
@@ -1625,7 +1643,8 @@ public Disk createDisk(Disk request) {
16251643
if (zone == null || !Grouping.AllocationState.Enabled.equals(zone.getAllocationState())) {
16261644
throw new InvalidParameterValueException("Datacenter for the specified storage domain is not found or not active");
16271645
}
1628-
Long diskOfferingId = volumeApiService.getCustomDiskOfferingIdForVolumeUpload(caller, zone);
1646+
Long diskOfferingId = volumeApiService.getCustomDiskOfferingIdForVolumeUpload(caller, zone,
1647+
VeeamControlService.InstanceEncryptVolumes.valueIn(zone.getId()));
16291648
if (diskOfferingId == null) {
16301649
throw new CloudRuntimeException("Failed to find custom offering for disk" + zone.getName());
16311650
}

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ApiRouteHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,6 @@ protected Api createApiObject(String basePath) {
124124
}
125125

126126
private static void add(List<Link> links, String href, String rel) {
127-
links.add(Link.of(href, rel));
127+
links.add(Link.of(rel, href));
128128
}
129129
}

plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/VeeamControlServiceImplTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void testGetConfigKeysContainsExpectedEntries() {
161161

162162
final ConfigKey<?>[] keys = service.getConfigKeys();
163163

164-
assertEquals(10, keys.length);
164+
assertEquals(11, keys.length);
165165
assertEquals(KEY_ENABLED, keys[0].key());
166166
}
167167

plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/adapter/ServerAdapterTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,8 +643,9 @@ public void testGetServiceOfferingFromRequest_NotCustomized_CpuMemoryMatch_Retur
643643
when(offering.isCustomized()).thenReturn(false);
644644
when(offering.getCpu()).thenReturn(2);
645645
when(offering.getRamSize()).thenReturn(1024);
646+
DataCenterVO zone = mock(DataCenterVO.class);
646647

647-
assertEquals(offering, serverAdapter.getServiceOfferingFromRequest(null, account, "uuid4", 2, 1024));
648+
assertEquals(offering, serverAdapter.getServiceOfferingFromRequest(zone, account, "uuid4", 2, 1024));
648649
}
649650

650651
@Test
@@ -655,8 +656,9 @@ public void testGetServiceOfferingFromRequest_Customized_ValidParams_SetsAndRetu
655656
doNothing().when(accountService).checkAccess(eq(account), eq(offering), any());
656657
when(offering.isCustomized()).thenReturn(true);
657658
doNothing().when(userVmManager).validateCustomParameters(eq(offering), any());
659+
DataCenterVO zone = mock(DataCenterVO.class);
658660

659-
ServiceOfferingVO result = serverAdapter.getServiceOfferingFromRequest(null, account, "uuid5", 2, 1024);
661+
ServiceOfferingVO result = serverAdapter.getServiceOfferingFromRequest(zone, account, "uuid5", 2, 1024);
660662

661663
assertEquals(offering, result);
662664
verify(offering).setCpu(2);

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ public VolumeVO doInTransaction(TransactionStatus status) {
673673

674674
Long volumeDiskOfferingId = diskOfferingId;
675675
if (volumeDiskOfferingId == null) {
676-
volumeDiskOfferingId = getCustomDiskOfferingIdForVolumeUpload(owner, zone);
676+
volumeDiskOfferingId = getCustomDiskOfferingIdForVolumeUpload(owner, zone, false);
677677
if (volumeDiskOfferingId == null) {
678678
throw new CloudRuntimeException(String.format("Unable to find custom disk offering in zone: %s for volume upload", zone.getUuid()));
679679
}
@@ -732,13 +732,23 @@ public String getVolumeNameFromCommand(String userSpecifiedName) {
732732
}
733733

734734
@Override
735-
public Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone) {
735+
public Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone, boolean encryptEnabledOnly) {
736736
Long offeringId = getDefaultCustomOfferingId(owner, zone);
737737
if (offeringId != null) {
738-
return offeringId;
738+
if (encryptEnabledOnly) {
739+
DiskOfferingVO offering = _diskOfferingDao.findById(offeringId);
740+
if (offering != null && offering.getEncrypt()) {
741+
return offeringId;
742+
}
743+
} else {
744+
return offeringId;
745+
}
739746
}
740747
List<DiskOfferingVO> offerings = _diskOfferingDao.listCustomDiskOfferings();
741748
for (DiskOfferingVO offering : offerings) {
749+
if (encryptEnabledOnly && !offering.getEncrypt()) {
750+
continue;
751+
}
742752
try {
743753
_configMgr.checkDiskOfferingAccess(owner, offering, zone);
744754
return offering.getId();

server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,6 @@
4040
import java.util.UUID;
4141
import java.util.concurrent.ExecutionException;
4242

43-
import com.cloud.event.EventTypes;
44-
import com.cloud.event.UsageEventUtils;
45-
import com.cloud.host.HostVO;
46-
import com.cloud.resourcelimit.CheckedReservation;
47-
import com.cloud.service.ServiceOfferingVO;
48-
import com.cloud.service.dao.ServiceOfferingDao;
49-
import com.cloud.vm.snapshot.VMSnapshot;
50-
import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
51-
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
5243
import org.apache.cloudstack.acl.ControlledEntity;
5344
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
5445
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
@@ -102,22 +93,30 @@
10293
import com.cloud.configuration.ConfigurationManager;
10394
import com.cloud.configuration.Resource.ResourceType;
10495
import com.cloud.dc.ClusterVO;
96+
import com.cloud.dc.DataCenter;
10597
import com.cloud.dc.DataCenterVO;
10698
import com.cloud.dc.HostPodVO;
10799
import com.cloud.dc.dao.ClusterDao;
108100
import com.cloud.dc.dao.DataCenterDao;
109101
import com.cloud.dc.dao.HostPodDao;
102+
import com.cloud.event.EventTypes;
103+
import com.cloud.event.UsageEventUtils;
110104
import com.cloud.exception.InvalidParameterValueException;
111105
import com.cloud.exception.PermissionDeniedException;
112106
import com.cloud.exception.ResourceAllocationException;
107+
import com.cloud.host.HostVO;
113108
import com.cloud.host.dao.HostDao;
114109
import com.cloud.hypervisor.Hypervisor.HypervisorType;
110+
import com.cloud.offering.DiskOffering;
115111
import com.cloud.org.Grouping;
116112
import com.cloud.projects.Project;
117113
import com.cloud.projects.ProjectManager;
114+
import com.cloud.resourcelimit.CheckedReservation;
118115
import com.cloud.serializer.GsonHelper;
119116
import com.cloud.server.ManagementService;
120117
import com.cloud.server.TaggedResourceService;
118+
import com.cloud.service.ServiceOfferingVO;
119+
import com.cloud.service.dao.ServiceOfferingDao;
121120
import com.cloud.storage.Storage.ProvisioningType;
122121
import com.cloud.storage.Volume.Type;
123122
import com.cloud.storage.dao.DiskOfferingDao;
@@ -145,8 +144,11 @@
145144
import com.cloud.vm.VirtualMachineManager;
146145
import com.cloud.vm.dao.UserVmDao;
147146
import com.cloud.vm.dao.VMInstanceDao;
147+
import com.cloud.vm.snapshot.VMSnapshot;
148+
import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
148149
import com.cloud.vm.snapshot.VMSnapshotVO;
149150
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
151+
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
150152

151153
@RunWith(MockitoJUnitRunner.class)
152154
public class VolumeApiServiceImplTest {
@@ -467,6 +469,111 @@ public void setup() throws InterruptedException, ExecutionException {
467469
when(_jobMgr.submitAsyncJob(any(AsyncJobVO.class), any(String.class), any(Long.class))).thenReturn(1L);
468470
}
469471

472+
@Test
473+
public void getCustomDiskOfferingIdForVolumeUploadReturnsDefaultOfferingWhenAccessibleAndEncryptionNotRequired() {
474+
Account owner = Mockito.mock(Account.class);
475+
DataCenter zone = Mockito.mock(DataCenter.class);
476+
DiskOfferingVO defaultOffering = Mockito.mock(DiskOfferingVO.class);
477+
478+
Mockito.when(defaultOffering.getState()).thenReturn(DiskOffering.State.Active);
479+
Mockito.when(defaultOffering.getId()).thenReturn(11L);
480+
Mockito.when(_diskOfferingDao.findByUniqueName(anyString())).thenReturn(defaultOffering);
481+
482+
Long result = volumeApiServiceImpl.getCustomDiskOfferingIdForVolumeUpload(owner, zone, false);
483+
484+
Assert.assertEquals(Long.valueOf(11L), result);
485+
Mockito.verify(_diskOfferingDao, Mockito.never()).listCustomDiskOfferings();
486+
}
487+
488+
@Test
489+
public void getCustomDiskOfferingIdForVolumeUploadReturnsDefaultOfferingWhenEncryptedOnlyAndDefaultOfferingIsEncrypted() {
490+
Account owner = Mockito.mock(Account.class);
491+
DataCenter zone = Mockito.mock(DataCenter.class);
492+
DiskOfferingVO defaultOffering = Mockito.mock(DiskOfferingVO.class);
493+
494+
Mockito.when(defaultOffering.getState()).thenReturn(DiskOffering.State.Active);
495+
Mockito.when(defaultOffering.getId()).thenReturn(12L);
496+
Mockito.when(_diskOfferingDao.findByUniqueName(anyString())).thenReturn(defaultOffering);
497+
Mockito.when(_diskOfferingDao.findById(12L)).thenReturn(defaultOffering);
498+
Mockito.when(defaultOffering.getEncrypt()).thenReturn(true);
499+
500+
Long result = volumeApiServiceImpl.getCustomDiskOfferingIdForVolumeUpload(owner, zone, true);
501+
502+
Assert.assertEquals(Long.valueOf(12L), result);
503+
Mockito.verify(_diskOfferingDao, Mockito.never()).listCustomDiskOfferings();
504+
}
505+
506+
@Test
507+
public void getCustomDiskOfferingIdForVolumeUploadFallsBackToEncryptedCustomOfferingWhenDefaultOfferingNotEncrypted() {
508+
Account owner = Mockito.mock(Account.class);
509+
DataCenter zone = Mockito.mock(DataCenter.class);
510+
DiskOfferingVO defaultOffering = Mockito.mock(DiskOfferingVO.class);
511+
DiskOfferingVO nonEncryptedOffering = Mockito.mock(DiskOfferingVO.class);
512+
DiskOfferingVO encryptedOffering = Mockito.mock(DiskOfferingVO.class);
513+
514+
Mockito.when(defaultOffering.getState()).thenReturn(DiskOffering.State.Active);
515+
Mockito.when(defaultOffering.getId()).thenReturn(13L);
516+
Mockito.when(_diskOfferingDao.findByUniqueName(anyString())).thenReturn(defaultOffering);
517+
Mockito.when(_diskOfferingDao.findById(13L)).thenReturn(defaultOffering);
518+
Mockito.when(defaultOffering.getEncrypt()).thenReturn(false);
519+
520+
Mockito.when(nonEncryptedOffering.getEncrypt()).thenReturn(false);
521+
Mockito.when(encryptedOffering.getEncrypt()).thenReturn(true);
522+
Mockito.when(encryptedOffering.getId()).thenReturn(23L);
523+
Mockito.when(_diskOfferingDao.listCustomDiskOfferings()).thenReturn(List.of(nonEncryptedOffering, encryptedOffering));
524+
525+
Long result = volumeApiServiceImpl.getCustomDiskOfferingIdForVolumeUpload(owner, zone, true);
526+
527+
Assert.assertEquals(Long.valueOf(23L), result);
528+
Mockito.verify(_configMgr).checkDiskOfferingAccess(owner, encryptedOffering, zone);
529+
Mockito.verify(_configMgr, Mockito.never()).checkDiskOfferingAccess(owner, nonEncryptedOffering, zone);
530+
}
531+
532+
@Test
533+
public void getCustomDiskOfferingIdForVolumeUploadFallsBackWhenDefaultOfferingIsNotAccessible() {
534+
Account owner = Mockito.mock(Account.class);
535+
DataCenter zone = Mockito.mock(DataCenter.class);
536+
DiskOfferingVO defaultOffering = Mockito.mock(DiskOfferingVO.class);
537+
DiskOfferingVO deniedOffering = Mockito.mock(DiskOfferingVO.class);
538+
DiskOfferingVO allowedOffering = Mockito.mock(DiskOfferingVO.class);
539+
540+
Mockito.when(defaultOffering.getState()).thenReturn(DiskOffering.State.Active);
541+
Mockito.when(_diskOfferingDao.findByUniqueName(anyString())).thenReturn(defaultOffering);
542+
Mockito.doThrow(new PermissionDeniedException("default denied")).when(_configMgr).checkDiskOfferingAccess(owner, defaultOffering, zone);
543+
544+
Mockito.when(allowedOffering.getId()).thenReturn(24L);
545+
Mockito.when(_diskOfferingDao.listCustomDiskOfferings()).thenReturn(List.of(deniedOffering, allowedOffering));
546+
Mockito.doThrow(new PermissionDeniedException("denied")).when(_configMgr).checkDiskOfferingAccess(owner, deniedOffering, zone);
547+
548+
Long result = volumeApiServiceImpl.getCustomDiskOfferingIdForVolumeUpload(owner, zone, false);
549+
550+
Assert.assertEquals(Long.valueOf(24L), result);
551+
Mockito.verify(_configMgr).checkDiskOfferingAccess(owner, deniedOffering, zone);
552+
Mockito.verify(_configMgr).checkDiskOfferingAccess(owner, allowedOffering, zone);
553+
}
554+
555+
@Test
556+
public void getCustomDiskOfferingIdForVolumeUploadReturnsNullWhenNoAccessibleEligibleCustomOfferingExists() {
557+
Account owner = Mockito.mock(Account.class);
558+
DataCenter zone = Mockito.mock(DataCenter.class);
559+
DiskOfferingVO defaultOffering = Mockito.mock(DiskOfferingVO.class);
560+
DiskOfferingVO firstEncrypted = Mockito.mock(DiskOfferingVO.class);
561+
DiskOfferingVO secondEncrypted = Mockito.mock(DiskOfferingVO.class);
562+
563+
Mockito.when(defaultOffering.getState()).thenReturn(DiskOffering.State.Inactive);
564+
Mockito.when(_diskOfferingDao.findByUniqueName(anyString())).thenReturn(defaultOffering);
565+
566+
Mockito.when(firstEncrypted.getEncrypt()).thenReturn(true);
567+
Mockito.when(secondEncrypted.getEncrypt()).thenReturn(true);
568+
Mockito.when(_diskOfferingDao.listCustomDiskOfferings()).thenReturn(List.of(firstEncrypted, secondEncrypted));
569+
Mockito.doThrow(new PermissionDeniedException("denied")).when(_configMgr).checkDiskOfferingAccess(owner, firstEncrypted, zone);
570+
Mockito.doThrow(new PermissionDeniedException("denied")).when(_configMgr).checkDiskOfferingAccess(owner, secondEncrypted, zone);
571+
572+
Long result = volumeApiServiceImpl.getCustomDiskOfferingIdForVolumeUpload(owner, zone, true);
573+
574+
Assert.assertNull(result);
575+
}
576+
470577
/**
471578
* TESTS FOR DETACH ROOT VOLUME, COUNT=4
472579
*/
@@ -2189,7 +2296,7 @@ public void testCreateVolumeOnSecondaryForAttachIfNeeded_NoSuitablePool_ThrowsEx
21892296
}
21902297

21912298
@Test
2192-
public void testCreateVolumeOnSecondaryForAttachIfNeeded_NoSuitablePool_ReturnSameVolumeInfo() {
2299+
public void testCreateVolumeOnSecondaryForAttachIfNeeded_NoSuitablePool_ReturnsSameVolumeInfo() {
21932300
VolumeInfo volumeToAttach = Mockito.mock(VolumeInfo.class);
21942301
Mockito.when(volumeToAttach.getState()).thenReturn(Volume.State.Allocated);
21952302
UserVmVO vm = Mockito.mock(UserVmVO.class);

0 commit comments

Comments
 (0)