mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Create API to reassign volume (#6938)
This commit is contained in:
parent
46924a5782
commit
61a722548f
@ -21,6 +21,7 @@ package com.cloud.storage;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
@ -119,6 +120,8 @@ public interface VolumeApiService {
|
||||
*/
|
||||
String extractVolume(ExtractVolumeCmd cmd);
|
||||
|
||||
Volume assignVolumeToAccount(AssignVolumeCmd cmd) throws ResourceAllocationException;
|
||||
|
||||
boolean isDisplayResourceEnabled(Long id);
|
||||
|
||||
void updateDisplay(Volume volume, Boolean displayVolume);
|
||||
|
||||
@ -0,0 +1,119 @@
|
||||
// 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.api.command.user.volume;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.AccountResponse;
|
||||
import org.apache.cloudstack.api.response.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.VolumeResponse;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.storage.Volume;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = AssignVolumeCmd.CMD_NAME, responseObject = VolumeResponse.class, description = "Changes ownership of a Volume from one account to another.", entityType = {
|
||||
Volume.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.18.0.0")
|
||||
public class AssignVolumeCmd extends BaseCmd implements UserCmd {
|
||||
public static final Logger LOGGER = Logger.getLogger(AssignVolumeCmd.class.getName());
|
||||
public static final String CMD_NAME = "assignVolume";
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume to be reassigned.")
|
||||
private Long volumeId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class,
|
||||
description = "The ID of the account to which the volume will be assigned. Mutually exclusive with parameter 'projectid'.")
|
||||
private Long accountId;
|
||||
|
||||
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
|
||||
description = "The ID of the project to which the volume will be assigned. Mutually exclusive with 'accountid'.")
|
||||
private Long projectid;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getVolumeId() {
|
||||
return volumeId;
|
||||
}
|
||||
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public Long getProjectid() {
|
||||
return projectid;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
Volume result = _volumeService.assignVolumeToAccount(this);
|
||||
if (result == null) {
|
||||
Map<String,String> fullParams = getFullUrlParams();
|
||||
if (accountId != null) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to move volume [%s] to account [%s].", fullParams.get(ApiConstants.VOLUME_ID),
|
||||
fullParams.get(ApiConstants.ACCOUNT_ID)));
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to move volume [%s] to project [%s].", fullParams.get(ApiConstants.VOLUME_ID),
|
||||
fullParams.get(ApiConstants.PROJECT_ID)));
|
||||
}
|
||||
|
||||
VolumeResponse response = _responseGenerator.createVolumeResponse(getResponseView(), result);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
|
||||
} catch (CloudRuntimeException | ResourceAllocationException e) {
|
||||
String msg = String.format("Assign volume command for volume [%s] failed due to [%s].", getFullUrlParams().get("volumeid"), e.getMessage());
|
||||
LOGGER.error(msg, e);
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return CMD_NAME.toLowerCase() + RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
Volume volume = _responseGenerator.findVolumeById(getVolumeId());
|
||||
|
||||
if (volume != null) {
|
||||
return volume.getAccountId();
|
||||
}
|
||||
|
||||
return Account.ACCOUNT_ID_SYSTEM;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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.storage.command;
|
||||
|
||||
import com.cloud.agent.api.Command;
|
||||
|
||||
public class MoveVolumeCommand extends Command {
|
||||
|
||||
private String srcPath;
|
||||
private String destPath;
|
||||
private String volumeName;
|
||||
private String volumeUuid;
|
||||
private String datastoreUri;
|
||||
|
||||
public MoveVolumeCommand(String volumeUuid, String volumeName, String destPath, String srcPath, String datastoreUri) {
|
||||
this.volumeName = volumeName;
|
||||
this.volumeUuid = volumeUuid;
|
||||
this.srcPath = srcPath;
|
||||
this.destPath = destPath;
|
||||
this.datastoreUri = datastoreUri;
|
||||
}
|
||||
|
||||
public String getSrcPath() {
|
||||
return srcPath;
|
||||
}
|
||||
|
||||
public String getDestPath() {
|
||||
return destPath;
|
||||
}
|
||||
|
||||
public String getVolumeName() {
|
||||
return volumeName;
|
||||
}
|
||||
|
||||
public String getVolumeUuid() {
|
||||
return volumeUuid;
|
||||
}
|
||||
|
||||
public String getDatastoreUri() {
|
||||
return datastoreUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -46,4 +46,6 @@ public interface EndPointSelector {
|
||||
EndPoint select(Scope scope, Long storeId);
|
||||
|
||||
EndPoint select(DataStore store, String downloadUrl);
|
||||
|
||||
EndPoint findSsvm(long dcId);
|
||||
}
|
||||
|
||||
@ -30,6 +30,8 @@ import com.cloud.exception.StorageAccessException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
public interface VolumeService {
|
||||
@ -108,4 +110,6 @@ public interface VolumeService {
|
||||
*/
|
||||
boolean copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(ObjectInDataStoreStateMachine.Event destinationEvent, Answer destinationEventAnswer,
|
||||
VolumeInfo sourceVolume, VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync);
|
||||
|
||||
void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount);
|
||||
}
|
||||
@ -1063,3 +1063,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`console_session` (
|
||||
CONSTRAINT `fk_consolesession__host_id` FOREIGN KEY(`host_id`) REFERENCES `cloud`.`host`(`id`),
|
||||
CONSTRAINT `uc_consolesession__uuid` UNIQUE (`uuid`)
|
||||
);
|
||||
|
||||
-- Add assignVolume API permission to default resource admin and domain admin
|
||||
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`) VALUES (UUID(), 2, 'assignVolume', 'ALLOW');
|
||||
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`) VALUES (UUID(), 3, 'assignVolume', 'ALLOW');
|
||||
|
||||
|
||||
@ -330,9 +330,15 @@ public class DefaultEndPointSelector implements EndPointSelector {
|
||||
if (storeScope.getScopeType() == ScopeType.ZONE) {
|
||||
dcId = storeScope.getScopeId();
|
||||
}
|
||||
// find ssvm that can be used to download data to store. For zone-wide
|
||||
// image store, use SSVM for that zone. For region-wide store,
|
||||
// we can arbitrarily pick one ssvm to do that task
|
||||
|
||||
return findSsvm(dcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an SSVM that can be used to execute a command.
|
||||
* For zone-wide image store, use SSVM for that zone. For region-wide store, we can arbitrarily pick one SSVM to do the task.
|
||||
* */
|
||||
public EndPoint findSsvm(long dcId) {
|
||||
List<HostVO> ssAHosts = listUpAndConnectingSecondaryStorageVmHost(dcId);
|
||||
if (ssAHosts == null || ssAHosts.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@ -18,6 +18,9 @@
|
||||
*/
|
||||
package org.apache.cloudstack.storage.volume;
|
||||
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@ -25,12 +28,14 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||
import com.cloud.storage.VMTemplateVO;
|
||||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
import com.cloud.storage.resource.StorageProcessor;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
import org.apache.cloudstack.annotation.dao.AnnotationDao;
|
||||
import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
|
||||
@ -65,6 +70,7 @@ import org.apache.cloudstack.storage.RemoteHostEndPoint;
|
||||
import org.apache.cloudstack.storage.command.CommandResult;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.DeleteCommand;
|
||||
import org.apache.cloudstack.storage.command.MoveVolumeCommand;
|
||||
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreProviderManager;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
@ -125,7 +131,9 @@ import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.dao.VolumeDetailsDao;
|
||||
import com.cloud.storage.snapshot.SnapshotApiService;
|
||||
import com.cloud.storage.snapshot.SnapshotManager;
|
||||
import com.cloud.storage.template.TemplateConstants;
|
||||
import com.cloud.storage.template.TemplateProp;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.ResourceLimitService;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
@ -136,9 +144,6 @@ import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import static com.cloud.storage.resource.StorageProcessor.REQUEST_TEMPLATE_RELOAD;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Component
|
||||
public class VolumeServiceImpl implements VolumeService {
|
||||
private static final Logger s_logger = Logger.getLogger(VolumeServiceImpl.class);
|
||||
@ -806,7 +811,7 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
// hack for Vmware: host is down, previously download template to the host needs to be re-downloaded, so we need to reset
|
||||
// template_spool_ref entry here to NOT_DOWNLOADED and Allocated state
|
||||
Answer ans = result.getAnswer();
|
||||
if (ans != null && ans instanceof CopyCmdAnswer && ans.getDetails().contains(REQUEST_TEMPLATE_RELOAD)) {
|
||||
if (ans instanceof CopyCmdAnswer && ans.getDetails().contains(StorageProcessor.REQUEST_TEMPLATE_RELOAD)) {
|
||||
if (tmplOnPrimary != null) {
|
||||
s_logger.info("Reset template_spool_ref entry so that vmware template can be reloaded in next try");
|
||||
VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(tmplOnPrimary.getDataStore().getId(), tmplOnPrimary.getId(), deployAsIsConfiguration);
|
||||
@ -2751,4 +2756,50 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
volDao.remove(vol.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount) {
|
||||
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
|
||||
|
||||
if (volumeStore == null) {
|
||||
s_logger.debug(String.format("Volume [%s] is not present in the secondary storage. Therefore we do not need to move it in the secondary storage.", volume));
|
||||
return;
|
||||
}
|
||||
s_logger.debug(String.format("Volume [%s] is present in secondary storage. It will be necessary to move it from the source account's [%s] folder to the destination "
|
||||
+ "account's [%s] folder.",
|
||||
volume.getUuid(), sourceAccount, destAccount));
|
||||
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(volume.getId(), DataStoreRole.Image);
|
||||
String datastoreUri = volumeInfo.getDataStore().getUri();
|
||||
Path srcPath = Paths.get(volumeInfo.getPath());
|
||||
String destPath = buildVolumePath(destAccount.getAccountId(), volume.getId());
|
||||
|
||||
EndPoint ssvm = _epSelector.findSsvm(volume.getDataCenterId());
|
||||
|
||||
MoveVolumeCommand cmd = new MoveVolumeCommand(volume.getUuid(), volume.getName(), destPath, srcPath.getParent().toString(), datastoreUri);
|
||||
|
||||
Answer answer = ssvm.sendMessage(cmd);
|
||||
|
||||
if (!answer.getResult()) {
|
||||
String msg = String.format("Unable to move volume [%s] from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage, due "
|
||||
+ "to [%s].",
|
||||
volume.getUuid(), srcPath.getParent(), sourceAccount, destPath, destAccount, answer.getDetails());
|
||||
s_logger.error(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
s_logger.debug(String.format("Volume [%s] was moved from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage.",
|
||||
volume.getUuid(), srcPath.getParent(), sourceAccount, destPath, destAccount));
|
||||
|
||||
volumeStore.setInstallPath(String.format("%s/%s", destPath, srcPath.getFileName().toString()));
|
||||
if (!_volumeStoreDao.update(volumeStore.getId(), volumeStore)) {
|
||||
String msg = String.format("Unable to update volume [%s] install path in the DB.", volumeStore.getVolumeId());
|
||||
s_logger.error(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected String buildVolumePath(long accountId, long volumeId) {
|
||||
return String.format("%s/%s/%s", TemplateConstants.DEFAULT_VOLUME_ROOT_DIR, accountId, volumeId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,6 +724,7 @@ import org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd;
|
||||
import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd;
|
||||
import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToVMSnapshotCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AddResourceDetailCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
@ -3717,6 +3718,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
cmdList.add(ListResourceIconCmd.class);
|
||||
cmdList.add(PatchSystemVMCmd.class);
|
||||
cmdList.add(ListGuestVlansCmd.class);
|
||||
cmdList.add(AssignVolumeCmd.class);
|
||||
|
||||
// Out-of-band management APIs for admins
|
||||
cmdList.add(EnableOutOfBandManagementForHostCmd.class);
|
||||
|
||||
@ -33,6 +33,9 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.projects.Project;
|
||||
import com.cloud.projects.ProjectManager;
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
|
||||
@ -93,6 +96,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
||||
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
|
||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
@ -183,6 +187,7 @@ import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallback;
|
||||
import com.cloud.utils.db.TransactionCallbackNoReturn;
|
||||
import com.cloud.utils.db.TransactionCallbackWithException;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import com.cloud.utils.db.UUIDManager;
|
||||
@ -318,6 +323,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
@Inject
|
||||
protected SnapshotHelper snapshotHelper;
|
||||
|
||||
@Inject
|
||||
protected ProjectManager projectManager;
|
||||
|
||||
protected Gson _gson;
|
||||
|
||||
private static final List<HypervisorType> SupportedHypervisorsForVolResize = Arrays.asList(HypervisorType.KVM, HypervisorType.XenServer,
|
||||
@ -3745,6 +3753,126 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
return orchestrateExtractVolume(volume.getId(), zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "Assigning volume to new account", async = false)
|
||||
public Volume assignVolumeToAccount(AssignVolumeCmd command) throws ResourceAllocationException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
VolumeVO volume = _volsDao.findById(command.getVolumeId());
|
||||
Map<String, String> fullUrlParams = command.getFullUrlParams();
|
||||
|
||||
validateVolume(fullUrlParams.get("volumeid"), volume);
|
||||
|
||||
Account oldAccount = _accountMgr.getActiveAccountById(volume.getAccountId());
|
||||
Account newAccount = getAccountOrProject(fullUrlParams.get("projectid"), command.getAccountId(), command.getProjectid(), caller);
|
||||
|
||||
validateAccounts(fullUrlParams.get("accountid"), volume, oldAccount, newAccount);
|
||||
|
||||
_accountMgr.checkAccess(caller, null, true, oldAccount);
|
||||
_accountMgr.checkAccess(caller, null, true, newAccount);
|
||||
|
||||
_resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.volume, ByteScaleUtils.bytesToGibibytes(volume.getSize()));
|
||||
_resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.primary_storage, volume.getSize());
|
||||
|
||||
Transaction.execute(new TransactionCallbackNoReturn() {
|
||||
@Override
|
||||
public void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
updateVolumeAccount(oldAccount, volume, newAccount);
|
||||
}
|
||||
});
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
protected void updateVolumeAccount(Account oldAccount, VolumeVO volume, Account newAccount) {
|
||||
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
|
||||
Volume.class.getName(), volume.getUuid(), volume.isDisplayVolume());
|
||||
_resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.volume, ByteScaleUtils.bytesToGibibytes(volume.getSize()));
|
||||
_resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.primary_storage, volume.getSize());
|
||||
|
||||
volume.setAccountId(newAccount.getAccountId());
|
||||
volume.setDomainId(newAccount.getDomainId());
|
||||
_volsDao.persist(volume);
|
||||
|
||||
_resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.volume, ByteScaleUtils.bytesToGibibytes(volume.getSize()));
|
||||
_resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.primary_storage, volume.getSize());
|
||||
|
||||
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
|
||||
volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(),
|
||||
volume.getUuid(), volume.isDisplayVolume());
|
||||
|
||||
volService.moveVolumeOnSecondaryStorageToAnotherAccount(volume, oldAccount, newAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the accounts are null, if the new account state is correct, and if the two accounts are the same.
|
||||
* Throws {@link InvalidParameterValueException}.
|
||||
* */
|
||||
protected void validateAccounts(String newAccountUuid, VolumeVO volume, Account oldAccount, Account newAccount) {
|
||||
if (oldAccount == null) {
|
||||
throw new InvalidParameterValueException(String.format("The current account of the volume [%s] is invalid.",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(volume, "name", "uuid")));
|
||||
}
|
||||
|
||||
if (newAccount == null) {
|
||||
throw new InvalidParameterValueException(String.format("UUID of the destination account is invalid. No account was found with UUID [%s].", newAccountUuid));
|
||||
}
|
||||
|
||||
if (newAccount.getState() == Account.State.DISABLED || newAccount.getState() == Account.State.LOCKED) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to assign volume to destination account [%s], as it is in [%s] state.", newAccount,
|
||||
newAccount.getState().toString()));
|
||||
}
|
||||
|
||||
if (oldAccount.getAccountId() == newAccount.getAccountId()) {
|
||||
throw new InvalidParameterValueException(String.format("The new account and the old account are the same [%s].", oldAccount));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the volume can be reassigned to another account.
|
||||
* Throws {@link InvalidParameterValueException} if volume is null.
|
||||
* Throws {@link PermissionDeniedException} if volume is attached to a VM or if it has snapshots.
|
||||
* */
|
||||
protected void validateVolume(String volumeUuid, VolumeVO volume) {
|
||||
if (volume == null) {
|
||||
throw new InvalidParameterValueException(String.format("No volume was found with UUID [%s].", volumeUuid));
|
||||
}
|
||||
|
||||
String volumeToString = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(volume, "name", "uuid");
|
||||
|
||||
if (volume.getInstanceId() != null) {
|
||||
VMInstanceVO vmInstanceVo = _vmInstanceDao.findById(volume.getInstanceId());
|
||||
String msg = String.format("Volume [%s] is attached to [%s], so it cannot be moved to a different account.", volumeToString, vmInstanceVo);
|
||||
s_logger.error(msg);
|
||||
throw new PermissionDeniedException(msg);
|
||||
}
|
||||
|
||||
List<SnapshotVO> snapshots = _snapshotDao.listByStatusNotIn(volume.getId(), Snapshot.State.Destroyed, Snapshot.State.Error);
|
||||
if (CollectionUtils.isNotEmpty(snapshots)) {
|
||||
throw new PermissionDeniedException(String.format("Volume [%s] has snapshots. Remove the volume's snapshots before assigning it to another account.", volumeToString));
|
||||
}
|
||||
}
|
||||
|
||||
protected Account getAccountOrProject(String projectUuid, Long accountId, Long projectId, Account caller) {
|
||||
if (projectId != null && accountId != null) {
|
||||
throw new InvalidParameterValueException("Both 'accountid' and 'projectid' were informed. You must inform only one of them.");
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
Project project = projectManager.getProject(projectId);
|
||||
if (project == null) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to find project [%s]", projectUuid));
|
||||
}
|
||||
|
||||
if (!projectManager.canAccessProjectAccount(caller, project.getProjectAccountId())) {
|
||||
throw new PermissionDeniedException(String.format("Account [%s] does not have access to project [%s].", caller, projectUuid));
|
||||
}
|
||||
|
||||
return _accountMgr.getAccount(project.getProjectAccountId());
|
||||
}
|
||||
|
||||
return _accountMgr.getActiveAccountById(accountId);
|
||||
}
|
||||
|
||||
private Optional<String> setExtractVolumeSearchCriteria(SearchCriteria<VolumeDataStoreVO> sc, VolumeVO volume) {
|
||||
final long volumeId = volume.getId();
|
||||
sc.addAnd("state", SearchCriteria.Op.EQ, ObjectInDataStoreStateMachine.State.Ready.toString());
|
||||
|
||||
@ -17,6 +17,10 @@
|
||||
package com.cloud.storage;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.projects.Project;
|
||||
import com.cloud.projects.ProjectManager;
|
||||
import com.cloud.storage.dao.SnapshotDao;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
@ -60,6 +64,8 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@ -191,12 +197,28 @@ public class VolumeApiServiceImplTest {
|
||||
private VolumeDataStoreVO volumeDataStoreVoMock;
|
||||
@Mock
|
||||
private AsyncCallFuture<VolumeApiResult> asyncCallFutureVolumeapiResultMock;
|
||||
@Mock
|
||||
private ArrayList<SnapshotVO> snapshotVOArrayListMock;
|
||||
|
||||
@Mock
|
||||
private SnapshotDao snapshotDaoMock;
|
||||
|
||||
@Mock
|
||||
private Project projectMock;
|
||||
|
||||
@Mock
|
||||
private ProjectManager projectManagerMock;
|
||||
|
||||
private long accountMockId = 456l;
|
||||
private long volumeMockId = 12313l;
|
||||
private long vmInstanceMockId = 1123l;
|
||||
private long volumeSizeMock = 456789921939l;
|
||||
|
||||
private String projectMockUuid = "projectUuid";
|
||||
private long projecMockId = 13801801923810L;
|
||||
|
||||
private long projectMockAccountId = 132329390L;
|
||||
|
||||
private long diskOfferingMockId = 100203L;
|
||||
|
||||
private long offeringMockId = 31902L;
|
||||
@ -208,6 +230,9 @@ public class VolumeApiServiceImplTest {
|
||||
Mockito.lenient().doReturn(accountMockId).when(accountMock).getId();
|
||||
Mockito.doReturn(volumeSizeMock).when(volumeVoMock).getSize();
|
||||
Mockito.doReturn(volumeSizeMock).when(newDiskOfferingMock).getDiskSize();
|
||||
Mockito.doReturn(projectMockUuid).when(projectMock).getUuid();
|
||||
Mockito.doReturn(projecMockId).when(projectMock).getId();
|
||||
Mockito.doReturn(projectMockAccountId).when(projectMock).getProjectAccountId();
|
||||
|
||||
Mockito.doReturn(Mockito.mock(VolumeApiResult.class)).when(asyncCallFutureVolumeapiResultMock).get();
|
||||
|
||||
@ -1317,6 +1342,138 @@ public class VolumeApiServiceImplTest {
|
||||
Assert.assertEquals(expectedResult, result);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void checkIfVolumeCanBeReassignedTestNullVolume() {
|
||||
volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), null);
|
||||
}
|
||||
|
||||
@Test (expected = PermissionDeniedException.class)
|
||||
public void checkIfVolumeCanBeReassignedTestAttachedVolume() {
|
||||
Mockito.doReturn(vmInstanceMockId).when(volumeVoMock).getInstanceId();
|
||||
|
||||
volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
|
||||
}
|
||||
|
||||
@Test (expected = PermissionDeniedException.class)
|
||||
@PrepareForTest (CollectionUtils.class)
|
||||
public void checkIfVolumeCanBeReassignedTestVolumeWithSnapshots() {
|
||||
Mockito.doReturn(null).when(volumeVoMock).getInstanceId();
|
||||
Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
PowerMockito.mockStatic(CollectionUtils.class);
|
||||
PowerMockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(true);
|
||||
|
||||
volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@PrepareForTest (CollectionUtils.class)
|
||||
public void checkIfVolumeCanBeReassignedTestValidVolume() {
|
||||
Mockito.doReturn(null).when(volumeVoMock).getInstanceId();
|
||||
Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
PowerMockito.mockStatic(CollectionUtils.class);
|
||||
PowerMockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(false);
|
||||
|
||||
volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void validateAccountsTestNullOldAccount() {
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, null, accountMock);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void validateAccountsTestNullNewAccount() {
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, accountMock, null);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void validateAccountsTestDisabledNewAccount() {
|
||||
Mockito.doReturn(Account.State.DISABLED).when(accountMock).getState();
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, null, accountMock);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void validateAccountsTestLockedNewAccount() {
|
||||
Mockito.doReturn(Account.State.LOCKED).when(accountMock).getState();
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, null, accountMock);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void validateAccountsTestSameAccounts() {
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, accountMock, accountMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateAccountsTestValidAccounts() {
|
||||
Account newAccount = new AccountVO(accountMockId+1);
|
||||
volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, accountMock, newAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
@PrepareForTest(UsageEventUtils.class)
|
||||
public void updateVolumeAccountTest() {
|
||||
PowerMockito.mockStatic(UsageEventUtils.class);
|
||||
Account newAccountMock = new AccountVO(accountMockId+1);
|
||||
|
||||
Mockito.doReturn(volumeVoMock).when(volumeDaoMock).persist(volumeVoMock);
|
||||
|
||||
volumeApiServiceImpl.updateVolumeAccount(accountMock, volumeVoMock, newAccountMock);
|
||||
|
||||
PowerMockito.verifyStatic(UsageEventUtils.class);
|
||||
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
|
||||
volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume());
|
||||
|
||||
Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.volume, ByteScaleUtils.bytesToGibibytes(volumeVoMock.getSize()));
|
||||
Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
|
||||
|
||||
Mockito.verify(volumeVoMock).setAccountId(newAccountMock.getAccountId());
|
||||
Mockito.verify(volumeVoMock).setDomainId(newAccountMock.getDomainId());
|
||||
|
||||
Mockito.verify(volumeDaoMock).persist(volumeVoMock);
|
||||
|
||||
Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.volume, ByteScaleUtils.bytesToGibibytes(volumeVoMock.getSize()));
|
||||
Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
|
||||
|
||||
PowerMockito.verifyStatic(UsageEventUtils.class);
|
||||
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
|
||||
volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume());
|
||||
|
||||
Mockito.verify(volumeServiceMock).moveVolumeOnSecondaryStorageToAnotherAccount(volumeVoMock, accountMock, newAccountMock);
|
||||
}
|
||||
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void getAccountOrProjectTestAccountAndProjectInformed() {
|
||||
volumeApiServiceImpl.getAccountOrProject(projectMock.getUuid(), accountMock.getId(), projectMock.getId(), accountMock);
|
||||
}
|
||||
|
||||
@Test (expected = InvalidParameterValueException.class)
|
||||
public void getAccountOrProjectTestUnableToFindProject() {
|
||||
Mockito.doReturn(null).when(projectManagerMock).getProject(projecMockId);
|
||||
volumeApiServiceImpl.getAccountOrProject(projectMock.getUuid(), null, projectMock.getId(), accountMock);
|
||||
}
|
||||
|
||||
@Test (expected = PermissionDeniedException.class)
|
||||
public void getAccountOrProjectTestCallerDoesNotHaveAccessToProject() {
|
||||
Mockito.doReturn(projectMock).when(projectManagerMock).getProject(projecMockId);
|
||||
Mockito.doReturn(false).when(projectManagerMock).canAccessProjectAccount(accountMock, projectMockAccountId);
|
||||
volumeApiServiceImpl.getAccountOrProject(projectMock.getUuid(), null, projectMock.getId(), accountMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAccountOrProjectTestValidProject() {
|
||||
Mockito.doReturn(projectMock).when(projectManagerMock).getProject(projecMockId);
|
||||
Mockito.doReturn(true).when(projectManagerMock).canAccessProjectAccount(accountMock, projectMockAccountId);
|
||||
volumeApiServiceImpl.getAccountOrProject(projectMock.getUuid(), null, projectMock.getId(), accountMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAccountOrProjectTestValidAccount() {
|
||||
volumeApiServiceImpl.getAccountOrProject(projectMock.getUuid(), accountMock.getId(),null, accountMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@PrepareForTest(UsageEventUtils.class)
|
||||
public void publishVolumeCreationUsageEventTestNullDiskOfferingId() {
|
||||
|
||||
@ -41,6 +41,7 @@ import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -58,6 +59,7 @@ import org.apache.cloudstack.storage.command.CopyCommand;
|
||||
import org.apache.cloudstack.storage.command.DeleteCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
|
||||
import org.apache.cloudstack.storage.command.MoveVolumeCommand;
|
||||
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
|
||||
@ -73,6 +75,7 @@ import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
import org.apache.cloudstack.utils.security.DigestHelper;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -311,6 +314,8 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
||||
return execute((GetDatadisksCommand)cmd);
|
||||
} else if (cmd instanceof CreateDatadiskTemplateCommand) {
|
||||
return execute((CreateDatadiskTemplateCommand)cmd);
|
||||
} else if (cmd instanceof MoveVolumeCommand) {
|
||||
return execute((MoveVolumeCommand)cmd);
|
||||
} else {
|
||||
return Answer.createUnsupportedCommandAnswer(cmd);
|
||||
}
|
||||
@ -544,6 +549,36 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
||||
return new CreateDatadiskTemplateAnswer(diskTemplate);
|
||||
}
|
||||
|
||||
public Answer execute(MoveVolumeCommand cmd) {
|
||||
String volumeToString = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "volumeUuid", "volumeName");
|
||||
|
||||
String rootDir = getRootDir(cmd.getDatastoreUri(), _nfsVersion);
|
||||
|
||||
if (!rootDir.endsWith("/")) {
|
||||
rootDir += "/";
|
||||
}
|
||||
|
||||
Path srcPath = Paths.get(rootDir + cmd.getSrcPath());
|
||||
Path destPath = Paths.get(rootDir + cmd.getDestPath());
|
||||
|
||||
try {
|
||||
s_logger.debug(String.format("Trying to create missing directories (if any) to move volume [%s].", volumeToString));
|
||||
Files.createDirectories(destPath.getParent());
|
||||
s_logger.debug(String.format("Trying to move volume [%s] to [%s].", volumeToString, destPath));
|
||||
Files.move(srcPath, destPath);
|
||||
|
||||
String msg = String.format("Moved volume [%s] from [%s] to [%s].", volumeToString, srcPath, destPath);
|
||||
s_logger.debug(msg);
|
||||
|
||||
return new Answer(cmd, true, msg);
|
||||
|
||||
} catch (IOException ioException) {
|
||||
s_logger.error(String.format("Failed to move volume [%s] from [%s] to [%s] due to [%s].", volumeToString, srcPath, destPath, ioException.getMessage()),
|
||||
ioException);
|
||||
return new Answer(cmd, ioException);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* return Pair of <Template relative path, Template name>
|
||||
* Template url may or may not end with .ova extension
|
||||
|
||||
@ -55,4 +55,14 @@ public class ByteScaleUtils {
|
||||
public static long bytesToMebibytes(long bytes) {
|
||||
return bytes / MiB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes to gibibytes.
|
||||
*
|
||||
* @param b The value in bytes to convert to gibibytes.
|
||||
* @return The parameter divided by 1024 * 1024 * 1024 (1 GiB).
|
||||
*/
|
||||
public static long bytesToGibibytes(long b) {
|
||||
return b / GiB;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,13 @@ public class ByteScaleUtilsTest {
|
||||
Assert.assertEquals(mib, ByteScaleUtils.bytesToMebibytes(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateBytesToGib(){
|
||||
long gib = 3000L;
|
||||
long b = 1024L * 1024L * 1024L * gib;
|
||||
Assert.assertEquals(gib, ByteScaleUtils.bytesToGibibytes(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateMebibytesToBytesIfIntTimesIntThenMustExtrapolateIntMaxValue() {
|
||||
int mib = 3000;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user